Claude Code Plugins

Community-maintained marketplace

Feedback

Defines the 'Composite Asset' problem and the correct data mapping patterns for Wing Commander Saga (WCS) assets. This skill should be used when understanding the relationship between POF 3D models and TBL data files, or when designing asset migration strategies that preserve gameplay functionality.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name wcs-asset-patterns
description Defines the 'Composite Asset' problem and the correct data mapping patterns for Wing Commander Saga (WCS) assets. This skill should be used when understanding the relationship between POF 3D models and TBL data files, or when designing asset migration strategies that preserve gameplay functionality.
version 1.0.0

WCS Asset Patterns - The Composite Asset Problem

Section 1: The "Composite Asset" Problem

A Wing Commander Saga asset is a runtime "hydration" of two distinct components, not a single file. Naive separation of these components results in functionally inert assets.

The "Skeleton" (.pof) - Spatial and Visual Foundation

The binary 3D model file containing:

  • 3D Geometry: Mesh data, texture coordinates, materials
  • Critical Game Logic Metadata: Hardpoints, subsystems, spatial attachment points
  • Hierarchical Structure: Submodel relationships and transforms
  • Runtime Hooks: Spatial anchors for weapons, engines, cameras, damage zones

The "Brain" (.tbl) - Behavioral and Statistical Data

The plain-text key-value database containing:

  • Ship Characteristics: Mass, velocity, shield/armor values
  • Weapon Configurations: Firing rates, damage values, energy requirements
  • Game Balance Parameters: AI behavior, mission suitability, tech level
  • Classification Data: Ship class, faction, role, prerequisites

The Non-Negotiable Mandate

A naive 3D model conversion will fail, producing "visually correct but functionally inert assets". Both components must be preserved and reunited in Godot through an intelligent import pipeline.

Section 2: The Solution Pipeline

The solution requires a coordinated chain of skills that maintain the relationship between skeleton and brain components.

Brain Pipeline: Data Extraction and Resource Generation

  1. parse-tbl - Extract structured data from .tbl files into JSON schema
  2. generate-resource - Create Godot Custom Resource scripts from the schema

Skeleton Pipeline: Metadata Extraction and Import Reconstruction

  1. parse-pof - Extract critical metadata from .pof files (geometry handled separately)
  2. generate-importer - Create EditorImportPlugin to reconstruct composite assets on import

Geometry Pipeline: 3D Conversion and Material Translation

  1. convert-pof-to-glb - Convert 3D geometry and materials to modern format
  2. EditorImportPlugin - Automatically reassemble composite asset with metadata

Integration Pipeline: Runtime Rehydration

The EditorImportPlugin serves as the bridge, automatically combining:

  • GLB Geometry: Visual representation with materials
  • JSON Metadata: Spatial information from POF parsing
  • Resource Data: Statistical information from TBL parsing

Section 3: Asset Metadata Reference

POF Chunk to Godot Node Mapping

This mapping defines how POF binary chunks are translated into Godot scene nodes during import.

.pof Chunk Godot Node Created Node.name Pattern Node.extras (JSON Data) Purpose
GPNT (Gun Point) Node3D GPNT_Primary_{bank}_{index} {"type": "gun_mount", "bank": int, "weapon_type": string} Spatial hook to instance weapon scenes with correct orientation
MPNT (Missile Point) Node3D MPNT_Secondary_{bank}_{index} {"type": "missile_mount", "bank": int, "missile_type": string} Spatial hook for missile launcher instantiation
SUBS (Subsystem) Area3D + CollisionShape3D SUBS_{subsystem_name} {"type": "subsystem", "name": string, "hitpoints": float, "critical": bool} Targetable area for component damage and system failures
GLOW (Thruster) Node3D THRUSTER_{thruster_name}_{index} {"type": "thruster", "name": string, "radius": float, "thrust_vector": array} Attachment point for engine particle effects
EYE (Eyepoint) Node3D EYE_{camera_name} {"type": "camera_pos", "name": string, "default_fov": float} Camera attachment positions for different view modes
DOCK (Docking Point) Node3D DOCK_{port_name} {"type": "docking_port", "name": string, "size": string, "aligns": array} Spatial anchors for docking mechanics and ship-to-ship interactions

