ffxiv-teamcraft / simulator

Crafting simulator for FINAL FANTASY XIV
MIT License
24 stars 18 forks source link

Bug: Version 1.1+ appears to break progression and quality increases #3

Closed karashiiro closed 5 years ago

karashiiro commented 5 years ago

Tried executing a Hempen Yarn craft on the latest version (1.2.0) and on 1.0.5. The following is the returned object from simulation.run(). The same code and the same parameters were used in both runs, which is to say that the version was controlled for. Notably, on versions 1.1.0 and above, this craft with a 1x Focused Touch -> 2x Focused Synthesis macro returns NaN at certain points, failing the craft with an undefined fail cause. I haven't formally tested this bug on other crafts, but I did notice it return NaN values with this same macro on the Rakshasa Ring of Aiming on version 1.1.0.

Latest version:

3|prima-extra     | { steps:
3|prima-extra     |    [ { action: Observe {},
3|prima-extra     |        success: true,
3|prima-extra     |        addedQuality: 0,
3|prima-extra     |        addedProgression: 0,
3|prima-extra     |        cpDifference: -7,
3|prima-extra     |        skipped: false,
3|prima-extra     |        solidityDifference: 0,
3|prima-extra     |        state: 'NORMAL',
3|prima-extra     |        failCause: undefined,
3|prima-extra     |        afterBuffTick: [Object] },
3|prima-extra     |      { action: FocusedTouch {},
3|prima-extra     |        success: true,
3|prima-extra     |        addedQuality: NaN,
3|prima-extra     |        addedProgression: 0,
3|prima-extra     |        cpDifference: -18,
3|prima-extra     |        skipped: false,
3|prima-extra     |        solidityDifference: -10,
3|prima-extra     |        state: 'NORMAL',
3|prima-extra     |        failCause: undefined,
3|prima-extra     |        afterBuffTick: [Object] },
3|prima-extra     |      { action: Observe {},
3|prima-extra     |        success: true,
3|prima-extra     |        addedQuality: NaN,
3|prima-extra     |        addedProgression: 0,
3|prima-extra     |        cpDifference: -7,
3|prima-extra     |        skipped: false,
3|prima-extra     |        solidityDifference: 0,
3|prima-extra     |        state: 'NORMAL',
3|prima-extra     |        failCause: undefined,
3|prima-extra     |        afterBuffTick: [Object] },
3|prima-extra     |      { action: FocusedSynthesis {},
3|prima-extra     |        success: true,
3|prima-extra     |        addedQuality: NaN,
3|prima-extra     |        addedProgression: NaN,
3|prima-extra     |        cpDifference: -5,
3|prima-extra     |        skipped: false,
3|prima-extra     |        solidityDifference: -10,
3|prima-extra     |        state: 'NORMAL',
3|prima-extra     |        failCause: undefined,
3|prima-extra     |        afterBuffTick: [Object] },
3|prima-extra     |      { action: Observe {},
3|prima-extra     |        success: true,
3|prima-extra     |        addedQuality: NaN,
3|prima-extra     |        addedProgression: NaN,
3|prima-extra     |        cpDifference: -7,
3|prima-extra     |        skipped: false,
3|prima-extra     |        solidityDifference: 0,
3|prima-extra     |        state: 'NORMAL',
3|prima-extra     |        failCause: undefined,
3|prima-extra     |        afterBuffTick: [Object] },
3|prima-extra     |      { action: FocusedSynthesis {},
3|prima-extra     |        success: true,
3|prima-extra     |        addedQuality: NaN,
3|prima-extra     |        addedProgression: NaN,
3|prima-extra     |        cpDifference: -5,
3|prima-extra     |        skipped: false,
3|prima-extra     |        solidityDifference: -10,
3|prima-extra     |        state: 'NORMAL',
3|prima-extra     |        failCause: undefined,
3|prima-extra     |        afterBuffTick: [Object] } ],
3|prima-extra     |   hqPercent: undefined,
3|prima-extra     |   success: false,
3|prima-extra     |   simulation:
3|prima-extra     |    Simulation {
3|prima-extra     |      recipe:
3|prima-extra     |       { id: 464,
3|prima-extra     |         job: 13,
3|prima-extra     |         rlvl: 1,
3|prima-extra     |         durability: 60,
3|prima-extra     |         quality: 312,
3|prima-extra     |         progress: 9,
3|prima-extra     |         lvl: 1,
3|prima-extra     |         stars: 0,
3|prima-extra     |         hq: 0,
3|prima-extra     |         quickSynth: 0,
3|prima-extra     |         controlReq: 0,
3|prima-extra     |         craftmanshipReq: 0,
3|prima-extra     |         ingredients: [Array],
3|prima-extra     |         yield: 1 },
3|prima-extra     |      actions:
3|prima-extra     |       [ Observe {},
3|prima-extra     |         FocusedTouch {},
3|prima-extra     |         Observe {},
3|prima-extra     |         FocusedSynthesis {},
3|prima-extra     |         Observe {},
3|prima-extra     |         FocusedSynthesis {} ],
3|prima-extra     |      _crafterStats:
3|prima-extra     |       CrafterStats {
3|prima-extra     |         jobId: 13,
3|prima-extra     |         craftsmanship: 1500,
3|prima-extra     |         _control: 1536,
3|prima-extra     |         cp: 539,
3|prima-extra     |         specialist: true,
3|prima-extra     |         level: 69,
3|prima-extra     |         levels: [Array] },
3|prima-extra     |      hqIngredients: [],
3|prima-extra     |      forceFailed: [],
3|prima-extra     |      progression: NaN,
3|prima-extra     |      quality: NaN,
3|prima-extra     |      startingQuality: 0,
3|prima-extra     |      state: 'NORMAL',
3|prima-extra     |      buffs: [],
3|prima-extra     |      success: undefined,
3|prima-extra     |      steps:
3|prima-extra     |       [ [Object], [Object], [Object], [Object], [Object], [Object] ],
3|prima-extra     |      lastPossibleReclaimStep: -1,
3|prima-extra     |      safe: false,
3|prima-extra     |      durability: 30,
3|prima-extra     |      availableCP: 490,
3|prima-extra     |      maxCP: 539 } }

