WoadStoat.MarkovNames 0.1.0

dotnet add package WoadStoat.MarkovNames --version 0.1.0
                    
NuGet\Install-Package WoadStoat.MarkovNames -Version 0.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="WoadStoat.MarkovNames" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="WoadStoat.MarkovNames" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="WoadStoat.MarkovNames" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add WoadStoat.MarkovNames --version 0.1.0
                    
#r "nuget: WoadStoat.MarkovNames, 0.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package WoadStoat.MarkovNames@0.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=WoadStoat.MarkovNames&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=WoadStoat.MarkovNames&version=0.1.0
                    
Install as a Cake Tool

WoadStoat.MarkovNames

.NET Build and Test

WoadStoat.MarkovNames is an engine-agnostic C# library for procedural name generation using Markov chains.

It can generate:

  • character names
  • place names
  • clan names
  • faction names
  • planet names
  • settlement names
  • ship names
  • company names
  • fantasy or sci-fi culture names

The library is designed for use in games, tools, content pipelines, and procedural generation systems.

It works with:

  • plain C#/.NET
  • MonoGame
  • Unity
  • Godot C#
  • custom engines
  • server-side tools

Features

  • Character-based Markov name generation
  • Token-based Markov name generation
  • Custom culture profiles
  • Name categories such as people, places, clans, planets
  • Seeded deterministic generation
  • JSON culture profile loading
  • JSON loading from files or strings
  • Guided prefixes and suffixes
  • Generation filters and custom validation
  • Engine-agnostic design
  • Unit-tested core behaviour

Quick Start

using WoadStoat.MarkovNames;

string[] names =
{
    "Aedan",
    "Alasdair",
    "Caelan",
    "Duncan",
    "Ewan",
    "Fergus",
    "Malcolm",
    "Ruaridh"
};

MarkovNameTrainer trainer = new MarkovNameTrainer(order: 2);
MarkovNameModel model = trainer.Train(names);

MarkovNameGenerator generator = new MarkovNameGenerator(model, seed: 12345);

string name = generator.Generate();

Console.WriteLine(name);

Generating Multiple Names

NameGenerationOptions options = new NameGenerationOptions
{
    MinLength = 4,
    MaxLength = 12,
    AvoidTrainingDuplicates = true,
    MaxAttempts = 500
};

IReadOnlyList<string> generatedNames = generator.GenerateMany(
    count: 20,
    options);

foreach (string generatedName in generatedNames)
{
    Console.WriteLine(generatedName);
}

Deterministic Generation

WoadStoat.MarkovNames supports seeded generation.

This means the same training data and same seed will produce the same sequence of names.

MarkovNameGenerator generatorA = new MarkovNameGenerator(model, seed: 123);
MarkovNameGenerator generatorB = new MarkovNameGenerator(model, seed: 123);

This is useful for procedural world generation.

For example:

int worldSeed = 12345;

string townName = library.Generate(
    "gaelic",
    "places",
    seed: worldSeed + 100);

Culture Profiles

A culture profile groups related name categories together.

NameCultureProfile profile = new NameCultureProfile("roman")
    .AddCategory("people", new[]
    {
        "Marcus",
        "Lucius",
        "Gaius",
        "Aurelius",
        "Cassius"
    })
    .AddCategory("places", new[]
    {
        "Roma",
        "Capua",
        "Ostia",
        "Ravenna"
    });

NameModelLibrary library = NameModelLibrary.Train(profile, order: 2);

string person = library.Generate("roman", "people", seed: 100);
string place = library.Generate("roman", "places", seed: 200);

Categories

Categories are developer-defined.

Common examples:

people
places
clans
families
settlements
planets
ships
factions
companies
rivers
mountains

A culture can contain as many categories as needed.


Token-Based Generation

Character-based generation works well for many simple names.

Token-based generation is better when a culture has meaningful letter clusters.

Examples:

Mac
Mc
ch
dh
ae
eo
kh
zh
ll
th

Example:

INameTokenizer tokenizer = new GreedyNameTokenizer(new[]
{
    "mac",
    "dh",
    "ch",
    "gh",
    "ae",
    "eo",
    "ai"
});

TokenMarkovNameTrainer trainer = new TokenMarkovNameTrainer(
    order: 2,
    tokenizer: tokenizer);