TBL to Resource Property Mapping

This mapping defines how TBL key-value pairs are translated into Godot resource properties.

.tbl Key Data Type Godot Property Example Value Validation Rules
$Name: String @export var ship_name: String "GTF Apollo" Required, non-empty
$Short Name: String @export var short_name: String "Apollo" Required, 3-8 chars
$Ship Class: String @export var ship_class: String "fighter" Must be valid class
$Mass: float @export var mass: float 20000.0 > 0, realistic range
$Max Velocity: float @export var max_velocity: float 70.0 > 0, meters per second
$Max Afterburner Velocity: float @export var max_afterburner_velocity: float 85.0 ≥ max_velocity
$Shields: float @export var shield_strength: float 150.0 ≥ 0
$Armor: float @export var armor_strength: float 100.0 ≥ 0
$Hitpoints: int @export var hitpoints: int 250 > 0
$Gun Banks: int @export var primary_banks: int 2 ≥ 1, ≤ max_supported
$Missile Banks: int @export var secondary_banks: int 1 ≥ 0

Advanced Mapping: Weapon Configuration

.tbl Weapon Key Godot Property Data Structure Purpose
$Primary Bank {N} Weapons: primary_weapons: Array[WeaponData] Custom Resource Array Weapon loadout per bank
+Weapon: weapon_data.weapon_name: String String Specific weapon type
$Damage: weapon_data.damage: float Float Base damage value
$Fire Rate: weapon_data.fire_rate: float Float Shots per second
$Energy Use: weapon_data.energy_cost: float Float Energy per shot

Section 4: Implementation Patterns

Data-First Migration Strategy

The migration follows a strict data-first approach to ensure no information loss.

Step 1: Extract and Structure Data

# parse-tbl skill output structure
{
  "ships": {
    "GTF Apollo": {
      "ship_name": "GTF Apollo",
      "short_name": "Apollo",
      "ship_class": "fighter",
      "mass": 20000.0,
      "max_velocity": 70.0,
      "shield_strength": 150.0,
      "armor_strength": 100.0,
      "hitpoints": 250,
      "primary_weapons": [
        {
          "weapon_name": "Subach HL-7",
          "damage": 15.0,
          "fire_rate": 8.5,
          "energy_cost": 2.0
        }
      ]
    }
  },
  "validation": {
    "total_ships": 1,
    "required_fields_present": true,
    "data_types_valid": true
  }
}

# parse-pof skill output structure
{
  "hardpoints": {
    "primary": [
      {
        "position": [1.2, 0.0, 3.1],
        "normal": [0.0, 0.0, 1.0],
        "bank": 0,
        "index": 0
      },
      {
        "position": [-1.2, 0.0, 3.1],
        "normal": [0.0, 0.0, 1.0],
        "bank": 0,
        "index": 1
      }
    ],
    "secondary": [
      {
        "position": [0.0, 0.5, 2.8],
        "normal": [0.0, 0.2, 1.0],
        "bank": 0,
        "index": 0
      }
    ]
  },
  "subsystems": [
    {
      "name": "engines",
      "bounds": {
        "min": [-2.1, -1.0, -4.2],
        "max": [2.1, 1.0, 0.0]
      },
      "hitpoints": 75,
      "critical": true
    },
    {
      "name": "weapons",
      "bounds": {
        "min": [-1.5, -0.8, 2.0],
        "max": [1.5, 0.8, 4.0]
      },
      "hitpoints": 50,
      "critical": false
    }
  ],
  "eyepoints": [
    {
      "name": "cockpit",
      "position": [0.0, 0.8, 1.2],
      "default_fov": 75.0
    }
  ]
}

