# hypa-ttfx
A C# / .NET 10 Native-AOT terminal text-effects CLI and library with 37 effects.
Pipe text in, pick an effect:
```sh
printf 'Hello\n' | ./artifacts/ttfx wipe
fortune | ./artifacts/ttfx --random-effect
```
## Examples
Every clip is [`hypa-logo.txt`](hypa-logo.txt) piped through that effect with `--seed 42`.
| beams | binarypath | blackhole |
| :---: | :---: | :---: |
|  |  |  |
| **bouncyballs** | **bubbles** | **burn** |
|  |  |  |
| **colorshift** | **crumble** | **decrypt** |
|  |  |  |
| **errorcorrect** | **expand** | **fireworks** |
|  |  |  |
| **highlight** | **laseretch** | **matrix** |
|  |  |  |
| **middleout** | **orbittingvolley** | **overflow** |
|  |  |  |
| **pour** | **print** | **rain** |
|  |  |  |
| **randomsequence** | **rings** | **scattered** |
|  |  |  |
| **slice** | **slide** | **smoke** |
|  |  |  |
| **spotlights** | **spray** | **swarm** |
|  |  |  |
| **sweep** | **synthgrid** | **thunderstorm** |
|  |  |  |
| **unstable** | **vhstape** | **waves** |
|  |  |  |
Regenerate with `./bin/build` and `tools/render_examples.py` (Pillow + ffmpeg).
## License
MIT. Attribution for the work this package derives from is in
[LICENSE](LICENSE) and [NOTICE](NOTICE).
## Deliberate divergences
| Topic | Behavior here | Why |
|---|---|---|
| Random number generator | xoshiro256++ | `--seed` sequences are not Python TTE's Mersenne Twister |
| Broken-pipe exit status | 0 | Upstream Python swallows `EPIPE`; we match that contract |
| SIGTERM exit status | Signal 15 (via `WIFSIGNALED`) | Not a divergence |
| Shell completions | Hand-written templates | Zero NuGet packages, so no completion generator |
| Plugin effects | Not supported | No Python interpreter to load them |
| Cell width | One codepoint = one cell (`Rune`) | Faithfully reproduces upstream; no `wcwidth` |
| Byte-exact parity | Verified on tested RIDs only | See **Fidelity** below |
## Fidelity
This is a *parity port*: given the same input, config, and seed, hypa-ttfx aims for
**byte-identical frame output** to the pinned reference binary (`REFERENCE.md`).
**Verified on this project's CI / local testing:**
| RID | Status |
|---|---|
| `osx-x64` | Verified locally (AOT publish on this developer host) |
| `osx-arm64` | Expected on Apple Silicon hosts (same gate; build with `./bin/build osx-arm64`) |
| `linux-x64` | Verified in CI (oracle suites behind Linux gate) |
| `linux-arm64` | Expected but unverified until a matching CI runner executes the oracle suites |
Publishing four RIDs under a claim tested on one machine would overclaim — maths-library
behavior, AOT codegen, and signal delivery can differ by architecture. The README states
what has actually been measured.
`./bin/test` runs the full gate: unit goldens, AOT publish, CLI corpus, signal tests, and
(on Linux or when `reference/ttfx` is present on macOS) byte-exact parity suites.
## Library
Other .NET 10+ programs consume the engine as `Hypa.Ttfx`:
```sh
dotnet add package Hypa.Ttfx
```
```csharp
using Ttfx;
IReadOnlyList frames = TextEffects.Render(
"wipe",
"Hello from .NET\n",
new TextEffectOptions { Seed = 42 });
foreach (string frame in TextEffects.EnumerateFrames("smoke", "Hello\n", new TextEffectOptions
{
Seed = 42,
EffectArguments = ["--final-gradient-stops", "8A008A", "00D1FF"],
}))
{
Console.Write(frame);
}
```
`Render` / `EnumerateFrames` use a virtual clock and ignore the process TTY
(canvas sizes to the input). `TextEffects.Run` writes a live animation to a
stream. Per-effect flags are the same tokens as the CLI.
The CLI is also a `dotnet tool` (framework-dependent; Native-AOT builds are
the GitHub Release assets):
```sh
dotnet tool install -g Hypa.Ttfx.Tool
printf 'Hello\n' | ttfx wipe
```
## Building
```sh
./bin/build # AOT publish to artifacts/ttfx (host RID)
./bin/build linux-x64 # cross-RID publish when toolchain is available
./bin/test
./bin/pack # Hypa.Ttfx + Hypa.Ttfx.Tool into artifacts/nuget
```
This repo consumes zero NuGet packages — everything comes from
`Microsoft.NETCore.App`. Downstream apps add `Hypa.Ttfx` as a package
reference; that is the supported way to reuse the engine.
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md) for build, test, and pull-request
expectations, and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). Security reports
go through [SECURITY.md](SECURITY.md), not a public issue.
## Releasing
Version lives in `Directory.Build.props`. A tag `vX.Y.Z` (semver, optional
prerelease suffix) runs `.github/workflows/release.yml`: macOS + Linux test
gates, Native-AOT binaries for `osx-arm64` / `osx-x64` / `linux-x64` /
`linux-arm64`, both nupkgs, a GitHub Release, nuget.org, and GitHub Packages.
```sh
# bump in Directory.Build.props, commit, then:
git tag v0.3.1
git push origin v0.3.1
```
One-time nuget.org setup (Trusted Publishing — no long-lived API key):
1. On nuget.org → Trusted Publishing, add a policy for this repo, workflow
file `release.yml`, environment `release`.
2. Store your nuget.org **username** (profile name, not email) as the
`NUGET_USER` Actions secret.
3. Optional fallback: `NUGET_API_KEY` if OIDC is not available yet.
`workflow_dispatch` stamps a version and uploads artifacts; turn on
`publish` only when you intend to cut the release.
## Usage
```
| ttfx [terminal options] [effect options]
ttfx --help
ttfx --help
ttfx --random-effect # --include-effects / --exclude-effects to filter
ttfx --print-completion bash|zsh
```
Terminal options go before the effect name; effect options after it. Names and defaults
match TerminalTextEffects.
## Reference pins
See [REFERENCE.md](REFERENCE.md) for the pinned reference commits.