Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
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
78 changes: 78 additions & 0 deletions .github/workflows/docs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: Docs

on:
push:
branches: [ main ]
paths:
- 'docsite/**'
- '.github/workflows/docs.yaml'
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches: [ main ]
paths:
- 'docsite/**'
- '.github/workflows/docs.yaml'

permissions:
contents: read

concurrency:
group: ${{ github.event_name == 'pull_request' && format('docs-pr-{0}', github.event.pull_request.number) || 'pages' }}
cancel-in-progress: true

jobs:
build:
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false

runs-on: ubuntu-latest
defaults:
run:
working-directory: docsite
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Install pngquant
run: sudo apt-get update && sudo apt-get install -y pngquant

- name: Setup Python
uses: actions/setup-python@v6
with:
python-version-file: "docsite/pyproject.toml"

- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true

- name: Install the project
run: uv sync --locked --all-extras --dev

- name: Build documentation
run: uv run zensical build -f zensical.toml --clean

- name: Upload artifact
if: github.event_name == 'push'
uses: actions/upload-pages-artifact@v4
with:
path: docsite/site

deploy:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: build
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Setup Pages
uses: actions/configure-pages@v5

- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
38 changes: 38 additions & 0 deletions .github/workflows/publish-preview.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Publish Preview

on:
workflow_dispatch:
push:
branches:
- main
paths-ignore:
- 'docs/**'
- 'docsite/**'
- 'README.md'

permissions:
contents: write
pull-requests: write

jobs:
build:
uses: LayeredCraft/devops-templates/.github/workflows/publish-preview.yml@v10.1
with:
solution: SharpMud.slnx
dotnetVersion: |
10.0.x
11.0.x
hasTests: true
prereleaseIdentifier: alpha
secrets: inherit

push:
needs: build
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: LayeredCraft/devops-templates/.github/actions/nuget-push@v10.1
with:
nuget_user: ${{ secrets.NUGET_USER }}
30 changes: 30 additions & 0 deletions .github/workflows/publish-release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Publish Release

on:
release:
types: [published]

permissions:
contents: write

jobs:
build:
uses: LayeredCraft/devops-templates/.github/workflows/publish-release.yml@v10.1
with:
solution: SharpMud.slnx
dotnetVersion: |
10.0.x
11.0.x
hasTests: true
secrets: inherit

push:
needs: build
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: LayeredCraft/devops-templates/.github/actions/nuget-push@v10.1
with:
nuget_user: ${{ secrets.NUGET_USER }}
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -495,3 +495,7 @@ sharpmud.db
sharpmud.db-wal
sharpmud.db-shm
sharpmud.db-journal

# docsite (Zensical/uv) build output and Python venv (uv.lock IS committed)
docsite/site/
docsite/.venv/
54 changes: 54 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<Project>
<PropertyGroup>
<!-- No VersionPrefix - release-drafter resolves the version from
conventional-commit PR titles, see
docs/adr/0006-nuget-package-distribution.md's Versioning and CI
section and .github/workflows/publish-preview.yaml/
publish-release.yaml. -->
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryUrl>https://github.com/LayeredCraft/sharp-mud</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<Authors>Nick Cipollina</Authors>
<PackageProjectUrl>https://github.com/LayeredCraft/sharp-mud</PackageProjectUrl>
Comment thread
ncipollina marked this conversation as resolved.
<PackageIcon>icon.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<!-- CS1591 (missing XML doc comment on a public member) is suppressed
repo-wide, not fixed here - documentation.md already tracks the
zero-XML-doc-comments gap as separate, open backfill work; turning
on GenerateDocumentationFile for packaging shouldn't silently turn
that into hundreds of build warnings as a side effect of this
change. New/substantially-changed public members still get real
doc comments per documentation.md - this only suppresses the
warning, it doesn't relax the convention. -->
<NoWarn>$(NoWarn);CS1591</NoWarn>
</PropertyGroup>

<PropertyGroup>
<DebugType>embedded</DebugType>
<NoPackageAnalysis>true</NoPackageAnalysis>
<LangVersion>latest</LangVersion>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
</PropertyGroup>

<!-- Samples/tests never get packed - only src/ packages are consumer-facing. -->
<PropertyGroup Condition="'$(IsTestProject)' == 'true'">
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup Condition="'$(IsPackable)' != 'false'">
<None Include="$(MSBuildThisFileDirectory)icon.png" Pack="true" PackagePath="" Visible="false" Condition="Exists('$(MSBuildThisFileDirectory)icon.png')" />
<None Include="$(MSBuildThisFileDirectory)README.md" Pack="true" PackagePath="" Visible="false" Condition="Exists('$(MSBuildThisFileDirectory)README.md')" />
</ItemGroup>

<ItemGroup Condition="'$(IsPackable)' != 'false'">
<PackageReference Include="Microsoft.SourceLink.GitHub">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
9 changes: 9 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="10.0.9" />
<PackageVersion Include="SQLitePCLRaw.lib.e_sqlite3" Version="3.53.3" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.9" />
<!-- SharpMud.Hosting's composition-root builder wraps this directly -
see docs/adr/0006-nuget-package-distribution.md. -->
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.9" />
<!-- SharpMud.Persistence.DynamoDb - pinned to the confirmed-stable
EF Core 10 build, not a preview EF Core 11 build - see ADR-0006. -->
<PackageVersion Include="EntityFrameworkCore.DynamoDb" Version="10.0.0" />
<!-- NuGet package metadata (Directory.Build.props) - matches
optimized-enums'/structured-logging's shape. -->
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<!-- Logging: Serilog + LayeredCraft.StructuredLogging call-site extensions,
per .agents/skills/engineering-workflow/references/coding-standards.md.
Console sink only - this repo runs in Docker (docs/deployment.md),
Expand Down
14 changes: 8 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@
FROM mcr.microsoft.com/dotnet/sdk:11.0.100-preview.5 AS build
WORKDIR /src

COPY Directory.Packages.props global.json ./
COPY Directory.Build.props Directory.Packages.props global.json ./
COPY src/SharpMud.Engine/SharpMud.Engine.csproj src/SharpMud.Engine/
COPY src/SharpMud.Ruleset.Classic/SharpMud.Ruleset.Classic.csproj src/SharpMud.Ruleset.Classic/
COPY src/SharpMud.Hosting/SharpMud.Hosting.csproj src/SharpMud.Hosting/
COPY src/SharpMud.Persistence/SharpMud.Persistence.csproj src/SharpMud.Persistence/
COPY src/SharpMud.Persistence.Sqlite/SharpMud.Persistence.Sqlite.csproj src/SharpMud.Persistence.Sqlite/
COPY src/SharpMud.Adapters.Cli/SharpMud.Adapters.Cli.csproj src/SharpMud.Adapters.Cli/
COPY src/SharpMud.Adapters.Telnet/SharpMud.Adapters.Telnet.csproj src/SharpMud.Adapters.Telnet/
COPY src/SharpMud.Host/SharpMud.Host.csproj src/SharpMud.Host/
RUN dotnet restore src/SharpMud.Host/SharpMud.Host.csproj
COPY samples/SharpMud.Samples.Classic/SharpMud.Samples.Classic.csproj samples/SharpMud.Samples.Classic/
RUN dotnet restore samples/SharpMud.Samples.Classic/SharpMud.Samples.Classic.csproj

COPY src/ src/
RUN dotnet publish src/SharpMud.Host/SharpMud.Host.csproj -c Release -o /app --no-restore
COPY samples/ samples/
RUN dotnet publish samples/SharpMud.Samples.Classic/SharpMud.Samples.Classic.csproj -c Release -o /app --no-restore

FROM mcr.microsoft.com/dotnet/runtime:11.0.0-preview.5-alpine3.24 AS runtime
WORKDIR /app
Expand All @@ -40,4 +42,4 @@ VOLUME /data
# needing the larger -extra image just for culture data we don't use.
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true

ENTRYPOINT ["dotnet", "SharpMud.Host.dll"]
ENTRYPOINT ["dotnet", "SharpMud.Samples.Classic.dll"]
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 LayeredCraft

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
21 changes: 21 additions & 0 deletions NOTICE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Notice

sharp-mud is licensed under the [MIT License](LICENSE).

## Design inspiration