Step 2: Generate Godot-Native Assets

# generate-resource skill output (ShipStats.gd)
@tool
extends Resource
class_name ShipStats

@export var ship_name: String
@export var short_name: String
@export var ship_class: String
@export var mass: float
@export var max_velocity: float
@export var max_afterburner_velocity: float
@export var shield_strength: float
@export var armor_strength: float
@export var hitpoints: int
@export var primary_banks: int
@export var secondary_banks: int

@export var primary_weapons: Array[WeaponData] = []
@export var secondary_weapons: Array[WeaponData] = []

func _validate_property(property_name: String) -> bool:
    match property_name:
        "mass":
            return mass > 0.0 and mass < 100000.0
        "max_velocity":
            return max_velocity > 0.0
        "shield_strength":
            return shield_strength >= 0.0
        _:
            return true

Step 3: Create Import Pipeline

# generate-importer skill output (WCSAssetImporter.gd)
@tool
extends EditorImportPlugin
class_name WCSAssetImporter

func _get_importer_name() -> String:
    return "WCS Composite Asset"

func _import(source_file: String, save_path: String, options: Dictionary, r_platform_variants: Dictionary, r_gen_files: Array) -> Error:
    # Load GLB geometry
    var glb_path = source_file.get_basename() + ".glb"
    var gltf_document := GLTFDocument.new()
    var gltf_state := GLTFState.new()
    var result := gltf_document.append_from_file(glb_path, gltf_state)

    if result != OK:
        push_error("Failed to load GLB: " + glb_path)
        return result

    # Create base scene
    var scene := gltf_document.generate_scene(gltf_state)

    # Load companion metadata
    var metadata_path = source_file.get_basename() + "_metadata.json"
    if not FileAccess.file_exists(metadata_path):
        push_warning("No metadata file found for: " + source_file)
        return FAILED

    var metadata_file = FileAccess.open(metadata_path, FileAccess.READ)
    var metadata_json = metadata_file.get_as_text()
    metadata_file.close()

    var json := JSON.new()
    var parse_result = json.parse(metadata_json)
    if parse_result != OK:
        push_error("Failed to parse metadata: " + metadata_path)
        return FAILED

    var metadata = json.data

    # Apply WCS-specific reconstructions
    _wcs_reconstruct_hardpoints(scene, metadata.get("hardpoints", {}))
    _wcs_reconstruct_subsystems(scene, metadata.get("subsystems", []))
    _wcs_reconstruct_eyepoints(scene, metadata.get("eyepoints", []))
    _wcs_reconstruct_thrusters(scene, metadata.get("thrusters", []))

    # Save as PackedScene
    var packed_scene := PackedScene.new()
    packed_scene.pack(scene)

    return ResourceSaver.save(packed_scene, save_path + ".scn")

Section 5: Runtime Rehydration Pattern

Composite Asset Assembly

At runtime, the asset manager must perform intelligent rehydration:

# Runtime asset loading and rehydration
func load_wcs_ship(ship_name: String) -> Node3D:
    # Load composite asset components
    var scene_path := "res://assets/ships/" + ship_name + ".scn"
    var stats_path := "res://resources/ships/" + ship_name + ".tres"

    # Load scene (includes reconstructed metadata nodes)
    var scene := load(scene_path).instantiate()

    # Load ship statistics resource
    var stats := load(stats_path)
    if not stats:
        push_error("Failed to load ship stats: " + stats_path)
        return null

    # Attach game data to scene
    scene.ship_stats = stats

    # Configure gameplay systems based on spatial metadata
    _configure_weapon_systems(scene)
    _setup_damage_systems(scene)
    _initialize_thruster_effects(scene)
    _configure_camera_positions(scene)

    return scene

