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
2 changes: 1 addition & 1 deletion .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ common --lockfile_mode=error

# We build nothing for Android, but rules_jvm_external (the //jni Maven jar)
# depends on rules_android, whose android_sdk_repository extension probes
# $ANDROID_HOME while analysing *any* target. When ANDROID_HOME is exported but
# $ANDROID_HOME while analyzing *any* target. When ANDROID_HOME is exported but
# the SDK has no platform APIs installed — as on GitHub's ubuntu runners after
# the free-disk-space step removes the SDK — that extension hard-fails and takes
# down unrelated builds (e.g. //wasm, //web). Pinning the var to empty makes the
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ dist/
build/

#
# Build artefacts are placed in these directories.
# Build artifacts are placed in these directories.
#
bin/
build/
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
## Introduction to DDS3

DDS3 is a double dummy solver for bridge hands. It is a drop-in replacement for DDS 2.9.0 which has been the leading solver for many years, based on the initial work of Bo Haglund in 2006 and the previous modernisation by Søren Hein in 2014. With Søren's encouragement, I have updated the official release in order to retain continuity for the user base, and I have been added as an administrator.
DDS3 is a double dummy solver for bridge hands. It is a drop-in replacement for DDS 2.9.0 which has been the leading solver for many years, based on the initial work of Bo Haglund in 2006 and the previous modernization by Søren Hein in 2014. With Søren's encouragement, I have updated the official release in order to retain continuity for the user base, and I have been added as an administrator.

DDS3 is a double dummy solver for bridge hands. Version 3.0 uses the same
search algorithm as version 2.x, but the source code has been modernised. The
search algorithm as version 2.x, but the source code has been modernized. The
project has been split into several subcomponents, each responsible for a
specific part of the search algorithm. This modularisation makes the codebase
specific part of the search algorithm. This modularization makes the codebase
easier to read and reason about, which helps not only humans but also modern
coding agents. Throughout the codebase, you will find evidence that Claude
Code and GitHub Copilot have made significant contributions to the
modernisation.
modernization.

There are build scripts for macOS, Linux, and Windows but I have myself only used the library on macOS.

Expand Down
4 changes: 2 additions & 2 deletions benchmarks/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ load("//:CPPVARIABLES.bzl", "DDS_CPPOPTS", "DDS_LINKOPTS", "DDS_LOCAL_DEFINES")
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")

