aether-shards/.agent/rules/ui/SkillTree/RULE.md

4.1 KiB

description globs alwaysApply
Skill Tree UI component - interactive progression tree for Explorer units src/ui/components/SkillTreeUI.js, src/ui/components/SkillTreeUI.ts, src/types/SkillTreeUI.ts false

Skill Tree UI Rule

This rule defines the technical implementation for the SkillTreeUI component. This component renders the interactive progression tree for a specific Explorer.

1. Visual Architecture

Style: "Voxel-Web". We will use CSS 3D Transforms to render the nodes as rotating cubes, keeping the UI lightweight but consistent with the game's aesthetic.

A. The Tree Container (Scroll View)

  • Layout: A vertical flex container
  • Tiers: Each "Rank" (Novice, Apprentice, etc.) is a horizontal row (Flexbox)
  • Connections: An <svg> overlay sits behind the nodes to draw connecting lines (cables)

B. The Node (CSS Voxel)

  • Structure: A div with preserve-3d containing 6 faces
  • Animation:
    • Locked: Static grey cube
    • Available: Slowly bobbing, pulsing color
    • Unlocked: Rotating slowly, emitting a glow (box-shadow)
  • Content: An icon (<img> or FontAwesome) is mapped to the Front face
  • A slide-up panel showing details for the selected node
  • Contains the "Unlock" button

2. TypeScript Interfaces (Data Model)

// src/types/SkillTreeUI.ts

export interface SkillTreeProps {
  /** The Unit object (source of state) */
  unit: Explorer;
  /** The Tree Definition (source of layout) */
  treeDef: SkillTreeDefinition;
}

export interface SkillNodeState {
  id: string;
  def: SkillNodeDefinition;
  status: 'LOCKED' | 'AVAILABLE' | 'UNLOCKED';
  /** Calculated position for drawing lines */
  domRect?: DOMRect;
}

export interface SkillTreeEvents {
  /** Dispatched when user attempts to spend SP */
  'unlock-request': {
    nodeId: string;
    cost: number;
  };
}

3. Interaction Logic

A. Node Status Calculation

The UI must determine the state of every node on render:

  1. UNLOCKED: unit.classMastery.unlockedNodes.includes(node.id)
  2. AVAILABLE: Not unlocked AND parent is Unlocked AND unit.level >= node.req
  3. LOCKED: Everything else

B. Connection Drawing

Since nodes are DOM elements, we need a ResizeObserver to track their positions.

  • Logic: Calculate center (x, y) of Parent Node and Child Node relative to the Container
  • Drawing: Draw a <path> or <line> in the SVG layer with a "Circuit Board" style (90-degree bends)
  • Styling:
    • If Child is Unlocked: Line is Bright Blue/Gold (Neon)
    • If Child is Available: Line is Dim
    • If Child is Locked: Line is Dark Grey

4. Conditions of Acceptance (CoA)

CoA 1: Dynamic Rendering

  • The Tree must handle variable depths (Tier 1 to Tier 5)
  • Nodes must visibly update state immediately when unit prop changes (e.g., after unlocking)

CoA 2: Validation Feedback

  • Clicking a "LOCKED" node should show the inspector but disable the button with a reason (e.g., "Requires: Shield Bash")
  • Clicking an "AVAILABLE" node with 0 SP should show "Insufficient Points"

CoA 3: Responsive Lines

  • If the window resizes, the SVG connecting lines must redraw to connect the centers of the cubes accurately

CoA 4: Scroll Position

  • On open, the view should automatically scroll to center on the highest tier that has an "Available" node, so the player sees their next step

5. Implementation Requirements

Create src/ui/components/SkillTreeUI.js as a LitElement:

  1. CSS 3D: Implement a .voxel-node class using transform-style: preserve-3d to create a cube. Use keyframes for rotation
  2. Layout: Render the tree tiers using flex-direction: column-reverse (Tier 1 at bottom)
  3. SVG Lines: Implement a _updateConnections() method that uses getBoundingClientRect() to draw lines between nodes in an absolute-positioned <svg>. Call this on resize and first render
  4. Interactivity: Clicking a node selects it. Show details in a fixed footer
  5. Logic: Calculate LOCKED/AVAILABLE/UNLOCKED state based on this.unit.unlockedNodes