Metadata-Version: 2.4
Name: Fortuna
Version: 6.1.1
Summary: A fast, explicit random generation toolkit
Keywords: random,random-number-generator,distributions,weighted-choice,simulation
Author-Email: Robert Sharp <webmaster@sharpdesigndigital.com>
License-Expression: MIT
License-File: LICENSE
License-File: src/Fortuna/vendor/Storm/LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Programming Language :: Cython
Classifier: Programming Language :: C++
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.14
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Project-URL: Homepage, https://github.com/BrokenShell/Fortuna
Project-URL: Documentation, https://github.com/BrokenShell/Fortuna/blob/main/docs/guide.md
Project-URL: Issues, https://github.com/BrokenShell/Fortuna/issues
Project-URL: Source, https://github.com/BrokenShell/Fortuna
Requires-Python: <3.15,>=3.14
Description-Content-Type: text/markdown

# Fortuna

Fortuna is a fast random-generation toolkit for Python 3.14. It combines an
explicit generator model, deterministic stream derivation, native bulk
generation, practical probability distributions, and prepared value selectors
for games, simulations, procedural generation, and generative systems.

Fortuna 6 is built on the vendored, immutable Storm 5.1.0 engine and is
licensed under MIT.

> **Toolchain-free installs on supported systems:** Fortuna 6 ships precompiled
> CPython 3.14 wheels for macOS arm64 and x86-64, Linux arm64 and x86-64, and
> Windows x86-64. On those targets, pip and uv install Fortuna without a C++
> compiler, platform SDK, or development toolchain. Windows support is notably
> improved: Visual Studio Build Tools are not required.

> **Not for cryptography:** Fortuna uses MT19937-64. Do not use it for
> passwords, tokens, keys, secrets, authentication, cryptography, gambling
> security, or security decisions. Entropy-seeded construction does not make
> Fortuna cryptographically secure. Python's `secrets` module is the standard
> library starting point for security-sensitive randomness.

## Installation

Fortuna requires Python 3.14:

```bash
python -m pip install Fortuna
```

With uv:

```bash
uv add Fortuna
```

