Oops, fix modeling logic

Oh huh, I guess most of the new items we had when I rewrote this were
Maraquan, and I didn't test enough on standard species-specific items.

Before this change, partially-modeled items for standard pets would
appear as fully modeled, because the presence of the "nonstandard"
color Orange (because of the Orange Chia) meant that the "standard" key
didn't actually have any unique bodies (it was all `["standard", 47]`).

Here, I take my own comments' advice and move away from the standard
label as part of the logic. Instead, we look first for nonstandard
colors with unique bodies, which we'll call out as modelable; and then
check whether there are any basic bodies *not* covered by those special
colors.

That way, compatibility with the Maraquan Acara (a unique body) means
we'll treat Maraquan as a modelable color; and then we'll ignore the
basic bodies, even though it *does* fit the basic Mynci, because there
aren't any compatible basic bodies that aren't *also* Maraquan bodies.

This also means that compatibility with both the Blue Acara and Orange
Acara does *not* preclude a normal item from needing basic pets for
models: because, while Orange is a slightly nonstandard color in the
case of Chia, it doesn't have its own unique body for this item, so we
ignore it when predicting compatibility with basic colors.
This commit is contained in:
Emi Matchu 2024-10-08 22:46:11 -07:00
parent 13a0362e6d
commit 71f0aa4908

View file

@ -291,43 +291,41 @@ class Item < ApplicationRecord
compatible_body_ids
else
# First, find our compatible pet types, then pair each body ID with its
# color. (We de-dupe standard colors into the key "standard", but it's
# still possible that a body might appear multiple times in this list,
# e.g. the Maraquan Mynci's body matches the standard Mynci body.)
#
# TODO: I feel like the more robust way to do this would be to flatten to
# whatever matches the colors labeled "basic", rather than
# "standard", because that's a much more reliable label in our data.
# But that's not as easy to formulate a query about; moving on!
compatible_pairs = compatible_pet_types.joins(:color).distinct.
pluck(Arel.sql('IF(colors.standard, "standard", colors.id)'), :body_id)
# color. (As an optimization, we omit standard colors, other than the
# basic colors. We also flatten the basic colors into the single color
# ID "basic", so we can treat them specially.)
compatible_pairs = compatible_pet_types.joins(:color).
merge(Color.nonstandard.or(Color.basic)).
distinct.pluck(
Arel.sql("IF(colors.basic, 'basic', colors.id)"), :body_id)
# Look for colors that have compatible bodies that no other colors have.
# We do this by doing the converse: look for bodies with only one color.
# (This helps us e.g. ignore the Maraquan Mynci throwing things off!)
# Group colors by body, to help us find bodies unique to certain colors.
compatible_color_ids_by_body_id = {}.tap do |h|
compatible_pairs.each do |(color_id, body_id)|
h[body_id] ||= []
h[body_id] << color_id
end
end
modelable_color_ids = compatible_color_ids_by_body_id.
filter { |k, v| v.size == 1 }.values.map(&:first).uniq
# Get all body IDs for the colors we decided are modelable: look up all
# matching pet types, and get their distinct body IDs.
conditions = modelable_color_ids.map do |color_id|
if color_id == "standard"
# Perhaps surprisingly, we filter to basic rather than standard
# colors here, to reduce the risk of getting unexpected bodies swept
# up in the mix. (e.g. the Chia is sometimes weird on
# otherwise-standard colors)
PetType.where(color: {basic: true})
else
PetType.where(color_id:)
end
end
predicted_pet_types = conditions.inject(PetType.none, &:or).joins(:color)
# Find non-basic colors with at least one unique compatible body. (This
# means we'll ignore e.g. the Maraquan Mynci, which has the same body as
# the Blue Mynci, as not indicating Maraquan compatibility in general.)
modelable_color_ids =
compatible_color_ids_by_body_id.
filter { |k, v| v.size == 1 && v.first != "basic" }.
values.map(&:first).uniq
# We can model on basic pets (perhaps in addition to the above) if we
# find at least one compatible basic body that doesn't *also* fit any of
# the modelable colors we identified above.
basic_is_modelable =
compatible_color_ids_by_body_id.values.
any? { |v| v.include?("basic") && (v & modelable_color_ids).empty? }
# Get all body IDs for the colors we decided are modelable.
predicted_pet_types =
(basic_is_modelable ? PetType.basic : PetType.none).
or(PetType.where(color_id: modelable_color_ids))
predicted_pet_types.distinct.pluck(:body_id).sort
end
end