# Reader + replay engine for recorded DDS workloads. Split from the CLI so the
# parsing and result-canonicalisation logic can be unit tested.
# parsing and result-canonicalization logic can be unit tested.
cc_library(
name = "replay_lib",
srcs = [
Expand Down Expand Up @@ -75,7 +75,7 @@ cc_test(
)

# Smoke test over a small committed recording, so the replay path (JSON parsing,
# PBN decoding, batching, result canonicalisation, verification) stays correct
# PBN decoding, batching, result canonicalization, verification) stays correct
# without needing a large recording. The sample was cut from a real session.
cc_test(
name = "replay_test",
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ python game.py --boards "Camrose 1-32.pbn" --auto True \

`testdata/sample-recording.jsonl` is a 6-call excerpt of the same file. It backs
the fast `//benchmarks:replay_test` smoke test, so CI exercises the replay path
(JSON parsing, PBN decoding, batching, result canonicalisation, verification)
(JSON parsing, PBN decoding, batching, result canonicalization, verification)
without spending two minutes on the full workload. Run it on its own with:

```sh
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/recording.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ auto find_runfile(const std::string& logical, const std::string& argv0)
}
}

// Materialised runfiles tree (Linux/macOS), or a run from the workspace root.
// Materialized runfiles tree (Linux/macOS), or a run from the workspace root.
std::vector<std::string> candidates;
for (const char* var : {"TEST_SRCDIR", "RUNFILES_DIR"})
if (const char* root = std::getenv(var))
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/recording.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ auto load_recording(const std::string& path, Recording& out, std::string& error)
-> bool;

// Resolve a Bazel runfile (e.g. "_main/benchmarks/testdata/x.jsonl") to a real
// path, or "" if it cannot be found. Windows does not materialise the runfiles
// path, or "" if it cannot be found. Windows does not materialize the runfiles
// tree -- that would need symlinks -- so the manifest has to be consulted there.
// `bazel test` exports the manifest location in the environment; `bazel run`
// does not, so pass argv[0] and the manifest beside the binary will be used.
Expand Down
2 changes: 1 addition & 1 deletion docs/coding_standards_and_ai_advice.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Coding Agents and Coding Standards

Coding agents are improving quickly, and the best tool or model for a task can change from one month to the next. This note collects coding guidance and tooling recommendations assembled during the first quarter of 2026, when most of the modernisation work for release 3.0.0 was completed.
Coding agents are improving quickly, and the best tool or model for a task can change from one month to the next. This note collects coding guidance and tooling recommendations assembled during the first quarter of 2026, when most of the modernization work for release 3.0.0 was completed.

This document does not prescribe a specific MCP server setup. MCP servers can be powerful, but they also introduce security risks, so the right deployment strategy depends on the environment.

Expand Down
4 changes: 2 additions & 2 deletions docs/dotnet_interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -349,13 +349,13 @@ and should be used with caution, as they may not manage resources as efficiently

### Play Analysis
- **int AnalysePlay(Deal dl, PlayTraceBin play, ...)**
analyses a play sequence for a single deal. Deprecated, but not yet implemented in the modern api.
analyzes a play sequence for a single deal. Deprecated, but not yet implemented in the modern api.

- **int AnalysePlay(DealPBN dl, PlayTracePBN play, ...)**
PBN-version. Deprecated, but not yet implemented in the modern api.

- **int AnalyseAllPlays(...)**
analyses multiple play sequences across multiple deals. Deprecated, but not yet implemented in the modern api.
analyzes multiple play sequences across multiple deals. Deprecated, but not yet implemented in the modern api.

---

Expand Down
2 changes: 1 addition & 1 deletion docs/python_interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ Batched form of `analyse_play_pbn` (wraps `AnalyseAllPlaysPBN`).
Legacy thread-resource hook (wraps the **deprecated** `SetMaxThreads` C API).

This does **not** control DDS's batch parallelism and is kept only for backward
compatibility. `solve_all_boards_*` already parallelise across the machine's
compatibility. `solve_all_boards_*` already parallelize across the machine's
hardware threads automatically (via `solve_boards_n`) — the value passed here does
not size that pool. `analyse_all_plays_pbn` currently runs sequentially.
`user_threads` must be `>= 0` (`0` = auto); raises `ValueError` for negative values.
Expand Down
2 changes: 1 addition & 1 deletion dotnet/DDS_Core.Tests/SmokeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace DDS_Core.Tests;

/// <summary>
/// End-to-end solving through the retargeted binding — the .NET analogue of
/// End-to-end solving through the retargeted binding — the .NET analog of
/// <c>DdsSmokeTest.java</c>. These are what prove the <c>dds_c_*</c> entry
/// points actually resolve and marshal correctly on a non-Windows platform.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion library/src/trans_table/trans_table_s.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,7 @@ auto TransTableS::build_path(
{
/* Find all winning nodes that correspond to current position */
found = false;
while (1) /* Find node amongst alternatives */
while (1) /* Find node among alternatives */
{
if ((np->win_mask_ == win_mask_[suit]) &&
(np->order_set_ == win_order_set[suit]))
Expand Down
4 changes: 2 additions & 2 deletions library/tests/system/configure_tt_api_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ auto stays_under(TransTable& tt, const double cap_kb) -> bool
/// A configuration that sets only the maximum must yield a table capped at
/// that maximum when it is created lazily; the built-in default used for the
/// unset value may not lift the cap.
TEST(ConfigureTtApiTest, AMaximumOnlyConfigurationIsHonouredOnLazyCreation)
TEST(ConfigureTtApiTest, AMaximumOnlyConfigurationIsHonoredOnLazyCreation)
{
// Arrange
ScopedEnv no_kind("DDS_TT_KIND", nullptr);
Expand Down Expand Up @@ -177,7 +177,7 @@ auto solve_known_deal(SolverContext& ctx, FutureTricks& fut) -> int
/// A table recreated between two solves of the same deal has not seen that
/// deal; the next solve must initialize it again rather than run against an
/// inert (never init()-ed) cache.
TEST(ConfigureTtApiTest, ATableRecreatedBetweenSolvesOfTheSameDealIsInitialisedAgain)
TEST(ConfigureTtApiTest, ATableRecreatedBetweenSolvesOfTheSameDealIsInitializedAgain)
{
// Arrange: one solve, then a kind change and back, which recreates the table.
ScopedEnv no_kind("DDS_TT_KIND", nullptr);
Expand Down
2 changes: 1 addition & 1 deletion library/tests/system/worker_count_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ TEST(ResolveWorkerCount, CapLargerThanCountClampsToCount)
EXPECT_EQ(resolve_worker_count(1000, 5), 5);
}

TEST(ResolveWorkerCount, CapSmallerThanCountAndHardwareIsHonoured)
TEST(ResolveWorkerCount, CapSmallerThanCountAndHardwareIsHonored)
{
// A cap of 1 is always <= count and <= hardware_concurrency.
EXPECT_EQ(resolve_worker_count(1, 8), 1);
Expand Down
2 changes: 1 addition & 1 deletion library/tests/trans_table/trans_table_p_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1122,7 +1122,7 @@ TEST(TransTablePMemoryTest, PoolingOutgrownBlocksNeverExceedsTheMaximum)
/// A caller that configures only the hard maximum gets exactly that maximum;
/// the unset default limit must not be replaced by a built-in value that then
/// floors the cap far above what was asked for.
TEST(TransTablePMemoryTest, AMaximumSetWithoutADefaultIsHonouredAsTheCap)
TEST(TransTablePMemoryTest, AMaximumSetWithoutADefaultIsHonoredAsTheCap)
{
// Arrange
TransTableP tt;
Expand Down
4 changes: 2 additions & 2 deletions python/src/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -623,7 +623,7 @@ auto register_analysis_bindings(py::module_& module) -> void
{
// initialize_static_memory: allocate the solver's static memory pools and
// perform one-time lookup-table initialization. This does NOT control the
// worker-thread count; use solve_all_boards_* (which parallelise across the
// worker-thread count; use solve_all_boards_* (which parallelize across the
// machine's hardware threads automatically) or one SolverContext per worker
// thread for per-board concurrency.
module.def(
Expand All @@ -635,7 +635,7 @@ auto register_analysis_bindings(py::module_& module) -> void
"Initialize the solver's static memory.\n\n"
"Allocates the transposition-table memory pools and performs one-time\n"
"lookup-table initialization. This does NOT control the number of worker\n"
"threads: solve_all_boards_* parallelise across the machine's hardware\n"
"threads: solve_all_boards_* parallelize across the machine's hardware\n"
"threads automatically, and for per-board concurrency from Python you\n"
"create one SolverContext per worker thread and pass it to solve_board /\n"
"solve_board_pbn.");
Expand Down
2 changes: 1 addition & 1 deletion specs/build-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ than re-encoding toolchain knowledge.
`cc_library`; `DDS_SCHEDULER_DEFINE` is appended only where scheduler timing is
wanted** (`local_defines = DDS_LOCAL_DEFINES + DDS_SCHEDULER_DEFINE`). Off by
default, these add zero cost.
- **WASM link flags are centralised** in `wasm_compat.bzl` (`WASM_LINKOPTS`):
- **WASM link flags are centralized** in `wasm_compat.bzl` (`WASM_LINKOPTS`):
memory growth, 256 MB initial memory, an 8 MB stack (DDS search recursion
overflows Emscripten's 64 KB default), and `PTHREAD_POOL_SIZE=8`. WASM
`wasm_cc_binary` targets also set `threads = "emscripten"` — see
Expand Down
2 changes: 1 addition & 1 deletion specs/python-binding.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ context API and the flat API from [dds-public-api](dds-public-api.md).
C++ [solver-context](solver-context.md); holding one across multiple solves reuses its
transposition table, mirroring the C++ reuse model. Guarded by
`context_reuse_test`.
- **Type conversions are centralised.** `converters.{cpp,hpp}` marshal Python
- **Type conversions are centralized.** `converters.{cpp,hpp}` marshal Python
values ↔ the C++ deal/table/par structs; the conversion contract (PBN strings,
binary deals, table/par result shapes) is guarded by `type_conversions_test`.
- **Wheels are built from the same package.** `dds3_wheel` (`py_wheel`,
Expand Down
2 changes: 1 addition & 1 deletion specs/solver-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ the opaque handle. See [dds-public-api](dds-public-api.md).
— even of the same cards — is treated as a new deal and runs
`SetDealTables()`, which `init()`s the replacement table; otherwise a table
recreated between two solves of one deal would never see the deal (inert on
`TransTableP`). Guarded by `ConfigureTtApiTest.ATableRecreatedBetweenSolvesOfTheSameDealIsInitialisedAgain`.
`TransTableP`). Guarded by `ConfigureTtApiTest.ATableRecreatedBetweenSolvesOfTheSameDealIsInitializedAgain`.
- **Hot-path facades are value-typed and inline-friendly, with different holds.**
`MoveGenContext` holds a raw `ThreadData*` so `move_gen()` can return a
value-typed facade without an atomic `shared_ptr` bump on every call.
Expand Down
2 changes: 1 addition & 1 deletion specs/web.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ DOM wiring) with an automated test pyramid.
> Per-file detail is in the BUILD file and the site sources; these are the
> capability-wide facts.

- **A dedicated, modularised WASM module — not the example CLIs.** `dds_web_wasm`
- **A dedicated, modularized WASM module — not the example CLIs.** `dds_web_wasm`
(`wasm_cc_binary` over `dds_web_wasm_cc`, source `dds_web_wasm.cpp`,
`threads = "emscripten"`) is built with `WASM_WEB_LINKOPTS`: `MODULARIZE=1`,
`EXPORT_NAME=createDdsModule`, exported entries `_dds_web_calc_table` and
Expand Down