hensei-api/docs/planning/artifacts-feature-plan.md

12 KiB

Artifacts Feature Plan

Overview

Artifacts are character equipment items in Granblue Fantasy that provide stat bonuses through a skill system. This document outlines the implementation plan for tracking artifacts in user collections and parties, following the same pattern as the existing weapon/summon collection system.

Business Requirements

User Stories

  1. As a user, I want to record artifacts I own in the game so I can track my collection
  2. As a user, I want to save artifact skill configurations so I can reference them when team building
  3. As a user, I want to track multiple copies of the same artifact with different skill configurations
  4. As a user, I want to equip artifacts from my collection to characters in parties
  5. As a user, I want to quick-build artifacts directly in parties without adding to my collection
  6. As a user, I want to see which characters have artifacts equipped in my parties

Core Concepts

Collection vs Grid Artifacts

  • Collection Artifacts: Represent artifacts in the user's inventory, independent of any party
  • Grid Artifacts: Represent artifacts equipped to characters within a specific party

Artifact Types

  • Standard Artifacts: Max level 150, 3 skill slots
  • Quirk Artifacts: Max level 200, 4 skill slots (character-specific)

Skill System

  • Group I Skills: Attack, HP, critical rate, etc. (max 2 per artifact)
  • Group II Skills: Enmity, stamina, charge bar speed, etc. (max 1 per artifact)
  • Group III Skills: Damage cap increases (max 1 per artifact)

Each skill has its own level that users can set when recording their artifacts.

Item Uniqueness Rules

  • Artifacts: Multiple instances of the same artifact allowed per user, each with unique skill configurations
  • Grid Artifacts: One artifact per character in a party

Technical Design

Database Schema

artifacts (Canonical Game Data)

- id: uuid (primary key)
- name_en: string (not null)
- name_jp: string (not null)
- series: integer (1=Ominous, 2=Saint, 3=Jinyao, etc.)
- weapon_specialty: integer (1=sabre, 2=dagger, etc.)
- rarity: integer (3=R, 4=SR, 5=SSR)
- is_quirk: boolean (default false)
- max_level: integer (default 5 for standard, 1 for quirk)
- created_at: timestamp
- updated_at: timestamp

Indexes:
- index on weapon_specialty
- index on rarity
- index on is_quirk

artifact_skills (Canonical Game Data)

- id: uuid (primary key)
- name_en: string (not null)
- name_jp: string (not null)
- skill_group: integer (1=Group I, 2=Group II, 3=Group III)
- effect_type: string (atk, hp, ca_dmg, skill_dmg, etc.)
- base_values: jsonb (array of possible starting values)
- growth_value: decimal (amount gained per level)
- max_level: integer (default 5)
- description_en: text
- description_jp: text
- created_at: timestamp
- updated_at: timestamp

Indexes:
- index on skill_group
- index on effect_type

collection_artifacts (User Collection)

- id: uuid (primary key)
- user_id: uuid (foreign key to users, not null)
- artifact_id: uuid (foreign key to artifacts, not null)
- level: integer (1-200)
- skill1_id: uuid (foreign key to artifact_skills)
- skill1_level: integer (1-15)
- skill2_id: uuid (foreign key to artifact_skills)
- skill2_level: integer (1-15)
- skill3_id: uuid (foreign key to artifact_skills)
- skill3_level: integer (1-15)
- skill4_id: uuid (foreign key to artifact_skills, optional for standard artifacts)
- skill4_level: integer (1-15, optional)
- created_at: timestamp
- updated_at: timestamp

Indexes:
- index on user_id
- index on artifact_id
- index on [user_id, artifact_id]

grid_artifacts (Party Equipment)

- id: uuid (primary key)
- party_id: uuid (foreign key to parties, not null)
- grid_character_id: uuid (foreign key to grid_characters, not null)
- collection_artifact_id: uuid (foreign key to collection_artifacts, optional)