1.0.5:

3|prima-extra     | { steps:
3|prima-extra     |    [ { action: Observe {},
3|prima-extra     |        success: true,
3|prima-extra     |        addedQuality: 0,
3|prima-extra     |        addedProgression: 0,
3|prima-extra     |        cpDifference: -7,
3|prima-extra     |        skipped: false,
3|prima-extra     |        solidityDifference: 0,
3|prima-extra     |        state: 'NORMAL',
3|prima-extra     |        failCause: undefined,
3|prima-extra     |        afterBuffTick: [Object] },
3|prima-extra     |      { action: FocusedTouch {},
3|prima-extra     |        success: true,
3|prima-extra     |        addedQuality: 988,
3|prima-extra     |        addedProgression: 0,
3|prima-extra     |        cpDifference: -18,
3|prima-extra     |        skipped: false,
3|prima-extra     |        solidityDifference: -10,
3|prima-extra     |        state: 'NORMAL',
3|prima-extra     |        failCause: undefined,
3|prima-extra     |        afterBuffTick: [Object] },
3|prima-extra     |      { action: Observe {},
3|prima-extra     |        success: true,
3|prima-extra     |        addedQuality: 0,
3|prima-extra     |        addedProgression: 0,
3|prima-extra     |        cpDifference: -7,
3|prima-extra     |        skipped: false,
3|prima-extra     |        solidityDifference: 0,
3|prima-extra     |        state: 'NORMAL',
3|prima-extra     |        failCause: undefined,
3|prima-extra     |        afterBuffTick: [Object] },
3|prima-extra     |      { action: FocusedSynthesis {},
3|prima-extra     |        success: true,
3|prima-extra     |        addedQuality: 0,
3|prima-extra     |        addedProgression: 1090,
3|prima-extra     |        cpDifference: -5,
3|prima-extra     |        skipped: false,
3|prima-extra     |        solidityDifference: -10,
3|prima-extra     |        state: 'NORMAL',
3|prima-extra     |        failCause: undefined,
3|prima-extra     |        afterBuffTick: [Object] },
3|prima-extra     |      { action: Observe {},
3|prima-extra     |        success: null,
3|prima-extra     |        addedQuality: 0,
3|prima-extra     |        addedProgression: 0,
3|prima-extra     |        cpDifference: 0,
3|prima-extra     |        skipped: true,
3|prima-extra     |        solidityDifference: 0,
3|prima-extra     |        state: 'NORMAL',
3|prima-extra     |        failCause: undefined,
3|prima-extra     |        afterBuffTick: [Object] },
3|prima-extra     |      { action: FocusedSynthesis {},
3|prima-extra     |        success: null,
3|prima-extra     |        addedQuality: 0,
3|prima-extra     |        addedProgression: 0,
3|prima-extra     |        cpDifference: 0,
3|prima-extra     |        skipped: true,
3|prima-extra     |        solidityDifference: 0,
3|prima-extra     |        state: 'GOOD',
3|prima-extra     |        failCause: undefined,
3|prima-extra     |        afterBuffTick: [Object] } ],
3|prima-extra     |   hqPercent: 100,
3|prima-extra     |   success: true,
3|prima-extra     |   simulation:
3|prima-extra     |    Simulation {
3|prima-extra     |      recipe:
3|prima-extra     |       { id: 464,
3|prima-extra     |         job: 13,
3|prima-extra     |         rlvl: 1,
3|prima-extra     |         durability: 60,
3|prima-extra     |         quality: 312,
3|prima-extra     |         progress: 9,
3|prima-extra     |         lvl: 1,
3|prima-extra     |         stars: 0,
3|prima-extra     |         hq: 0,
3|prima-extra     |         quickSynth: 0,
3|prima-extra     |         controlReq: 0,
3|prima-extra     |         craftmanshipReq: 0,
3|prima-extra     |         ingredients: [Array],
3|prima-extra     |         yield: 1 },
3|prima-extra     |      actions:
3|prima-extra     |       [ Observe {},
3|prima-extra     |         FocusedTouch {},
3|prima-extra     |         Observe {},
3|prima-extra     |         FocusedSynthesis {},
3|prima-extra     |         Observe {},
3|prima-extra     |         FocusedSynthesis {} ],
3|prima-extra     |      _crafterStats:
3|prima-extra     |       CrafterStats {
3|prima-extra     |         jobId: 13,
3|prima-extra     |         craftsmanship: 1500,
3|prima-extra     |         _control: 1536,
3|prima-extra     |         cp: 539,
3|prima-extra     |         specialist: true,
3|prima-extra     |         level: 69,
3|prima-extra     |         levels: [Array] },
3|prima-extra     |      hqIngredients: [],
3|prima-extra     |      forceFailed: [],
3|prima-extra     |      progression: 1090,
3|prima-extra     |      quality: 988,
3|prima-extra     |      startingQuality: 0,
3|prima-extra     |      state: 'NORMAL',
3|prima-extra     |      buffs: [],
3|prima-extra     |      success: true,
3|prima-extra     |      steps:
3|prima-extra     |       [ [Object], [Object], [Object], [Object], [Object], [Object] ],
3|prima-extra     |      lastPossibleReclaimStep: -1,
3|prima-extra     |      safe: false,
3|prima-extra     |      durability: 40,
3|prima-extra     |      availableCP: 502,
3|prima-extra     |      maxCP: 539 } }
Supamiu commented 5 years ago

