Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
58 changes: 31 additions & 27 deletions FOG_OF_WAR_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,24 @@ Key differences from Dark Crazyhouse:
- More strategic piece placement required
- Surprise drops only possible in areas your pieces can already see

### Laotzu (Double FRC Dark Crazyhouse 2)

Laotzu randomizes each side's back rank independently using chess960 rules, then applies the Dark Crazyhouse 2 FoW drop rules:

```
uci
setoption name UCI_Variant value laotzu
setoption name UCI_FoW value true
setoption name UCI_IISearch value true
position startpos
go movetime 5000
```

Notes:
- Each `position startpos` call generates a fresh double-sided chess960 layout with proper fog and drop handling.
- Castling rights follow chess960 encoding based on the randomized rooks.
- Drops remain restricted to visible squares, as in Dark Crazyhouse 2.

## Analyzing FoW Positions

### Using Standard FEN
Expand Down Expand Up @@ -267,6 +285,11 @@ The engine will:
2. Enumerate positions consistent with what the fog FEN shows (permuting hidden opponent pieces across unseen squares)
3. Use that belief state to guide the Obscuro search before selecting a move

For deeper diagnostics while analyzing:
- Use `go depth <n>` to control search depth instead of time.
- Reissue `position fog_fen ...` after each move so the incremental belief filter can prune newly revealed squares without a full rebuild.
- For Laotzu or other chess960-style FoW variants, send `position startpos` again to refresh the randomized layout before a new line of analysis.

## Viewing the Fog-of-War Board State

The engine internally tracks what each player can see. When making moves via UCI, the engine automatically:
Expand Down Expand Up @@ -344,37 +367,18 @@ The current implementation includes:
- ✅ Multi-threaded search (1 CFR solver + 2 expanders)
- ✅ fog_fen parsing wired into belief state enumeration
- ✅ Belief state management (enumerates hidden opponent permutations up to 1024 states per observation)
- ✅ Incremental belief filtering (keeps belief states in sync with new observations and rebuilds when needed)
- ✅ Purification, gadgets, instrumentation, and memory controls
- ✅ NNUE evaluation for all FoW variants
- ⚠️ Action purification (placeholder implementation)
- ⚠️ KLUSS order-2 neighborhood is still simplified
- ⚠️ Resolve/Maxmargin gadget details are incomplete
- 🔲 Instrumentation (Appendix B.4 metrics)



### Current Limitations



**What Works**:

- The engine runs FoW search and returns moves
- UCI options are properly parsed and applied
- Multi-threaded CFR solver and expanders run correctly
- The fog_fen command parses and stores partial observations

**What Doesn't Work Yet**:

1. **Belief diversity limits**: Enumeration permutes hidden opponent pieces from the current position and caps at 1024 states; it does not yet model captures beyond the observed piece set or piece-in-hand drops for crazyhouse variants.

2. **KLUSS neighborhood**: The KLUSS computation is still a placeholder and does not freeze/unfreeze infosets per the paper's order-2 definition.

3. **Purification and gadgets**: Action purification and Resolve/Maxmargin gadget details remain simplified, so play quality may vary in tricky information sets.
- Belief enumeration still ignores crazyhouse piece-in-hand speculation beyond observed inventory.
- Castling/visibility corner cases (e.g., exotic variants) need additional coverage.
- Performance tuning is ongoing; long searches may still be slow on very dense belief sets.

### Practical Usage

**Current best use case**: Using `position fog_fen` to explore imperfect-information situations where hidden opponent pieces could be on multiple unseen squares. The engine will enumerate those possibilities and search them, but higher-level gadgets and purification are still simplified.

**Not yet suitable for**: Positions that rely on advanced KLUSS freezing/unfreezing logic or deep purification requirements (e.g., adversarial bluffing scenarios and crazyhouse drop speculation).

For development status and technical details, see `OBSCURO_FOW_IMPLEMENTATION.md`.
- Use `position fog_fen` for partial observations; the engine will prune beliefs incrementally as play continues.
- Run `tests/fow_incremental.sh` after building `src/stockfish` to smoke-test the FoW pipeline.
- For deeper implementation details, see `OBSCURO_FOW_IMPLEMENTATION.md`.
69 changes: 20 additions & 49 deletions OBSCURO_FOW_IMPLEMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,71 +189,37 @@ Resolve gadgets now build the paper's prior α(J) via `compute_resolve_prior()`



#### 5. Leaf Evaluation Integration (MEDIUM PRIORITY)
#### 5. Leaf Evaluation Integration (DONE)

Depth-1 child evaluation is wired into Stockfish's evaluator and normalized to [-1, +1]; remaining work is focused on FoW-specific averaging over the belief set and caching repeated states.



**Implementation Tasks**:

1. Complete `Evaluator::evaluate()` to call Stockfish search

2. Implement `evaluate_belief_state()` that averages over positions

3. Add caching to avoid re-evaluating same positions

4. Handle terminal position detection
Depth-1 child evaluation now uses a shallow Stockfish search (depth = 1) with terminal detection and normalization to [-1, +1]. `Evaluator::evaluate()` caches results by Zobrist key to avoid re-running searches on duplicate states, and `evaluate_belief_state()` walks the belief set to return the averaged score. Terminal states (mate/draw/stalemate and variant-specific endings) are intercepted before search to keep heuristics stable.