# Quick-build fields (when not using collection)
- artifact_id: uuid (foreign key to artifacts, optional)
- level: integer (1-200)
- skill1_id: uuid (foreign key to artifact_skills)
- skill1_level: integer (1-15)
- skill2_id: uuid (foreign key to artifact_skills)
- skill2_level: integer (1-15)
- skill3_id: uuid (foreign key to artifact_skills)
- skill3_level: integer (1-15)
- skill4_id: uuid (foreign key to artifact_skills, optional)
- skill4_level: integer (1-15, optional)

- created_at: timestamp
- updated_at: timestamp

Indexes:
- unique index on grid_character_id (one artifact per character)
- index on party_id
- index on collection_artifact_id
- index on artifact_id

Model Relationships

# User model additions
has_many :collection_artifacts, dependent: :destroy

# Artifact model (canonical game data)
class Artifact < ApplicationRecord
  has_many :collection_artifacts, dependent: :restrict_with_error
  has_many :grid_artifacts, dependent: :restrict_with_error

  validates :name_en, :name_jp, presence: true
  validates :rarity, inclusion: { in: 3..5 }

  scope :standard, -> { where(is_quirk: false) }
  scope :quirk, -> { where(is_quirk: true) }

  def max_level
    is_quirk ? 200 : 150
  end

  def max_skill_slots
    is_quirk ? 4 : 3
  end
end

# ArtifactSkill model (canonical game data)
class ArtifactSkill < ApplicationRecord
  validates :name_en, :name_jp, presence: true
  validates :skill_group, inclusion: { in: 1..3 }
  validates :max_level, presence: true

  scope :group_i, -> { where(skill_group: 1) }
  scope :group_ii, -> { where(skill_group: 2) }
  scope :group_iii, -> { where(skill_group: 3) }
end

# CollectionArtifact model
class CollectionArtifact < ApplicationRecord
  belongs_to :user
  belongs_to :artifact
  belongs_to :skill1, class_name: 'ArtifactSkill', optional: true
  belongs_to :skill2, class_name: 'ArtifactSkill', optional: true
  belongs_to :skill3, class_name: 'ArtifactSkill', optional: true
  belongs_to :skill4, class_name: 'ArtifactSkill', optional: true

  has_one :grid_artifact, dependent: :nullify

  validates :level, numericality: {
    greater_than_or_equal_to: 1,
    less_than_or_equal_to: 200
  }
  validates :skill1_level, :skill2_level, :skill3_level,
            numericality: { greater_than_or_equal_to: 1, less_than_or_equal_to: 15 },
            allow_nil: true
  validates :skill4_level,
            numericality: { greater_than_or_equal_to: 1, less_than_or_equal_to: 15 },
            allow_nil: true

  # Validate skill4 only exists for quirk artifacts
  validate :skill4_only_for_quirk

  def skills
    [skill1, skill2, skill3, skill4].compact
  end

  private

  def skill4_only_for_quirk
    if skill4_id.present? && artifact && !artifact.is_quirk
      errors.add(:skill4_id, "can only be set for quirk artifacts")
    end
  end
end

# GridArtifact model
class GridArtifact < ApplicationRecord
  belongs_to :party
  belongs_to :grid_character
  belongs_to :collection_artifact, optional: true
  belongs_to :artifact, optional: true # For quick-build
  belongs_to :skill1, class_name: 'ArtifactSkill', optional: true
  belongs_to :skill2, class_name: 'ArtifactSkill', optional: true
  belongs_to :skill3, class_name: 'ArtifactSkill', optional: true
  belongs_to :skill4, class_name: 'ArtifactSkill', optional: true

  validates :grid_character_id, uniqueness: true
  validate :validate_artifact_source

  def from_collection?
    collection_artifact_id.present?
  end

  def artifact_details
    if from_collection?
      collection_artifact.artifact
    else
      artifact
    end
  end

  def skills
    if from_collection?
      collection_artifact.skills
    else
      [skill1, skill2, skill3, skill4].compact
    end
  end

  private

  def validate_artifact_source
    if collection_artifact_id.blank? && artifact_id.blank?
      errors.add(:base, "Must specify either collection artifact or quick-build artifact")
    end

    if collection_artifact_id.present? && artifact_id.present? && !from_collection?
      errors.add(:base, "Cannot specify both collection and quick-build artifact")
    end
  end