Can you reproduce this using 1.2.1? If you do, the perfect next step would be to implement a test able to see this bug in order to fix this using TDD.

karashiiro commented 5 years ago

Sorry for the delay, was waiting on XIVAPI's DB search to be repaired. The results with 1.2.1 seem identical to the bugged output in 1.1.0+.

3|prima-extra  | { steps:
3|prima-extra  |    [ { action: Observe {},
3|prima-extra  |        success: true,
3|prima-extra  |        addedQuality: 0,
3|prima-extra  |        addedProgression: 0,
3|prima-extra  |        cpDifference: -7,
3|prima-extra  |        skipped: false,
3|prima-extra  |        solidityDifference: 0,
3|prima-extra  |        state: 'NORMAL',
3|prima-extra  |        failCause: undefined,
3|prima-extra  |        afterBuffTick: [Object] },
3|prima-extra  |      { action: FocusedTouch {},
3|prima-extra  |        success: true,
3|prima-extra  |        addedQuality: NaN,
3|prima-extra  |        addedProgression: 0,
3|prima-extra  |        cpDifference: -18,
3|prima-extra  |        skipped: false,
3|prima-extra  |        solidityDifference: -10,
3|prima-extra  |        state: 'GOOD',
3|prima-extra  |        failCause: undefined,
3|prima-extra  |        afterBuffTick: [Object] },
3|prima-extra  |      { action: Observe {},
3|prima-extra  |        success: true,
3|prima-extra  |        addedQuality: NaN,
3|prima-extra  |        addedProgression: 0,
3|prima-extra  |        cpDifference: -7,
3|prima-extra  |        skipped: false,
3|prima-extra  |        solidityDifference: 0,
3|prima-extra  |        state: 'GOOD',
3|prima-extra  |        failCause: undefined,
3|prima-extra  |        afterBuffTick: [Object] },
3|prima-extra  |      { action: FocusedSynthesis {},
3|prima-extra  |        success: true,
3|prima-extra  |        addedQuality: NaN,
3|prima-extra  |        addedProgression: NaN,
3|prima-extra  |        cpDifference: -5,
3|prima-extra  |        skipped: false,
3|prima-extra  |        solidityDifference: -10,
3|prima-extra  |        state: 'NORMAL',
3|prima-extra  |        failCause: undefined,
3|prima-extra  |        afterBuffTick: [Object] },
3|prima-extra  |      { action: Observe {},
3|prima-extra  |        success: true,
3|prima-extra  |        addedQuality: NaN,
3|prima-extra  |        addedProgression: NaN,
3|prima-extra  |        cpDifference: -7,
3|prima-extra  |        skipped: false,
3|prima-extra  |        solidityDifference: 0,
3|prima-extra  |        state: 'NORMAL',
3|prima-extra  |        failCause: undefined,
3|prima-extra  |        afterBuffTick: [Object] },
3|prima-extra  |      { action: FocusedSynthesis {},
3|prima-extra  |        success: true,
3|prima-extra  |        addedQuality: NaN,
3|prima-extra  |        addedProgression: NaN,
3|prima-extra  |        cpDifference: -5,
3|prima-extra  |        skipped: false,
3|prima-extra  |        solidityDifference: -10,
3|prima-extra  |        state: 'GOOD',
3|prima-extra  |        failCause: undefined,
3|prima-extra  |        afterBuffTick: [Object] } ],
3|prima-extra  |   hqPercent: undefined,
3|prima-extra  |   success: false,
3|prima-extra  |   simulation:
3|prima-extra  |    Simulation {
3|prima-extra  |      recipe:
3|prima-extra  |       { id: 464,
3|prima-extra  |         job: 13,
3|prima-extra  |         rlvl: 1,
3|prima-extra  |         durability: 60,
3|prima-extra  |         quality: 312,
3|prima-extra  |         progress: 9,
3|prima-extra  |         lvl: 1,
3|prima-extra  |         stars: 0,
3|prima-extra  |         hq: 0,
3|prima-extra  |         quickSynth: 0,
3|prima-extra  |         controlReq: 0,
3|prima-extra  |         craftmanshipReq: 0,
3|prima-extra  |         ingredients: [Array],
3|prima-extra  |         yield: 1 },
3|prima-extra  |      actions:
3|prima-extra  |       [ Observe {},
3|prima-extra  |         FocusedTouch {},
3|prima-extra  |         Observe {},
3|prima-extra  |         FocusedSynthesis {},
3|prima-extra  |         Observe {},
3|prima-extra  |         FocusedSynthesis {} ],
3|prima-extra  |      _crafterStats:
3|prima-extra  |       CrafterStats {
3|prima-extra  |         jobId: 13,
3|prima-extra  |         craftsmanship: 1500,
3|prima-extra  |         _control: 1536,
3|prima-extra  |         cp: 539,
3|prima-extra  |         specialist: true,
3|prima-extra  |         level: 69,
3|prima-extra  |         levels: [Array] },
3|prima-extra  |      hqIngredients: [],
3|prima-extra  |      forceFailed: [],
3|prima-extra  |      progression: NaN,
3|prima-extra  |      quality: NaN,
3|prima-extra  |      startingQuality: 0,
3|prima-extra  |      state: 'NORMAL',
3|prima-extra  |      buffs: [],
3|prima-extra  |      success: undefined,
3|prima-extra  |      steps:
3|prima-extra  |       [ [Object], [Object], [Object], [Object], [Object], [Object] ],
3|prima-extra  |      lastPossibleReclaimStep: -1,
3|prima-extra  |      safe: false,
3|prima-extra  |      durability: 30,
3|prima-extra  |      availableCP: 490,
3|prima-extra  |      maxCP: 539 } }
Supamiu commented 5 years ago

