Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/* BDSP Blink Extraction
*
* From: https://github.com/PokemonAutomation/
*
*/

#ifndef PokemonAutomation_PokemonBDSP_BlinkExtraction_H
#define PokemonAutomation_PokemonBDSP_BlinkExtraction_H

#include <stddef.h>
#include <string>
#include <vector>
#include "PokemonBDSP/Programs/RngManipulation/PokemonBDSP_BlinkModel.h"
#include "PokemonBDSP/Programs/RngManipulation/PokemonBDSP_StateSolver.h"
#include "PokemonBDSP_EyeBlinkDetector.h"

namespace PokemonAutomation{
namespace NintendoSwitch{
namespace PokemonBDSP{


struct Blink{
double seconds = 0;
double depth = 0;
size_t frames = 0;
};

struct BlinkEvent{
double seconds = 0;
BlinkType type = BlinkType::Single;
};

struct TickFit{
double period_seconds = 0;
double rms_ticks = 0;
double worst_ticks = 0;
};


// How far the match has to fall below its resting level to count as a blink
double auto_blink_threshold(const std::vector<BlinkMatchSample>& samples);


std::vector<Blink> extract_blinks(
const std::vector<BlinkMatchSample>& samples, double threshold,
WallClock origin = WallClock::min()
);

// A second blink arriving within "double_blink_seconds" is the same roll
std::vector<BlinkEvent> group_blinks(
const std::vector<Blink>& blinks,
double double_blink_seconds = 0.55
);

// The NPC tick is nominally 1.017 s but measures 1.0197 s on real hardware
TickFit fit_tick_period(
const std::vector<BlinkEvent>& events,
double lowest_seconds = 1.000,
double highest_seconds = 1.040
);

bool build_samples(
const std::vector<std::vector<BlinkEvent>>& streams,
const std::vector<uint8_t>& slots,
double period_seconds,
std::vector<BlinkSample>& samples,
std::string& failure_reason
);

bool last_blink_anchor(
const std::vector<std::vector<BlinkEvent>>& streams,
const std::vector<uint8_t>& slots,
double period_seconds,
uint64_t& advance,
double& seconds,
size_t& stream_index
);


// Move an anchor forward onto a later blink from the same stream.
bool step_blink_anchor(
double elapsed_seconds,
double period_seconds,
uint8_t npcs,
uint64_t previous_advance,
uint64_t& advance
);


}
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/* BDSP Blink Model
*
* From: https://github.com/PokemonAutomation/
*
*/

#include <algorithm>
#include "Common/Cpp/Exceptions.h"
#include "PokemonBDSP_BlinkModel.h"

namespace PokemonAutomation{
namespace NintendoSwitch{
namespace PokemonBDSP{


// The fraction is 23 bits over 2^23 - 1.
static const double POKEMON_BLINK_FRACTION_SCALE = 8388607.0;
static const uint32_t POKEMON_BLINK_FRACTION_MASK = 0x7fffff;

// Longest and shortest gap the game can produce, and the span between them.
static const double POKEMON_BLINK_LONGEST_SECONDS =
BDSP_POKEMON_BLINK_MAX_SECONDS + BDSP_POKEMON_BLINK_OFFSET_SECONDS;
static const double POKEMON_BLINK_SPAN_SECONDS =
BDSP_POKEMON_BLINK_MAX_SECONDS - BDSP_POKEMON_BLINK_MIN_SECONDS;


const char* blink_type_name(BlinkType type){
switch (type){
case BlinkType::Single: return "Single";
case BlinkType::Double: return "Double";
}
return "?";
}

double bdsp_range_float(uint32_t roll, double minimum, double maximum){
// 23 bits of mantissa divided by 2^23 - 1, not 2^23.
double fraction = (double)(roll & POKEMON_BLINK_FRACTION_MASK) / POKEMON_BLINK_FRACTION_SCALE;
return fraction * minimum + (1.0 - fraction) * maximum;
}

double bdsp_pokemon_blink_interval(uint32_t roll){
return bdsp_range_float(roll, BDSP_POKEMON_BLINK_MIN_SECONDS, BDSP_POKEMON_BLINK_MAX_SECONDS)
+ BDSP_POKEMON_BLINK_OFFSET_SECONDS;
}


bool bdsp_pokemon_blink_fraction(double interval_seconds, uint32_t& fraction){
// bdsp_range_float runs backwards, so a longer gap means a smaller fraction.
double scaled = (POKEMON_BLINK_LONGEST_SECONDS - interval_seconds) / POKEMON_BLINK_SPAN_SECONDS;

// The two extreme intervals are not exactly representable
const double SLACK = 1e-9;
if (!(scaled >= -SLACK) || scaled > 1.0 + SLACK){
// NaN is rejected rather than sliding through.
return false;
}
scaled = scaled < 0.0 ? 0.0 : scaled;

double value = scaled * POKEMON_BLINK_FRACTION_SCALE;
fraction = value >= (double)POKEMON_BLINK_FRACTION_MASK
? POKEMON_BLINK_FRACTION_MASK
: (uint32_t)value;
return true;
}

bool bdsp_pokemon_blink_bucket_with_margin(
double interval_seconds, uint32_t& bucket, double& margin_seconds
){
uint32_t fraction = 0;
if (!bdsp_pokemon_blink_fraction(interval_seconds, fraction)){
return false;
}

const size_t SHIFT = 23 - BDSP_POKEMON_BLINK_KNOWN_BITS;
const uint32_t BUCKET_WIDTH = (uint32_t)1 << SHIFT;
bucket = fraction >> SHIFT;

// Distance to whichever end of the bucket is nearer, converted from fraction
// units back into seconds of timing error.
uint32_t into_bucket = fraction - (bucket << SHIFT);
uint32_t to_next = BUCKET_WIDTH - into_bucket;
uint32_t nearest = into_bucket < to_next ? into_bucket : to_next;

margin_seconds = (double)nearest
* (BDSP_POKEMON_BLINK_MAX_SECONDS - BDSP_POKEMON_BLINK_MIN_SECONDS)
/ 8388607.0;

// An interval near either end of the range has nowhere to be wrong towards,
// so cap the margin by the distance to the range itself.
double to_longest = POKEMON_BLINK_LONGEST_SECONDS - interval_seconds;
double to_shortest = interval_seconds
- (BDSP_POKEMON_BLINK_MIN_SECONDS + BDSP_POKEMON_BLINK_OFFSET_SECONDS);
margin_seconds = std::min(margin_seconds, std::min(to_longest, to_shortest));
margin_seconds = std::max(margin_seconds, 0.0);

return true;
}

std::vector<BlinkTick> generate_blink_ticks(
Pokemon::Xorshift128 rng,
size_t ticks,
uint8_t npcs,
uint8_t slot
){
if (npcs == 0){
throw InternalProgramError(
nullptr, PA_CURRENT_FUNCTION,
"generate_blink_ticks(): There must be at least one NPC."
);
}
if (slot >= npcs){
throw InternalProgramError(
nullptr, PA_CURRENT_FUNCTION,
"generate_blink_ticks(): Slot is outside the per-tick order."
);
}

std::vector<BlinkTick> ret;
ret.reserve(ticks);

// Skip forward to the observed NPC's place
rng.advance(slot);

for (size_t c = 0; c < ticks; c++){
uint32_t roll = rng.next();
BlinkTick tick;
tick.blinked = npc_blinks(roll);
tick.type = NPC_blink_type(roll);
ret.emplace_back(tick);

rng.advance((uint64_t)npcs - 1);
}

return ret;
}




}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/* BDSP Blink Model
*
* From: https://github.com/PokemonAutomation/
*
*/

#ifndef PokemonAutomation_PokemonBDSP_BlinkModel_H
#define PokemonAutomation_PokemonBDSP_BlinkModel_H

#include <stddef.h>
#include <stdint.h>
#include <vector>
#include "Pokemon/Pokemon_Xorshift128.h"

namespace PokemonAutomation{
namespace NintendoSwitch{
namespace PokemonBDSP{


const double BDSP_NPC_TICK_SECONDS = 1.017;
const double BDSP_POKEMON_BLINK_MIN_SECONDS = 3.0;
const double BDSP_POKEMON_BLINK_MAX_SECONDS = 12.0;
const double BDSP_POKEMON_BLINK_OFFSET_SECONDS = 0.285;


enum class BlinkType : uint8_t{
Single = 0,
Double = 1,
};
const char* blink_type_name(BlinkType type);


// An NPC blinks when bits 1-3 of its roll are all zero: about a 1 in 8 chance.
inline bool npc_blinks(uint32_t roll){
return (roll & 0x0e) == 0;
}
// Bit 0 decides whether it is a single or a double blink.
inline BlinkType NPC_blink_type(uint32_t roll){
return (BlinkType)(roll & 1);
}


// The game's float generator.
// Note that it runs backwards: a roll of zero gives the maximum,
// and a roll of all-ones gives the minimum
double bdsp_range_float(uint32_t roll, double minimum, double maximum);

// Seconds until a Pokemon model's next blink.
double bdsp_pokemon_blink_interval(uint32_t roll);


const size_t BDSP_POKEMON_BLINK_KNOWN_BITS = 4;

// Recover the 23-bit fraction from a measured interval.
// Returns false if no roll could have produced this interval at all.
bool bdsp_pokemon_blink_fraction(double interval_seconds, uint32_t& fraction);

// Recover just the top BDSP_POKEMON_BLINK_KNOWN_BITS of the fraction, along with
// how much timing error that reading could absorb before it would flip to the
// neighbouring value.
// Returns false only if no roll could have produced this interval.
bool bdsp_pokemon_blink_bucket_with_margin(
double interval_seconds, uint32_t& bucket, double& margin_seconds
);

struct BlinkTick{
bool blinked = false;
BlinkType type = BlinkType::Single; // Only meaningful when "blinked".
};

// Predict what one NPC would do over the given number of ticks.
std::vector<BlinkTick> generate_blink_ticks(
Pokemon::Xorshift128 rng,
size_t ticks,
uint8_t npcs = 1,
uint8_t slot = 0
);


}
}
}
#endif
Loading
Loading