The feature/conquest branch drifted too far from develop without completing the core gameplay loop. This doc captures all known packet wire formats, handler states, confirmed values from captures, and a prioritised table of unknowns needed before the feature can be implemented cleanly. Also updates unimplemented.md to link to the new reference.
20 KiB
Conquest War (討伐征戦 / Seibatsu)
Tracks what is known about the Conquest War event system and what remains to be reverse-engineered before it can be fully implemented in Erupe.
The feature/conquest branch (origin) attempted a partial implementation but drifted too far
from develop without completing the core gameplay loop and is not mergeable in its current
state. Its findings are incorporated below.
Game Context
Conquest War (討伐征戦, also called Seibatsu) is a weekly rotating time-limited event introduced in the G2 update (July 2013). Players hunt legendary monsters and race to level them up on a per-player leaderboard.
The event follows a three-week, three-phase cycle tracked server-side as the "Earth" system:
| Week | Phase | Japanese | Description |
|---|---|---|---|
| 1 | Conquest (Seibatsu) | 討伐征戦 | Hunting phase — players level their monsters |
| 2 | Pallone Festival | パローネ祭典 | Side festival event concurrent with conquest rewards |
| 3 | Tower (Dure) | 塔 | Tower climbing event for additional rewards |
Conquest Mechanics
- Each player has their own independent monster (not shared with others).
- Players hunt their own monster or join quests at the same or higher level.
- A monster starts at level 1 and caps at 9999.
- Level gain per hunt: +5 (no faints), +3 (one faint), +1 (multiple faints).
- As the monster levels, its stats scale up, making each subsequent hunt harder.
- At week end, rewards are distributed based on the player's rank on the per-monster leaderboard.
Target Monsters (configurable)
The live service used Shantien, Disufiroa, G-Rank Black Fatalis, and
G-Rank Crimson Fatalis. The branch defaults to monster IDs [116, 107, 2, 36]
(Deviljho, Rajang, Rathalos, Gore Magala — suitable for G8 and below, where the original
four are not available).
For clients at RealClientMode <= G8, only the first 3 monsters are exposed; G9+ exposes 4.
Reward Distribution Types
The DistributionType field in reward packets uses these sentinel values:
| Value | Meaning |
|---|---|
7201 |
Item reward (ItemID + quantity) |
7202 |
N-Points (currency) |
7203 |
Guild contribution points |
Packet Overview
Thirteen packets implement the Conquest/Earth system. All live in network/mhfpacket/.
None have Build() implemented (all return NOT IMPLEMENTED) — responses are built
directly in handler code using byteframe.
MsgMhfGetEarthStatus — Client → Server → Client
Fetches the current Earth event windows and which monsters are active.
Request (msg_mhf_get_earth_status.go):
AckHandle uint32
Unk0 uint32 — unknown; never used by handler
Unk1 uint32 — unknown; never used by handler
Response (built in handlers_earth.go → handleMsgMhfGetEarthStatus):
for each active earth event (up to 3: Conquest, Pallone, Tower):
[uint32] StartTime — Unix timestamp
[uint32] EndTime — Unix timestamp
[int32] StatusID — 1 or 2 (Conquest); 11 (Pallone active) or 12 (Pallone reward); 21 (Tower)
[int32] EarthID — unique event ID from DB row
[int32] MonsterID × N — active conquest monsters (3 for G8, 4 for G9+)
Status ID semantics: the difference between 1 and 2 for the Conquest phase is not
known. The branch selects 1 when the hunt week is active and 2 otherwise, but this is
a guess.
Current state: Implemented. Event windows are generated from a single events table row
(event_type = 'earth'). A 21-day rolling cycle is computed from that row's start_time.
Debug mode (EarthDebug = true) collapses the windows to week boundaries for faster testing.
MsgMhfGetEarthValue — Client → Server → Client
Fetches numeric values associated with the current Earth event (kill counts, floor tallies, special flags).
Request (msg_mhf_get_earth_value.go):
AckHandle uint32
Unk0 uint32 — unknown
Unk1 uint32 — unknown
ReqType uint32 — 1, 2, or 3 (see below)
Unk3–Unk6 uint32 — unknown; never used by handler
Response: a variable-length array of 6-uint32 entries, wrapped in doAckEarthSucceed.
Each entry: [ID, Value, Unk, Unk, Unk, Unk]. The last four fields are always zero in
known captures.
| ReqType | Known entries | Notes |
|---|---|---|
| 1 | {1, 100}, {2, 100} |
Block + DureSlays count — exact meaning unclear |
| 2 | {1, 5771}, {2, 1847} |
Block + Floors? — "Floors?" is a guess |
| 3 | {1001, 36} getTouhaHistory; {9001, 3} getKohouhinDropStopFlag; {9002, 10, 300} getKohouhinForceValue |
ttcSetDisableFlag relationship unknown |
Current state: Implemented with hardcoded values. No database persistence.
MsgMhfReadBeatLevel — Client → Server → Client
Reads the player's current conquest beat levels (monster progress values) from the server.
Request (msg_mhf_read_beat_level.go):
AckHandle uint32
Unk0 uint32 — always 1 in the JP client (hardcoded literal)
ValidIDCount uint32 — always 4 in the JP client
IDs [16]uint32 — always [0x74, 0x6B, 0x02, 0x24, 0, 0, ...] (hardcoded)
Response: ValidIDCount entries of [ID uint32, Value uint32, 0 uint32, 0 uint32].
Default value if no DB data: {0,1, 0,1, 0,1, 0,1} (level 1 for each slot).
Current state: Fully implemented. Beat levels are read from characters.conquest_data
(16-byte BYTEA). Defaults to level 1 if the column is NULL.
MsgMhfUpdateBeatLevel — Client → Server → Client
Saves the player's updated conquest beat levels after a quest.
Request (msg_mhf_update_beat_level.go):
AckHandle uint32
Unk1 uint32 — unknown
Unk2 uint32 — unknown
Data1 [16]int32 — unknown purpose; entirely discarded by the handler
Data2 [16]int32 — beat level data; only first 4 values are stored
Response: {0x00, 0x00, 0x00, 0x00}.
Current state: Implemented, but incomplete. Only Data2[0..3] is written to the DB.
Data1 and Data2[4..15] are silently ignored. Unk1/Unk2 purposes are unknown.
MsgMhfReadBeatLevelAllRanking — Client → Server → Client
Fetches the global leaderboard for a given monster.
Request (msg_mhf_read_beat_level_all_ranking.go):
AckHandle uint32
Unk0 uint32
MonsterID int32 — which monster's ranking to fetch
Unk2 int32 — unknown
Response structure (from known captures):
[uint32] Unk
[int32] Unk
[int32] Unk
for each of 100 entries:
[uint32] Rank
[uint32] Level
[32 bytes] HunterName (null-padded)
Current state: Stubbed. Returns 100 zero-filled entries. No database ranking data exists.
MsgMhfReadBeatLevelMyRanking — Client → Server → Client
Fetches the player's own rank on the conquest leaderboard.
Request (msg_mhf_read_beat_level_my_ranking.go):
AckHandle uint32
Unk0 uint32
Unk1 uint32
Unk2 [16]int32 — unknown; possibly the same ID array as ReadBeatLevel
Current state: Stubbed. Returns an empty buffer. Response format unknown.
MsgMhfReadLastWeekBeatRanking — Client → Server → Client
Purpose is partially understood: the handler comment says "controls the monster headings for the other menus". Likely provides context for which monster's data to display.
Request (msg_mhf_read_last_week_beat_ranking.go):
AckHandle uint32
Unk0 uint32
EarthMonster int32
Response (current stub): [EarthMonster uint32, 0, 0, 0]. Actual format unknown.
Current state: Minimal stub. Response structure not reverse-engineered.
MsgMhfGetBreakSeibatuLevelReward — Client → Server → Client
Returns per-monster level-break milestone rewards (items granted at specific level thresholds).
Request (msg_mhf_get_break_seibatu_level_reward.go):
AckHandle uint32
Unk0 uint32 — unknown; debug-printed but never used
EarthMonster int32
Response: variable-length array of reward entries via doAckEarthSucceed:
[int32] ItemID
[int32] Quantity
[int32] Level — the level threshold at which this reward unlocks
[int32] Unk — always 0 in known data
Current state: Implemented with hardcoded per-monster reward tables. Item IDs were derived from packet captures. No database backend.
MsgMhfGetWeeklySeibatuRankingReward — Client → Server → Client
Returns reward tables for conquest ranking, Pallone Festival routes, and Tower floors. The most complex handler in the branch.
Request (msg_mhf_get_weekly_seibatu_ranking_reward.go):
AckHandle uint32
Unk0 uint32 — unknown; debug-printed but never used
Operation uint32 — 1 = conquest ranking, 3 = Pallone festival, 5 = event rewards
ID uint32 — event/route ID (for Op=1: aligns with EarthStatus 1 and 2)
EarthMonster uint32
Response format for Operation = 1 (conquest ranking rewards):
per entry:
[int32] Unk0
[int32] ItemID
[uint32] Amount
[int32] PlaceFrom
[int32] PlaceTo
Response format for Operations 3 and 5 (Pallone/Tower):
per entry:
[int32] Index0 — floor number (Op=5) or place rank (Op=3)
[int32] Index1
[uint32] Index2 — distribution slot (Op=5 tower dure: 1 or 2)
[int32] DistributionType — 7201/7202/7203
[int32] ItemID
[int32] Amount
Current state: Implemented with hardcoded tables derived from packet captures.
- Operation 1: All four monsters return the same bracket table (ranks 1–100, 101–1000, 1000–1001). The tables are identical for all monsters — this may be correct, or captures were only recorded for one monster.
- Operation 3 (Pallone): 91 entries across 11 routes, all zero-filled — format is known but content is not.
- Operation 5 (Tower): Tower dure kill rewards (260001) and 155-entry floor reward table (260003, floors 1–1500) are hardcoded from captures.
Note in source: "Can only have 10 in each dist" — the maximum entries per distribution slot before the client discards them is 10.
MsgMhfGetFixedSeibatuRankingTable — Client → Server → Client
Returns a static "fixed" leaderboard (likely a seeded/display ranking, not live player data).
The handler notes this packet is not triggered when EarthStatus == 1, suggesting it
belongs to the reward-week display rather than the hunt-week display.
Request (msg_mhf_get_fixed_seibatu_ranking_table.go):
AckHandle uint32
Unk0 uint32 — unknown
Unk1 int32 — unknown
EarthMonster int32
Unk3 int32 — unknown
Unk4 int32 — unknown
Response: up to 9 entries:
[int32] Rank
[int32] Level
[32 bytes] HunterName (null-padded)
Current state: Implemented with 9 hardcoded "Hunter N" placeholder entries per monster.
Unk1, Unk3, Unk4 purposes unknown.
MsgMhfGetSeibattle — Client → Server → Client
Fetches Seibattle (guild-vs-guild battle) data. The GuildID field suggests this is
guild-specific, but the handler ignores it entirely.
Request (msg_mhf_get_seibattle.go):
AckHandle uint32
Unk0 uint8
Type uint8 — 1=timetable, 3=key score, 4=career, 5=opponent, 6=convention result,
7=char score, 8=cur result
GuildID uint32
Unk3 uint8 — unknown
Unk4 uint16 — unknown
Response: varies by Type. Timetable (Type=1) returns 3 eight-hour battle windows
computed from midnight. All other types return zero-filled structs.
Current state: Stubbed. No database queries, no guild-specific data. The seibattle guild-vs-guild combat system is entirely unimplemented.
MsgMhfPostSeibattle — Client → Server → Client
Submits a seibattle result. All fields are unknown.
Request (msg_mhf_post_seibattle.go):
AckHandle uint32
Unk0 uint8
Unk1 uint8
Unk2 uint32
Unk3 uint8
Unk4 uint16
Unk5 uint16
Unk6 uint8
Current state: Stubbed. Returns {0,0,0,0}. No data is read or persisted.
MsgMhfGetAdditionalBeatReward — Client → Server → Client
Purpose unclear. The handler comment states: "Actual responses in packet captures are all just giant batches of null bytes. I'm assuming this is because it used to be tied to an actual event that no longer triggers in the client."
Request (msg_mhf_get_additional_beat_reward.go):
AckHandle uint32
Unk0–Unk3 uint32 — all unknown
Current state: Returns 260 (0x104) zero bytes. Whether real responses were ever
non-zero is unknown.
Database Schema
The branch adds two migrations:
-- schemas/patch-schema/23-earth.sql
ALTER TYPE event_type ADD VALUE 'earth';
-- schemas/patch-schema/24-conquest.sql
ALTER TABLE public.characters ADD COLUMN IF NOT EXISTS conquest_data BYTEA;
And seeds four conquest quests (schemas/bundled-schema/ConquestQuests.sql):
quest IDs 54257, 54258, 54277, 54370 — all quest_type = 33, max_players = 0.
Missing tables required for a full implementation:
| Table | Purpose |
|---|---|
conquest_rankings |
Per-player, per-monster beat level leaderboard |
conquest_reward_claims |
Track which level-break and ranking rewards have been claimed |
seibattle_scores |
Guild seibattle results and career records |
seibattle_schedules |
Persistent timetable (currently computed in memory) |
Configuration
Two keys were added to config.go / config.json by the branch:
| Key | Type | Default | Purpose |
|---|---|---|---|
EarthDebug |
bool | false |
Collapses event windows to week boundaries for testing |
EarthMonsters |
[]int32 | [116, 107, 2, 36] |
Active conquest target monster IDs |
What Is Already Understood
- The three-phase Earth event cycle (Conquest → Pallone → Tower) and its 21-day rolling
window, keyed to a single
eventsrow. GetEarthStatusresponse wire format: per-phase[Start, End, StatusID, EarthID, MonsterIDs…].ReadBeatLevelrequest is fully hardcoded by the JP client (IDs0x74, 0x6B, 0x02, 0x24); no dynamic ID resolution is needed.- Per-character beat level storage: 4 × int32, 16 bytes, in
characters.conquest_data. - Level-break reward item IDs and quantities for monsters 116, 107, 2, 36 (from captures).
- Weekly ranking reward brackets for conquest (ranks 1–100, 101–1000, 1000–1001).
- Tower floor reward table (floors 1–1500, item IDs and quantities from captures).
- Tower dure kill reward distributions (dist 1 and 2, from captures).
GetWeeklySeibatuRankingRewardresponse wire format for all three operations.GetFixedSeibatuRankingTableresponse wire format (rank + level + 32-byte name).GetBreakSeibatuLevelRewardresponse wire format (ItemID + Quantity + Level + Unk).- Distribution type sentinels:
7201= item,7202= N-Points,7203= guild contribution. - The 10-entry-per-distribution-slot limit in weekly seibatu ranking rewards.
GetSeibattletimetable format: 3 × 8-hour windows from midnight.- Conquest quest IDs:
54257,54258,54277,54370(type 33).
What Needs RE Before Full Implementation
High Priority — blocks any functional gameplay
| Unknown | Where to look | Notes |
|---|---|---|
Semantics of EarthStatus IDs 1 vs 2 |
Packet captures during hunt week vs reward week | Currently guessed; wrong selection may break phase detection |
MsgMhfUpdateBeatLevel.Data1[16] |
Captures with known quest outcome | Second int32 array entirely discarded; may carry the level gain delta |
MsgMhfUpdateBeatLevel.Unk1 / Unk2 |
Same captures | May carry monster ID or quest ID needed for routing |
ReadBeatLevelAllRanking response structure |
Captures from an active leaderboard | Header fields (3 × uint32 before the 100 entries) unknown |
ReadBeatLevelMyRanking response structure |
Same | Format entirely unknown; returns empty today |
ReadLastWeekBeatRanking full response |
Captures after week rollover | Only monster ID echoed back today |
Medium Priority — required for accurate reward flow
| Unknown | Where to look | Notes |
|---|---|---|
MsgMhfPostSeibattle all fields (Unk0–Unk6) |
Captures after a seibattle result | Handler does nothing today; this is the score submission path |
GetSeibattle types 3–8 response formats |
Captures for each Type value |
Currently all return zero structs |
GetSeibattle.Unk0 / Unk3 / Unk4 |
Same captures | Likely context selectors for guild/season |
GetEarthValue.Unk0 / Unk1 / Unk3–Unk6 |
Captures across different event phases | 6 of the 8 request fields are unknown |
GetEarthStatus.Unk0 / Unk1 |
Captures across phases | Never used by the handler; may be version or session flags |
GetWeeklySeibatuRankingReward Op=3 content |
Captures during Pallone Festival | 91 entries are zero-filled placeholders |
| Claim tracking semantics | Compare reward endpoint with gacha claim flow | No "claimed" flag exists anywhere in the schema |
Low Priority — cosmetic / completeness
| Unknown | Where to look | Notes |
|---|---|---|
GetFixedSeibatuRankingTable.Unk1 / Unk3 / Unk4 |
Captures | Likely unused alignment or version fields |
GetBreakSeibatuLevelReward.Unk0 |
Captures with different monsters | Debug-printed; may be season or event ID |
ReadBeatLevelAllRanking.Unk0 / Unk2 |
Captures | Likely pagination or season selectors |
GetAdditionalBeatReward full structure |
Captures if the packet is ever non-null | May be permanently dead in the last client version |
| Pallone Festival route semantics | JP wiki / community guides | 11 routes × 13 entries, all content unknown |
| Original live-service event scheduling cadence | JP wiki archives | Cycle length and reset time not publicly documented |
Relation to Other Systems
Gacha service (svc_gacha.go): The reward distribution model (distribution type,
item ID, amount, rank brackets) is structurally similar to the gacha reward pipeline.
Conquest reward claiming can likely reuse or adapt GachaService.ClaimRewards and its
point transaction infrastructure.
Raviente siege (sys_channel_server.go, handlers_register.go): Conquest quests may
use the same MsgMhfRegisterEvent / semaphore pattern for quest slot management, though
this has not been confirmed with captures.
Tower event (feature/tower branch): The Tower phase is part of the same Earth event
cycle. The GetWeeklySeibatuRankingReward handler already covers Tower rewards (Op=5).
The two branches should be coordinated or merged.
Known Code Quality Issues in the Branch
The following must be fixed before any part of this branch is merged:
fmt.Printfdebug prints left in packetParse()methods:msg_mhf_get_break_seibatu_level_reward.go,msg_mhf_get_weekly_seibatu_ranking_reward.go,msg_mhf_get_fixed_seibatu_ranking_table.go,msg_mhf_read_last_week_beat_ranking.go- The
cleanupEarthStatusfunction wipesconquest_datafor all characters on event expiry — this erases history. Completed conquest data should be archived, not deleted. - The branch introduced a large
handlers.goconsolidation file that deleted existing test files (handlers_achievement_test.go,channel_isolation_test.go, etc.). These must be restored. - DB access in
handlers_earth.gouses raws.server.dbcalls instead of the repo pattern. Any merge must route these throughCharacterRepoandEventRepointerfaces. EarthMonstersconfig currently accepts any IDs; a G9+ client will crash if fewer than 4 monsters are configured whenRealClientMode > G8.