Fixed hand evaluator LUT generation

This commit is contained in:
Jan-Peter von Hunnius 2025-11-16 16:02:26 +01:00
parent 7819b1ee27
commit 51e1f58ef9
17 changed files with 2045 additions and 660 deletions

View file

@ -17,7 +17,6 @@ A comprehensive poker bot testing framework with Java-compatible hand evaluation
## Features
- **Java-Compatible**: Full compatibility with Java Meerkat API for seamless tool integration
- **Bot Testing Framework**: Specifically designed for automated poker bot testing and development
- **Blazing Fast**: O(1) hand evaluation using perfect hash jump tables
- **Memory Efficient**: ~130MB for complete evaluation tables (79% reduction from 625MB)

View file

@ -1,27 +1,5 @@
# Poker AI Testbed & Live Server Achievements
## Major Technical Achievements
### 🚀 Perfect Hash Jump Table Implementation ✅
**Revolutionary Memory Optimization**
- **79% Memory Reduction**: Reduced evaluation table size from 625MB to ~130MB
- **Advanced Algorithm**: Perfect hash jump table with suit canonicalization
- **Bottom-up Construction**: Cache-optimized trie construction for optimal performance
- **Zero Breaking Changes**: Full API compatibility maintained for existing users
**Technical Innovation Details**
- **Suit Canonicalization**: Lexicographically smallest suit permutation algorithm
- **Three-Level Trie Structure**: Level 5 (terminal), Level 6 (intermediate), Level 7 (root)
- **Memory Layout Optimization**: Sequential access patterns for CPU cache efficiency
- **Streaming Construction**: Handles large datasets without excessive memory allocation
**Performance Improvements**
- **Faster 6-Card Evaluation**: Improved evaluation speed for 6-card hands
- **Enhanced 7-Card Performance**: Better performance for 7-card hand evaluation
- **Cache-Friendly Access**: Optimized memory access patterns for modern CPUs
- **Reduced Memory Pressure**: Significantly lower memory footprint for large-scale simulations
## Completed Phases and Items
### Phase 1: Foundation ✅
@ -29,38 +7,12 @@
**Basic poker engine**
- Card/Hand/Board representations ✅
- Fast LUT hand evaluator (5, 6, and 7 cards) ✅
- Game state management ✅
- Game Info and Player Info traits ✅
- Event reporting and action scheme ✅
- Basic game rules (NL/FL/PL) ✅
**Configuration system**
- TOML configuration structures ✅
- Bot parameter configuration ✅
- Game type configurations (Cash/SNG/MTT, buyins, blinds, hands per level, etc.) ✅
- Simulation parameters (hands/tournaments count, buyin thresholds for bots, etc.) ✅
## Success Metrics
- **Bot Performance:** Profitability across different game types
- **Simulation Speed:** Ability to run millions of hands efficiently
- **Memory Efficiency:** 79% reduction in evaluation table memory usage
- **Live Reliability:** 99.9% uptime with correct action execution
- **Flexibility:** Easy configuration for new sites and game types
- **Technical Innovation:** State-of-the-art perfect hash jump table implementation
- **Evolution:** Continuous improvement through simulation results
- **Monitoring:** Meaningful debug and performance logging and visualization
## Technical Specifications
### Memory Achievement Breakdown
- **Original Implementation**: 625MB for complete 7-card evaluation tables
- **New Implementation**: ~130MB for equivalent functionality (79% reduction)
- **Target Size**: Exactly 32-35 million u32 entries for optimal memory usage
- **Construction Time**: 2-3 minutes for complete table generation
### Performance Characteristics
- **5-Card Evaluation**: ~50-100 nanoseconds (O(1) lookup)
- **6-Card Evaluation**: ~200-300 nanoseconds (best 5-card selection)
- **7-Card Evaluation**: ~400-600 nanoseconds (best 5-card selection)
- **Hash Calculation**: ~20-30 nanoseconds (card canonicalization)

View file

