2026-01-03 10:57:58 -08:00
|
|
|
require_relative '../rails_helper'
|
|
|
|
|
|
|
|
|
|
RSpec.describe Outfit do
|
|
|
|
|
fixtures :zones, :colors, :species
|
|
|
|
|
|
|
|
|
|
# Helper to create a pet state with specific swf_assets
|
|
|
|
|
def build_pet_state(pet_type, pose: "HAPPY_FEM", swf_assets: [])
|
|
|
|
|
pet_state = PetState.create!(
|
|
|
|
|
pet_type: pet_type,
|
|
|
|
|
pose: pose,
|
2026-01-03 11:05:23 -08:00
|
|
|
swf_asset_ids: swf_assets.empty? ? [0] : swf_assets.map(&:id)
|
2026-01-03 10:57:58 -08:00
|
|
|
)
|
|
|
|
|
pet_state.swf_assets = swf_assets
|
|
|
|
|
pet_state
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# Helper to create a SwfAsset for biology (pet layers)
|
|
|
|
|
def build_biology_asset(zone, body_id:, zones_restrict: "")
|
|
|
|
|
@remote_id = (@remote_id || 0) + 1
|
|
|
|
|
SwfAsset.create!(
|
|
|
|
|
type: "biology",
|
|
|
|
|
remote_id: @remote_id,
|
|
|
|
|
url: "https://images.neopets.example/biology_#{@remote_id}.swf",
|
|
|
|
|
zone: zone,
|
|
|
|
|
body_id: body_id,
|
|
|
|
|
zones_restrict: zones_restrict
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# Helper to create a SwfAsset for items (object layers)
|
|
|
|
|
def build_item_asset(zone, body_id:, zones_restrict: "")
|
|
|
|
|
@remote_id = (@remote_id || 0) + 1
|
|
|
|
|
SwfAsset.create!(
|
|
|
|
|
type: "object",
|
|
|
|
|
remote_id: @remote_id,
|
|
|
|
|
url: "https://images.neopets.example/object_#{@remote_id}.swf",
|
|
|
|
|
zone: zone,
|
|
|
|
|
body_id: body_id,
|
|
|
|
|
zones_restrict: zones_restrict
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# Helper to create an item with specific swf_assets
|
|
|
|
|
def build_item(name, swf_assets: [])
|
|
|
|
|
item = Item.create!(
|
|
|
|
|
name: name,
|
|
|
|
|
description: "Test item",
|
|
|
|
|
thumbnail_url: "https://images.neopets.example/thumbnail.png",
|
|
|
|
|
rarity: "Common",
|
|
|
|
|
price: 100,
|
|
|
|
|
zones_restrict: "",
|
|
|
|
|
species_support_ids: ""
|
|
|
|
|
)
|
|
|
|
|
swf_assets.each do |asset|
|
|
|
|
|
ParentSwfAssetRelationship.create!(
|
|
|
|
|
parent: item,
|
|
|
|
|
swf_asset: asset
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
item
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe "#visible_layers" do
|
|
|
|
|
before do
|
|
|
|
|
# Clean up any existing pet types to avoid conflicts
|
|
|
|
|
PetType.destroy_all
|
|
|
|
|
|
|
|
|
|
# Create a basic pet type for testing
|
|
|
|
|
@pet_type = PetType.create!(
|
|
|
|
|
species: species(:acara),
|
|
|
|
|
color: colors(:blue),
|
|
|
|
|
body_id: 1,
|
|
|
|
|
created_at: Time.new(2005)
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "basic layer composition" do
|
|
|
|
|
it "returns pet layers when no items are worn" do
|
|
|
|
|
# Create biology assets for the pet
|
|
|
|
|
head = build_biology_asset(zones(:head), body_id: 1)
|
|
|
|
|
body = build_biology_asset(zones(:body), body_id: 1)
|
|
|
|
|
|
|
|
|
|
pet_state = build_pet_state(@pet_type, swf_assets: [head, body])
|
|
|
|
|
outfit = Outfit.new(pet_state: pet_state)
|
|
|
|
|
|
|
|
|
|
layers = outfit.visible_layers
|
|
|
|
|
|
|
|
|
|
expect(layers).to contain_exactly(head, body)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "returns pet layers and item layers when items are worn" do
|
|
|
|
|
# Create pet layers
|
|
|
|
|
head = build_biology_asset(zones(:head), body_id: 1)
|
|
|
|
|
body = build_biology_asset(zones(:body), body_id: 1)
|
|
|
|
|
pet_state = build_pet_state(@pet_type, swf_assets: [head, body])
|
|
|
|
|
|
|
|
|
|
# Create item layers
|
2026-01-04 13:31:01 -08:00
|
|
|
hat_asset = build_item_asset(zones(:hat1), body_id: 1)
|
2026-01-03 10:57:58 -08:00
|
|
|
hat = build_item("Test Hat", swf_assets: [hat_asset])
|
|
|
|
|
|
|
|
|
|
outfit = Outfit.new(pet_state: pet_state)
|
|
|
|
|
outfit.worn_items = [hat]
|
|
|
|
|
|
|
|
|
|
layers = outfit.visible_layers
|
|
|
|
|
|
|
|
|
|
expect(layers).to contain_exactly(head, body, hat_asset)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "includes body_id=0 items that fit all pets" do
|
|
|
|
|
# Create pet layers
|
|
|
|
|
head = build_biology_asset(zones(:head), body_id: 1)
|
|
|
|
|
pet_state = build_pet_state(@pet_type, swf_assets: [head])
|
|
|
|
|
|
|
|
|
|
# Create a background item (body_id=0, fits all)
|
|
|
|
|
bg_asset = build_item_asset(zones(:background), body_id: 0)
|
|
|
|
|
background = build_item("Test Background", swf_assets: [bg_asset])
|
|
|
|
|
|
|
|
|
|
outfit = Outfit.new(pet_state: pet_state)
|
|
|
|
|
outfit.worn_items = [background]
|
|
|
|
|
|
|
|
|
|
layers = outfit.visible_layers
|
|
|
|
|
|
|
|
|
|
expect(layers).to contain_exactly(head, bg_asset)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "items restricting pet layers (Rule 3a)" do
|
|
|
|
|
it "hides pet layers in zones that items restrict" do
|
|
|
|
|
# Create pet layers including hair
|
|
|
|
|
head = build_biology_asset(zones(:head), body_id: 1)
|
|
|
|
|
hair = build_biology_asset(zones(:hairfront), body_id: 1)
|
|
|
|
|
pet_state = build_pet_state(@pet_type, swf_assets: [head, hair])
|
|
|
|
|
|
|
|
|
|
# Create a hat that restricts the hair zone
|
|
|
|
|
# zones_restrict is a bitfield where position 37 (Hair Front zone id) is "1"
|
|
|
|
|
zones_restrict = "0" * 36 + "1" + "0" * 20 # bit 37 = 1
|
2026-01-04 13:31:01 -08:00
|
|
|
hat_asset = build_item_asset(zones(:hat1), body_id: 1, zones_restrict: zones_restrict)
|
2026-01-03 10:57:58 -08:00
|
|
|
hat = build_item("Hair-hiding Hat", swf_assets: [hat_asset])
|
|
|
|
|
|
|
|
|
|
outfit = Outfit.new(pet_state: pet_state)
|
|
|
|
|
outfit.worn_items = [hat]
|
|
|
|
|
|
|
|
|
|
layers = outfit.visible_layers
|
|
|
|
|
|
|
|
|
|
# Hair should be hidden, but head and hat should be visible
|
|
|
|
|
expect(layers).to contain_exactly(head, hat_asset)
|
|
|
|
|
expect(layers).not_to include(hair)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "hides multiple pet layers when item restricts multiple zones" do
|
|
|
|
|
# Create pet layers
|
|
|
|
|
head = build_biology_asset(zones(:head), body_id: 1)
|
|
|
|
|
hair_front = build_biology_asset(zones(:hairfront), body_id: 1)
|
|
|
|
|
head_transient = build_biology_asset(zones(:headtransientbiology), body_id: 1)
|
|
|
|
|
pet_state = build_pet_state(@pet_type, swf_assets: [head, hair_front, head_transient])
|
|
|
|
|
|
|
|
|
|
# Create an item that restricts both Hair Front (37) and Head Transient Biology (38)
|
|
|
|
|
zones_restrict = "0" * 36 + "11" + "0" * 20 # bits 37 and 38 = 1
|
2026-01-04 13:31:01 -08:00
|
|
|
hood_asset = build_item_asset(zones(:hat1), body_id: 1, zones_restrict: zones_restrict)
|
2026-01-03 10:57:58 -08:00
|
|
|
hood = build_item("Agent Hood", swf_assets: [hood_asset])
|
|
|
|
|
|
|
|
|
|
outfit = Outfit.new(pet_state: pet_state)
|
|
|
|
|
outfit.worn_items = [hood]
|
|
|
|
|
|
|
|
|
|
layers = outfit.visible_layers
|
|
|
|
|
|
|
|
|
|
# Both hair_front and head_transient should be hidden
|
|
|
|
|
expect(layers).to contain_exactly(head, hood_asset)
|
|
|
|
|
expect(layers).not_to include(hair_front, head_transient)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "pets restricting body-specific item layers (Rule 3b)" do
|
|
|
|
|
it "hides body-specific items in zones the pet restricts" do
|
|
|
|
|
# Create a pet with a layer that restricts the Static zone (46)
|
|
|
|
|
head = build_biology_asset(zones(:head), body_id: 1)
|
|
|
|
|
zones_restrict = "0" * 45 + "1" + "0" * 10 # bit 46 = 1
|
|
|
|
|
restricting_layer = build_biology_asset(zones(:body), body_id: 1, zones_restrict: zones_restrict)
|
|
|
|
|
pet_state = build_pet_state(@pet_type, swf_assets: [head, restricting_layer])
|
|
|
|
|
|
|
|
|
|
# Create a body-specific Static item
|
|
|
|
|
static_asset = build_item_asset(zones(:static), body_id: 1)
|
|
|
|
|
static_item = build_item("Body-specific Static", swf_assets: [static_asset])
|
|
|
|
|
|
|
|
|
|
outfit = Outfit.new(pet_state: pet_state)
|
|
|
|
|
outfit.worn_items = [static_item]
|
|
|
|
|
|
|
|
|
|
layers = outfit.visible_layers
|
|
|
|
|
|
|
|
|
|
# The body-specific static item should be hidden
|
|
|
|
|
expect(layers).to contain_exactly(head, restricting_layer)
|
|
|
|
|
expect(layers).not_to include(static_asset)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "allows body_id=0 items even in zones the pet restricts" do
|
|
|
|
|
# Create a pet with a layer that restricts the Background Item zone (48)
|
|
|
|
|
# Background Item is type_id 3 (universal zone), so body_id=0 items should always work
|
|
|
|
|
head = build_biology_asset(zones(:head), body_id: 1)
|
|
|
|
|
zones_restrict = "0" * 47 + "1" + "0" * 10 # bit 48 = 1
|
|
|
|
|
restricting_layer = build_biology_asset(zones(:body), body_id: 1, zones_restrict: zones_restrict)
|
|
|
|
|
pet_state = build_pet_state(@pet_type, swf_assets: [head, restricting_layer])
|
|
|
|
|
|
|
|
|
|
# Create a body_id=0 Background Item (fits all bodies, universal zone)
|
|
|
|
|
bg_item_asset = build_item_asset(zones(:backgrounditem), body_id: 0)
|
|
|
|
|
bg_item = build_item("Universal Background Item", swf_assets: [bg_item_asset])
|
|
|
|
|
|
|
|
|
|
outfit = Outfit.new(pet_state: pet_state)
|
|
|
|
|
outfit.worn_items = [bg_item]
|
|
|
|
|
|
|
|
|
|
layers = outfit.visible_layers
|
|
|
|
|
|
|
|
|
|
# The body_id=0 item should be visible even though the zone is restricted
|
|
|
|
|
expect(layers).to contain_exactly(head, restricting_layer, bg_item_asset)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "UNCONVERTED pets (Rule 3b special case)" do
|
|
|
|
|
it "rejects all body-specific items" do
|
|
|
|
|
# Create an UNCONVERTED pet
|
|
|
|
|
head = build_biology_asset(zones(:head), body_id: 1)
|
|
|
|
|
body = build_biology_asset(zones(:body), body_id: 1)
|
|
|
|
|
pet_state = build_pet_state(@pet_type, pose: "UNCONVERTED", swf_assets: [head, body])
|
|
|
|
|
|
|
|
|
|
# Create both body-specific and body_id=0 items
|
2026-01-04 13:31:01 -08:00
|
|
|
body_specific_asset = build_item_asset(zones(:hat1), body_id: 1)
|
2026-01-03 10:57:58 -08:00
|
|
|
body_specific_item = build_item("Body-specific Hat", swf_assets: [body_specific_asset])
|
|
|
|
|
|
|
|
|
|
universal_asset = build_item_asset(zones(:background), body_id: 0)
|
|
|
|
|
universal_item = build_item("Universal Background", swf_assets: [universal_asset])
|
|
|
|
|
|
|
|
|
|
outfit = Outfit.new(pet_state: pet_state)
|
|
|
|
|
outfit.worn_items = [body_specific_item, universal_item]
|
|
|
|
|
|
|
|
|
|
layers = outfit.visible_layers
|
|
|
|
|
|
|
|
|
|
# Only body_id=0 items should be visible
|
|
|
|
|
expect(layers).to contain_exactly(head, body, universal_asset)
|
|
|
|
|
expect(layers).not_to include(body_specific_asset)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "rejects body-specific items regardless of zone restrictions" do
|
|
|
|
|
# Create an UNCONVERTED pet with no zone restrictions
|
|
|
|
|
head = build_biology_asset(zones(:head), body_id: 1)
|
|
|
|
|
pet_state = build_pet_state(@pet_type, pose: "UNCONVERTED", swf_assets: [head])
|
|
|
|
|
|
|
|
|
|
# Create a body-specific item in a zone the pet doesn't restrict
|
2026-01-04 13:31:01 -08:00
|
|
|
hat_asset = build_item_asset(zones(:hat1), body_id: 1)
|
2026-01-03 10:57:58 -08:00
|
|
|
hat = build_item("Body-specific Hat", swf_assets: [hat_asset])
|
|
|
|
|
|
|
|
|
|
outfit = Outfit.new(pet_state: pet_state)
|
|
|
|
|
outfit.worn_items = [hat]
|
|
|
|
|
|
|
|
|
|
layers = outfit.visible_layers
|
|
|
|
|
|
|
|
|
|
# The body-specific item should still be hidden
|
|
|
|
|
expect(layers).to contain_exactly(head)
|
|
|
|
|
expect(layers).not_to include(hat_asset)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "pets restricting their own layers (Rule 3c)" do
|
|
|
|
|
it "hides pet layers in zones the pet itself restricts" do
|
|
|
|
|
# Create a pet with a horn asset and a layer that restricts the horn's zone
|
|
|
|
|
# (Simulating the Wraith Uni case)
|
|
|
|
|
body = build_biology_asset(zones(:body), body_id: 1)
|
|
|
|
|
|
|
|
|
|
# Create a horn in the Head Transient Biology zone (38)
|
|
|
|
|
horn = build_biology_asset(zones(:headtransientbiology), body_id: 1)
|
|
|
|
|
|
|
|
|
|
# Create a layer that restricts zone 38
|
|
|
|
|
zones_restrict = "0" * 37 + "1" + "0" * 20 # bit 38 = 1
|
|
|
|
|
restricting_layer = build_biology_asset(zones(:head), body_id: 1, zones_restrict: zones_restrict)
|
|
|
|
|
|
|
|
|
|
pet_state = build_pet_state(@pet_type, swf_assets: [body, horn, restricting_layer])
|
|
|
|
|
|
|
|
|
|
outfit = Outfit.new(pet_state: pet_state)
|
|
|
|
|
|
|
|
|
|
layers = outfit.visible_layers
|
|
|
|
|
|
|
|
|
|
# The horn should be hidden by the pet's own restrictions
|
|
|
|
|
expect(layers).to contain_exactly(body, restricting_layer)
|
|
|
|
|
expect(layers).not_to include(horn)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "applies self-restrictions in combination with item restrictions" do
|
|
|
|
|
# Create a pet with multiple layers, some restricted by itself
|
|
|
|
|
body = build_biology_asset(zones(:body), body_id: 1)
|
|
|
|
|
hair = build_biology_asset(zones(:hairfront), body_id: 1)
|
|
|
|
|
|
|
|
|
|
# Pet restricts its own Head zone (30)
|
|
|
|
|
zones_restrict = "0" * 29 + "1" + "0" * 25 # bit 30 = 1
|
|
|
|
|
head = build_biology_asset(zones(:head), body_id: 1)
|
|
|
|
|
restricting_layer = build_biology_asset(zones(:eyes), body_id: 1, zones_restrict: zones_restrict)
|
|
|
|
|
|
|
|
|
|
pet_state = build_pet_state(@pet_type, swf_assets: [body, hair, head, restricting_layer])
|
|
|
|
|
|
|
|
|
|
# Add an item that restricts Hair Front (37)
|
|
|
|
|
item_zones_restrict = "0" * 36 + "1" + "0" * 20 # bit 37 = 1
|
2026-01-04 13:31:01 -08:00
|
|
|
hat_asset = build_item_asset(zones(:hat1), body_id: 1, zones_restrict: item_zones_restrict)
|
2026-01-03 10:57:58 -08:00
|
|
|
hat = build_item("Hair-hiding Hat", swf_assets: [hat_asset])
|
|
|
|
|
|
|
|
|
|
outfit = Outfit.new(pet_state: pet_state)
|
|
|
|
|
outfit.worn_items = [hat]
|
|
|
|
|
|
|
|
|
|
layers = outfit.visible_layers
|
|
|
|
|
|
|
|
|
|
# Hair should be hidden by item, Head should be hidden by pet's own restrictions
|
|
|
|
|
expect(layers).to contain_exactly(body, restricting_layer, hat_asset)
|
|
|
|
|
expect(layers).not_to include(hair, head)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "depth sorting and layer ordering" do
|
|
|
|
|
it "sorts layers by zone depth" do
|
|
|
|
|
# Create layers in various zones with different depths
|
|
|
|
|
background = build_biology_asset(zones(:background), body_id: 1) # depth 3
|
|
|
|
|
body_layer = build_biology_asset(zones(:body), body_id: 1) # depth 18
|
|
|
|
|
head_layer = build_biology_asset(zones(:head), body_id: 1) # depth 34
|
|
|
|
|
|
|
|
|
|
pet_state = build_pet_state(@pet_type, swf_assets: [head_layer, background, body_layer])
|
|
|
|
|
|
|
|
|
|
outfit = Outfit.new(pet_state: pet_state)
|
|
|
|
|
|
|
|
|
|
layers = outfit.visible_layers
|
|
|
|
|
|
|
|
|
|
# Should be sorted by depth: background (3) < body (18) < head (34)
|
|
|
|
|
expect(layers[0]).to eq(background)
|
|
|
|
|
expect(layers[1]).to eq(body_layer)
|
|
|
|
|
expect(layers[2]).to eq(head_layer)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "places item layers after pet layers at the same depth" do
|
|
|
|
|
# Create a pet layer and item layer in zones with the same depth
|
|
|
|
|
# Static zone has depth 48
|
|
|
|
|
pet_static = build_biology_asset(zones(:static), body_id: 1)
|
|
|
|
|
pet_state = build_pet_state(@pet_type, swf_assets: [pet_static])
|
|
|
|
|
|
|
|
|
|
item_static = build_item_asset(zones(:static), body_id: 0)
|
|
|
|
|
static_item = build_item("Static Item", swf_assets: [item_static])
|
|
|
|
|
|
|
|
|
|
outfit = Outfit.new(pet_state: pet_state)
|
|
|
|
|
outfit.worn_items = [static_item]
|
|
|
|
|
|
|
|
|
|
layers = outfit.visible_layers
|
|
|
|
|
|
|
|
|
|
# Both should be present, with item layer last (on top)
|
|
|
|
|
expect(layers).to eq([pet_static, item_static])
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "sorts complex outfits correctly by depth" do
|
|
|
|
|
# Create a complex outfit with multiple pet and item layers
|
|
|
|
|
background = build_biology_asset(zones(:background), body_id: 1) # depth 3
|
|
|
|
|
body_layer = build_biology_asset(zones(:body), body_id: 1) # depth 18
|
|
|
|
|
head_layer = build_biology_asset(zones(:head), body_id: 1) # depth 34
|
|
|
|
|
|
|
|
|
|
pet_state = build_pet_state(@pet_type, swf_assets: [head_layer, background, body_layer])
|
|
|
|
|
|
|
|
|
|
# Add items at various depths
|
|
|
|
|
bg_item = build_item_asset(zones(:backgrounditem), body_id: 0) # depth 4
|
2026-01-04 13:31:01 -08:00
|
|
|
hat_asset = build_item_asset(zones(:hat1), body_id: 1) # depth 44
|
2026-01-03 10:57:58 -08:00
|
|
|
shirt_asset = build_item_asset(zones(:shirtdress), body_id: 1) # depth 26
|
|
|
|
|
|
|
|
|
|
bg = build_item("Background Item", swf_assets: [bg_item])
|
|
|
|
|
hat = build_item("Hat", swf_assets: [hat_asset])
|
|
|
|
|
shirt = build_item("Shirt", swf_assets: [shirt_asset])
|
|
|
|
|
|
|
|
|
|
outfit = Outfit.new(pet_state: pet_state)
|
|
|
|
|
outfit.worn_items = [hat, bg, shirt]
|
|
|
|
|
|
|
|
|
|
layers = outfit.visible_layers
|
|
|
|
|
|
|
|
|
|
# Expected order by depth:
|
2026-01-04 13:31:01 -08:00
|
|
|
# background (3), bg_item (4), body_layer (18), shirt_asset (26),
|
|
|
|
|
# head_layer (34), hat_asset (44)
|
|
|
|
|
expect(layers.map(&:depth)).to eq([3, 4, 18, 26, 34, 44])
|
|
|
|
|
expect(layers).to eq([background, bg_item, body_layer, shirt_asset, head_layer, hat_asset])
|
2026-01-03 10:57:58 -08:00
|
|
|
end
|
|
|
|
|
end
|
2026-01-03 11:05:23 -08:00
|
|
|
|
|
|
|
|
context "alt styles (alternative pet appearances)" do
|
|
|
|
|
before do
|
|
|
|
|
# Create an alt style with its own body_id distinct from regular pets
|
|
|
|
|
@alt_style = AltStyle.create!(
|
|
|
|
|
species: species(:acara),
|
|
|
|
|
color: colors(:blue),
|
|
|
|
|
body_id: 999, # Distinct from the regular pet's body_id (1)
|
|
|
|
|
series_name: "Nostalgic",
|
|
|
|
|
thumbnail_url: "https://images.neopets.example/alt_style.png"
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "uses alt style layers instead of pet state layers" do
|
|
|
|
|
# Create regular pet layers
|
|
|
|
|
regular_head = build_biology_asset(zones(:head), body_id: 1)
|
|
|
|
|
regular_body = build_biology_asset(zones(:body), body_id: 1)
|
|
|
|
|
pet_state = build_pet_state(@pet_type, swf_assets: [regular_head, regular_body])
|
|
|
|
|
|
|
|
|
|
# Create alt style layers (with the alt style's body_id)
|
|
|
|
|
alt_head = build_biology_asset(zones(:head), body_id: 999)
|
|
|
|
|
alt_body = build_biology_asset(zones(:body), body_id: 999)
|
|
|
|
|
@alt_style.swf_assets = [alt_head, alt_body]
|
|
|
|
|
|
|
|
|
|
# Create outfit with alt_style
|
|
|
|
|
outfit = Outfit.new(pet_state: pet_state, alt_style: @alt_style)
|
|
|
|
|
|
|
|
|
|
layers = outfit.visible_layers
|
|
|
|
|
|
|
|
|
|
# Should use alt style layers, not pet state layers
|
|
|
|
|
expect(layers).to contain_exactly(alt_head, alt_body)
|
|
|
|
|
expect(layers).not_to include(regular_head, regular_body)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "only includes body_id=0 items with alt styles" do
|
|
|
|
|
# Create alt style layers
|
|
|
|
|
alt_head = build_biology_asset(zones(:head), body_id: 999)
|
|
|
|
|
@alt_style.swf_assets = [alt_head]
|
|
|
|
|
pet_state = build_pet_state(@pet_type)
|
|
|
|
|
|
|
|
|
|
# Create a body-specific item for the alt style's body_id
|
2026-01-04 13:31:01 -08:00
|
|
|
body_specific_asset = build_item_asset(zones(:hat1), body_id: 999)
|
2026-01-03 11:05:23 -08:00
|
|
|
body_specific_item = build_item("Body-specific Hat", swf_assets: [body_specific_asset])
|
|
|
|
|
|
|
|
|
|
# Create a universal item (body_id=0)
|
|
|
|
|
universal_asset = build_item_asset(zones(:background), body_id: 0)
|
|
|
|
|
universal_item = build_item("Universal Background", swf_assets: [universal_asset])
|
|
|
|
|
|
|
|
|
|
outfit = Outfit.new(pet_state: pet_state, alt_style: @alt_style)
|
|
|
|
|
outfit.worn_items = [body_specific_item, universal_item]
|
|
|
|
|
|
|
|
|
|
layers = outfit.visible_layers
|
|
|
|
|
|
|
|
|
|
# Only the universal item should appear
|
|
|
|
|
expect(layers).to contain_exactly(alt_head, universal_asset)
|
|
|
|
|
expect(layers).not_to include(body_specific_asset)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "does not include items from the regular pet's body_id" do
|
|
|
|
|
# Create alt style layers
|
|
|
|
|
alt_body = build_biology_asset(zones(:body), body_id: 999)
|
|
|
|
|
@alt_style.swf_assets = [alt_body]
|
|
|
|
|
pet_state = build_pet_state(@pet_type)
|
|
|
|
|
|
|
|
|
|
# Create an item that fits the regular pet's body_id (1)
|
2026-01-04 13:31:01 -08:00
|
|
|
regular_item_asset = build_item_asset(zones(:hat1), body_id: 1)
|
2026-01-03 11:05:23 -08:00
|
|
|
regular_item = build_item("Regular Pet Hat", swf_assets: [regular_item_asset])
|
|
|
|
|
|
|
|
|
|
outfit = Outfit.new(pet_state: pet_state, alt_style: @alt_style)
|
|
|
|
|
outfit.worn_items = [regular_item]
|
|
|
|
|
|
|
|
|
|
layers = outfit.visible_layers
|
|
|
|
|
|
|
|
|
|
# The regular pet item should not appear on the alt style
|
|
|
|
|
expect(layers).to contain_exactly(alt_body)
|
|
|
|
|
expect(layers).not_to include(regular_item_asset)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "applies item restriction rules with alt styles" do
|
|
|
|
|
# Create alt style layers including hair
|
|
|
|
|
alt_head = build_biology_asset(zones(:head), body_id: 999)
|
|
|
|
|
alt_hair = build_biology_asset(zones(:hairfront), body_id: 999)
|
|
|
|
|
@alt_style.swf_assets = [alt_head, alt_hair]
|
|
|
|
|
pet_state = build_pet_state(@pet_type)
|
|
|
|
|
|
|
|
|
|
# Create a universal hat that restricts the hair zone
|
|
|
|
|
zones_restrict = "0" * 36 + "1" + "0" * 20 # bit 37 (Hair Front) = 1
|
2026-01-04 13:31:01 -08:00
|
|
|
hat_asset = build_item_asset(zones(:hat1), body_id: 0, zones_restrict: zones_restrict)
|
2026-01-03 11:05:23 -08:00
|
|
|
hat = build_item("Hair-hiding Hat", swf_assets: [hat_asset])
|
|
|
|
|
|
|
|
|
|
outfit = Outfit.new(pet_state: pet_state, alt_style: @alt_style)
|
|
|
|
|
outfit.worn_items = [hat]
|
|
|
|
|
|
|
|
|
|
layers = outfit.visible_layers
|
|
|
|
|
|
|
|
|
|
# Hair should be hidden by the hat's zone restrictions
|
|
|
|
|
expect(layers).to contain_exactly(alt_head, hat_asset)
|
|
|
|
|
expect(layers).not_to include(alt_hair)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "applies pet restriction rules with alt styles" do
|
|
|
|
|
# Create alt style with a layer that restricts a zone
|
|
|
|
|
alt_head = build_biology_asset(zones(:head), body_id: 999)
|
|
|
|
|
zones_restrict = "0" * 47 + "1" + "0" * 10 # bit 48 (Background Item) = 1
|
|
|
|
|
restricting_layer = build_biology_asset(zones(:body), body_id: 999, zones_restrict: zones_restrict)
|
|
|
|
|
@alt_style.swf_assets = [alt_head, restricting_layer]
|
|
|
|
|
pet_state = build_pet_state(@pet_type)
|
|
|
|
|
|
|
|
|
|
# Create a universal Background Item
|
|
|
|
|
bg_item_asset = build_item_asset(zones(:backgrounditem), body_id: 0)
|
|
|
|
|
bg_item = build_item("Universal Background Item", swf_assets: [bg_item_asset])
|
|
|
|
|
|
|
|
|
|
outfit = Outfit.new(pet_state: pet_state, alt_style: @alt_style)
|
|
|
|
|
outfit.worn_items = [bg_item]
|
|
|
|
|
|
|
|
|
|
layers = outfit.visible_layers
|
|
|
|
|
|
|
|
|
|
# body_id=0 items should still appear even in restricted zones
|
|
|
|
|
# (because they're not body-specific)
|
|
|
|
|
expect(layers).to contain_exactly(alt_head, restricting_layer, bg_item_asset)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "applies self-restriction rules with alt styles" do
|
|
|
|
|
# Create alt style that restricts its own horn layer
|
|
|
|
|
alt_body = build_biology_asset(zones(:body), body_id: 999)
|
|
|
|
|
alt_horn = build_biology_asset(zones(:headtransientbiology), body_id: 999)
|
|
|
|
|
zones_restrict = "0" * 37 + "1" + "0" * 20 # bit 38 (Head Transient Biology) = 1
|
|
|
|
|
restricting_layer = build_biology_asset(zones(:head), body_id: 999, zones_restrict: zones_restrict)
|
|
|
|
|
@alt_style.swf_assets = [alt_body, alt_horn, restricting_layer]
|
|
|
|
|
pet_state = build_pet_state(@pet_type)
|
|
|
|
|
|
|
|
|
|
outfit = Outfit.new(pet_state: pet_state, alt_style: @alt_style)
|
|
|
|
|
|
|
|
|
|
layers = outfit.visible_layers
|
|
|
|
|
|
|
|
|
|
# The horn should be hidden by the alt style's own restrictions
|
|
|
|
|
expect(layers).to contain_exactly(alt_body, restricting_layer)
|
|
|
|
|
expect(layers).not_to include(alt_horn)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "sorts alt style and item layers by depth correctly" do
|
|
|
|
|
# Create alt style layers at various depths
|
|
|
|
|
alt_background = build_biology_asset(zones(:background), body_id: 999) # depth 3
|
|
|
|
|
alt_body = build_biology_asset(zones(:body), body_id: 999) # depth 18
|
|
|
|
|
alt_head = build_biology_asset(zones(:head), body_id: 999) # depth 34
|
|
|
|
|
@alt_style.swf_assets = [alt_head, alt_background, alt_body]
|
|
|
|
|
pet_state = build_pet_state(@pet_type)
|
|
|
|
|
|
|
|
|
|
# Add universal items at various depths
|
|
|
|
|
bg_item = build_item_asset(zones(:backgrounditem), body_id: 0) # depth 4
|
2026-01-04 13:31:01 -08:00
|
|
|
trinket = build_item_asset(zones(:righthanditem1), body_id: 0) # depth 46
|
2026-01-03 11:05:23 -08:00
|
|
|
|
|
|
|
|
bg = build_item("Background Item", swf_assets: [bg_item])
|
|
|
|
|
trinket_item = build_item("Trinket", swf_assets: [trinket])
|
|
|
|
|
|
|
|
|
|
outfit = Outfit.new(pet_state: pet_state, alt_style: @alt_style)
|
|
|
|
|
outfit.worn_items = [trinket_item, bg]
|
|
|
|
|
|
|
|
|
|
layers = outfit.visible_layers
|
|
|
|
|
|
|
|
|
|
# Expected order by depth:
|
2026-01-04 13:31:01 -08:00
|
|
|
# alt_background (3), bg_item (4), alt_body (18), alt_head (34), trinket (46)
|
|
|
|
|
expect(layers.map(&:depth)).to eq([3, 4, 18, 34, 46])
|
|
|
|
|
expect(layers).to eq([alt_background, bg_item, alt_body, alt_head, trinket])
|
2026-01-03 11:05:23 -08:00
|
|
|
end
|
|
|
|
|
end
|
2026-01-03 10:57:58 -08:00
|
|
|
end
|
|
|
|
|
end
|