Can you show me the exact call you're making? Seems like a simple test can't reproduce this, as you can see from the commit above

karashiiro commented 5 years ago

Removed the JSON processing for clarity, everything in recipe can be retrieved from here.

// Lines 32-40
const jobIdMap = new Map();
jobIdMap.set("CRP", 8);
jobIdMap.set("BSM", 9);
jobIdMap.set("ARM", 10);
jobIdMap.set("GSM", 11);
jobIdMap.set("LTW", 12);
jobIdMap.set("WVR", 13);
jobIdMap.set("ALC", 14);
jobIdMap.set("CUL", 15);
// Line 181
const craftingMacro = Teamcraft.CraftingActionsRegistry.deserializeRotation(craftStats.macro);
// Lines 230-291
var craft = { // "recipe" is a JSON response from https://xivapi.com/recipe/<ID>
    "id": recipe.ID,
    "job": recipe.ClassJob.ID,
    "rlvl": recipe.RecipeLevelTable.ID,
    "durability": recipe.RecipeLevelTable.Durability,
    "quality": recipe.RecipeLevelTable.Quality,
    "progress": Math.floor(recipe.RecipeLevelTable.Difficulty / 2),
    "lvl": recipe.RecipeLevelTable.ClassJobLevel,
    "stars": recipe.RecipeLevelTable.Stars,
    "hq": hq, // 1 or 0
    "quickSynth": quickSynth, // 1 or 0
    "controlReq": recipe.RequiredControl,
    "craftmanshipReq": recipe.RequiredCraftsmanship,
    "ingredients": [],
    "yield": recipe.AmountResult
};

