Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dependency Layers Meta Issue #5642

Open
Hanmac opened this issue Jul 18, 2024 · 18 comments
Open

Dependency Layers Meta Issue #5642

Hanmac opened this issue Jul 18, 2024 · 18 comments
Assignees
Labels

Comments

@Hanmac
Copy link
Contributor

Hanmac commented Jul 18, 2024

Collection of issues related to "Layer Dependency"

Which Layers should be affected?

  • 613.1a Layer 1: Rules and effects that modify copiable values are applied.
    A token Metamorphic Alteration and Brudiclad, Telchor Engineer (copiable values are handled differently by us)
  • 613.1b Layer 2: Control-changing effects are applied.
    Confiscate enchanting a Mind Control
  • 613.1c Layer 3: Text-changing effects are applied.
    Exchange of Words targeting Volrath's Shapeshifter (Volrath's Shapeshifter is a special case entirely)
  • 613.1d Layer 4: Type-changing effects are applied.
    Cards like Blood Moon
    or Graaz, Unstoppable Juggernaut applying to previous non-creatures that got animated
  • 613.1f Layer 6: Ability-adding effects, keyword counters, ability-removing effects, and effects that say an object can’t have an ability are applied.
    Like Cavalry Master that refers to other creatures with abilities
    or stuff that makes the card loose all abilities
  • 613.4b Layer 7b: Effects that set power and/or toughness to a specific number or value are applied. Effects that refer to the base power and/or toughness of a creature apply in this layer.
    Andrios, Roaming Explorer for example, the pump ability otherwise wouldn't make the static ability apply for these cards

for which Layer 4 and unlikely Layer 6 could have ways to cause a dependency Loop

Main Dependency Rule:

613.8a An effect is said to “depend on” another if (a) it’s applied in the same layer (and, if applicable, sublayer) as the other effect; (b) applying the other would change the text or the existence of the first effect, what it applies to, or what it does to any of the things it applies to; and (c) neither effect is from a characteristic-defining ability or both effects are from characteristic-defining abilities. Otherwise, the effect is considered to be independent of the other effect.

For this, it would be good to first separate the characteristic-defining ones.
I haven't seen an example for a dependency between two CharacteristicDefining abilities (in the same layer?)

Related Issues:

@Hanmac
Copy link
Contributor Author

Hanmac commented Jan 14, 2025

This is important for Lifecraft Engine

Vehicle creatures you control are the chosen creature type in addition to their other types.

For this to work correct, Animate Effects would need to use StaticAbility Layers

@tool4ever @tehdiplomat @Northmoc I might need some input there
Like if i need to do Animate → StaticAbility, then I don't want all the Effects in the Command Zone :(

@Hanmac
Copy link
Contributor Author

Hanmac commented Jan 17, 2025

Places that use addChangedCardTypes to be changed for Layer 4 using this as LinkedHashMap:

@tool4ever
Copy link
Contributor

tool4ever commented Jan 19, 2025

Imo one of the better ressources (not too outdated and lots of examples) is:
https://outsidetheasylum.blog/dependency/

Like the link suggests we might consider implementing it as directed graph, which when visualized might help players and our debugging too.
This one looks promising: https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/alg/cycle/package-summary.html

Here's my rough pseudocode:

For Layer X
[...assume all CDA are already applied the same way if really needed]
	while unapplied Statics left
		For each possible pair of Statics {A,B}, {B,A} etc.
		(it's noteworthy that this should be a flat loop instead of recursion after each static)
			store Affected + trait changes of A
			apply B
			// check these three (one is already enough for dependency):
			1. does Static A still exist?
			2. is getAffectedCards still equal?
			3. are the results still the same? This one probably involves some hardcoded param checks, e.g. AddPower$
			(or we apply both with engine and maybe StaticEffect can implement equals to compare?)
			undo B
		build dependency graph and remove all edges that are part of a loop
		find + apply lowest layer timestamp node (static) without outgoing edge
[...players after objects, not sure there's a case where the above would be needed there 🤔]

I couldn't really think of any better approach with heuristics but I think we can still do it!

@Hanmac
Copy link
Contributor Author

Hanmac commented Jan 20, 2025

@tool4ever checkout this Repo:
https://github.com/Soothsilver/ContinuousEffectsSandbox
(i noticed that it was already linked in the linked blog)

the problem there is that it can't do dependency loop yet

some cases from this blog wasn't even on my radar yet

Our Main Problem is the checking "if Applying A first, would that change what B applies to" would require to make a Copy of the Game State first
And that could be too complicated

@Hanmac
Copy link
Contributor Author

Hanmac commented Jan 20, 2025

@tool4ever
I think the more tricky ones might be trying to have Bestow use Effect Layers because it's a "would be" effect that looks into the future

@tool4ever
Copy link
Contributor

Our Main Problem is the checking "if Applying A first, would that change what B applies to" would require to make a Copy of the Game State first And that could be too complicated

It would certainly be faster if we can do it on the existing game objects:

  1. applyContinuousAbility(A)
  2. compare B
  3. game.getStaticEffects().getStaticEffect(A).remove()

I think the engine could handle it like that, the View would be frozen anyway and the rest is just Tables/Maps being modified.
To compare calling getAffectedCards and some AbilityUtils stuff should also not touch anything else.

@Hanmac
Copy link
Contributor Author

Hanmac commented Jan 20, 2025

the View would be frozen anyway

I don't think the frozen is applied strong enough
for example right now when i can look at top card of library, it does flicker multiple times

@tool4ever
Copy link
Contributor

That might be because TrackableProperty.PlayerMayLook has FreezeMode.IgnoresFreeze

Could investigate if that's still needed for netplay (?) otherwise we can still implement updateView logic when running statics

I also have a branch where players get handled in better order:
tool4ever@78fe805

@Hanmac
Copy link
Contributor Author

Hanmac commented Jan 20, 2025

I also have a branch where players get handled in better order:
tool4ever@78fe805

For your code, i would made a separate applyContinuousAbility just for Players
Also:

613.10. ... All such effects are applied in timestamp order after the determination of objects’ characteristics.

Wouldn't that mean these should NOT use Layers?


my first attempt for this issue would be something like this:


sortStaticAbilties(List<StaticAbility> list) {
    result = [];
    result.addAll(sortStaticAbiltiesByTimestampAndDependency(list.filter(CDA));
    result.addAll(sortStaticAbiltiesByTimestampAndDependency(list.filter(NonCDA));
    return result;
}

sortStaticAbiltiesByTimestampAndDependency(List<StaticAbility> list)
{
    if (list.size <= 1) {
        return list;
    }
    // TODO check for dependency
    return list.sortBy(TimeStamp)
}

until i noticed that this wouldn't work unless all effects use Static Abilties

@Hanmac
Copy link
Contributor Author

Hanmac commented Jan 21, 2025

Layer6:

addChangedCardTraits

  • StaticAbilityContinous
  • MANABOND CounterType (playtest)
  • AnimateEffectBase
  • Suspected (Can't Block)
  • PlayerKeywords

addChangedCardKeywords

  • StaticAbilityContinous
  • Suspected (Menace)
  • Mana Keywords (ManaSpent to cast Spells, give them Keywords)
  • Ability CounterType
  • PumpAllEffect
  • PumpEffect
  • DebuffEffect (when Protection from all Colors is split)
  • ProtectAllEffect
  • ProtectEffect
  • ControlGainEffect
  • ChangeZoneEffect (Uneath)
  • CloneEffect and TokenEffectBase with PumpKeyword
  • Bestow animate Bestow as Static Ability #6811

@tool4ever
Copy link
Contributor

tool4ever commented Jan 21, 2025

I'm also gunning for AnimateSubAbility #6816

@Hanmac
Copy link
Contributor Author

Hanmac commented Jan 21, 2025

@tool4ever did you comment to the wrong issue?

@tool4ever
Copy link
Contributor

Well I guess I can move the details to a sub-issue

@Hanmac
Copy link
Contributor Author

Hanmac commented Jan 22, 2025

Imo one of the better ressources (not too outdated and lots of examples) is:
https://outsidetheasylum.blog/dependency/

I don't understand this case:

You control Xenograft naming "Zombie", Urborg, Tomb of Yawgmoth, and Kormus Bell. What does Urborg look like?
It's a 1/1 creature with no creature types.
In layer 4, the only dependency is Kormus Bell on Urborg, so we apply Xenograft first, not doing anything. Then we apply Urborg, making itself a Swamp, and then Kormus Bell making it a creature.

Doesn't Xenograft depend on Kormus Bell depending on Urborg, Tomb of Yawgmoth?

I think I need to ask a Judge to explain it to me better

@tool4ever
Copy link
Contributor

You're thinking "too far" into the future...
But the Judge explains since a dependency is defined as a relation between effects X and Y, you only ever compare pair-wise while building a dependency graph.

This is what I tried to express with: "it's noteworthy that this should be a flat loop instead of recursion after each static"
It's a great simplification for our goal. 🚀

  • A: Each creature you control is the chosen type in addition to its other types.
  • B: Each land is a Swamp in addition to its other land types.
  • C: All Swamps are 1/1 black creatures that are still lands.

Now you only check like this:

  • Apply A vs. B then A:
    • A still exists ✅
    • Affected stays identical (Empty) ✅
    • A still tries to do the same ✅

+

  • Apply A vs. C then A:
    • A still exists ✅
    • Affected stays identical (Empty) ✅
    • A still tries to do the same ✅
  1. ➡ No dependency for Xenograft exists
  2. B +C would be checked accordingly1
  3. only then you'd make a decision and move on to a new "round"

Also important here:

For all of these questions, the timestamp order of objects/effects is the order they're mentioned in.

➡ If A had the latest you would get the 1/1 Zombie.

Assume that that there are no permanents on the battlefield other than the ones mentioned.

➡ Or if you already control a Swamp at the start of L4 (via intrinsic / L1 copy / L2 control change) then yes, a dependency would exist because A would "directly" depend on C

Hope that made sense :)

Footnotes

  1. It's possible we could skip this step partly in code because if we start from the earliest timestamped effect the first dependency free one we find can be safely applied already? 🤔

@Hanmac
Copy link
Contributor Author

Hanmac commented Jan 23, 2025

@tool4ever yeah i was asking them directly on twitter too, i just didn't link the post there yet

in my opinion, a much deeper check would make more sense, but so it is easier for us, i think

@tool4ever tool4ever mentioned this issue Jan 25, 2025
2 tasks
@tool4ever
Copy link
Contributor

I could also fix cases like this:
Image

However this requires switching changedCardTypes into HashBasedTable so it doesn't mess up the insertion order.

Might be something to think about in a follow-up.

@Hanmac
Copy link
Contributor Author

Hanmac commented Jan 25, 2025

@tool4ever I was thinking about LinkedHashMap

But we need to change the other effects fir this too

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants