Files
Erupe/CLAUDE.md
Houmgaor c5fd0444f4 docs: update architecture docs to reflect 6-service layer
The CLAUDE.md layered architecture section implied all handlers go
through services. In practice, services handle cross-repo coordination
while handlers call repos directly for simple CRUD. Updated the
diagram, added a services table, and added an "Adding Business Logic"
guide. Marked improvements.md item 4 as done.
2026-02-24 16:18:31 +01:00

160 lines
8.1 KiB
Markdown

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Erupe is a Go server emulator for Monster Hunter Frontier, a shut-down MMORPG. It handles authentication, world selection, and gameplay in a single binary running four TCP/HTTP servers. Go 1.25+ required.
## Build & Test Commands
```bash
go build -o erupe-ce # Build server
go build -o protbot ./cmd/protbot/ # Build protocol bot
go test -race ./... -timeout=10m # Run tests (race detection mandatory)
go test -v ./server/channelserver/... # Test one package
go test -run TestHandleMsg ./server/channelserver/... # Single test
go test -coverprofile=coverage.out ./... && go tool cover -func=coverage.out # Coverage (CI requires ≥50%)
gofmt -w . # Format
golangci-lint run ./... # Lint (v2 standard preset, must pass CI)
```
Docker (from `docker/`):
```bash
docker compose up db pgadmin # PostgreSQL + pgAdmin (port 5050)
docker compose up server # Erupe (after DB is healthy)
```
## Architecture
### Four-Server Model (single binary, orchestrated from `main.go`)
```
Client ←[Blowfish TCP]→ Sign Server (53312) → Authentication, sessions
→ Entrance Server (53310) → Server list, character select
→ Channel Servers (54001+) → Gameplay, quests, multiplayer
→ API Server (8080) → REST API (/health, /version, V2 sign)
```
Each server is in its own package under `server/`. The channel server is by far the largest (~200 files).
### Channel Server Packet Flow
1. `network/crypt_conn.go` decrypts TCP stream (Blowfish)
2. `network/mhfpacket/` deserializes binary packet into typed struct (~453 packet types, one file each)
3. `handlers_table.go` dispatches via `buildHandlerTable()` (~200+ `PacketID → handlerFunc` entries)
4. Handler in appropriate `handlers_*.go` processes it (organized by game system)
Handler signature: `func(s *Session, p mhfpacket.MHFPacket)`
### Layered Architecture
```
handlers_*.go → svc_*.go (service layer) → repo_*.go (data access)
(where needed) ↓
repo_interfaces.go (21 interfaces)
repo_mocks_test.go (test doubles)
```
- **Handlers**: Parse packets, call services or repos, build responses. Must always send ACK (see Error Handling below). Simple CRUD operations call repos directly; multi-step or cross-repo logic goes through services.
- **Services**: Encapsulate business logic that spans multiple repos or requires orchestration beyond simple CRUD. Not a mandatory pass-through — handlers call repos directly for straightforward data access.
- **Repositories**: All SQL lives in `repo_*.go` files behind interfaces in `repo_interfaces.go`. The `Server` struct holds interface types, not concrete implementations. Handler code must never contain inline SQL.
- **Sign server** has its own repo pattern: 3 interfaces in `server/signserver/repo_interfaces.go`.
#### Services
| Service | File | Methods | Purpose |
|---------|------|---------|---------|
| `GuildService` | `svc_guild.go` | 6 | Member operations, disband, resign, leave, scout — triggers cross-repo mail |
| `MailService` | `svc_mail.go` | 4 | Send/broadcast mail with message type routing |
| `GachaService` | `svc_gacha.go` | 6 | Gacha rolls (normal/stepup/box), point transactions, reward resolution |
| `AchievementService` | `svc_achievement.go` | 2 | Achievement fetch with score computation, increment |
| `TowerService` | `svc_tower.go` | 3 | Tower gem management, tenrourai progress capping, guild RP donation |
| `FestaService` | `svc_festa.go` | 2 | Event lifecycle (expiry/cleanup/creation), soul submission filtering |
Each service takes repo interfaces + `*zap.Logger` in its constructor, making it testable with mocks. Tests live in `svc_*_test.go` files alongside the service.
### Key Subsystems
| File(s) | Purpose |
|---------|---------|
| `sys_session.go` | Per-connection state: character, stage, semaphores, send queue |
| `sys_stage.go` | `StageMap` (`sync.Map`-backed), multiplayer rooms/lobbies |
| `sys_channel_server.go` | Server lifecycle, Raviente shared state, world management |
| `sys_semaphore.go` | Distributed locks for events (Raviente siege, guild ops) |
| `channel_registry.go` | Cross-channel operations (worldcast, session lookup, mail) |
| `handlers_cast_binary.go` | Binary state relay between clients (position, animation) |
| `handlers_helpers.go` | `loadCharacterData`/`saveCharacterData` shared helpers |
| `guild_model.go` | Guild data structures |
### Binary Serialization
`common/byteframe.ByteFrame` — sequential big-endian reads/writes with sticky error pattern (`bf.Err()`). Used for all packet parsing, response building, and save data manipulation. Use `encoding/binary` only for random-access reads at computed offsets on existing `[]byte` slices.
### Database
PostgreSQL with embedded auto-migrating schema in `server/migrations/`:
- `sql/0001_init.sql` — consolidated baseline
- `seed/*.sql` — demo data (applied via `migrations.ApplySeedData()` on fresh DB)
- New migrations: `sql/0002_description.sql`, etc. (each runs in its own transaction)
The server runs `migrations.Migrate()` automatically on startup.
### Configuration
Two reference files: `config.example.json` (minimal) and `config.reference.json` (all options). Loaded via Viper in `config/config.go`. All defaults registered in code. Supports 40 client versions (S1.0 → ZZ) via `ClientMode`. If `config.json` is missing, an interactive setup wizard launches at `http://localhost:8080`.
### Protocol Bot (`cmd/protbot/`)
Headless MHF client implementing the complete sign → entrance → channel flow. Shares `common/` and `network/crypto` but avoids `config` dependency via its own `conn/` package.
## Concurrency
Lock ordering: `Server.Mutex → Stage.RWMutex → semaphoreLock`. Stage map uses `sync.Map`; individual `Stage` structs have `sync.RWMutex`. Cross-channel operations go exclusively through `ChannelRegistry` — never access other servers' state directly.
## Error Handling in Handlers
The MHF client expects `MsgSysAck` for most requests. Missing ACKs cause client softlocks. On error paths, always send `doAckBufFail`/`doAckSimpleFail` before returning.
## Testing
- **Mock repos**: Handler tests use `repo_mocks_test.go` — no database needed
- **Table-driven tests**: Standard pattern (see `handlers_achievement_test.go`)
- **Race detection**: `go test -race` is mandatory in CI
- **Coverage floor**: CI enforces ≥50% total coverage
## Adding a New Packet
1. Define struct in `network/mhfpacket/msg_*.go` (implements `MHFPacket` interface: `Parse`, `Build`, `Opcode`)
2. Add packet ID constant in `network/packetid.go`
3. Register handler in `server/channelserver/handlers_table.go`
4. Implement handler in appropriate `handlers_*.go` file
## Adding a Database Query
1. Add method signature to the relevant interface in `repo_interfaces.go`
2. Implement in the corresponding `repo_*.go` file
3. Add mock implementation in `repo_mocks_test.go`
## Adding Business Logic
If the new logic involves multi-step orchestration, cross-repo coordination, or non-trivial data transformation:
1. Add or extend a service in the appropriate `svc_*.go` file
2. Wire it in `sys_channel_server.go` (constructor + field on `Server` struct)
3. Add tests in `svc_*_test.go` using mock repos
4. Call the service from the handler instead of the repo directly
Simple CRUD operations should stay as direct repo calls from handlers — not everything needs a service.
## Known Issues
See `docs/anti-patterns.md` for structural patterns and `docs/technical-debt.md` for specific fixable items with file paths and line numbers.
## Contributing
- Branch naming: `feature/`, `fix/`, `refactor/`, `docs/`
- Commit messages: conventional commits (`feat:`, `fix:`, `refactor:`, `docs:`)
- Update `CHANGELOG.md` under "Unreleased" for all changes