@ -2,27 +2,19 @@
## Step-by-Step Implementation Plan
### Phase 1: Foundation ✅
### Phase 1: Foundation
**Basic poker engine**
- Fast LUT hand evaluator (5, 6, and 7 cards) ✅
- Game state management ✅
- Game Info and Player Info traits ✅
- Event reporting and action scheme ✅
- Basic game rules (NL/FL/PL) ✅
- Game state management
- Game Info and Player Info traits
- Event reporting and action scheme
- Basic game rules (NL/FL/PL)
**Configuration system**
- TOML configuration structures ✅
- Bot parameter configuration ✅
- Game type configurations (Cash/SNG/MTT, buyins, blinds, hands per level, etc.) ✅
- Simulation parameters (hands/tournaments count, buyin thresholds for bots, etc.) ✅
**🚀 Memory Optimization Achievement ✅**
- **Perfect Hash Jump Table**: Revolutionary 79% memory reduction (625MB → ~130MB)
- **Suit Canonicalization**: Advanced isomorphic hand reduction algorithm
- **Bottom-up Trie Construction**: Cache-optimized memory layout
- **Zero Breaking Changes**: Full API compatibility maintained
- **Performance Enhancement**: Faster evaluation for 6 and 7-card hands
- TOML configuration structures
- Bot parameter configuration
- Game type configurations (Cash/SNG/MTT, buyins, blinds, hands per level, etc.)
- Simulation parameters (hands/tournaments count, buyin thresholds for bots, etc.)
### Phase 2: Simulation Testbed
**Goal:** Mass simulation capability

View file

@ -6,19 +6,11 @@ Create a **comprehensive poker AI ecosystem** with two main components:
- **Massive Simulation Testbed** – For developing and testing bot strategies across all game types (NL/FL/PL, Cash/SNG/MTT) with configurable parameters
- **Live Action Server** – For deploying bots in real poker environments with automatic table selection
---
## Core Architecture Components
### 1. Advanced Evaluation Engine
- **Perfect Hash Jump Tables**: Revolutionary memory-efficient evaluation (~130MB vs 625MB)
- **Suit Canonicalization**: Advanced isomorphic hand reduction for optimal performance
- **Bottom-up Trie Construction**: Cache-optimized memory layout for CPU efficiency
- **Zero Breaking Changes**: Full backward compatibility with existing evaluation APIs
### 2. Simulation Testbed
### 1. Simulation Testbed
- **Configurable bots** (Rule-based, Nash EQ, Statistical, Hand-reading, Reinforcement Learning)
- **Mass parallel simulations** with GPU acceleration (e.g., for turn and river rollouts)

View file

@ -23,9 +23,11 @@ sha2 = "0.10"
tempfile = "3.23.0"
once_cell = "1.19"
chrono = { version = "0.4", features = ["serde"] }
indicatif = "0.17"
[dev-dependencies]
proptest = "1.0"
indicatif = "0.17"
[package.metadata.docs.rs]
all-features = true

File diff suppressed because it is too large Load diff

View file

