L2J

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 coreYour game-pack integration
Connection, authentication, reconnectJDBC connection provider
Request parsing and response serializationAccount and character SQL
Capability inferencePassword and access-level policy
Catalog matching, ranking, and limitsMapping loaded templates to domain models
portal_command schema and migrationLive player and template lookup
Replay and state-transition validationPack-specific validation and mutation calls
APPLYING → mutation → APPLIED orderingExplicit 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 database

Connection lifecycle

  1. The GameServer builds a Bridge after world/data initialization.
  2. Forgeport.start(bridge) loads config/portal.properties.
  3. When journaled delivery exists, the core creates or migrates portal_command through the registered connection provider.
  4. The connector authenticates with the generated token.
  5. It advertises capabilities inferred from actual registrations.
  6. Heartbeats and requests use those registered providers and handlers.
  7. 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 APPLIED command returns ALREADY_APPLIED without running again.
  • A refusal or retry returned during validation writes no journal row.
  • Failure to persist APPLYING prevents the game change.
  • An exception after APPLYING has an uncertain final result and requires reconciliation instead of automatic replay.
  • Failure to persist APPLIED never reports success.
  • State updates accept only a current APPLYING row 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.

On this page