func _configure_weapon_systems(ship_scene: Node3D):
    var stats := ship_scene.ship_stats as ShipStats

    # Find hardpoint nodes
    for i in range(stats.primary_banks):
        var hardpoint_node := ship_scene.find_node("GPNT_Primary_0_" + str(i))
        if hardpoint_node:
            var weapon_data := stats.primary_weapons[i] if i < stats.primary_weapons.size() else null
            if weapon_data:
                # Instance weapon scene at hardpoint location
                var weapon_scene := load(weapon_data.scene_path).instantiate()
                hardpoint_node.add_child(weapon_scene)
                weapon_scene.global_transform = hardpoint_node.global_transform

Damage System Integration

func _setup_damage_systems(ship_scene: Node3D):
    var stats := ship_scene.ship_stats as ShipStats

    # Find subsystem nodes and connect to damage system
    for subsystem_node in ship_scene.find_children("*", "Area3D"):
        if subsystem_node.name.begins_with("SUBS_"):
            var subsystem_name := subsystem_node.name.substr(5)  # Remove "SUBS_" prefix

            # Connect to ship's damage system
            var damageable_component := DamageableComponent.new()
            damageable_component.subsystem_name = subsystem_name
            damageable_component.max_hitpoints = _get_subsystem_hitpoints(stats, subsystem_name)

            subsystem_node.add_child(damageable_component)

            # Connect damage signals
            damageable_component.damage_taken.connect(_on_subsystem_damage_taken)
            damageable_component.destroyed.connect(_on_subsystem_destroyed)

Section 6: Validation Requirements

Critical Success Criteria

  • Metadata Completeness: Every .glb has companion .json metadata file with all required fields
  • Spatial Accuracy: All hardpoints maintain exact spatial positions from original POF
  • Subsystem Integrity: Subsystem volumes match original collision bounds within tolerance
  • Data Preservation: Ship stats preserve original balance values without drift
  • Node Consistency: Node naming conventions are consistent for runtime access patterns
  • Functional Testing: All weapon mounting points work correctly at runtime
  • Performance Requirements: Import process completes within acceptable time limits

Failure Modes to Detect

  • Missing Metadata: Incomplete composites without .json files
  • Node Hierarchy Errors: Broken spatial relationships or incorrect parent-child relationships
  • Data Validation Failures: NaN or invalid values in stats resources
  • Count Mismatches: Different hardpoint counts between POF metadata and generated nodes
  • Spatial Corruption: Hardpoints outside model bounds or with invalid orientations
  • Resource Link Failures: Broken references between scenes and resource files

Automated Validation Pipeline

# Validation script for composite assets
func validate_composite_asset(ship_name: String) -> Dictionary:
    var result := {
        "valid": true,
        "errors": [],
        "warnings": []
    }

    # Check file existence
    var scene_path := "res://assets/ships/" + ship_name + ".scn"
    var stats_path := "res://resources/ships/" + ship_name + ".tres"
    var metadata_path := "res://assets/ships/" + ship_name + "_metadata.json"

    if not FileAccess.file_exists(scene_path):
        result.errors.append("Missing scene file")
        result.valid = false

    if not FileAccess.file_exists(stats_path):
        result.errors.append("Missing stats resource")
        result.valid = false

    if not FileAccess.file_exists(metadata_path):
        result.errors.append("Missing metadata file")
        result.valid = false

    # Validate node structure
    var scene := load(scene_path).instantiate()
    var hardpoints := scene.find_children("*GPNT*", "Node3D", true, false)
    var subsystems := scene.find_children("*SUBS*", "Area3D", true, false)

    if hardpoints.is_empty():
        result.warnings.append("No weapon hardpoints found")

    if subsystems.is_empty():
        result.warnings.append("No subsystems found")

    return result

This comprehensive pattern ensures that Wing Commander Saga assets maintain their complete gameplay functionality while gaining modern Godot capabilities through the composite asset approach. The strict separation and intelligent recombination of skeleton and brain components preserves the original game design intent while enabling new possibilities.