nfty 1.7.0 — spritesheets, a debug number, and a space that outgrew a long
Spritesheets, a debug number on every sprite, and a DNA space that stopped fitting in a long.
2,286 tests, 0 failures.
Two tags in one release: v1.6.0 is the large-integer audit, v1.7.0 the features on top of it.
Export as spritesheet
Every asset stitched into one image, left to right then top to bottom in the Set's own
numbering — so cell n is asset n+1, and an engine indexing frames agrees with a reader
counting rows without anything being recorded. You give it columns and rows; both boxes open
already filled with the squarest grid that holds the collection, and the pixel size sits beside
them so the two numbers explain themselves.
A short last row leaves transparent cells, because that is what a spritesheet looks like and
closing the gap would renumber every frame after it. A missing asset leaves a hole rather than
failing the sheet — a gap among drawn cells is unmistakable, which is the point.
There is a real ceiling, and it refuses with the numbers rather than dying: a sheet is one
contiguous raster, so 500 assets at 1000px in a 23×23 grid is 529 megapixels and about 2 GB of
RGBA. A refusal an author can act on beats an OutOfMemoryException half a minute in.
A set number stamped on every sprite
Loose sprites lose their filenames the moment they are dragged into an engine or a chat window,
and a folder of five hundred near-identical characters is unsortable. Tick the box, pick a corner,
and every exported asset carries its set number — so a loose sprite cross-references
nfty/NNNN.json with nothing else written down.
The digits are a 3×5 bitmap font, not a typeface. A real font would mean a new dependency
beside an ImageSharp pinned on purpose, and would put grey anti-aliased ink on art that has no
grey anywhere in it. White over a one-pixel black halo, so it reads on any ground; it declines to
draw rather than clip, because a "12" cropped to "1" is a wrong answer where a missing stamp is
merely a missing one.
It never touches your Set. The export renders a stamped copy and ships that. Stamping runs
before stitching, so the sheet carries the same numbers its sprites do.
Progress that means something
Opening a Set ran on the UI thread. That unpacks a .set into a temporary directory and reads a
file per asset, so a real collection froze the window for seconds with nothing on screen saying
why. The async path had always existed — nothing was calling it.
There is one small card for every job that has no screen of its own, and the export's bar is a
real fraction now instead of spinning whatever it was doing: a barber pole for minutes is
indistinguishable from a hang. Indeterminate survives only where the work genuinely cannot count.
The export card is three pages — CONTENTS, IMAGES, SEAL — with the manifest and the footer
outside the tabs, because what is going and what is stopping it are the two things you should
never have to go looking for. It also stopped stretching, which was costing about 230px of empty
panel on every page.
The DNA space outgrew a long
Six layers of ten variants at a 10°/10% quantize is about 2.2e21 distinct assets, against a
long's 9.22e18 — so that book's headline figure read more than 9,223,372,036,854,775,807: a
constant, printed where an author is trying to read a count. The totals are BigInteger now and
the ceiling is gone rather than raised again, along with the saturating arithmetic that
existed to keep a number inside a type too small to hold it.
The figure prints in full whenever it fits, measured against the cell at the window you
actually have — so a maximised window shows what the smallest one abbreviates — and the exact
digits are always on the tooltip. Past the named magnitudes it becomes an exponent, because there
is no name above "quintillion" that a reader knows.
Seven smaller things went with it, each probed by reverting the fix:
- A
NaNcolorization range validated clean. Every check inCheckRangeis false forNaN,
so the inverted-range test and both axis tests all waved it through. Describeclaimed "at most 1 in a trillion" on a bounded figure, which is a tighter bound
than the arithmetic supports. That branch had only ever been tested unbounded.- A target over an
AtLeastspace warned that the supply would not fit. That total is a
floor, so it proves nothing — and it is exactly the count a big book produces. - A Set past ten thousand assets loaded in the wrong order: the reader sorted by filename, and
the stem pads to four, so10000.jsonsorts before9999.json. - A non-positive enumeration budget was laundered into an answer instead of refused.
- One unchecked multiply in the odds walk, safe only at the default budget.
- Rare traits all printed
0%— the same string a trait nothing carries gets — and sorted
arbitrarily, because a data source was rounding for display. Two of the three surfaces that
print a share were also culture-sensitive, under comments promising otherwise.
Downloads
| Size | .NET 10 needed | |
|---|---|---|
| Portable | 84 MB | no — unzip anywhere and run. Start here if you are unsure. |
| Single file | 76 MB | no — one .exe. Unpacks itself on first run, so that launch is slower. |
| Single file, .NET | 14 MB | yes — one .exe, runtime left out. |
| Framework-dependent | 14 MB | yes — the same, as a folder. |
The two .NET builds need the .NET 10 desktop
runtime; the other two carry everything.