docs(fort-attack): document Interceptor's Base event RE status

Add fort-attack-event.md capturing everything known about the fort
attack event system (packet wire formats, register plumbing, DB schema
gap, quest IDs) and what needs reverse-engineering before implementation
is possible. The feature/enum-event branch covered only scheduling and
is not mergeable; its findings are preserved here for future reference.

Update unimplemented.md to point to the new doc and correctly describe
the branch scope.
This commit is contained in:
Houmgaor
2026-03-22 20:11:22 +01:00
parent f03476e6e0
commit 63312dac1b
2 changed files with 203 additions and 2 deletions

201
docs/fort-attack-event.md Normal file
View File

@@ -0,0 +1,201 @@
# Fort Attack Event (迎撃拠点 / Interceptor's Base)
Tracks what is known about the Interceptor's Base fort attack event system and what remains to be
reverse-engineered before it can be implemented in Erupe.
The `feature/enum-event` branch (origin) attempted a partial implementation but is not mergeable in
its current state. Its useful findings are incorporated below.
---
## Game Context
The **Interceptor's Base** (迎撃拠点) is a persistent field introduced in Forward.1 (April 2011).
Guilds defend a fortress adjacent to Mezeporta against invading Elder Dragons. The fort has a
**durability meter** — if monster attacks reduce it to 0% the quest fails regardless of time or
lives remaining. Monsters known to attack include Rukodiora, Rebidiora, Teoleskatle, Yamatukami,
Shengaroren, Harudomerugu, Rusted Kushala Daora, Belkyruros, Abiologu, and Keoaruboru.
**Keoaruboru** (古龍の侵攻 culmination, added MHF-Z Z1.1) is the hardest variant. Its limbs
accumulate heat as the fight progresses; if any limb reaches maximum heat it fires a beam at the
fort dealing 20% durability damage and resetting all heat. The fort starts at 80% integrity, meaning
four unchecked beams cause quest failure. Managing heat across limbs is the central mechanic.
The event was scheduled by Capcom's live servers on a cycle. The exact trigger frequency is not
publicly documented in either English or Japanese sources.
---
## Packet Overview
Five packets are involved. All live in `network/mhfpacket/`.
### `MsgMhfEnumerateEvent` (0x72) — Client → Server → Client
The client polls this on login to learn what fort attack events are currently scheduled.
**Request** (`msg_mhf_enumerate_event.go`): `AckHandle uint32` + two zeroed `uint16`.
**Response** built in `handleMsgMhfEnumerateEvent` (`handlers_event.go`):
```
[uint8] event count
for each event:
[uint16] EventType — 0 = nothing; 1 or 2 = "Ancient Dragon has attacked the fort"
[uint16] Unk1 — unknown; always 0 in known captures
[uint16] Unk2 — unknown; always 0
[uint16] Unk3 — unknown; always 0
[uint16] Unk4 — unknown; always 0
[uint32] StartTime — Unix timestamp (seconds) when event begins
[uint32] EndTime — Unix timestamp when event ends
if EventType == 2:
[uint8] quest file count
[uint16] quest file ID × N
```
What `EventType == 1` means vs `EventType == 2` is not known. The quest file ID list only appears
when `EventType == 2`. The semantics of Unk1Unk4 are entirely unknown.
**Current state**: Handler returns an empty event list (0 events). The `feature/enum-event` branch
adds DB-backed scheduling with a configurable `Duration` / `RestartAfter` cycle and a hardcoded
list of 19 quest IDs, but has a logic bug (inverted `rows.Next()` check) and uses raw DB calls
instead of the repo pattern.
---
### `MsgMhfRegisterEvent` — Client → Server → Client
Sent when a player attempts to join a fort attack session.
**Request** (`msg_mhf_register_event.go`):
```
AckHandle uint32
Unk0 uint16 — unknown
WorldID uint16
LandID uint16
CheckOnly bool — if true, only check whether an event is active (don't join)
[uint8 zeroed padding]
```
**Response** (4 bytes): `WorldID uint8 | LandID uint8 | RaviID uint16`
**Current state**: Implemented in `handlers_register.go`. On `CheckOnly=true` with no active
Raviente semaphore it returns a zeroed 4-byte success. Otherwise it echoes back the world/land IDs
and `s.server.raviente.id`. This is the Raviente siege plumbing reused — whether it is correct for
fort attack (as opposed to the Raviente siege proper) is unknown.
---
### `MsgMhfReleaseEvent` — Client → Server
Sent when a player leaves a fort attack session. Carries `RaviID uint32` (the session ID returned
by RegisterEvent) plus a zeroed `uint32`.
**Current state**: Always returns `_ACK_EFAILED` (0x41). The correct success response format is
unknown — packet `Build()` is also unimplemented.
---
### `MsgMhfGetRestrictionEvent` — Client → Server → Client
Purpose unknown. Likely fetches per-player or per-world restrictions for event participation
(e.g. quest rank gate, prior completion check).
**Current state**: Packet `Parse()` and `Build()` both return `NOT IMPLEMENTED`. Handler is an
empty no-op (`handleMsgMhfGetRestrictionEvent`). No captures of this packet are known.
---
### `MsgMhfSetRestrictionEvent` — Client → Server → Client
Purpose unknown. Likely sets restriction state after an event completes or a player qualifies.
**Request** (`msg_mhf_set_restriction_event.go`):
```
AckHandle uint32
Unk0 uint32 — unknown
Unk1 uint32 — unknown
Unk2 uint32 — unknown
Unk3 uint8 — unknown
```
**Current state**: Handler returns a zeroed 4-byte success. `Build()` is unimplemented. Packet
semantics are entirely unknown.
---
## Shared State (Registers)
The Raviente siege uses three named register banks (`raviRegisterState`, `raviRegisterSupport`,
`raviRegisterGeneral`) served via `MsgSysLoadRegister` and mutated via `MsgSysOperateRegister`.
The fort attack event likely uses the same register mechanism for shared state (fort durability,
Keoaruboru heat accumulation, etc.), but which register IDs and slot indices map to which fort
variables has not been reverse-engineered.
`handleMsgSysNotifyRegister` is a stub (`// stub: unimplemented`) — this handler broadcasts
register updates to other players in the session. It must be implemented for multi-player fort
state synchronisation to work.
---
## Database
The `events` table (`server/migrations/sql/0001_init.sql`) already supports timestamped events:
```sql
CREATE TABLE public.events (
id SERIAL PRIMARY KEY,
event_type event_type NOT NULL,
start_time TIMESTAMP WITH TIME ZONE NOT NULL
);
```
The `event_type` enum currently contains `festa`, `diva`, `vs`, `mezfes`. Adding `ancientdragon`
requires a migration:
```sql
ALTER TYPE event_type ADD VALUE 'ancientdragon';
```
The `feature/enum-event` branch placed this in `schemas/patch-schema/event-ancientdragon.sql`,
which is outside the numbered migration sequence and will not be auto-applied. It needs to be
added as `server/migrations/sql/0002_ancientdragon_event_type.sql` (or folded into the next
migration).
---
## What Needs RE Before Implementation
| Unknown | Where to look | Priority |
|---------|---------------|---------|
| Semantics of `EventType` values (1 vs 2, others?) | Packet captures during event window | High |
| Meaning of Unk1Unk4 in the EnumerateEvent response | Packet captures + client disassembly | Medium |
| Correct `MsgMhfReleaseEvent` success response format | Packet captures | High |
| `MsgMhfGetRestrictionEvent` full structure (parse + response) | Packet captures | High |
| `MsgMhfSetRestrictionEvent` field semantics (Unk0Unk3) | Packet captures | Medium |
| Which register IDs / slots carry fort durability | Packet captures during fort quest | High |
| Keoaruboru heat accumulation register mapping | Packet captures during Keoaruboru quest | High |
| Whether `MsgMhfRegisterEvent` reuses Raviente state correctly for fort | Packet captures + comparison with Raviente behaviour | Medium |
| Original event scheduling cadence (cycle length, trigger time) | Live server logs / JP wiki sources | Low |
---
## What Is Already Understood
- `MsgMhfEnumerateEvent` response wire format (field order, types, conditional quest ID list)
- `StartTime` / `EndTime` are Unix timestamps (confirmed by the feature branch)
- `MsgMhfRegisterEvent` request structure and plausible response format (echoes world/land + ravi ID)
- `MsgMhfReleaseEvent` request structure (carries the ravi session ID)
- `MsgMhfSetRestrictionEvent` request structure (5 fields, semantics unknown)
- The fort event cycles via the `events` table and can share the existing Raviente semaphore infrastructure
- Quest file IDs for fort quests: `20001, 2000420006, 2001120013, 2001820029` (from feature branch config; unvalidated against captures)
---
## Relation to Raviente
The Raviente siege (`sys_channel_server.go`, `handlers_register.go`) is the closest implemented
analogue. It uses the same `MsgMhfRegisterEvent` / `MsgSysOperateRegister` / `MsgSysLoadRegister`
pipeline. Fort attack implementation can likely reuse or extend this infrastructure rather than
building a separate system. The key difference is that Raviente is always available (with its own
scheduling), while fort attacks are event-gated via `MsgMhfEnumerateEvent`.

View File

@@ -57,7 +57,7 @@ Grouped by handler file / game subsystem. Handlers with an open branch are marke
| Handler | Notes |
|---------|-------|
| `handleMsgMhfGetRestrictionEvent` | Fetch event-based gameplay restrictions — **[`feature/enum-event`]** (4 commits) |
| `handleMsgMhfGetRestrictionEvent` | Fetch event-based gameplay restrictions — see `docs/fort-attack-event.md` |
### Guild (`handlers_guild.go`)
@@ -189,7 +189,7 @@ that needs no reply). Others are genuine feature gaps.
| Branch | Commits ahead | Handlers targeted |
|--------|:---:|-------------------|
| `feature/enum-event` | 4 | `GetRestrictionEvent` |
| `feature/enum-event` | 4 | `EnumerateEvent` scheduling only — not mergeable, see `docs/fort-attack-event.md` |
| `feature/conquest` | 4 | Conquest quest handlers |
| `feature/tower` | 4 | Tower handlers |