Skip to content

First Login

Purpose

First login is a stateful onboarding flow that doubles as character creation. A new account does not yet have a Player record; the flow runs an AI-driven dialogue against a security guard at the Callisto Colony shipyard, evaluates the player's persuasion against rarity-tier thresholds, and on success creates the Player row with a starter ship, credits, and initial state. The flow is resumable, has a guaranteed-safe failure path (escape pod), and works fully offline (manual provider fallback).

Inputs

The state machine reads: - The player's User row (auth identity, locale). - PlayerFirstLoginState row (if any) — has the player completed onboarding before? - FirstLoginSession row (if any) — in-progress session for resume. - ShipPresentationOptions — the 3+ ships offered this attempt. - ShipRarityConfig table — admin-tunable spawn probability + persuasion thresholds. - DialogueExchange rows for this session — full back-and-forth history. - AI provider chain (same as ARIA dialogue) for response evaluation. - The player's free-form text input.

The system fires on: - GET /api/v1/first-login/status — bootstraps or resumes a session. - POST /api/v1/first-login/session — start a new session. - POST /api/v1/first-login/dialogue — submit the player's next response. - POST /api/v1/first-login/claim — finalize the ship claim once persuasion succeeds. - POST /api/v1/first-login/abandon — accept the escape-pod fallback.

Process

                  ┌──────────────────────┐
                  │   App launch (auth)  │
                  └──────────┬───────────┘
                             │
                  ┌──────────▼──────────────┐
                  │ should_show_first_login │
                  │ (PlayerFirstLoginState  │
                  │  null or completed=False)│
                  └──────────┬──────────────┘
                  no  →  go to game (skip flow)
                  yes →  enter state machine
                             │
              ┌──────────────▼──────────────┐
   STATE 1 →  │  WELCOME / NARRATIVE OPEN   │
              │  (shipyard backdrop, cat)   │
              └──────────────┬──────────────┘
                             │
              ┌──────────────▼──────────────┐
   STATE 2 →  │  SHIP PRESENTATION          │
              │  - generate 3+ ships, rolled│
              │    against ShipRarityConfig │
              │  - persist ShipPresentation │
              │    Options                  │
              └──────────────┬──────────────┘
                             │
              ┌──────────────▼──────────────┐
   STATE 3 →  │  SHIP CHOICE                │
              │  player picks claimed ship  │
              │  → record_player_ship_claim │
              └──────────────┬──────────────┘
                             │
              ┌──────────────▼──────────────┐
   STATE 4 →  │  TUTORIAL / DIALOGUE LOOP   │
              │  guard probes 4 topics:     │
              │   • identity_verification   │
              │   • arrival_details         │
              │   • ship_knowledge          │
              │   • situational_awareness   │
              │  per turn:                  │
              │   - generate_guard_question │
              │   - record_player_answer    │
              │   - _analyze_player_response│
              │     (persuasiveness, conf., │
              │      consistency, skill,    │
              │      inconsistencies, key   │
              │      info, believability)   │
              │  cat_mention_bonus: +0.15   │
              └──────────────┬──────────────┘
                             │
              ┌──────────────▼──────────────┐
   STATE 5 →  │  EVALUATE OUTCOME           │
              │  _evaluate_dialogue_outcome │
              │  compare aggregate          │
              │  persuasion vs rarity       │
              │  threshold (weak/avg/strong)│
              └──┬───────────────────┬──────┘
                 │ pass              │ fail (after retries)
                 ▼                   ▼
       ┌─────────────────┐   ┌──────────────────────┐
       │ STATE 6: SPAWN  │   │ STATE 6': ESCAPE POD │
       │ - create Player │   │ auto_approve_escape  │
       │ - create Ship   │   │ _pod (always passes) │
       │ - set credits   │   │ - 500 credits        │
       │   per table     │   │ - negotiation=weak   │
       │ - sector=1      │   └──────────┬───────────┘
       │ - mark first    │              │
       │   _login.compl. │              │
       └────────┬────────┘              │
                │                       │
                └───────────┬───────────┘
                            ▼
              ┌──────────────────────────────┐
              │  STATE 7: COMPLETE           │
              │  complete_first_login()      │
              │  - PlayerFirstLoginState     │
              │    persisted                 │
              │  - finalize Ship + Player    │
              │  - emit notification         │
              └──────────────────────────────┘

Ship-claim difficulty matrix

DEFAULT_SHIP_CONFIGS from the service:

Ship Tier Spawn % Base credits Weak / Avg / Strong threshold
Escape Pod 1 100 1,000 0.30 / 0.30 / 0.30
Light Freighter 2 50 2,500 0.40 / 0.35 / 0.30
Scout Ship 3 25 2,000 0.55 / 0.50 / 0.45
Fast Courier 3 20 3,000 0.55 / 0.50 / 0.45
Cargo Hauler 4 10 5,000 0.65 / 0.55 / 0.50
Defender 5 5 7,000 0.75 / 0.65 / 0.60
Colony Ship 6 3 10,000 0.80 / 0.70 / 0.65
Carrier 7 1 15,000 0.85 / 0.75 / 0.70

Threshold selected based on the AI's negotiation_skill classification (weak / average / strong) of the player's dialogue.

Persistence (resume)

  • FirstLoginSession.id is bound to the User.
  • Each DialogueExchange is persisted as it happens; reload mid-flow returns the full history.
  • Re-opening the app at any state replays from the last persisted state.

Skip / abandon

  • Authenticated player whose PlayerFirstLoginState.completed = True is routed to the main game; the flow does not run.
  • A player who explicitly abandons calls auto_approve_escape_pod — the safest outcome (escape pod + 500 credits + negotiation_skill = weak).

AI evaluation

The evaluator (_analyze_player_response) returns:

{
  persuasiveness: float (0..1),
  confidence_level: enum,
  consistency: float (0..1),
  negotiation_skill: weak | average | strong,
  inconsistencies: [],
  key_information: { player_name?, claimed_ship?, ... },
  overall_believability: float (0..1)
}

The provider chain matches the ARIA dialogue chain: primary → secondary → manual rule-based provider. The manual provider replicates the same response shape using pattern-matching, so first login works fully offline.

Cat easter egg

If the player mentions the orange cat from the opening narrative, the manual provider awards a flat +0.15 persuasion bonus. The bonus is applied regardless of the active provider (the rule-based detector runs in parallel). The detection is stored on the session for medal / achievement use later.

Outputs / state changes

On success path: - Player row created (sector_id=1, current_sector_id=1, default turns=1000, max_turns=1000, military_rank="Recruit", personal_reputation=0, aria_consciousness_level=1, aria_bonus_multiplier=1.0). - Player.credits set to claimed-ship's base credits (1,000–15,000). - Ship row created via ship_specifications_seeder and assigned as Player.current_ship. - PlayerFirstLoginState.completed = True, with negotiation outcome / cat-boost flags. - FirstLoginSession.outcome = success.

On escape-pod path: - Player row created with Escape Pod ship, 500 credits, negotiation_skill = weak. - All other defaults identical.

Always: - DialogueExchange rows record the full conversation. - ShipPresentationOptions records which ships were shown. - notification event fires on completion.

Invariants

  1. A User cannot finish first login twice — once PlayerFirstLoginState.completed = True, the flow refuses to start a new session.
  2. The escape-pod outcome always succeeds — there is no failure mode that leaves the player without a ship.
  3. Every dialogue evaluation persists before a state advance — interruption does not lose history.
  4. Spawn (state 6) is a single transaction: Player, Ship, and FirstLoginState change together.
  5. AI provider failure never blocks the flow — manual fallback is always available.
  6. Player.first_login.completed = True means no side-effects are pending.
  7. Initial sector is always Sector 1 of Terran Space (the canonical Earth-area starter region).

Failure modes

Mode Target handling
AI provider down Manual fallback evaluates dialogue; flow proceeds normally.
Player closes tab mid-flow State persisted at every transition; resume picks up where they left off.
Player insists on a ship they can't justify After N attempts (default 3), flow auto-routes to escape pod with explicit notice.
Spawn transaction fails Whole transaction rolls back; player can retry; session not marked complete.
Duplicate user (race) Unique constraint on Player.user_id rejects the second insert; second request returns the first Player row.
Provider returns malformed analysis Default to negotiation_skill = average and persuasiveness = 0.5; do not crash.
Ship spec missing in seeder Fail spawn; surface clear admin-facing error; player retries with a different ship.
Locale unsupported Default to English; cat-detection still works (English-only patterns + locale-specific aliases).

Source map

Concern Path (target)
Orchestrator services/gameserver/src/services/first_login_service.py
AI dialogue helpers services/gameserver/src/services/ai_dialogue_service.py
Multi-provider AI services/gameserver/src/services/ai_provider_service.py
Manual fallback services/gameserver/src/services/enhanced_manual_provider.py
Guard personalities services/gameserver/src/utils/guard_personalities.py
Ship specifications seeder services/gameserver/src/core/ship_specifications_seeder.py
First-login models services/gameserver/src/models/first_login.py
API routes services/gameserver/src/api/routes/first_login.py
Admin tools services/gameserver/src/api/routes/admin_first_login.py
Client component services/player-client/src/pages/FirstLogin/*