TokenMarkovNameModel model = trainer.Train(new[]
{
    "MacLeod",
    "MacDonald",
    "MacKenzie",
    "Aedan",
    "Eoghan",
    "Donnchadh"
});

TokenMarkovNameGenerator generator = new TokenMarkovNameGenerator(
    model,
    seed: 12345);

string name = generator.Generate();

Guided Prefixes

Guided prefixes let the generator begin from a required prefix instead of repeatedly generating and rejecting names.

NameGenerationOptions options = new NameGenerationOptions
{
    MinLength = 5,
    MaxLength = 16,
    RequiredPrefix = "Mac",
    UseGuidedPrefix = true,
    AvoidTrainingDuplicates = true,
    MaxAttempts = 1000
};

string clanName = generator.Generate(options);

Useful for:

Mac-style clans
Inver-style settlements
House names
corporate prefixes
alien caste prefixes
planet catalogue prefixes

Guided Suffixes

Guided suffixes append and validate a required suffix.

NameGenerationOptions options = new NameGenerationOptions
{
    MinLength = 5,
    MaxLength = 14,
    RequiredSuffix = "us",
    UseGuidedSuffix = true,
    AvoidTrainingDuplicates = true,
    MaxAttempts = 1000
};

This is useful for Roman-style names, family names, faction naming conventions, and settlement naming rules.

Suffix Join Modes

Required suffixes can be joined using different modes:

NameGenerationOptions options = new NameGenerationOptions
{
    RequiredSuffix = "ius",
    UseGuidedSuffix = true,
    SuffixJoinMode = SuffixJoinMode.MergeOverlappingSubstring
};

---

## Generation Filters

`NameGenerationOptions` can reject generated names that do not fit your rules.