@ -13,7 +13,7 @@
use super::errors::EvaluatorError;
use super::evaluator::{HandRank, HandValue};
use super::integration::{benchmark_evaluation, utils, EvaluatorComparison, MathEvaluator};
use super::integration::{EvaluatorComparison, MathEvaluator};
use super::tables::{CanonicalMapping, JumpTable};
use crate::card::PackedCard;
use crate::{Card, Hand};
@ -250,7 +250,7 @@ pub fn canonicalization_example() -> Result<(), EvaluatorError> {
mapping
.canonical_cards
.iter()
.map(|&c| (c & 0x03))
.map(|&c| c & 0x03)
.collect::<Vec<_>>()
);
println!(" Suit mapping: {:?}", mapping.suit_map);
@ -444,14 +444,14 @@ fn extract_suits(cards: &[PackedCard]) -> Vec<u8> {
}
/// Helper function to extract ranks from cards
fn extract_ranks(cards: &[PackedCard]) -> Vec<u8> {
cards.iter().map(|card| card.rank()).collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn extract_ranks(cards: &[PackedCard]) -> Vec<u8> {
cards.iter().map(|card| card.rank()).collect()
}
#[test]
fn test_basic_usage_example() {
assert!(basic_usage_example().is_ok());

View file

@ -64,7 +64,7 @@
use super::errors::EvaluatorError;
use super::evaluator::{HandRank, HandValue};
use super::tables::{CanonicalMapping, JumpTable, JumpTableEntry};
use super::tables::JumpTable;
use crate::card::PackedCard;
use crate::{Card, Hand};
use std::time::{Duration, Instant};
@ -215,7 +215,7 @@ impl MathEvaluator {
pub fn convert_cards(cards: &[Card]) -> Vec<PackedCard> {
cards
.iter()
.map(|card| PackedCard::from_card(card))
.map(|card| PackedCard::new(card.rank(), card.suit()).unwrap())
.collect()
}
@ -223,7 +223,7 @@ pub fn convert_cards(cards: &[Card]) -> Vec<PackedCard> {
pub fn convert_cards_fixed<const N: usize>(cards: &[Card; N]) -> [PackedCard; N] {
let mut result = [PackedCard::new(0, 0).unwrap(); N];
for (i, card) in cards.iter().enumerate() {
result[i] = PackedCard::from_card(card);
result[i] = PackedCard::new(card.rank(), card.suit()).unwrap();
}
result
}
@ -392,13 +392,13 @@ impl EvaluatorComparison {
let mut core_times = Vec::new();
// Benchmark math evaluator
for cards in &test_cases {
for _cards in &test_cases {
let math_time = benchmark_evaluation(|c| self.math_evaluator.evaluate_7_card(c));
math_times.push(math_time);
}
// Benchmark core evaluator
for cards in &test_cases {
for _cards in &test_cases {
let core_time = benchmark_evaluation(|c| self.core_evaluator.evaluate_7_card(c));
core_times.push(core_time);
}
@ -473,8 +473,6 @@ pub mod utils {
/// Validate that math evaluator produces same results as holdem_core
pub fn validate_evaluator_compatibility() -> Result<(), EvaluatorError> {
use std::str::FromStr;
let comparison = EvaluatorComparison::new()?;
// Test a few representative hands
@ -647,19 +645,7 @@ mod tests {
#[test]
fn test_benchmark_function() {
use std::str::FromStr;
let test_cards = [
Card::from_str("As").unwrap(),
Card::from_str("Ks").unwrap(),
Card::from_str("Qs").unwrap(),
Card::from_str("Js").unwrap(),
Card::from_str("Ts").unwrap(),
Card::from_str("7h").unwrap(),
Card::from_str("6d").unwrap(),
];
let elapsed = benchmark_evaluation(|cards| {
let elapsed = benchmark_evaluation(|_cards| {
// Simple evaluation for testing
HandValue::new(HandRank::HighCard, 0)
});
@ -672,7 +658,7 @@ mod tests {
let comparison = EvaluatorComparison::new();
assert!(comparison.is_ok());
let mut comparison = EvaluatorComparison::new().unwrap();
let comparison = EvaluatorComparison::new().unwrap();
let results = comparison.compare_evaluations(&[]);
// Should handle empty hand list gracefully

View file

@ -13,16 +13,25 @@
//! - **`property_tests`**: Property-based testing for evaluation correctness
//! - **`examples`**: Usage examples and performance benchmarks
pub mod algorithmic_evaluator;
pub mod benchmark_utils;
pub mod errors;
pub mod evaluator;
pub mod examples;
pub mod file_io;
pub mod integration;
pub mod integration_utils;
pub mod property_tests;
pub mod singleton;
pub mod tables;
pub mod tables_new;
pub mod test_utils;
#[cfg(test)]
mod algorithmic_evaluator_test;
// Re-export commonly used types from local modules
pub use algorithmic_evaluator::{AlgorithmicEvaluator, EvaluationStats};
pub use errors::EvaluatorError;
pub use evaluator::{Evaluator, HandRank, HandValue};

View file

@ -22,11 +22,11 @@
//! 4. **Coverage**: Ensure comprehensive testing of all code paths
use super::errors::EvaluatorError;
use super::evaluator::{HandRank, HandValue};
use super::integration::{benchmark_evaluation, utils, EvaluatorComparison, MathEvaluator};
use super::evaluator::HandRank;
use super::integration::{benchmark_evaluation, EvaluatorComparison, MathEvaluator};
use super::tables::{CanonicalMapping, JumpTable};
use crate::card::PackedCard;
use crate::{Card, Hand};
use crate::Card;
use std::str::FromStr;
/// Comprehensive test suite for the math evaluator system
@ -103,6 +103,19 @@ impl EvaluatorTestSuite {
let mut passed = 0;
let mut failed = 0;
// Create progress bar for correctness tests
let total_tests = test_hands.len() * 3; // 3 tests per hand (5, 6, 7 card)
let pb = indicatif::ProgressBar::new(total_tests as u64);
pb.set_style(
indicatif::ProgressStyle::default_bar()
.template(
"{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta})",
)
.unwrap()
.progress_chars("#>-"),
);
pb.set_message("Testing correctness");
for (i, cards) in test_hands.iter().enumerate() {
// Test 5-card evaluation
let math_result_5 = self
@ -112,6 +125,7 @@ impl EvaluatorTestSuite {
.core_evaluator
.evaluate_5_card(&cards[..5].try_into().unwrap());
pb.inc(1);
if math_result_5 == core_result_5 {
passed += 1;
} else {
@ -131,6 +145,7 @@ impl EvaluatorTestSuite {
.core_evaluator
.evaluate_6_card(&cards[..6].try_into().unwrap());
pb.inc(1);
if math_result_6 == core_result_6 {
passed += 1;
} else {
@ -147,6 +162,7 @@ impl EvaluatorTestSuite {
let math_result_7 = self.math_evaluator.evaluate_7_card(cards);
let core_result_7 = self.core_evaluator.evaluate_7_card(cards);
pb.inc(1);
if math_result_7 == core_result_7 {
passed += 1;
} else {
@ -161,6 +177,8 @@ impl EvaluatorTestSuite {
self.stats.total_tests += 3;
}
pb.finish_with_message("Correctness tests completed");
self.stats.passed_tests += passed;
self.stats.failed_tests += failed;
@ -172,16 +190,6 @@ impl EvaluatorTestSuite {
fn run_performance_tests(&mut self) -> Result<(), EvaluatorError> {
println!("Running performance tests...");
let test_cards = [
Card::from_str("As").unwrap(),
Card::from_str("Ks").unwrap(),
Card::from_str("Qs").unwrap(),
Card::from_str("Js").unwrap(),
Card::from_str("Ts").unwrap(),
Card::from_str("7h").unwrap(),
Card::from_str("6d").unwrap(),
];
// Test 5-card performance
let time_5 = benchmark_evaluation(|cards| {
self.math_evaluator
@ -259,7 +267,6 @@ impl EvaluatorTestSuite {
// Test evaluator comparison utility
let comparison = EvaluatorComparison::new()?;
let test_hands = utils::generate_test_hands();
let results = comparison.compare_evaluations(&[]);
// Should handle empty input gracefully
@ -301,21 +308,21 @@ impl EvaluatorTestSuite {
let result_5 = self
.math_evaluator
.evaluate_5_card(&cards[..5].try_into().unwrap());
assert!(result_5.rank as u8 <= HandRank::HighCard as u8);
assert!(result_5.rank as u8 <= HandRank::RoyalFlush as u8);
}
if cards.len() >= 6 {
let result_6 = self
.math_evaluator
.evaluate_6_card(&cards[..6].try_into().unwrap());
assert!(result_6.rank as u8 <= HandRank::HighCard as u8);
assert!(result_6.rank as u8 <= HandRank::RoyalFlush as u8);
}
if cards.len() >= 7 {
let result_7 = self
.math_evaluator
.evaluate_7_card(&cards[..7].try_into().unwrap());
assert!(result_7.rank as u8 <= HandRank::HighCard as u8);
assert!(result_7.rank as u8 <= HandRank::RoyalFlush as u8);
}
println!("Regression test {} passed", i);
@ -480,6 +487,7 @@ pub fn test_jump_table_properties() -> Result<(), EvaluatorError> {
// Test memory usage is within bounds
let memory_usage = table.memory_usage();
let max_memory = 200_000_000; // 200MB absolute max
let target_memory = 130_000_000; // ~130MB target for Cactus Kev's algorithm
assert!(
memory_usage <= max_memory,
@ -487,6 +495,14 @@ pub fn test_jump_table_properties() -> Result<(), EvaluatorError> {
memory_usage
);
// Verify memory usage is close to target (~130MB)
assert!(
memory_usage < target_memory * 2, // Allow some tolerance but should be much less than 2x target
"Memory usage far exceeds target: {} (target ~{}MB)",
memory_usage,
target_memory / 1024 / 1024
);
// Test that all entries are properly initialized
for i in 0..100 {
let entry = table.get(i);
@ -502,10 +518,23 @@ pub fn run_stress_tests() -> Result<(), EvaluatorError> {
println!("Running stress tests...");
let mut evaluator = MathEvaluator::new()?;
let num_hands = 1000;
// Create progress bar for stress test setup
let setup_pb = indicatif::ProgressBar::new(num_hands);
setup_pb.set_style(
indicatif::ProgressStyle::default_bar()
.template(
"{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta})",
)
.unwrap()
.progress_chars("#>-"),
);
setup_pb.set_message("Generating test hands");
// Generate many test hands
let mut test_hands = Vec::new();
for i in 0..1000 {
for i in 0..num_hands {
let rank1 = (i * 7) % 13;
let suit1 = (i * 7) % 4;
let rank2 = (i * 11) % 13;
@ -526,18 +555,37 @@ pub fn run_stress_tests() -> Result<(), EvaluatorError> {
];
test_hands.push(cards);
}
setup_pb.inc(1);
}
setup_pb.finish_with_message("Test hands generated");
// Create progress bar for evaluation
let eval_pb = indicatif::ProgressBar::new(test_hands.len() as u64);
eval_pb.set_style(
indicatif::ProgressStyle::default_bar()
.template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta}) - {msg}")
.unwrap()
.progress_chars("#>-")
);
eval_pb.set_message("Evaluating hands");
// Evaluate all hands and measure performance
let start_time = std::time::Instant::now();
for cards in &test_hands {
for (i, cards) in test_hands.iter().enumerate() {
let _result = evaluator.evaluate_7_card(cards);
// Update progress every 100 hands to minimize overhead
if i % 100 == 0 || i == test_hands.len() - 1 {
eval_pb.set_position((i + 1) as u64);
}
}
let elapsed = start_time.elapsed();
let avg_time = elapsed / test_hands.len() as u32;
eval_pb.finish_with_message("Stress test completed");
println!(
"Stress test: {} hands in {:?}, average: {:?}",
test_hands.len(),

File diff suppressed because it is too large Load diff

View file

@ -355,7 +355,7 @@ impl Hand {
/// println!("Card: {}", card);
/// }
/// ```
pub fn iter(&self) -> std::slice::Iter<Card> {
pub fn iter(&self) -> std::slice::Iter<'_, Card> {
self.cards[0..self.len].iter()
}

View file

@ -76,6 +76,8 @@ pub use board::Street;
/// Re-export error types for convenience
pub use errors::PokerError;
/// Re-export algorithmic evaluator for direct evaluation
pub use evaluator::algorithmic_evaluator::{AlgorithmicEvaluator, EvaluationStats};
/// Re-export evaluator types for convenience
pub use evaluator::evaluator::{Evaluator, HandRank, HandValue};

View file

@ -403,11 +403,8 @@ fn test_integration_data_consistency() {
// This module provides integration tests for the advanced evaluator system,
// ensuring compatibility and correct interaction with the holdem_core evaluator.
use holdem_core::card::PackedCard;
use holdem_core::evaluator::integration::{EvaluatorComparison, MathEvaluator};
use holdem_core::evaluator::tables::JumpTable;
use holdem_core::evaluator::{HandRank, HandValue};
use holdem_core::{Card, Hand};
use holdem_core::evaluator::integration::MathEvaluator;
use holdem_core::Card;
use std::str::FromStr;
/// Integration test suite for the math evaluator

View file

@ -53,9 +53,9 @@ mod card_properties {
((12, 3), (12, 0)), // Ace spades > Ace hearts (Spades=4 > Hearts=3)
((12, 0), (12, 1)), // Ace hearts > Ace diamonds (Hearts=3 > Diamonds=2)
((12, 1), (12, 2)), // Ace diamonds > Ace clubs (Diamonds=2 > Clubs=1)
((5, 3), (5, 0)), // Five spades > Five hearts
((5, 0), (5, 1)), // Five hearts > Five diamonds
((5, 1), (5, 2)), // Five diamonds > Five clubs
((3, 3), (3, 0)), // Five spades > Five hearts
((3, 0), (3, 1)), // Five hearts > Five diamonds
((3, 1), (3, 2)), // Five diamonds > Five clubs
];
for ((rank1, suit1), (rank2, suit2)) in test_cases {

View file

@ -1,13 +0,0 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc d0090c2206be394c0d408b3e3007d10014ef89cc0b289519b1a830b2c3626662 # shrinks to cards = [Card { rank: 4, suit: 0 }, Card { rank: 5, suit: 0 }, Card { rank: 2, suit: 1 }, Card { rank: 8, suit: 2 }, Card { rank: 1, suit: 3 }, Card { rank: 9, suit: 3 }]
cc ca376e60fdef26e0b7b9a3d96000de681d13a84e917ab55f95cbf536b720412c # shrinks to cards = [Card { rank: 5, suit: 0 }, Card { rank: 8, suit: 0 }, Card { rank: 12, suit: 1 }, Card { rank: 0, suit: 2 }, Card { rank: 7, suit: 2 }, Card { rank: 12, suit: 2 }, Card { rank: 7, suit: 3 }]
cc e8cbfce99bb2beeefb23c4cc317e22fb04bae3f8acd7a7edc81f7457fdb6e1fb # shrinks to hand = [Card { rank: 12, suit: 3 }, Card { rank: 11, suit: 3 }, Card { rank: 10, suit: 3 }, Card { rank: 9, suit: 3 }, Card { rank: 8, suit: 3 }]
cc d2513c8954856423773cdcdfd3a336a17f92a6984c9a4ef803b74d960678ec7f # shrinks to cards = [Card { rank: 8, suit: 0 }, Card { rank: 11, suit: 1 }, Card { rank: 2, suit: 2 }, Card { rank: 11, suit: 3 }, Card { rank: 12, suit: 3 }]
cc 3cb176da85ed776101a34339707b68f3bafa03ebd9f7fb495c36590e6a64c3f5 # shrinks to cards = [Card { rank: 7, suit: 1 }, Card { rank: 6, suit: 2 }, Card { rank: 11, suit: 2 }, Card { rank: 12, suit: 2 }, Card { rank: 11, suit: 3 }]
cc 487fd65fa6cee58dfe754c01cdff15913fea01e4b90b6c0bfc2d45823a587f90 # shrinks to hand = [Card { rank: 12, suit: 3 }, Card { rank: 11, suit: 3 }, Card { rank: 10, suit: 3 }, Card { rank: 9, suit: 3 }, Card { rank: 8, suit: 3 }]
cc 7e46fa2f7f3ecb235753efedbca0d196d025147d51fc16f863e965af0da32654 # shrinks to hand1 = [Card { rank: 12, suit: 3 }, Card { rank: 11, suit: 3 }, Card { rank: 10, suit: 3 }, Card { rank: 9, suit: 3 }, Card { rank: 8, suit: 3 }], hand2 = [Card { rank: 12, suit: 3 }, Card { rank: 11, suit: 3 }, Card { rank: 10, suit: 3 }, Card { rank: 9, suit: 3 }, Card { rank: 8, suit: 3 }]

View file

@ -1 +1 @@
{"rustc_fingerprint":9168241167641241758,"outputs":{"16495917692426387086":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\n","stderr":""},"15729799797837862367":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\jan\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""},"4614504638168534921":{"success":true,"status":"","code":0,"stdout":"rustc 1.81.0 (eeb90cda1 2024-09-04)\nbinary: rustc\ncommit-hash: eeb90cda1969383f56a2637cbd3037bdf598841c\ncommit-date: 2024-09-04\nhost: x86_64-pc-windows-msvc\nrelease: 1.81.0\nLLVM version: 18.1.7\n","stderr":""}},"successes":{}}
{"rustc_fingerprint":12042185895431567242,"outputs":{"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.90.0 (1159e78c4 2025-09-14)\nbinary: rustc\ncommit-hash: 1159e78c4747b02ef996e55082b704c09b970588\ncommit-date: 2025-09-14\nhost: x86_64-pc-windows-msvc\nrelease: 1.90.0\nLLVM version: 20.1.8\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\jan\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""}},"successes":{}}