end

# GridCharacter model additions
has_one :grid_artifact, dependent: :destroy

# Party model additions
has_many :grid_artifacts, dependent: :destroy

API Design

Endpoints

Collection Artifacts
GET    /api/v1/collection/artifacts
  Query params: page, limit, artifact_id (filter)
  Response: Paginated list of user's artifacts

GET    /api/v1/collection/artifacts/:id
  Response: Single collection artifact details

POST   /api/v1/collection/artifacts
  Body: artifact_id, level, skill1_id, skill1_level, skill2_id, skill2_level, etc.
  Response: Created collection artifact

PUT    /api/v1/collection/artifacts/:id
  Body: Updated fields
  Response: Updated collection artifact

DELETE /api/v1/collection/artifacts/:id
  Response: Success/error status
Grid Artifacts (Party Equipment)
GET    /api/v1/parties/:party_id/grid_artifacts
  Response: List of artifacts equipped in party

POST   /api/v1/parties/:party_id/grid_artifacts
  Body: { grid_character_id, collection_artifact_id } OR
        { grid_character_id, artifact_id, level, skills... } (quick-build)
  Response: Created grid artifact

PUT    /api/v1/parties/:party_id/grid_artifacts/:id
  Body: Updated artifact reference or properties
  Response: Updated grid artifact

DELETE /api/v1/parties/:party_id/grid_artifacts/:id
  Response: Success/error status
Canonical Data Endpoints
GET    /api/v1/artifacts
  Query: is_quirk?, page, limit
  Response: List of all artifacts

GET    /api/v1/artifacts/:id
  Response: Artifact details

GET    /api/v1/artifact_skills
  Query: skill_group?, page, limit
  Response: List of all artifact skills

GET    /api/v1/artifact_skills/:id
  Response: Skill details
Collection Management
GET    /api/v1/users/:user_id/collection/artifacts
  Response: View another user's artifact collection (respects privacy settings)

GET    /api/v1/collection/statistics
  Response: {
    total_artifacts: 50,
    breakdown_by_rarity: {standard: 45, quirk: 5},
    breakdown_by_level: {...}
  }

Security Considerations

  1. Authorization: Collection management endpoints require authentication
  2. Ownership: Users can only modify their own collection
  3. Privacy Controls: Respect user's collection_privacy settings when viewing collections
  4. Validation: Strict validation of skill combinations and levels
  5. Rate Limiting: Standard rate limiting on all collection endpoints

Performance Considerations

  1. Eager Loading: Include skills and artifact data in collection queries
  2. Batch Operations: Support bulk artifact operations for imports
  3. Indexed Queries: Proper indexes on frequently filtered columns
  4. Pagination: Mandatory pagination for collection endpoints

Implementation Phases

Phase 1: Core Models and Database

  • Create migrations for artifacts, skills, and collections
  • Implement Artifact and ArtifactSkill models
  • Implement CollectionArtifact model
  • Seed canonical artifact and skill data

Phase 2: API Controllers and Blueprints

  • Implement collection artifacts CRUD controller
  • Create artifact blueprints
  • Add authentication and authorization
  • Write controller specs

Phase 3: Grid Integration

  • Implement GridArtifact model
  • Create grid artifacts controller
  • Add artifact support to party endpoints
  • Integrate with existing party system

Phase 4: Frontend Integration

  • Update frontend models and types
  • Create artifact management UI
  • Add artifact selection in party builder
  • Implement collection views

Success Metrics

  1. Performance: All endpoints respond within 200ms for standard operations
  2. Reliability: 99.9% uptime for artifact services
  3. User Adoption: 50% of active users use artifact tracking within 3 months
  4. Data Integrity: Zero data loss incidents