Card-script param declarations (batch 1: framework classes)#11007
Card-script param declarations (batch 1: framework classes)#11007MostCromulent wants to merge 14 commits into
Conversation
Fails the build when a framework or effect class reads a card-script parameter it does not declare in OPTIONAL_PARAMS/REQUIRED_PARAMS, or declares one that appears nowhere in its source. The check is opt-in per class, so declarations can be added a few classes at a time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the hardcoded list of framework file paths with a walk over the module source roots, classifying each declarer as base or effect by location. Discovery keys on the field assignment (shared with the parse helpers so they can't drift), tolerating an explicit "new String[]" initializer, and a guard fails loudly if the walk finds nothing rather than passing vacuously. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implements the IHasForgeParams interface suggested in review: a class that reads card-script params implements it and lists them in its OPTIONAL_PARAMS field (effects also a REQUIRED_PARAMS), so the upcoming card-script linter can ask a class which params it accepts. getOptionalParams/getRequiredParams are default methods that read the runtime class's field via getClass().getField rather than returning it directly. Because the field is static, a direct return on a base class would hand subclasses the base's field; reading getClass()'s field gives each its own layer (or the inherited one when it declares none), so declarers need no per-class override and there is no wrong-layer hazard. The drift test discovers declarers by scanning the classpath for implementors instead of a hardcoded path list. forge-ai classes are off the forge-game test classpath (the dependency runs forge-ai -> forge-game), so its declarers are found by walking source; discovery fails loudly if it comes up empty or misses a known framework class. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
|
||
| // The literal forms a card-script parameter read takes in source | ||
| private static final Pattern GETPARAM = Pattern.compile( | ||
| "(?<![A-Za-z0-9_])(?:getParam|hasParam|getParamOrDefault|getParamOrDefaultBoolean)\\(\\s*\"([^\"]+)\""); |
There was a problem hiding this comment.
getParamOrDefaultBoolean doesn't exist?
| public class AbilityUtils { | ||
| public class AbilityUtils implements IHasForgeParams { | ||
| public static final String[] OPTIONAL_PARAMS = { | ||
| "AbilityCount", "AnnounceMax", "Destination", "ETB", "ForgetOtherTargets", |
There was a problem hiding this comment.
Destination is effect specific?
| */ | ||
| public final class AbilityFactory { | ||
| public final class AbilityFactory implements IHasForgeParams { | ||
| public static final String[] OPTIONAL_PARAMS = { |
There was a problem hiding this comment.
I'd still like to see additionalAbilityKeys unioned here less manually
| public class Cost implements Serializable { | ||
| public class Cost implements Serializable, IHasForgeParams { | ||
| public static final String[] OPTIONAL_PARAMS = { | ||
| "AffectedZone", "Amount", "Announce", "Collected", "CollectedCards", "Color", |
There was a problem hiding this comment.
would like to see the Hash* fields queried instead if possible
| public abstract class SpellAbility extends CardTraitBase implements ISpellAbility, IIdentifiable, Comparable<SpellAbility> { | ||
| public static final String[] OPTIONAL_PARAMS = { | ||
| "AlternateCost", "Amount", "Announce", "Boast", "CantCopy", "CloakUp", "CostDesc", | ||
| "CumulativeUpkeep", "DisguiseUp", "DividedAsYouChoose", "Exhaust", "Hidden", |
There was a problem hiding this comment.
sometimes, like with CumulativeUpkeep they are only used internally as helper
so for this case if it'd show up in a script it'd actually be wrong
conceptually I'm not sure yet if we need a third category for params that are technically still implemented but very exotic/deprecated instead 🤔
|
In general direction seems good.
I want runtime so the Card Workshop can eventually also make partial use of these. 💡 For now I'd still like to see the scope more clearly defined:
So the future order could be this:
Might actually be worth a markdown file explaining these workflow steps 🤔 |
Batch 1 of the per-class card-script parameter declarations proposed in #10984 (comment).
Summary
This batch covers the framework/base classes rather than individual effects. These classes hold the parameters shared across nearly every ability — costs, conditions, activation/target restrictions, descriptions,
Defined, sub-ability keys, and AI hints — so their params are all optional and together form the base set that every effect inherits. Effect classes (later batches) will declare their own params on top of this base and additionally mark which are required.Each class now exposes a
public static final String[] OPTIONAL_PARAMSlisting the params it reads, making the parameters a class consumes self-documenting.How the params were chosen
The list for each class was produced programmatically, not hand-picked: a regex scanner reads each class's source for the literal forms a param read takes —
getParam/hasParam/getParamOrDefaultcalls, the defined-resolution helpers (getCards,getDefinedPlayersOrTargeted, …), map lookups, and positional helpers likeaddToCombat— and each class declares exactly that set. The same matcher backs the drift test, so the lists are mechanically reproducible rather than a one-off snapshot.Claude then adversarially reviewed the scanner's rules and its output against the source: this is what caught false positives — e.g. defined-values like
"Self"and"Targeted"being mistaken for param names — and tightened the rules.SpellAbilityAiis the one hand-curated list: as a cross-cutting reader it touches params owned by many effects, so it declares only its ownAI*knobs.The drift test
CardScriptParamDeclarationTestfails if a class reads a param it doesn't declare, or declares one that appears nowhere in its source — so the declarations can't silently fall out of sync with the code. It is opt-in per class, so later batches can land a few classes at a time.🤖 Generated with Claude Code