> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pcm.game/llms.txt
> Use this file to discover all available pages before exploring further.

# Implementation plan 18.2

# Implementation Plan 18.2: Landscape Pivot & State Stabilization

This document outlines the detailed architectural design and step-by-step implementation plan to transition the active game arena from a vertical scrolling portrait layout to a modern, desktop-native landscape split-screen. It also stabilizes the backend state transition engine to eliminate fatal database transaction hangs (`RESOLVING` status lock) caused by floating-point math violations on Prisma `Int` columns.

***

## 📋 Architectural Context & Objectives

Our objective is to consolidate Sandbox Crucible UI/UX feedback and backend state machine failures into a single, cohesive patch. This plan centers on:

1. **Responsive Viewport Containment (Phase A)**: Eliminate global page scrolling. Lock active board UI inside a dual-column flex container fitting standard viewport heights (`h-screen lg:overflow-hidden`).
2. **Component Separation & Collisions (Phase B)**: Adapt contender slot layouts based on their geographic grid placement to prevent interactive action overlaps with the centered News Stub, and sync wallet aesthetics directly with the physical Political Compass Meme coordinates (Labor Top-Left, Dinars Top-Right).
3. **Player Retention Flow (Phase C)**: Eliminate dead-ends inside the game loop by offering clear secondary routing once a match reaches its terminal `RESOLVED` status.
4. **Integer Math Safety (Phase D)**: Ensure 100% database schema compliance by rounding ELO deltas, payouts, distance outcomes, and tax increments before hitting Prisma transactions.

***

## 🛠️ Proposed Changes

### Phase A: The Landscape Pivot

This phase re-architects the primary screen layout of the active game board into a geographic dual-column interface designed specifically for landscape/desktop monitors.

#### \[MODIFY] [page.tsx](file:///home/andrew/pcmtg-core/frontend/app/arena/%5BlobbyId%5D/page.tsx)

* **Target**: Overhaul the outer container and the main grid flow in the `DEBATE_PHASE`.
* **Implementation Design**:
  * Add `h-screen flex flex-col overflow-hidden` to the root layout container in `/arena/[lobbyId]/page.tsx` to completely lock down the viewport height and prevent global body scrollbars.
  * Refactor the compact header to remain height-bounded (`shrink-0 flex items-center justify-between border-b border-gray-800 pb-3 mb-4`).
  * Restructure the main active area (`DEBATE_PHASE`) to split the screen into two main columns using `flex flex-col lg:flex-row items-stretch justify-between gap-6 w-full h-full min-h-0`:
    1. **Left Column (The Arena)**:
       * CSS classes: `flex-1 min-h-0 flex items-center justify-center bg-zinc-950/20 border border-zinc-900/60 rounded-3xl relative overflow-hidden h-full p-4 lg:p-6`.
       * Renders the classic 2x2 Political Compass quadrant grid and the centered, absolutely positioned `Active News Stub`. This column dynamically expands to fill the remaining height.
    2. **Right Column (The Command Center Sidebar)**:
       * CSS classes: `w-full lg:w-96 shrink-0 flex flex-col h-full min-h-0 bg-gray-900/20 border border-gray-800/80 rounded-3xl p-4 lg:p-5 overflow-hidden`.
       * To preserve space and ensure scroll-free immersion, this Right Column will be structured as an internally scrollable command panel using `overflow-y-auto h-full pr-1 scrollbar-thin scrollbar-thumb-zinc-800`.
       * Inside this column, we place:
         * **Tactical HUD**: The existing coordinates map, base values, delta preview calculations, and the primary "Lock In Vector Stack" action button.
         * **BASED/CRINGE Action Buttons**: The local player's primary vote-casting buttons when they are required to vote on opponents' vectors.
         * **PlayerHand (The Fanned Hand)**: Relocate `PlayerHand` from the bottom-level full-width section to the bottom section of this scrollable Right Column sidebar. The cards will render neatly as an internal deck or compact list within the command center, ensuring all interaction remains visible on a single screen.

***

### Phase B: Slot Collisions & Wallet Alignment

This phase adapts layout components dynamically to resolve collision overlapping and realigns visual currency geography.

#### 1. Preventing Contender Slot Center Collisions

