I'm starting to learn how AI agent stuff works, and a lot of what I'm finding is that rushing them into feature development sets you up for disaster, but that having strong collaboration conversations with helpful context works wonders.
So, I'm starting by creating that context: I had a little "here's the codebase" walkthrough conversation with Claude Code, and it generated these docs as output—which came out solid from the jump, with a few tweaks from me for improved nuance.
My hope is that this can serve both as an improved starting point for human collaborators _and_ if I let future Claude instances play around in here. That's a big theme of what I've found with AI tools so far: don't try to get clever, don't expect the world, just give them the same support you'd give people—and then everybody wins 🤞
15 KiB
Dress to Impress: Customization System Architecture
Dress to Impress (DTI) models Neopets's pet customization system: layered 2D images of pets wearing clothing items. This guide explains how DTI's data models represent the customization system and how they combine to render outfit images.
Core Models
The customization system is built on these key models:
Species and Color
- Species: The type of Neopet (Acara, Zafara, etc.)
- Color: The paint color applied to a pet (Blue, Maraquan, Halloween, etc.)
- Colors have flags:
basic(starter colors like Blue/Red),standard(follows typical body shape),nonstandard(unusual shapes)
PetType
PetType = Species + Color
- Represents the combination of a species and color (e.g., "Blue Acara", "Maraquan Acara")
- Contains the critical
body_idfield: the physical shape/compatibility ID - Body ID determines what clothing items are compatible with this pet
- Example: "Blue Acara" and "Red Acara" share the same body_id (they have the same shape), but "Maraquan Acara" has a different body_id (different shape)
PetState
PetState = A visual variant of a PetType
- Represents different presentations: gender (feminine/masculine) and mood (happy/sad/sick)
- Standard poses:
HAPPY_FEM,HAPPY_MASC,SAD_FEM,SAD_MASC,SICK_FEM,SICK_MASC - Special case:
UNCONVERTED- legacy pets from before the customization system (now mostly replaced by Alt Styles) - Has many
swf_assets(the actual visual layers for the pet's appearance)
Item
- Represents a wearable clothing item
- Can have different appearances for different body IDs
- Tracks
cached_compatible_body_ids: which bodies this item has been seen on - Tracks
cached_occupied_zone_ids: which zones this item's layers occupy
Item.Appearance
Not a database model - a Struct that represents "this item on this body"
- Created on-the-fly when rendering
- Contains: the item, the body (id + species), and the relevant
swf_assets - Why it exists: items can look completely different on different bodies
SwfAsset
The actual visual layer - a single image in the final composite
- Two types (via
typefield):'biology': Pet appearance layers (tied to PetStates)'object': Item appearance layers (tied to Items)
- Has a
body_id: either a specific body, or0meaning "fits all bodies" - Belongs to a
Zone(which determines rendering depth/order) - Contains URLs for the image assets (Flash SWF legacy, HTML5 canvas/SVG modern)
- Has
zones_restrict: a bitfield indicating which zones this asset restricts
Zone
- Defines a layer position in the rendering stack
- Has a
depth: lower depths render behind, higher depths render in front - Has a
label: human-readable name (e.g., "Hat", "Background") - Multiple zones can share the same label but have different depths (for items that "wrap around" pets)
AltStyle
Alternative pet appearance - a newer system for non-standard pet looks
- Used for "Nostalgic" (pre-customization) appearances and other special styles
- Replaces the pet's normal layers entirely (not additive)
- Has its own
body_id(distinct from regular pet body IDs) - Most items are incompatible - only
body_id=0items work with alt styles - Example: "Nostalgic Grey Zafara" is an AltStyle, not a PetState
The Rendering Pipeline
This is the core of DTI's customization system: how we turn database records into layered outfit images.
Overview
Input: An Outfit (a pet appearance + a list of worn items)
Output: A sorted list of SwfAsset layers to render, bottom-to-top
Step 1: Choose the Biology Layers
# If alt_style is present, use its layers; otherwise use pet_state's layers
biology_layers = outfit.alt_style ? outfit.alt_style.swf_assets : outfit.pet_state.swf_assets
- Alt styles completely replace pet layers (they don't layer on top)
- Regular pets use their pet_state's layers
- All biology layers have
type = 'biology'
Step 2: Load Item Appearances
# Get how each worn item looks on this body
body_id = outfit.alt_style ? outfit.alt_style.body_id : outfit.pet_type.body_id
item_appearances = Item.appearances_for(outfit.worn_items, body_id)
item_layers = item_appearances.flat_map(&:swf_assets)
- For each worn item, find the
swf_assetsthat match this body - Matching logic: asset's
body_idmust equal the pet'sbody_id, OR asset'sbody_id = 0(fits all) - For alt styles: only
body_id=0items will match (body-specific items are incompatible) - All item layers have
type = 'object'
Step 3: Apply Restriction Rules
This is where it gets complex. We need to hide certain layers based on zone restrictions.
Rule 3a: Items Restrict Pet Layers
# Collect all zones that items restrict
item_restricted_zone_ids = item_appearances.flat_map(&:restricted_zone_ids)
# Hide pet layers in those zones
biology_layers.reject! { |layer| item_restricted_zone_ids.include?(layer.zone_id) }
Example: The "Zafara Agent Hood" restricts the "Hair Front" and "Head Transient Biology" zones.
How it works:
- Items have a
zones_restrictbitfield indicating which zones they restrict - When an item restricts zone 5, all pet layers in zone 5 are hidden
- This allows items to "replace" parts of the pet's appearance
Rule 3b: Pets Restrict Body-Specific Item Layers
This rule is asymmetric and more complex!
Note: This is a legacy rule, originally built for Unconverted pets. Now, Unconverted pets don't exist… but pet states do still technically support zone restrictions. We should examine whether any existing pet states still use this feature, and consider simplifying it out of the system if not.
# Collect all zones that the pet restricts
pet_restricted_zone_ids = biology_layers.flat_map(&:restricted_zone_ids)
# Special case: Unconverted pets can't wear ANY body-specific items
if pet_state.pose == "UNCONVERTED"
item_layers.reject! { |layer| layer.body_specific? }
else
# Other pets: hide body-specific items only in restricted zones
item_layers.reject! do |layer|
layer.body_specific? && pet_restricted_zone_ids.include?(layer.zone_id)
end
end
Example: Unconverted pets can wear backgrounds (body_id=0) but not species-specific clothing.
Key distinction:
- Items restricting zones → hide pet layers
- Pets restricting zones → hide body-specific item layers
- Unconverted pets are special: they reject ALL body-specific items, regardless of zone
Why body_specific? matters:
- Items with
body_id=0fit everyone and are never hidden by pet restrictions - Items with specific body IDs are "body-specific" and can be hidden
Rule 3c: Pets Restrict Their Own Layers
# Pets can hide parts of themselves too
biology_layers.reject! { |layer| pet_restricted_zone_ids.include?(layer.zone_id) }
Example: The Wraith Uni has a horn asset, but its zone restrictions hide it.
Why this exists: Sometimes the pet data includes layers that shouldn't be visible, probably to enable the Neopets team to more easily manage certain kinds of appearance assets. This allows the pet's metadata to control which of its own layers render.
Step 4: Sort by Depth and Render
all_layers = biology_layers + item_layers
all_layers.sort_by(&:depth)
- Pet layers first, then item layers (maintains order for same-depth assets)
- Sort by zone depth: lower depths behind, higher depths in front
- The sorted list is the final render order: first layer on bottom, last layer on top
Important: When a pet layer and item layer share the same zone (and thus the same depth), the item appears on top. This is achieved by putting item layers second in the concatenation and relying on sort stability.
Understanding Compatibility
Why body_id Is the Key
You might expect items to be compatible with a species (all Acaras) or a color (all Maraquan pets). But the system uses body_id instead.
Why?
- Some colors share the same body shape across species (e.g., most "Blue" pets follow a standard shape)
- Some colors have unique body shapes (e.g., Maraquan pets are aquatic and need different clothing)
- Some species have unique shapes even in "standard" colors
- Body ID captures the actual physical compatibility, regardless of color or species
Example:
- Blue Acara (body_id: 93) and Red Acara (body_id: 93) → same body, share items
- Blue Acara (body_id: 93) and Maraquan Acara (body_id: 112) → different bodies, different items
- An item compatible with body 93 fits both Blue and Red Acaras
Body ID 0: Fits All
- Items with
body_id=0assets fit every pet - Examples: backgrounds, foregrounds, trinkets
- These are the only items compatible with alt styles
Standard vs. Nonstandard Bodies
- Standard bodies: Follow typical species shapes (usually "Blue" or other basic colors)
- Nonstandard bodies: Unusual shapes (Maraquan, Baby, Mutant, etc.)
- This distinction helps with modeling predictions (more on that below)
How Compatibility Is Discovered
DTI doesn't know in advance which items fit which bodies. Instead, users contribute compatibility data through "modeling":
- A user loads their pet wearing an item
- DTI's backend calls Neopets APIs to fetch the pet's appearance data
- The response includes which asset IDs are present
- DTI records: "Item X has asset Y for body_id Z"
- Over time, the database learns which items fit which bodies
This crowdsourced approach is why DTI is "self-sustaining" - users passively contribute data just by using the site.
Modeling and Data Sources
The Modeling Process
"Modeling" is DTI's term for crowdsourcing appearance data from users:
- User submits a pet name, often to use as the base of a new outfit
- DTI makes an API request to Neopets using the legacy Flash/AMF protocol
- Response contains:
- Pet's species, color, mood, gender
- List of biology asset IDs (the pet's appearance)
- List of object asset IDs (items the pet is wearing)
- Metadata for each asset (zone, manifest URLs, etc.)
- DTI creates/updates records:
PetType(if new species+color combo)PetState(if new pose/mood/gender combo)SwfAssetrecords for each assetParentSwfAssetRelationshiplinking assets to pets/items
See app/models/pet/modeling_snapshot.rb for the full implementation.
Cached Fields
To avoid expensive queries, several models cache computed data:
-
Item:
cached_compatible_body_ids: Which bodies we've seen this item oncached_occupied_zone_ids: Which zones this item's assets occupycached_predicted_fully_modeled: Whether we think we've seen all compatible bodies
-
PetState:
swf_asset_ids: Direct list of asset IDs (for avoiding duplicate states)
These fields are updated automatically when new modeling data arrives.
Prediction Logic
DTI tries to predict which bodies an item should work on, based on which bodies we've already seen it modeled on. This helps prioritize modeling work and estimate how "complete" an item's data is.
The Maraquan Mynci Problem
The core challenge is avoiding false positives. Consider: most Maraquan pets have unique, aquatic body shapes and wear special Maraquan-themed items. But the Maraquan Mynci shares the same body_id as basic Myncis - it can wear any standard Mynci item.
Naive approach: "We saw this item on a Maraquan pet, so predict it fits all Maraquan pets"
Problem: Most items fit the Maraquan Mynci but NOT other Maraquan pets!
The Solution: Unique Body Detection
For each item, we check whether this item's modeling data shows a color in a "modelable" way: the item must fit at least one body_id that ONLY that color uses (not shared with any other color).
Example 1 - Real Maraquan item:
- Modeled on: Maraquan Acara (body 112), Maraquan Zafara (body 98)
- Both bodies are unique to Maraquan (no basic pets share them)
- Prediction: This fits all Maraquan pets ✓
Example 2 - Standard Mynci item:
- Modeled on: Blue Mynci (body 47), Maraquan Mynci (body 47)
- Body 47 is shared by basic and Maraquan
- Maraquan has NO unique body for this item
- Prediction: This is a standard item for Myncis, not a Maraquan item ✓
Example 3 - Maraquan item on Mynci:
- Modeled on: Maraquan Acara (body 112), basic Mynci (body 47)
- Body 112 is unique to Maraquan
- Maraquan HAS a unique body (the Acara)
- Prediction: This fits all Maraquan pets (including Mynci) ✓
Basic Color Handling
Basic colors (Blue, Red, Green, etc.) always share the same body IDs in practice, so they're treated as a group. An item is predicted to fit all basic pets if we find at least one basic body that doesn't also fit any of the modelable colors identified above.
This prevents false positives: if an item fits both a unique Maraquan body AND a basic/Maraquan shared body (like the Mynci), we treat it as a Maraquan item, not a universal basic item.
Edge Cases
The algorithm has several early exits:
- Manual override: If
modeling_status_hintis set to "done", trust current data is complete - Fits all (
body_id=0): Item fits everyone, prediction complete - Single body: Only one body seen - could be species-specific, stay conservative
- No data: No bodies yet - optimistically predict all basic bodies for recently-released items
Why This Works
This approach is self-sustaining: no manual color flagging needed. As users model items, the unique body pattern emerges naturally, and predictions improve automatically.
See Item#predicted_body_ids in app/models/item.rb:306-373 for the full implementation.
Flash → HTML5 Transition
Neopets originally used Flash (SWF files) for customization. Over time, they've migrated to HTML5 (Canvas/SVG).
SwfAsset fields:
url: Legacy SWF file URL (mostly unused now)manifest_url: JSON manifest for HTML5 assetshas_image: Whether we generated a PNG from the SWF (legacy)
Modern rendering:
- Load the manifest JSON from
manifest_url - Parse it to find canvas library JS, SVG files, or PNG images
- Use Impress 2020 (the frontend) to render them
Fallbacks:
- If
manifest_urlis missing or 404s, fall back to legacy PNG images - If those are missing too, the asset can't be rendered
Summary: Key Takeaways
- body_id is the compatibility key, not species or color
- Restrictions are subtractive: start with all layers, then hide some via restriction rules
- Restrictions are asymmetric: items hide pet layers, pets hide body-specific items
- Unconverted pets are special: they reject all body-specific items (and are no longer available on Neopets)
- Alt styles replace pet layers and only work with body_id=0 items
- Data is crowdsourced through user modeling, not pre-populated
- The system evolved over time: Flash→HTML5, UC→Alt Styles, etc.
Code References
- Rendering logic:
Outfit#visible_layersinapp/models/outfit.rb - Item appearances:
Item.appearances_forinapp/models/item.rb - Modeling:
Pet::ModelingSnapshotinapp/models/pet/modeling_snapshot.rb - Body compatibility:
Item#compatible_body_ids,Item#predicted_body_idsinapp/models/item.rb - Pet state poses:
PetState#pose,PetState.with_poseinapp/models/pet_state.rb