```csharp
NameGenerationOptions options = new NameGenerationOptions
{
    MinLength = 4,
    MaxLength = 12,
    AvoidTrainingDuplicates = true,
    MaxAttempts = 1000,
    MaxConsecutiveIdenticalCharacters = 2,
    CustomValidator = name => !name.EndsWith("son")
};

options.ForbiddenSubstrings.Add("xxx");
options.ForbiddenSubstrings.Add("qq");
options.ForbiddenCharacters.Add('$');
options.ForbiddenCharacters.Add('^');

Available options include:

MinLength
MaxLength
MaxAttempts
AvoidTrainingDuplicates
CapitaliseFirstLetter
LowercaseRest
RequiredPrefix
RequiredSuffix
UseGuidedPrefix
UseGuidedSuffix
ForbiddenSubstrings
ForbiddenCharacters
AllowedCharacters
MaxConsecutiveIdenticalCharacters
CustomValidator

JSON Culture Profiles

Culture profiles can be stored in JSON.

Example:

{
  "cultureKey": "gaelic",
  "order": 2,
  "useTokens": true,
  "tokens": [
    "mac",
    "mc",
    "mh",
    "dh",
    "ch",
    "gh",
    "ae",
    "ai",
    "eo",
    "io",
    "ua",
    "nn",
    "ll"
  ],
  "categories": {
    "people": [
      "Aedan",
      "Caelan",
      "Eoghan",
      "Ruairidh",
      "Fionnlagh",
      "Domhnall",
      "Eachann",
      "Coinneach",
      "Murchadh",
      "Donnchadh"
    ],
    "clans": [
      "MacLeod",
      "MacDonald",
      "MacKenzie",
      "MacGregor",
      "MacArthur",
      "MacNab"
    ],
    "places": [
      "Inverness",
      "Dunblane",
      "Aviemore",
      "Callander",
      "Mallaig",
      "Tobermory"
    ]
  }
}

Load from a file:

TokenNameModelLibrary library =
    NameCultureProfileJsonLoader.TrainTokenLibraryFromProfileFile(
        "Data/gaelic.profile.json");

Load from a JSON string:

string json = File.ReadAllText("Data/gaelic.profile.json");

TokenNameModelLibrary library =
    NameCultureProfileJsonLoader.TrainTokenLibraryFromProfileJson(json);

Multi-Culture JSON Profile Sets

You can also define multiple cultures in one file:

{
  "profiles": [
    {
      "cultureKey": "gaelic",
      "order": 2,
      "useTokens": true,
      "tokens": ["mac", "dh", "ch", "ae", "eo"],
      "categories": {
        "people": ["Aedan", "Caelan", "Eoghan"],
        "clans": ["MacLeod", "MacDonald", "MacGregor"]
      }
    },
    {
      "cultureKey": "roman",
      "order": 2,
      "useTokens": true,
      "tokens": ["us", "ius", "ae", "qu"],
      "categories": {
        "people": ["Marcus", "Lucius", "Gaius", "Aurelius"]
      }
    }
  ]
}

Load it with:

TokenNameModelLibrary library =
    NameCultureProfileJsonLoader.TrainTokenLibraryFromProfileSetFile(
        "Data/cultures.profile-set.json");

Saving and Loading Trained Token Libraries

Raw culture profile JSON is useful for editing training data.

Trained model JSON is useful when you want to train once and load quickly later.

using WoadStoat.MarkovNames;
using WoadStoat.MarkovNames.Serialization;

TokenNameModelLibrary library =
    NameCultureProfileJsonLoader.TrainTokenLibraryFromProfileJson(profileJson);

string trainedJson =
    TokenNameModelJsonSerializer.LibraryToJson(library);

TokenNameModelLibrary loaded =
    TokenNameModelJsonSerializer.LibraryFromJson(trainedJson);

string name = loaded.Generate(
    "gaelic",
    "clans",
    seed: 12345);

Unified High-Level API

For most projects, the easiest entry point is NameGeneratorLibrary.

It can load character-based and token-based profiles through one API.

using WoadStoat.MarkovNames;

NameGeneratorLibrary library =
    NameGeneratorLibrary.FromProfileJson(profileJson);

string name = library.Generate(
    "gaelic",
    "clans",
    seed: 12345,
    new NameGenerationOptions
    {
        MinLength = 5,
        MaxLength = 16,
        RequiredPrefix = "Mac",
        UseGuidedPrefix = true
    });

A profile set can contain both character and token models:

NameGeneratorLibrary library =
    NameGeneratorLibrary.FromProfileSetJson(profileSetJson);

string roman = library.Generate("roman", "people", seed: 100);
string clan = library.Generate("gaelic", "clans", seed: 200);

The lower-level APIs remain available when you need direct control:

MarkovNameTrainer
MarkovNameGenerator
TokenMarkovNameTrainer
TokenMarkovNameGenerator
NameModelLibrary
TokenNameModelLibrary

Unity Usage

WoadStoat.MarkovNames does not depend on Unity.

You can load a JSON profile through a TextAsset.

using UnityEngine;
using WoadStoat.MarkovNames;

public sealed class NameGeneratorExample : MonoBehaviour
{
    [SerializeField] private TextAsset profileJson;

    private TokenNameModelLibrary library;

    private void Awake()
    {
        library = NameCultureProfileJsonLoader
            .TrainTokenLibraryFromProfileJson(profileJson.text);
    }

    private void Start()
    {
        string name = library.Generate(
            "gaelic",
            "people",
            seed: 12345);

        Debug.Log(name);
    }
}

MonoGame Usage

WoadStoat.MarkovNames does not depend on MonoGame.

You can use it during startup, world generation, or content loading.

using WoadStoat.MarkovNames;

public sealed class WorldNameService
{
    private readonly TokenNameModelLibrary _library;

    public WorldNameService(string profileJson)
    {
        _library = NameCultureProfileJsonLoader
            .TrainTokenLibraryFromProfileJson(profileJson);
    }

    public string GenerateTownName(int worldSeed, int townIndex)
    {
        return _library.Generate(
            "gaelic",
            "places",
            seed: worldSeed + townIndex,
            new NameGenerationOptions
            {
                MinLength = 4,
                MaxLength = 14,
                AvoidTrainingDuplicates = true
            });
    }
}

Installing a Local Package

After packing the project locally, install it into another project with:

dotnet add package WoadStoat.WoadStoat.MarkovNames \
  --version 0.1.0 \
  --source /path/to/WoadStoat.MarkovNames/artifacts/packages

Example:

dotnet add package WoadStoat.WoadStoat.MarkovNames \
  --version 0.1.0 \
  --source /workspaces/WoadStoat.MarkovNames/WoadStoat.MarkovNames/artifacts/packages

Running the Console Example

dotnet run --project examples/WoadStoat.MarkovNames.ConsoleExample

Running Tests

dotnet test

Packing Locally

Build and test:

dotnet clean
dotnet build -c Release
dotnet test -c Release

Create the package:

mkdir -p artifacts/packages
dotnet pack src/WoadStoat.MarkovNames/WoadStoat.MarkovNames.csproj -c Release -o artifacts/packages

The package will be created under:

artifacts/packages/

Expected files:

WoadStoat.WoadStoat.MarkovNames.0.1.0.nupkg
WoadStoat.WoadStoat.MarkovNames.0.1.0.snupkg

The .nupkg file is the actual NuGet package.

The .snupkg file is the symbols package, useful for debugging.


Testing the Package in a Fresh Project

Create a test project:

mkdir WoadStoat.MarkovNamesPackageTest
cd WoadStoat.MarkovNamesPackageTest
dotnet new console

Install the local package:

dotnet add package WoadStoat.WoadStoat.MarkovNames \
  --version 0.1.0 \
  --source /path/to/WoadStoat.MarkovNames/artifacts/packages

Example test program:

using WoadStoat.MarkovNames;

string[] samples =
{
    "Aedan",
    "Alasdair",
    "Caelan",
    "Duncan",
    "Ewan",
    "Fergus",
    "Malcolm",
    "Ruaridh"
};

MarkovNameModel model = new MarkovNameTrainer(order: 2).Train(samples);

MarkovNameGenerator generator = new MarkovNameGenerator(model, seed: 12345);

NameGenerationOptions options = new NameGenerationOptions
{
    MinLength = 4,
    MaxLength = 12,
    AvoidTrainingDuplicates = true,
    MaxAttempts = 1000
};

for (int i = 0; i < 10; i++)
{
    Console.WriteLine(generator.Generate(options));
}

Run:

dotnet run

Project Structure

WoadStoat.MarkovNames/
├── src/
│   └── WoadStoat.MarkovNames/
│       ├── MarkovNameTrainer.cs
│       ├── MarkovNameModel.cs
│       ├── MarkovNameGenerator.cs
│       ├── TokenMarkovNameTrainer.cs
│       ├── TokenMarkovNameModel.cs
│       ├── TokenMarkovNameGenerator.cs
│       ├── NameCultureProfile.cs
│       ├── NameModelLibrary.cs
│       ├── TokenNameModelLibrary.cs
│       └── NameCultureProfileJsonLoader.cs
│
├── examples/
│   └── WoadStoat.MarkovNames.ConsoleExample/
│
├── tests/
│   └── WoadStoat.MarkovNames.Tests/
│
├── docs/
│   ├── json-profile-format.md
│   ├── tokenisation.md
│   └── generation-options.md
│
└── samples/

Order 1: loose, chaotic, good for alien or strange names
Order 2: good general default
Order 3: closer to training data
Order 4+: may overfit, especially with small datasets

Suggested defaults:

human names: 2 or 3
place names: 2 or 3
alien names: 1 or 2
faction names: 2
short datasets: 1 or 2
large datasets: 2 or 3

Notes on Training Data

For best results:

  • keep categories focused
  • avoid mixing people, places, and factions in one category
  • use at least 20–50 examples per category where possible
  • use token-based generation for languages with important letter clusters
  • use order 2 as the default starting point
  • use deterministic seeds for repeatable procedural worlds

Poor category:

Duncan
Inverness
MacLeod
Ben Nevis
The Red Company

Better categories:

people: Duncan, Ewan, Malcolm
places: Inverness, Dunblane, Mallaig
clans: MacLeod, MacDonald, MacGregor
factions: Red Company, Iron League

Documentation

Additional documentation:


Current Status

WoadStoat.MarkovNames currently supports:

  • character-based Markov generation
  • token-based Markov generation
  • culture/category model libraries
  • JSON profile files
  • JSON profile strings
  • guided prefixes and suffixes
  • generation filters
  • deterministic seeded generation
  • unit tests
  • local NuGet package generation

Future improvements may include:

  • GitHub Actions CI
  • token model serialisation
  • improved suffix generation
  • larger sample culture packs
  • Unity and MonoGame sample projects
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.0 136 5/19/2026