L2J connector architecture
Responsibility boundaries, lifecycle, and delivery safety for L2J
Read this after the first server connects. It explains why the L2J integration contains pack code while the connector core owns Forgeport protocol and delivery safety.
Responsibility boundary
| Forgeport connector core | Your game-pack integration |
|---|---|
| Connection, authentication, reconnect | JDBC connection provider |
| Request parsing and response serialization | Account and character SQL |
| Capability inference | Password and access-level policy |
| Catalog matching, ranking, and limits | Mapping loaded templates to domain models |
portal_command schema and migration | Live player and template lookup |
| Replay and state-transition validation | Pack-specific validation and mutation calls |
APPLYING → mutation → APPLIED ordering | Explicit EnterWorld event hook |
The core imports no pack and performs no reflection, scanning, autodetection, or runtime adapter selection. Your integration imports the public domain API and the exact game-server classes it uses.
Forgeport platform
↓
L2J connector core
↓
ForgeportIntegration.java
↓
game server APIs and databaseConnection lifecycle
- The GameServer builds a
Bridgeafter world/data initialization. Forgeport.start(bridge)loadsconfig/portal.properties.- When journaled delivery exists, the core creates or migrates
portal_commandthrough the registered connection provider. - The connector authenticates with the generated token.
- It advertises capabilities inferred from actual registrations.
- Heartbeats and requests use those registered providers and handlers.
- A closed connection retries with capped exponential backoff.
Type-safe request boundary
The connector core parses incoming protocol JSON. Pack code receives normalized
Java values such as DeliveryTarget, ItemGrant, and SkillGrant, and returns
objects such as GameCharacter, GameItem, or CredentialsResult.
Catalog providers return fresh streams. The core owns query normalization, exact/prefix/substring ranking, deterministic sorting, deduplication, limits, and protocol serialization.
Delivery safety
Forgeport may replay a command after a reconnect. The stable command id is the journal key, but it never crosses into pack code.
handler validates and returns a prepared change
→ core inserts APPLYING
→ core executes the prepared change
→ core updates APPLYING to APPLIED
→ core reports APPLIED- An existing
APPLIEDcommand returnsALREADY_APPLIEDwithout running again. - A refusal or retry returned during validation writes no journal row.
- Failure to persist
APPLYINGprevents the game change. - An exception after
APPLYINGhas an uncertain final result and requires reconciliation instead of automatic replay. - Failure to persist
APPLIEDnever reports success. - State updates accept only a current
APPLYINGrow and exactly one update.
For a live-world mutation, Delivery.apply(...) preserves this ordering but
cannot make game memory and JDBC one atomic resource. A crash inside that gap is
therefore intentionally marked for reconciliation.
For database-only entitlements, Delivery.applyInTransaction(...) uses one
connection and transaction for APPLYING, pack SQL, and APPLIED. A rollback
is retryable; an unknown commit result still requires reconciliation. Its
optional post-commit callback is for cache synchronization only.
Public API surface
The integration uses Forgeport, Bridge, Delivery, CharacterServices,
the model records/enums, and small functional interfaces. Internal classes such
as protocol codecs, grant parsing, the JDBC journal, and connector dispatch are
not part of pack integration.