A source build requires a C++20 toolchain. Contributors working from a checkout
should use the reproducible development setup in
[the development guide](https://github.com/BrokenShell/Fortuna/blob/main/docs/development.md).

## Five-minute tour

Fortuna's module functions are convenient when a process-local random stream is
appropriate:

```python
import Fortuna

initiative = Fortuna.d(20)
fireball_damage = Fortuna.dice(8, 6)
ability_scores = Fortuna.ability_dice(count=6)
morale_adjustment = Fortuna.plus_or_minus_triangular(2)
```

The corresponding methods on `Generator` provide explicit ownership and
repeatability:

```python
world = Fortuna.Generator(0xD06E_0A5E)

room_count = world.random_int(5, 12)
room_sizes = world.random_int(3, 10, count=room_count)
```

Two generators with the same seed produce the same stable Fortuna-owned draw
schedule:

```python
first = Fortuna.Generator(42)
second = Fortuna.Generator(42)

assert first.dice(3, 6, count=20) == second.dice(3, 6, count=20)
```

## Module defaults and explicit generators

Module-level calls use a Fortuna-owned thread-local generator initialized from
process-local entropy. They are appropriate for convenient independent draws:

```python
wand_charges = Fortuna.random_int(1, 20)
```

Use an explicit `Generator` when ownership, reproducibility, or worker stream
identity matters:

```python
campaign_seed = 8128
encounters = Fortuna.for_stream(campaign_seed, "encounters")
treasure = Fortuna.for_stream(campaign_seed, "treasure")

encounter_roll = encounters.d(100)
treasure_roll = treasure.d(100)
```

`for_stream(root_seed, stream_id)` deterministically derives a generator from
an unsigned 64-bit root seed and an `int`, `str`, or `bytes` identifier. The
stream identifier is an input to seed derivation, not a decorative label, so
changing it produces a different deterministic generator sequence. Identifier
types are intentionally distinct.

Use `from_entropy()` when an explicitly owned but nondeterministic generator is
required:

```python
generator = Fortuna.from_entropy()
```

`seed(value)` is deterministic, including `seed(0)`. It changes only the
calling thread's module default. Other threads and existing explicit generators
keep their current states.

## Scalar and bulk generation

Every numeric generation function and its `Generator` method accepts the
keyword-only argument `count`:

```python
one_roll = Fortuna.d(20)
many_rolls = Fortuna.d(20, count=100_000)

one_sample = Fortuna.normal_variate(0.0, 1.0)
many_samples = Fortuna.normal_variate(0.0, 1.0, count=100_000)
```

Bulk generation performs the sampling loop in the native extension without
holding the Python GIL and returns a list. `count=0` returns an empty list.
Arguments are validated before the engine advances.

The public numeric families include:

- Uniform booleans, integers, ranges, indexes, floats, and canonical draws.
- Dice, ability dice, and symmetric `plus_or_minus` distributions.
- Triangular, normal, log-normal, exponential, gamma, Weibull, beta, Pareto,
  von Mises, binomial, negative-binomial, geometric, Poisson, extreme-value,
  chi-squared, Cauchy, Fisher F, and Student's t distributions.
- Front, center, and back triangular index profiles and prepared normal value
  profiles.

The complete domains and failure contracts live in the
[API reference](https://github.com/BrokenShell/Fortuna/blob/main/docs/api.md).

## Collection operations

Fortuna provides uniform selection, sampling without replacement, and an
in-place native Knuth-B shuffle:

```python
monsters = ["goblin", "owlbear", "mimic", "dragon"]

monster = Fortuna.random_value(monsters)
patrol = Fortuna.sample(monsters, 2)
Fortuna.shuffle(monsters)
```

An explicit generator can be supplied to each module helper or used directly:

```python
generator = Fortuna.Generator(7)

monster = Fortuna.random_value(monsters, generator=generator)
patrol = generator.sample(monsters, 2)
generator.shuffle(monsters)
```

Fortuna selects Knuth-B for its large-workload behavior. In controlled
macOS arm64 release-build measurements it holds a substantial advantage at one
million elements; at intermediate sizes the two traversals are close and can
exchange small wins. Exact ratios remain machine- and build-specific.

## Prepared value generation

Prepared value engines materialize their inputs once and remain ordinary
Python callables. They are useful when the same table is drawn repeatedly.

### RandomValue

`RandomValue` combines uniform, cyclic, wide, and positional selection behind
one small interface:

```python
loot = Fortuna.RandomValue(["copper", "potion", "wand"])

uniform_drop = loot()
same_as_uniform = loot.uniform()
next_in_order = loot.cycle()
front_loaded_drop = loot.front_triangular()
center_loaded_drop = loot.center_triangular()
back_loaded_drop = loot.back_triangular()
strongly_front_loaded_drop = loot.front_normal()
strongly_center_loaded_drop = loot.center_normal()
strongly_back_loaded_drop = loot.back_normal()
wide_drop = loot.truffle_shuffle()
```

Its methods are ordinary bound callables. This supports the prepared-generator
pattern directly:

```python
loot_gen = Fortuna.RandomValue(["copper", "potion", "wand"]).front_triangular

drop = loot_gen()
more_drops = [loot_gen() for _ in range(20)]
```

`take(count, ...)` repeats the engine's default uniform strategy:

```python
loot = Fortuna.RandomValue(["copper", "potion", "wand"])
treasure_pile = loot.take(10)
```

### TruffleShuffle

`TruffleShuffle` is a stateful wide selector designed for broad uniformity with
reduced localized clumping. It shuffles the table once, then moves through that
permutation by randomized short distances:

```python
encounter = Fortuna.TruffleShuffle(
    ["goblins", "wolves", "bandits", "skeletons", "giant spiders"]
)

next_encounter = encounter()
encounter_week = encounter.take(7)
```

TruffleShuffle allows repeats. Its design combines reduced localized
repetition with broad long-run coverage. See
[Algorithm and design notes](https://github.com/BrokenShell/Fortuna/blob/main/docs/algorithms.md)
for the model and its origin.

### WeightedChoice

`WeightedChoice` accepts relative weights directly or through the explicit
`relative=` keyword:

```python
rarity = Fortuna.WeightedChoice(
    relative=[
        (80, "common"),
        (18, "rare"),
        (2, "legendary"),
    ]
)

item_rarity = rarity()
```

Weights are relative and may total any positive finite value. A weight of zero
is allowed, and at least one weight must be positive. Passing the table as the
first positional argument is equivalent to `relative=table`.

Cumulative tables can be transcribed directly from printed roll tables:

```python
treasure = Fortuna.WeightedChoice(
    cumulative=[
        (30, "coins"),
        (60, "gem"),
        (90, "potion"),
        (100, "magic item"),
    ]
)

treasure_found = treasure()
```

Cumulative boundaries must be finite, nonnegative, and nondecreasing, with a
positive final boundary. Equal boundaries represent zero-weight entries.
Native generators use Storm's prepared cumulative selector with logarithmic
lookup for repeated draws.

### Callable values

Prepared value engines resolve selected callables after selection. This makes
nested procedural tables concise and evaluates only the selected path:

```python
coins = Fortuna.RandomValue(
    [
        lambda: f"{Fortuna.dice(3, 6)} copper pieces",
        lambda: f"{Fortuna.dice(2, 6)} silver pieces",
        lambda: f"{Fortuna.d(6)} gold pieces",
    ]
)

treasure = coins()
```

Pass `resolve_callables=False` when callable objects are the intended values.
Callable cycles and runaway chains raise `RuntimeError`.

## Positional profiles

Use a prepared `RandomValue` when selecting values from an ordered table:

```python
table = ["common", "uncommon", "rare", "very rare", "legendary"]
treasure = Fortuna.RandomValue(table)

front = treasure.front_triangular()
center = treasure.center_triangular()
back = treasure.back_triangular()

strong_front = treasure.front_normal()
strong_center = treasure.center_normal()
strong_back = treasure.back_normal()
```

- `front_triangular` uses the smaller of two uniform indexes.
- `center_triangular` uses their integer midpoint.
- `back_triangular` uses the larger index.
- `front_normal` and `back_normal` stretch mirrored half-normal curves across
  the table.
- `center_normal` stretches a complete centered normal curve across the table.

The normal profiles place their farthest positions at three standard
deviations. Every item remains reachable while the ends of each curve carry
approximately 1.1% of its peak weight before normalization.

## Process and thread behavior

Module defaults are thread-local. A forked child invalidates the inherited
module default and reseeds it before its next draw. A generator created through
`from_entropy()` behaves similarly.

Explicit deterministic generators intentionally preserve copied state through
`fork`. Derive a separate stream for each worker that needs a distinct
sequence:

```python
worker_generator = Fortuna.for_stream(root_seed, worker_id)
```

Built-in operations sharing one exact `Generator` are serialized, but thread
scheduling still determines which thread receives each draw. Value engines
also contain mutable strategy state, so callers own their synchronization. Use
one value-engine instance per worker or synchronize it externally.

Fork only after concurrent use of a shared explicit generator has quiesced; a
child could otherwise inherit its native mutex in a locked state.

## Reproducibility boundary

Fortuna's bounded integer, index, range, dice, canonical, bounded-triangular,
stream-derivation, uniform value-selection, sampling, and shuffle schedules are
stable across supported platforms throughout the Fortuna 6 line.

Standard-library probability distributions, `random_float`, custom floating
transforms, `RandomValue`'s normal profiles, `TruffleShuffle`'s Poisson
movement, and `WeightedChoice`'s real draw depend partly on C++
standard-library implementations. They are repeatable within one platform and
toolchain build. Their exact seeded sequences are platform- and
toolchain-specific. See
[Algorithm and design notes](https://github.com/BrokenShell/Fortuna/blob/main/docs/algorithms.md)
for the complete boundary.

## Documentation

- [Practical guide](https://github.com/BrokenShell/Fortuna/blob/main/docs/guide.md):
  game-oriented examples and composition patterns.
- [API reference](https://github.com/BrokenShell/Fortuna/blob/main/docs/api.md):
  complete public surface, domains, and failure contracts.
- [Algorithm and design notes](https://github.com/BrokenShell/Fortuna/blob/main/docs/algorithms.md):
  engine boundaries, TruffleShuffle, profiles, shuffle, and reproducibility.
- [Migrating from Fortuna 5](https://github.com/BrokenShell/Fortuna/blob/main/docs/migration-5-to-6.md):
  renamed, changed, and removed interfaces.
- [Development guide](https://github.com/BrokenShell/Fortuna/blob/main/docs/development.md):
  build, test, benchmark, process, and contribution contracts.
- [Changelog](https://github.com/BrokenShell/Fortuna/blob/main/CHANGELOG.md):
  release-level changes.

## Development

```bash
uv sync --frozen --group dev --reinstall-package Fortuna
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run pyright
uv run python -m benchmarks
uv build
```

Correctness and statistical validation live under `tests/`. Performance
measurement lives under `benchmarks/`; ordinary CI gates correctness, while
timing provides evidence for engineering decisions.

## License

Fortuna is available under the
[MIT License](https://github.com/BrokenShell/Fortuna/blob/main/LICENSE). Its
vendored Storm header is also MIT licensed; provenance and checksums are
recorded in the
[vendoring record](https://github.com/BrokenShell/Fortuna/blob/main/src/Fortuna/vendor/Storm/README.md).