for (var i = 0; i <= 9; i++) {
    if (recipe[`AmountIngredient${i}`] > 0) {
        craft.ingredients.push({
            "id": recipe[`ItemIngredient${i}ID`],
            "amount": recipe[`AmountIngredient${i}`]
        });
    }
}

// Set stats
const STATS = new Teamcraft.CrafterStats(
    jobIdMap.get(userJob),                  // User job (int)
    craftStats.craftmanship,                // Craftsmanship (int)
    craftStats.control,                     // Control (int)
    craftStats.cp,                          // CP (int)
    craftStats.specialist,                  // Specialist (true/false)
    jobLevelMap.get(jobIdMap.get(userJob)), // User job level (int)
    [
        jobLevelMap.get(jobIdMap.get("CRP")), // int
        jobLevelMap.get(jobIdMap.get("BSM")), // int
        jobLevelMap.get(jobIdMap.get("ARM")), // int
        jobLevelMap.get(jobIdMap.get("GSM")), // int
        jobLevelMap.get(jobIdMap.get("LTW")), // int
        jobLevelMap.get(jobIdMap.get("WVR")), // int
        jobLevelMap.get(jobIdMap.get("ALC")), // int
        jobLevelMap.get(jobIdMap.get("CUL")), // int
    ]
);

// Execute craft
const simulation = new Teamcraft.Simulation(craft, craftingMacro, STATS);
const result = simulation.run();
const failCause = (reason) => {
    if (reason === "DURABILITY_REACHED_ZERO") return " because the craft's durability hit zero.";
    if (reason === "MISSING_LEVEL_REQUIREMENT") return " because you aren't a high enough level to attempt that craft.";
    if (reason === "NOT_ENOUGH_CP") return " because you don't have enough CP to execute that macro.";
    if (reason === "NOT_SPECIALIST") return " because you can't use Specialist actions without being a Specialist.";
    if (reason === "NO_INNER_QUIET") return " because you didn't execute Inner Quiet when it was required.";
    return ".";
};
console.log(result);
const reliabilityReport = simulation.getReliabilityReport();

logger.log('info', `Result: ${result.success ? "Success!" : "The craft failed" + failCause(result.failCause) + ","} Quality: ${result.hqPercent}%`);
Supamiu commented 5 years ago

Ok I found the issue (finally).

This is because your recipe doesn't provide a suggestedCraftsmanship or it doesn't provide a suggestedControl (in this case, both).

karashiiro commented 5 years ago

Oh, it was 3e2a778, adding those properties fixed it. Thank you for the clarification, closing the issue.