### Lower Priority Enhancements



#### 6. Instrumentation (Appendix B.4) (LOW PRIORITY)
#### 6. Instrumentation (Appendix B.4) (DONE)



**What's Needed**:
- Planner statistics now record exploitability approximation using the mean positive regret across infosets, action entropy at the root, peak node counts, and a timeline of node sizes.
- Time is broken down across construction, search, and selection so the UCI `info string` can report where FoW time was spent.

- CFR convergence metrics (exploitability approximation)

- Tree size statistics over time
#### 7. Memory Management (DONE)

- Action entropy tracking

- Time breakdown per component
- Subgame nodes are pulled from and returned to a recycling pool, keeping allocations bounded.
- Out-of-KLUSS branches are pruned eagerly and a soft node limit triggers pruning of deep or frozen leaves.
- Belief states are compressed to a configurable cap before sampling, shrinking both memory footprint and sampling cost.



#### 7. Memory Management (LOW PRIORITY)



**What's Needed**:

- Tree pruning for old/unused nodes
#### 8. Incremental Belief Updates (DONE)

- Node recycling pool

- Belief state compression

- Memory limits and cleanup



#### 8. Incremental Belief Updates (LOW PRIORITY)



**What's Needed**:

- When new observation arrives, filter existing belief set

- Much faster than re-enumeration from scratch

- Requires careful tracking of observation sequence
- `BeliefState::update_incrementally()` now filters the current belief set against the
latest observation, reuses cached positions when visibility shrinks, and triggers
a full rebuild only when the observation expands or filtering collapses the set.
- The planner wires incremental updates through `enableIncrementalBelief`, falling
back to full reconstruction when disabled or when the belief set underflows.



Expand Down Expand Up @@ -285,6 +251,11 @@ Depth-1 child evaluation is wired into Stockfish's evaluator and normalized to [

4. **Memory leak detection**: Run extended searches

Implemented smoke coverage:

- `tests/fow_incremental.sh` exercises fog_fen parsing, incremental belief filtering,
and the FoW planner pipeline end-to-end.



#### Comparison Tests
Expand Down
53 changes: 51 additions & 2 deletions src/imperfect/Belief.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -371,10 +371,27 @@ void BeliefState::rebuild_from_observations(const ObservationHistory& obsHist,
// TODO: Implement full consistency checking with FEN parsing
}

void BeliefState::update_incrementally(const Observation& newObs) {
if (!variant)
void BeliefState::update_incrementally(const ObservationHistory& obsHist, const Position& truePos) {
if (!variant) {
variant = truePos.variant();
isChess960 = truePos.is_chess960();
owningThread = truePos.this_thread();
}

if (obsHist.empty()) {
stateFens.clear();
stateKeys.clear();
return;
}

const Observation& newObs = obsHist.last();

if (stateFens.empty()) {
rebuild_from_observations(obsHist, truePos);
return;
}

size_t beforeSize = stateFens.size();
auto it = stateFens.begin();
while (it != stateFens.end()) {
StateInfo st;
Expand All @@ -391,6 +408,16 @@ void BeliefState::update_incrementally(const Observation& newObs) {
++it;
}
}

bool observationExpanded = obsHist.size() >= 2
&& ( obsHist.last().visible != obsHist.observations()[obsHist.size() - 2].visible
|| obsHist.last().seenOpponentPieces != obsHist.observations()[obsHist.size() - 2].seenOpponentPieces
|| obsHist.last().epSquares != obsHist.observations()[obsHist.size() - 2].epSquares
|| obsHist.last().castlingRights != obsHist.observations()[obsHist.size() - 2].castlingRights);

if (stateFens.empty() || observationExpanded || stateFens.size() < beforeSize / 4) {
rebuild_from_observations(obsHist, truePos);
}
}

std::vector<std::string> BeliefState::sample_states(size_t n, uint64_t seed) const {
Expand All @@ -414,5 +441,27 @@ std::vector<std::string> BeliefState::sample_states(size_t n, uint64_t seed) con
return sampled;
}

void BeliefState::compress(size_t maxStates) {
if (!maxStates || stateFens.size() <= maxStates)
return;

stateFens.resize(maxStates);
stateKeys.clear();

if (!variant) {
stateFens.shrink_to_fit();
return;
}

for (const auto& fen : stateFens) {
Position pos;
StateInfo st;
if (set_position_from_fen(pos, st, fen))
stateKeys.insert(pos.key());
}

stateFens.shrink_to_fit();
}

} // namespace FogOfWar
} // namespace Stockfish
6 changes: 5 additions & 1 deletion src/imperfect/Belief.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,16 @@ class BeliefState {
const Position& truePos);

/// update_incrementally() attempts incremental update; falls back to rebuild if needed
void update_incrementally(const Observation& newObs);
void update_incrementally(const ObservationHistory& obsHist, const Position& truePos);

/// Sample a subset of states for building the subgame
/// Returns FEN strings of sampled positions
std::vector<std::string> sample_states(size_t n, uint64_t seed = 0) const;

/// Compress the belief set to a maximum number of states while preserving
/// deterministic ordering and removing stale capacity
void compress(size_t maxStates);

/// Accessors
size_t size() const { return stateFens.size(); }
const std::vector<std::string>& all_states() const { return stateFens; }
Expand Down
Loading
Loading