sharp-mud's engine design (the `Thing`/`Behavior` composition model, the
generic event system, session/transport abstractions, and several other
architectural decisions) is informed by a close reading of
[WheelMUD](https://github.com/DavidRieman/WheelMUD)'s source code and
architecture. See [docs/research/wheelmud-findings.md](docs/research/wheelmud-findings.md)
for the full source-dive record of what was adopted, what was deliberately
changed, and why.

sharp-mud is a clean-room reimplementation, not a fork or derivative of
WheelMUD's source — no WheelMUD code is included in this repository.
WheelMUD itself is licensed under the
[Microsoft Public License (MS-PL)](https://github.com/DavidRieman/WheelMUD/blob/main/src/LICENSE.txt),
which does not apply to sharp-mud's own, independently-written code (see
[ADR-0006](docs/adr/0006-nuget-package-distribution.md)'s License and
naming section for the full reasoning).
46 changes: 30 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,38 +12,52 @@ to the genre's feel while built with current .NET architecture.
## Solution layout

```
SharpMud.sln
SharpMud.slnx
src/
SharpMud.Engine/ # Thing/Behavior, events, generic behaviors,
# command pipeline, session, tick loop.
# Zero deps on any ruleset.
SharpMud.Ruleset.Classic/ # D&D-flavored ruleset: stats, combat, kill/flee.
# References Engine only.
SharpMud.Persistence/ # EF Core repositories
SharpMud.Adapters.Cli/ # local stdin/stdout session adapter
SharpMud.Adapters.Telnet/ # raw TCP session adapter + listener
SharpMud.Host/ # composition root / entry point / hub world content
SharpMud.Engine/ # Thing/Behavior, events, generic behaviors,
# command pipeline, session, tick loop.
# Zero deps on any ruleset.
SharpMud.Hosting/ # generic-host composition helpers, ruleset-agnostic
SharpMud.Persistence/ # EF Core repositories, provider-agnostic
SharpMud.Persistence.Sqlite/ # SQLite provider
SharpMud.Persistence.DynamoDb/ # DynamoDB provider
SharpMud.Adapters.Cli/ # local stdin/stdout session adapter
SharpMud.Adapters.Telnet/ # raw TCP session adapter + listener
SharpMud/ # meta-package (pulls in every SharpMud.* package)
Comment thread
ncipollina marked this conversation as resolved.
Outdated
samples/
SharpMud.Samples.Classic/ # D&D-flavored sample ruleset + composition root
tests/
SharpMud.Engine.Tests/
SharpMud.Ruleset.Classic.Tests/
SharpMud.Hosting.Tests/
SharpMud.Persistence.Tests/
SharpMud.Host.Tests/
SharpMud.Adapters.Cli.Tests/
SharpMud.Adapters.Telnet.Tests/
SharpMud.Samples.Classic.Tests/
```

## Building & testing

```
dotnet build SharpMud.sln
dotnet test SharpMud.sln
dotnet build SharpMud.slnx
dotnet test SharpMud.slnx
```

## Running

```
dotnet run --project src/SharpMud.Host # local single-player CLI
dotnet run --project src/SharpMud.Host -- --telnet [port] # telnet server, default port 4000
dotnet run --project samples/SharpMud.Samples.Classic # local single-player CLI
dotnet run --project samples/SharpMud.Samples.Classic -- --telnet [port] # telnet server, default port 4000
```

## Using SharpMud as a library

`src/` publishes as NuGet packages (`SharpMud.Engine`, `SharpMud.Hosting`,
`SharpMud.Persistence`(`.Sqlite`/`.DynamoDb`), `SharpMud.Adapters.Cli`/
`.Telnet`), plus a `SharpMud` meta-package pulling in all of them.
`samples/SharpMud.Samples.Classic` is a full reference consumer — start there
to see how a ruleset composes against the packages. See
[ADR-0006](docs/adr/0006-nuget-package-distribution.md) for the design.

## Containerized

```
Expand Down
8 changes: 5 additions & 3 deletions SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ persistent public MUD.
the prior art this is adapted from and
[docs/engine-vs-ruleset.md](docs/engine-vs-ruleset.md) for the concrete
design. The game we're actually building (classic D&D-flavored stats/combat,
the hand-built hub) lives in a separate `SharpMud.Ruleset.Classic` project
that consumes the engine the same way a third party's game would.
the hand-built hub) lives in `samples/SharpMud.Samples.Classic`, a reference
consumer of the published `SharpMud.*` NuGet packages
([ADR-0006](docs/adr/0006-nuget-package-distribution.md)) that consumes the
engine the same way a third party's game would.

## Architecture

Expand All @@ -38,7 +40,7 @@ optionally a `LockableBehavior`). This is adapted directly from WheelMUD (see
[docs/research/wheelmud-findings.md](docs/research/wheelmud-findings.md)) and
is the mechanism that makes the engine/ruleset split real: `SharpMud.Engine`
ships generic, ruleset-agnostic behaviors (rooms, exits, containment,
identity); `SharpMud.Ruleset.Classic` adds the D&D-flavored ones (stats,
identity); `SharpMud.Samples.Classic` adds the D&D-flavored ones (stats,
Comment thread
ncipollina marked this conversation as resolved.
combat, dice-roll character creation) purely by composing more `Behavior`s
onto the same `Thing`s — the engine never references ruleset types. Full
design in [docs/engine-vs-ruleset.md](docs/engine-vs-ruleset.md).
Expand Down
Loading
Loading