* **File**: [page.tsx](file:///home/andrew/pcmtg-core/frontend/app/arena/%5BlobbyId%5D/page.tsx)
* **Target**: Inside the `ContenderSlot` component, adapt the layout flow dynamically based on the quadrant the slot represents.
* **Problem**: In Authoritarian (top row) slots, rendering the BASED/CRINGE buttons below the card slot pushes the buttons towards the absolute center of the Political Compass, causing them to collide with and overlap the central `Active News Stub`.
* **Solution**:
  * Add an optional `quadrant?: string` prop to `ContenderSlotProps` and pass the player's quadrant from the parent rendering loop.
  * Inside `ContenderSlot`, conditionally toggle the layout flow of the contender card container and action buttons.
  * If the player's quadrant is `AUTH_LEFT` or `AUTH_RIGHT`, replace `flex-col` with `flex-col-reverse` on the parent card-and-button column wrapper:
    ```typescript theme={null}
    const isAuthoritarian = quadrant === 'AUTH_LEFT' || quadrant === 'AUTH_RIGHT';
    return (
        <div className={`flex ${isAuthoritarian ? 'flex-col-reverse' : 'flex-col'} items-center gap-3 shrink-0`}>
            {/* Card and Buttons render in inverse order for Top-Row quadrants */}
        </div>
    );
    ```
  * This pushes the BASED/CRINGE buttons *above* the card in the Authoritarian slots (upwards and away from the center News Stub) and keeps them *below* the card in Libertarian slots (downwards and away from the center News Stub), entirely eliminating overlap collision.

#### 2. Compass-Aligned Wallet Currency Order

* **File**: [WalletOverlay.tsx](file:///home/andrew/pcmtg-core/frontend/components/WalletOverlay.tsx)
* **Target**: Swap the rendering index of **Dinars** and **Labor** inside the grid display to mirror geographic political positions.
* **Geography Map**:
  * **Top-Left**: `AUTH_LEFT` (Labor / ⚒)
  * **Top-Right**: `AUTH_RIGHT` (Dinars / đ)
  * **Bottom-Left**: `LIB_LEFT` (Pronouns / ❀)
  * **Bottom-Right**: `LIB_RIGHT` (Monke / 🐒)
* **Modifications**:
  * Swap the rendering order in BOTH **Inline Mode** (lines 251-285) and the **Floating Drawer list** (lines 362-396) so `labor` (Labor) is declared and rendered before `dinars` (Dinars).
  * This ensures that when read as a 2x2 grid in inline mode, Labor correctly aligns with the top-left and Dinars with the top-right, creating a flawless mental map with the Political Compass.

***

### Phase C: The Infinite Loop (Post-Match Summary Navigation)

This phase addresses post-match navigation limits by providing immediate secondary looping actions.

#### \[MODIFY] [page.tsx](file:///home/andrew/pcmtg-core/frontend/app/arena/%5BlobbyId%5D/page.tsx)

* **Target**: Rework the action segment of the `RESOLVED` page view (lines 985-992).
* **Problem**: When a match concludes, only a single button is provided to "Return to Arena Entryway", which interrupts the play-queue loop.
* **Solution**:
  * Add two prominent, high-fidelity action buttons positioned side-by-side or cleanly stacked inside the summary pane:
    1. **"Next Match" (Primary Action)**:
       * Renders as a highly visible, pulsing green/indigo gradient button (`bg-gradient-to-r from-emerald-600 to-indigo-600 hover:from-emerald-500 hover:to-indigo-500 shadow-lg hover:shadow-emerald-500/20`).
       * Redirects the player back to `/arena` and automatically triggers a new matchmaking queue or triggers an immediate join sequence to maintain high combat engagement.
    2. **"Return to Entryway" (Secondary Action)**:
       * Renders as a neutral, high-quality dark bordered button (`border border-zinc-700 hover:bg-zinc-800 text-zinc-300`).
       * Gracefully exits the lobby and redirects the player to the entryway dashboard (`/arena`).

***

### Phase D: The RESOLVING State Hotfix (Backend Integer Safety)

This phase resolves a critical backend exception where floating-point math violations on Postgres `Int` columns caused a fatal transaction rollback, locking lobbies in an infinite `RESOLVING` state.

#### \[MODIFY] [resolve.js](file:///home/andrew/pcmtg-core/backend/src/routes/resolve.js)

* **Problem**: Faction tax yields (e.g. `payout * 0.10`) and multi-axis Euclidean distances generate float values (e.g. `23.7291`). When these are sent as values to Prisma database `increment` operations on strict Postgres integer columns (such as `elo` and `currentGip`), the SQL query crashes with a type exception, forcing a database rollback. Because the state machine status update to `RESOLVED` occurs after this database transaction, the table remains permanently locked in `RESOLVING`.
* **Solution**:
  * Update `resolve.js` to ensure absolute type compliance by wrapping all ELO, distance, and tax calculations in `Math.round()` prior to hitting database operations.
  * **Refactor `applyPlayerResolveMutation`** (lines 75-148):
    * Wrap the database ELO update:
      ```javascript theme={null}
      await tx.playerAccount.update({
        where: { id: player.postgresId },
        data: { elo: { increment: Math.round(player.eloDelta) } }
      });
      ```
    * Wrap the database Wallet balance update:
      ```javascript theme={null}
      const finalBalance = Math.max(0, currentBalance + Math.round(player.nativePayout));
      const walletUpdateData = { [player.nativeCurrency]: finalBalance };
      if (player.basedPill) {
        walletUpdateData.based_pills = { increment: Math.round(player.basedPill) };
      }
      ```
    * Wrap GIP tax calculations and faction updates:
      ```javascript theme={null}
      const tax = Math.round(player.nativePayout * 0.10);
      player.gipDelta = tax;
      if (tax > 0) {
        taxUpdates.push({ quadrantId: player.quadrantId, tax });
        await tx.quadrantMacroState.update({
          where: { ticker: player.quadrantId },
          data: { currentGip: { increment: Math.round(tax) } }
        });
      }
      ```
  * **Wrap Distance Outcomes**:
    * Wrap `calculateDistance` outcomes or distance values inside the ranking map loop (line 254) in `Math.round()` to guarantee strict integer compliance before values are distributed to the payload:
      ```javascript theme={null}
      const distance = Math.round(calculateDistance(playerCalcVector, targetCalcVector));
      ```
  * **Trace Logging Overhaul (Catch Block)** (lines 482-486):
    * Expand the catch block of the `POST /debate/resolve` router to dump complete error stack traces:
      ```javascript theme={null}
      } catch (error) {
        console.error('[RESOLVE ENDPOINT ERROR STACK]:', error); // Dump full stack, not just message
        return res.status(400).json({ error: error.message, stack: error.stack });
      }
      ```

***

## 🧪 Verification Plan

### Automated Tests

Run the Express backend test suite to ensure that ELO and payouts resolve cleanly without any SQL compiler/runtime type exceptions:

```bash theme={null}
cd backend && npm run test
```

### Manual Verification Flow

1. **Currency Orientation Audit**:
   * Open `/arena` (with local servers running). Inspect the dashboard wallet overlay. Confirm that **Labor** is listed top-left, and **Dinars** is listed top-right. Open the profile control dropdown, and confirm the vertical balance order lists Labor then Dinars.
2. **Layout Overhaul & Scroll-Lock Test**:
   * Join a match (`/arena/[lobbyId]`). Verify that the active debate phase is presented as a high-end landscape split layout. Attempt to force-scroll the page vertically—the page viewport should remain completely locked and static.
   * Confirm that the `PlayerHand` renders cleanly inside the Right Column sidebar (Command Center) alongside the Tactical HUD and Lock-In action button.
3. **Collision Avoidance Audit**:
   * Position a player in `AUTH_LEFT` or `AUTH_RIGHT`. Open the vote-casting prompt. Confirm that the "BASED/CRINGE" voting buttons render *above* the card frame, perfectly avoiding any central News Stub overlap.
4. **Prisma Integer Compliance & Flow Check**:
   * Play a match to completion. Ensure that the backend successfully resolves and transitions the state from `RESOLVING` to `RESOLVED` with zero database transaction exceptions.
   * Observe that the post-match summary displays both the "Next Match" queue button and "Return to Entryway" secondary button side-by-side.
