# Fantasy: a zero-reflection C# server framework driven by protocol files

> Fantasy is a C# distributed server framework for large multiplayer games that replaces runtime reflection with compile-time source generators, and it routes calls across servers without client-side addresses. Its own support table rules out the desktop targets a general-purpose library pitch would need.

**qq362946/Fantasy** — C # Game Framework, but not limited to games. Can be used for non game business development

- Repository: https://github.com/qq362946/Fantasy
- Website: https://github.com/qq362946/Fantasy
- Stars: 1,465 · Forks: 236
- Language: C#
- License: NOASSERTION
- Published: 2026-10-14 · Updated: 2026-10-14 · Language: en
- Canonical page: https://hysenlabs.com/projects/qq362946-fantasy

## No install command, only a documentation entry point

The README carries no package installation line, no dotnet add package statement and no version pin for consumers. It links to a single entry point for both documentation and quick start, `Docs/README.md`, and points at `Skills/README.md` for the agent Skill. What the repository layout offers instead is a set of runnable shapes under `examples/`: a server, a client, a console app, a config folder and a tools folder. A first useful look is therefore reading the example server against the docs rather than adding a package.

Two more root entries tell you where the work happens. `Fantasy.Packages/` is where the shipped packages are assembled, and `nupkg/` holds the built output. A separate `Fantasy.Benchmark/` project sits in the repository root, which tells you the author keeps a benchmark project there, but the README publishes no benchmark figures and this article quotes none.

The runtime target is stated in the badges as .NET 8.0+ with C# 12.0, and client support is pinned to Unity 2022.3.62 or newer across Windows, macOS, iOS and Android.

## The protocol file is the only registration Fantasy asks for

Traditional message plumbing, as the README frames it, costs about 50 lines: build a request, fill fields, serialize it with ProtoBuf, push bytes through a generic call, then handle the callback, timeout and error paths yourself. That block is quoted here as the contrast case.

```csharp
var message = new C2G_LoginRequest();
message.Account = "player123";
message.Password = "password";
var bytes = ProtoBuf.Serialize(message);
var response = await session.Call(opCode, bytes);
```

Fantasy replaces it with one generated extension method on the session.

```csharp
var response = await session.C2G_Login("player123", "password");
```

The server side is equally short, and this is the shape you copy when writing your first handler.

```csharp
public class C2G_LoginHandler : Message<Session, C2G_Login, G2C_Login>
{
    protected override async FTask Run(Session session, C2G_Login request, G2C_Login response)
    {
        response.Token = await AuthService.Login(request.Account);
    }
}
```

Nobody wires that class to an opcode anywhere in your code. A source generator emits the registration, so the routing exists at compile time rather than being discovered by scanning loaded assemblies. Consequence for adopters: the message type names, not configuration files, decide what your server exposes, and a handler that never compiles never runs.

## A roaming handler executes on the Map server, not on the Gate

Roaming is the feature that separates a distributed framework from a library you have to bolt cross-server plumbing onto. You tag a message pair with the roaming request and response interfaces and the framework moves the call for you.

```csharp
message C2M_EnterMap // IRoamingRequest
{
    int32 MapId = 1;
}
```

The client calls `session.C2M_EnterMap(1001)` on its Gate connection and never learns a Map address. The handler, written as `Roaming<Session, C2M_EnterMap, M2C_EnterMap>`, executes on the Map server, and its response carries a scene id back through the same connection. Forwarding needs no configuration, which is the whole point: there is no server table to keep in sync when you add a new game server type.

The trade-off lands on naming and debugging. Because the same client call can execute on a different process, a stack trace and a log line no longer identify where the code ran, and the framework's own guidance for finding bugs is code review against framework conventions rather than stepping through the call.

## Rendezvous hash routing returns a zero when no scene is online

Fixed server lists with modulo selection get rebalanced badly when nodes come and go. Fantasy replaces that with a Control Center holding topology for Machine, Process, World, Database and Scene objects, scenes that register themselves on start, renew a heartbeat in batches, deregister on clean shutdown and expire on crash, plus namespace, WorldGroup and World scoping for separate environments.

Lookup happens through a hash keyed by your own routing value, here an account id, and returns an address you call directly.

```csharp
var mapAddress = await NetServiceDiscovery.DiscoverAddressByHashAsync(
    SceneType.Map,
    routingKey: accountId,
    worldId: worldId);

if (mapAddress == 0)
    throw new InvalidOperationException("No online Map scene was discovered.");
```

Two details matter more than the headline. A zero address is a normal return, not an exception, so every call site has to branch on it. And rendezvous hash means adding or removing a node moves only the keys that map to it, so capacity planning is a routing-key distribution problem rather than a division problem. The hot path reads a local cache, and a discovered address feeds `Scene.Send` and `Scene.Call` without another lookup.

## One connection helper spans four transports

The same message definition is used over TCP, KCP, WebSocket and HTTP, and the choice is a configuration switch rather than a code change.

```csharp
var session = await NetworkHelper.Connect("127.0.0.1:20000", NetworkProtocolType.TCP);
```

Swapping the enum value to KCP, WebSocket or HTTP changes the transport and nothing else, so port 20000 in the example can front an MMORPG gate, a realtime duel server on KCP or an H5 client on WebSocket. The README assigns TCP to reliable server-to-server traffic, KCP to low-latency realtime combat, WebSocket to browser and mini-game clients with Unity WebGL publishing, and HTTP to REST endpoints, web admin panels and GM tooling.

Cross-server work uses a publish and subscribe path instead of point calls, so a server that defeats a world boss publishes one event and every subscriber server receives it. That covers cross-server announcements, leaderboards and PvP without a shared database read per recipient.

## Entities compose components instead of inheriting behaviour

The entity model is composition-first. A Player is a thin data class, behaviour arrives as components, and systems subscribe to entity types.

```csharp
public class Player : Entity
{
    public string Name { get; set; }
    public int Level { get; set; }
}
```

```csharp
public class PlayerAwakeSystem : AwakeSystem<Player>
{
    protected override void Awake(Player self)
    {
        Log.Info($"玩家 {self.Name} 上线了!");
    }
}
```

Bags, equipment and skills attach with `player.AddComponent<BagComponent>()` and similar calls, so feature ownership stays with whoever owns the component rather than with one central player class that grows a field per feature. AwakeSystem runs without manual registration because the same generator handles systems and handlers.

For non-game work, the same machinery carries over: a chat back end or an IoT collector has entities, addressable components, message handlers and a need for cross-node routing, and none of those depend on game vocabulary.

## The support table rules out WinForms, WPF, Console and Godot

Two rows in the support table undercut the claim that the framework is not limited to games. WinForms and WPF are marked unsupported with a note that they are intended for GM tools and server monitoring panels, and Console is marked unsupported too. Godot is unsupported, deferred to its .NET version. Only Windows Server, Linux Server, macOS and Unity targets, including WebGL over WebSocket, carry full support.

So the GM tooling, REST admin APIs and HTTP monitoring endpoints the README advertises have no supported desktop host inside this project. You would host them as an HTTP service in one of the supported server platforms, which costs more than the surrounding prose mentions.

The other contradiction sits in the metadata. The README badge reads as an MIT licence, while the repository record classifies the licence as Other, a custom licence GitHub cannot categorise, with a LICENSE file to consult. A commercial title has to go by that file, not by the label.

## A packaged Skill targets Claude, Codex, OpenCode and OpenClaw

The project ships an agent-facing Skill named `fantasy-net`, documented at `Docs/05-AI/01-FantasySkill.md` with install notes at `Skills/README.md`. The `Skills/` directory at the repository root holds it, and Prompt examples run from creating a Player entity with its AwakeSystem, writing a player confirmation flow as an EventAwaiter and reviewing Roaming code against framework conventions, to drafting a `Fantasy.config` that wires a Gate, a Map, a MapControl and MongoDB together.

That last example is the useful one for evaluators. A single config file describing Gate, Map, MapControl and MongoDB is the real integration surface of this framework, and the Skill can produce it from a natural-language description, which is faster than hand-authoring the topology. Root entries `Tools/` and `Fantasy.Packages/` sit alongside it for scaffolding and packaging.

The gap is equally concrete. The README documents no prompt-level guarantees about which generator outputs the Skill produces, and it publishes no evaluation for generated code correctness, so treat the Skill as a code generator with a framework vocabulary loaded rather than as a reviewed implementation path. The last push landed on 2026-09-07, and the AI directory is recent enough that its guidance may still shift as generator output changes.

## Conclusion

Fantasy fits teams already committed to C# who need cross-server routing built into their entity model, and it fits AI agent workflows that already speak the fantasy-net Skill. It does not fit Godot, WinForms, WPF or Console work, and its support table is worth reading before you plan a GM panel. Verify first that the source generator registers your handlers in your build, because the README shows generated registration code rather than documenting a fallback. If that holds on .NET 8.0+, the next thing to write by hand is the Fantasy.config that names your Gate, Map and MapControl.

## FAQ

### What runtime does Fantasy need and which clients can connect?

The README badges state .NET 8.0+ with C# 12.0, and client support covers Unity 2022.3.62 or newer on Windows, macOS, iOS and Android, plus Unity WebGL over WebSocket.

### How does Fantasy route a request to a different game server?

You mark the message pair with the roaming request and response interfaces, the Gate forwards the call by message type with no address configuration, and the handler runs on the target server, returning the response through the client's original connection.

### Which platforms does Fantasy explicitly not support?

Godot is marked unsupported pending its .NET version, and WinForms, WPF and Console are all listed as not yet supported, which leaves no supported desktop host in the project for the GM tools and monitoring panels the README mentions.

### Does Fantasy use reflection at runtime, and how are handlers registered?

The README states that registration code is generated at compile time by source generators rather than found by scanning loaded assemblies, which is why the project claims Native AOT support, and it requires no manual registration step from the developer.

### Can an AI coding agent work with Fantasy?

Yes, through the packaged `fantasy-net` Skill, which the README says works with Claude, Codex, OpenCode and OpenClaw for writing entities, components, systems, protocols, handlers, config and database code, and for reviewing existing projects against framework conventions.

## Sources

- [Issues](https://github.com/qq362946/Fantasy/issues)
- [Project website](https://github.com/qq362946/Fantasy)
- [qq362946/Fantasy on GitHub](https://github.com/qq362946/Fantasy)
- [README](https://github.com/qq362946/Fantasy/blob/main/README.md)
- [Releases](https://github.com/qq362946/Fantasy/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/qq362946-fantasy
