Repository navigation
v4.0.0 - Game features removed leaving only sprites, no picture shown again by default (`--fill-held off`) save meant holds, `inspect-holds` compares deliveries with hold lists, and `video-chain` plays keyed clip pieces as one timed move
Upgrading from 3.15.0: sprite-gen sound, sprite-gen game-pack and sprite-gen ui-kit are removed, with gen --layout-guide ui-kit and background-tile --axis xy. These game-making features were added for building games. Making games is outside the scope of this engine, which makes sprites; features that belong to sprites stay. Files an earlier version wrote (sound folders, game packs, UI kit folders) stay on disk; nothing reads them.
Upgrading from 3.15.0: no picture is shown again by default. Output changes where a picture was shown again: a loop whose last cells or wrap step showed one picture, a loop whose repairs left a run, an aligned set whose loops or alignment showed one, a stalled video-stop, a scene layer cut from a clip shown faster than its cells, and the cell-boundary frames of 24, 60 and 15 fps strips in a scene. Every output that showed no picture again is unchanged (the six cuts pinned before, a stop with no held cell, a set aligned with no held cell, pinned on commit 4f5ccd5, and scenes at 25, 20, 30, 12 and 48 fps), and so is every output cut with --fill-held off (except the scene boundary-frame fix, which --fill-held off does not undo). Metadata grows: the strip fps and holds; the loop report cut_args, held_fill.again, held_fill.wrap_held, held_fill.before.wrap and keep on kept runs; cycle_align.cut_again; the Grok report held; bake.json held; the scene report's inspection.repeats and fill_held.
Removed
| Removed | Use |
|---|---|
sprite-gen sound (ElevenLabs sound effects and instrumental music with Opus and AAC exports, a SQLite request cache and a browser loop helper), the package sprite_gen.audio (sound, provider, cache, processing, manifest, playback and the browser helper sprite-gen-audio-v2.mjs, which the wheel no longer carries), docs/sound.md |
No replacement. |
The ElevenLabs provider: sprite_gen.gen.address.ELEVENLABS and its entry in address.PROVIDERS, SPRITE_GEN_ELEVENLABS_BASE_URL, sprite_gen.gen.retry.ELEVENLABS_BUSY and the ElevenLabs rows of retry.UNTAKEN and retry.ACT_FIRST; ELEVENLABS_API_KEY is no longer read |
No replacement. sprite-gen capabilities no longer lists the sound verb or the elevenlabs provider; its providers are openai, xai and anthropic. |
sprite-gen game-pack (game atlases with playback policies, sounds, version 2 images, tiles and UI nine-slice entries), sprite_gen.compose.game_pack, docs/game-pack.md |
compose-atlas and export-aseprite (Phaser and Flame) for engine exports. |
sprite_gen.spec.assets.load_asset_states, which only game-pack called |
load_asset and PLAYBACK_FIELDS stay. |
sprite-gen ui-kit (splitting a game UI kit sheet into nine-slice elements), sprite_gen.frames.ui_kit, docs/ui-kit.md |
No replacement. Its "Alpha cleanup" section moved to docs/sheet-slicing.md ("Alpha cleanup" under "Transparent sheets"); slice-sheet --alpha-floor and --alpha-edge are unchanged. |
gen --layout-guide ui-kit, gen.ui_kit_guide_size and the ui_kit_guide argument of gen.still_prompt |
No replacement. The bare --layout-guide and --layout-guide figure are unchanged. |
background-tile --axis xy (one tile that repeats both ways, made for game ground tiles), sprite_gen.background.tile.inspect_tile_xy and the cyclic seam it used |
No replacement. --axis x and --axis y are unchanged, and so are their reports. |
- Stays the same: non-looping playback (the
video-looponce cut, theloop,hold_lastandend_framemetadata, the play modes), effects (pixel-draw --kind effect, brightness alpha, lifetime cuts),video-stop,slice-sheet --scale sheetand--fit contain, the alpha haze cleanup, the refusal of a painted transparency checkerboard,logo,compose-atlasandexport-aseprite. - Command line:
sprite-gen sound,sprite-gen game-packandsprite-gen ui-kitend as an unknown command with argparse exit code 2;gen --layout-guide ui-kitandbackground-tile --axis xyend as an invalid choice with argparse exit code 2. - Dependencies: the removed features declared no Python dependency.
ffmpeg,ffprobeand the AAC encoder stay required by the video features that use them (face-bake, the video source reading,sprite-gen video); only the sound checks for them were removed. - Old changelog links: the links in the v2.45.0 section that pointed at
docs/game-pack.mdanddocs/ui-kit.mdnow point at those files in thev2.45.0tag on GitHub; their wording is unchanged. - Tests: Removed
tests/audio/(test_sound.py,render_playback.mjs,prepare_playback_fixture.py),tests/compose/test_game_pack.py,tests/compose/test_game_pack_audio.py,tests/compose/test_game_pack_v2.py,tests/frames/test_ui_kit.pyandtests/background/test_tile_xy.py; the game pack test oftests/video/test_stop.pyand oftests/frames/test_slice_sheet_scale.py, theload_asset_statestest oftests/spec/test_assets.py, the three UI kit guide tests oftests/gen/test_layout_guide.py, and the ElevenLabs cases oftests/gen/test_provider_address.pyandtests/workflow/test_capabilities.py. Changed:tests/gen/test_painted_checkerboard.pyrefuses the checkerboard throughgenwith the figure layout guide;tests/workflow/test_capabilities.pyreads a flag's--no-alias offprepareand a required exclusive group offvideo-sway;tests/packaging/test_curation_asset_packaging.pychecks that the wheel carries the face playersprite_gen/video/sprite-gen-face-v1.mjs.
No picture shown again by default
The clip is a walk 24 frames long whose loop comes back to its first picture and stays there: when cut to 26 cells, its last two cells (24 and 25) show the same picture as cell 0, so three cells in a row across the loop's wrap show one picture and the foot stops there on every loop. Both panels cut the same frames with rife-v4.25 and default settings. The left panel is the cut made by 3.15.0, labelled "3.15.0 poses 25/26", which kept those two last cells as filmed because a run that ended on a loop's last cell was not filled and the step across the wrap was not read, while the right panel is the cut made by 4.0.0, labelled "4.0.0 poses 26/26", where 4.0.0 reads the loop as a ring and makes cells 24 and 25 again with RIFE between cell 23 and cell 0 (cell 0 unchanged), each cell RIFE made is outlined in blue on the right, and the foot keeps moving through the wrap.
- Changed:
video-loopreads a loop as a ring, so a run that ends on the loop's last cell and a cut one cell too long are made again (held_fill.wrap_same,held_fill.before.wrap,held_fill.wrap_held). Before, a run that ended on a loop's last cell was kept as filmed and the last cell and the first were never read as a run. Now the run that ends on the last cell is made again between its first cell and the loop's first cell (its next pose is the first cell, across the wrap); the first cell is never changed. When the last cell (or last cells) shows the picture of the first cell, the wrap steps from a picture to the same picture: those last cells are made again between the cell before them and the first cell, and the first cell stays, read on the cut's frames as filmed (held_fill.wrap_same). A run joined across the wrap (cells at the end plus cells at the start, all one picture) longer thanHOLD_MAX3 cells in total is a hold the motion means: both parts are kept as filmed. A frame made at the wrap is judged as every frame the fill makes; refused, the cells are kept as filmed and awarning:line names them and the faults.held_fill.before.wraprecords the run across the wrap (start,length,joined,filled,made),held_fill.wrap_heldsays the cut as filmed showed one picture across its wrap, and a loop's last cell that shows its first cell's picture is not counted as a pose. - New: the cut is read again after its repairs (
held_fill.again). The fill runs where it ran before: after the anchor andsource_projection, before the ghost screen, the wrap blend, the part pop and the jump repair. Once the repairs have run (the jump repair, the ghost RIFE frames, the eased retime, the overlapped wrap, the wrap blend, the part pop, the seam bridge), the cut is read again for held cells, and a run a repair left (one the first fill did not see) is made again the same way, judged the same way, with the same keeps. It is recorded asheld_fill.againin the loop report and the strip metadata (before,made,frames,cells,poses,held_runs); itswarning:lines start withread again after the repair:. A filmed ghost the repair gave way to the frame beside it (ghost_given_way) is not tried again; its own warning already names it. The repairs themselves are unchanged: they keep the cut's cell count and beat and are not a fill.--fill-held offturns the read-again off too. Measured on the tests' synthetic walker without a staff: its repairs left four runs and one wrap step showing a picture again (42/48 poses/cells); read again, it is 48/48. - New: a run kept as the motion means it carries
keep,held_fill.KEEPSnames the kinds, and the strip metadata gainsholdsandfps(held_fill.intended,held_fill.strip_holds). The rules for a hold the motion means are unchanged: a run longer thanHOLD_MAX3 cells (motion-hold); every run of a cut drawn on twos or threes (drawn); the opening pose (opening) and the closing pose (closing) of a cut played once (one-shot, once, once-return);hold_last; atlas clones. Each run kept for one of these reasons carrieskeep(its kind) inheld_fill.before.held_runs, andheld_fill.KEEPSnames the kinds:motion-hold,drawn,opening,closing. The strip metadata (<name>.strip.json) gainsholds: a list of{from, to, kind, reason}, one per kept run, as cells of the strip (fromandtoboth included), the same list form a renderer's hold list uses;inspect-holdsandscene-renderread it as holds meant.held_fill.intended(record)gives the runs,held_fill.strip_holdsmaps them onto the strip's cells. The strip metadata also gainsfps, the strip's exact rate (cells over the cycle's seconds, rounded to 6 places), next todelay_ms, which stays rounded to hundredths. - New: the loop report records
cut_args, andloop.cut_againcuts the same frames again.cut_argsis every argument the cut was asked with.loop.cut_again(report, out_dir, report_path=..., **changes)cuts the same frames again with those arguments and the changes given (for examplefill_held="off"), with no provider call. It refuses a report from beforecut_argswas recorded, and frames that moved. - Changed:
video-cycle-align(andvideo-set --align-cycles auto) aligns as filmed and fills at the end (cycle_align.cut_again,cycle.source/,--fill-held); newvideo-cycle-align --out-fps. The alignment makes its frames between from frames as filmed, never from frames the fill made. A loop never aligned whose held cellsvideo-loopmade again (held_fill.made, orheld_fill.again.made) is cut again from its frames with--fill-held off, using its report'scut_args(no provider call); that cut is kept ascycle.source/(its frames, strip and strip record) and aligned, andcycle_align.cut_againrecordsreport,fill_held(off),cells,made_before. If it cannot be cut again (no report beside it, a report from before cuts recorded their arguments, frames moved, the cut again took another window of frames), the alignment stops, names the loop and why, and says: cut it again withvideo-loop --fill-held offand align that. Then each loop as aligned is read for held cells and filled at the end (--fill-held auto, the default), asvideo-loopfills a loop: its first cell kept, a loop drawn on twos or threes and a hold longer than three cells kept, each frame made judged with theheldfault among the others; this also catches a frame the alignment's own resampling took twice (--between nearest, or a frame made refused and the nearer source frame taken). The strip record's and the report row'sheld_fillsay what was done;holdslists what was kept.--fill-held offdelivers the aligned loops as they come out of the alignment.--out-fps Fgives every loop of the set at F, above the loops' own rate, once aligned: each cell at its own time, made between the cells beside it, never a cell shown again, every loop the same number of cells; the strip metadata saysout_fpsandsource_fps; a rate not above the loops' own is refused; a loop already aligned at a higher rate is aligned again from itscycle.source/at its source rate. A set whose loops show no held cell is aligned as before, pinned by a picture digest measured on the engine before this change (commit 4f5ccd5). - Changed:
video-setcuts the walks and runs it aligns with--fill-held offand lets the alignment fill them; an item no alignment took is cut again filled (loop.cut_again.whynot aligned);video-set --out-fpswith alignment is no longer refused. The walks and runs a set aligns (filmed in two or more directions,--align-cycles auto) are cut with--fill-held off; the alignment fills them at the end. A standalonevideo-loopand every other item still fill as before. An item cut to be aligned that no alignment took (for example the alignment was skipped) is cut again with the fill on (loop.cut_again), so what the set delivers is filled. The aligned walks and runs are given--out-fpsonce aligned (video-cycle-align --out-fps), the rest byvideo-loop --out-fps. Each item'sloop.posesandloop.held_fillare those of the loop as delivered (after the alignment where it ran). - Changed: an effect's lifetime is named, not filled. A
lifetimecut is read for held cells and each run is named in awarning:line (cells A-B of the effect's lifetime show one picture, played as filmed, an effect's lifetime is not filled). No frame is made. Measured on four synthetic effects (a slash, sparks, a burst, a flame; glowing on black, keyed by brightness alpha, 24 fps), three stalls each, filled with rife-v4.25: 12 frames were made and the judge refused none, all on the first try; two were broken (the sparks smeared into one grey mass, the flame double-exposed) and a third left a flash that should have faded as a grey disc. The ghost screen read none of the four (drawn part-covered), so the judge cannot tell a bad frame of an effect. Filling would deliver those frames, so the lifetime stays a warning. - Changed:
video-stopfills held cells by default (--fill-held auto); its sidecar recordsheld_fillandholds. Before, a stop was read for held cells and recordedheld_fillwithmodeoff; it never made a frame. Now each held run's cells after its first are made again with RIFE between the run's first cell and the next pose, asvideo-loopmakes a cut played once. The opening pose, the closing still, a run longer than three cells and a clip drawn on twos or threes are kept.--fill-held offplays every frame as filmed, as before, with a warning line for each held run. A frame between refused keeps its run as filmed with a warning; with no RIFE installed every held cell is played as filmed with a warning naming the install command andheld_fill.unmade. A RIFE that is found but fails stops the stop (reportstatus: failed) with: fix RIFE, or pass--fill-held off. A stop with no held cell is the same pictures as before. - New:
video,video-extend,video-edit,video-resume(Grok clips) andface-bakeread for a frame shown again and warn; the report gainsheld,bake.jsongainsheld. The published mp4 is read for a frame shown again (frames decoded by ffmpeg, theheld_fill.same_stepsrule; an opaque clip's background, the most common colour of a 4 px border when it covers half the border, is left out). A run that is not an opening, a closing or a run longer than three frames printsvideo: warning: clip.mp4 shows one picture on frames A-B .... Nothing is filled: the clip is kept as the provider made it;video-loopmakes such cells again when the clip is cut. Without ffmpeg the report saysread: falseand a warning line, and the clip is published as before.face-bakereads the baked body cells; a run within a clip, across the loop's wrap or where a clip comes back to the loop printsface-bake: warning: frames A-B show one body picture .... Runs the clip means (longer than three frames, the opening or closing of a clip played once, cells a body row of the pack lists asholds) are recorded without a warning. No frame is made and the verdict does not change. - Fixed:
scene-renderandscene-inspectread a strip's cells atdelay_ms, rounded to hundredths, so a scene frame on a cell boundary showed the cell before it (spec.assets.frame_seconds,SNAP_MS).delay_msis 41.67 ms for 24 fps. Where the rounded delay is longer than the exact one (24, 60 and 15 fps strips), a 24 fps strip in a 24 fps scene showed its first cell twice, and in a 20 fps scene frames 5, 10 and 15 showed cells 5, 11 and 17 where 0.25 s x 24 = 6, 12 and 18 are right. The cells are now read at the strip'sfpswhere its sidecar gives one, and a strip from before (nofps) whosedelay_msis within 0.01 ms of a whole rate is read at that whole rate. Those boundary frames change;--fill-held offdoes not undo this. Strips at 25, 20, 30, 12 and 48 fps render byte for byte as before (pinned). - New:
scene-render --fill-held autogives a layer cut from a filmed clip at the scene's times; the report gainsinspection.repeatsandfill_held; newscene-render --between-cache DIR. By default, a layer whose asset is a cut of a filmed clip (avideo-looporvideo-stopstrip, its sidecar namessource_cut) and would show a cell again at the scene's frames (a 24 fps cut in a 60 fps scene, aplayback_rateunder 1) is given at the scene's times: each frame the cell its time falls on, or RIFE's frame between the two cells either side at its own time, judged asvideo-loopjudges a frame made for a held cell. Held on purpose stays held: before the layer's start, a one-shot's end andhold_last, a run its strip lists inholds, and every layer that is not a video cut (a still, an atlas row, a pixel drawing), which holds its cells as drawn. A layer that shows no cell again keeps its cells as its timing picks them (at the strip's exact rate) and makes no frame; every strip's cells are now read at its exact rate, so a frame on a cell boundary of a 24, 60 or 15 fps strip can change (a 24 fps strip in a 20 fps scene showed frames 5, 10 and 15 as cells 5, 11 and 17 before, and now as cells 6, 12 and 18); strips at 25, 20, 30, 12 and 48 fps render byte for byte as before (pinned in tests). A frame between refused shows the nearer cell again and is named; with no RIFE installed the cells are given as cut and a warning names the frames and the install command; a RIFE that fails stops the render, naming--fill-held off.--fill-held offgives every layer's cells as its timing picks them.inspection.repeatsis each layer's table of scene frames that show the cell the frame before shows: each run, the cell, whether it is held on purpose and why;fill_heldis in the report.--between-cache DIRkeeps the frames made between cells across renders, under the bytes of the two cells, the time and the RIFE that made them, and takes them from there on the next render; the report'sbetween_cachecounts hits and misses. Nothing is kept without it. - New:
sprite-gen inspect-holds(qa) reads a delivery for frames that show a figure's picture again and compares them with hold lists (--holds,--gate,--key,--box,--log,--log-figure). It reads frames with alpha, a keyed clip with--key, a figure's box with--box, or a renderer's log of cells with--logand--log-figure, with the same rule asheld_fill.same_stepson the figure's pixels. It compares them with hold lists (--holds, a strip'sholds, the list form a renderer uses) and reports listed and unlisted holds;--gateexits 1 where any hold is on no list. A render that draws a figure at a new scale or sub-pixel place on every frame is read by its--log. Its full documentation isdocs/asset-tools.md"Frames shown again". - Fixed:
inspect-holdsreads a frame's largest part as a figure however few pixels it has, reads nothing in a--boxthat holds no pixel, exits 2 where no frame was read against the frame before, and reportsstepsandinspected; the Grok clip reading ofvideo,video-extend,video-editandvideo-resumedoes not read a clip whose every frame is the canvas alone. A part is a speck, and is not read, where it holds under 2 % of the pixels of the frame's largest part, or under 64 px; the 64 px floor applied to every part, the frame's largest included, so a frame whose only figure had under 64 px read no figure at all: a 7 x 9 block (63 px) shown on three identical frames of a 24 x 24 clear canvas read an empty table and--gateexited 0 with nothing on stderr, although its frames 1 and 2 show the picture of the frame before, where an 8 x 8 block (64 px) read 2 unlisted holds and--gateexited 1. Now the 64 px floor is never more than half the frame's largest part, so the largest part is always a figure and a part half its size or more is a figure too (two small figures in one frame are both read); both blocks read 2 unlisted holds (frames 1-2) and--gateexits 1, and where a frame's largest part has 128 px or more the rule is exactly what it was. Dots of 1, 2 and 3 px far from the figure, the same on every frame, are still not read. With--box, two frames whose box held no pixel with alpha over 8 (clear there, or past the frame's edge) read as one picture shown again, so a box over clear frames, or past the frame's edge, read a hold on every frame after the first; now such a frame shows no figure there and two such frames are no picture shown again. The report gains each figure'ssteps(its frames read against the frame before; on a loop read across its wrap, the wrap is one step) andinspected(the number of figures read on one step or more); the other fields are unchanged, exit 0, 1 and 2 keep their meanings, and a reading that reads no frame against the frame before is now one of the errors (exit 2). A reading that reads no frame against the frame before (no frame: an empty folder,--framesthat starts past the last frame of a folder, a video, a strip or a log, a log of no line; no figure on any frame: clear frames, frames whose every pixel has alpha 8 or under, a key that leaves nothing, a box that holds nothing, a log whose--log-figurekeys name no cell on any line; no figure shown on two frames in a row, for example one frame) is an error: it exits 2 with or without--gate, its report is still printed on stdout and written to--reportwithinspected0, and one line on stderr says why. Before, most of these passed--gate(exit 0, an empty table, nothing on stderr); an empty folder and a log of no line exited 2 but printed no report; a strip given--framespast its last cell was refused with the wrong reason (as opaque frames); a box over nothing failed the gate on frames that showed nothing. In Python,inspect(...)andread(...)insprite_gen.qa.holdsraiseNothingRead, aValueErrorwhosereportattribute is the report, withinspected0. The Grok clip reading ofvideo,video-extend,video-editandvideo-resumeread a clip whose every frame is the canvas alone (no pixel left once the canvas colour is taken out) as one run of all its frames, recorded as a hold the motion means, and said nothing; now such a clip is not read, its report'sheldisreadfalse withframes,backgroundand awhy, the warning line says its frames were not read for a picture shown again, and the clip is still published. - New:
sprite-gen video-chainplays pieces of keyed clips as one move at the caller's time (--piece,--bridge,--out-fps,--places,--holds,--refuse,--fill-held,--gate). It joins keyed clips of matching canvas, key and rate, making frames between join points or retimed places with RIFE. A run of frames showing one picture, unless a list of holds meant names it, is made again between the poses beside it, and any unlisted frame showing the picture before it fails the run. Pieces that disagree in size, key, or rate, opaque frames, overlapping paths, or places off the chain refuse the run before anything is touched. By default every fault the judge finds refuses a frame;--refusenames those that do; which to leave out is the caller's to decide by looking at the material;held, the bar a frame made off its time must stand under and the final reading refuse whatever it says; a fault left out stays on the frame's record and awarning:line; the judge and its bars do not change.held_fillgainsFAULTSand arefuseargument onat_timesandfill,loop.fill_cutgainsrefuse,framesgainsKEYS_READandkey_kind, aheld_filltry gainsjudgedand, withrefuse,kept_faults, and thevideo-framesreport gainskey_read. - Python callers.
held_fill: newwrap_same,KEEPS,intended,again,strip_holds;premultiplied(was_premultiplied);fill(..., wrap=, drawn_on=);reading(..., wrap=).loop: newcut_again;fill_cut,fill_lines,out_fps_lines(were_fill_held,_fill_lines,_out_fps_lines).align: newfilled,unfilled_source,CUT_AGAIN_DIR;align_set(..., fill_held=, out_fps=).batch: newheld_summary,loop_summary,cut_again_unaligned;align_gaits(..., fill_held=, out_fps=).spec.assets: newframe_seconds,SNAP_MS,FrameSequence.index_at.scene.render.Renderer(..., fill_held=, interpolate=, between_cache=). New modulesprite_gen.qa.holds.stop.make_stop(..., fill_held=, interpolate=). - Tests:
tests/video/test_no_repeat_default.py,tests/scene/test_scene_fill_held.py,tests/qa/test_inspect_holds.py,tests/gen/test_video_held.py,tests/video/test_face_bake_held.py(new);tests/video/test_held_fill.py,tests/video/test_stop.py,tests/video/test_face_layer.py,tests/video/test_root_motion.py,tests/video/test_loop_prop_seam_held.py,tests/video/test_follow_read_on.py,tests/video/test_motion_anchor.py(changed).test_no_repeat_default.pycovers a loop's last cells, a cut one cell too long, the read again after the repairs, the strip'sholds,cut_argsandcut_again, the lifetime warning, the alignment's end fill, its cut again with the fill off and its refusal,--out-fpsonce aligned, a set with no held cell aligned as before, andvideo-setcutting aligned items as filmed and cutting again the ones no alignment took.test_held_fill.py's wrap tests: a run at a loop's wrap is made, a last cell showing the first again is made and the first kept, a hold across the wrap longer than three cells kept whole, a cut played once reads no wrap.test_stop.py: the stop fills by default; with no held cell and with--fill-held offit is the pictures pinned before.test_face_layer.py,test_root_motion.pyandtest_loop_prop_seam_held.py: the strip metadata growsfpsandholds, the old pins otherwise unchanged.test_motion_anchor.py: its anchored cells 5 to 7 differ by one pixel, so the test cuts with--fill-held offto check the anchor's own cells. No-repeat outputs keep their pictures: the six cuts pinned before, a stop with no held cell, a set aligned with no held cell (pinned on commit 4f5ccd5) and scenes at 25, 20, 30, 12 and 48 fps. Two alignment tests now reach the RIFE binary through the alignment's fill at the end:test_cycle_align_aligns_a_two_step_loop_as_the_one_cycle_it_isintest_loop_prop_seam_held.pyandtest_the_first_alignment_records_the_cut_it_was_made_from_and_later_ones_keep_itintest_follow_read_on.py. Like the other tests the default fill sends to the binary often (the rule indocs/release.md), each now has a timeout of its own,@pytest.mark.timeout(900), and pins the binary's calls by model withas_pinned(exactly forrife-v4.6, between a floor and a top forrife-v4.25), and the second test is now markedslow, so the fast bundle (a plainpytest) leaves it out.
Install or update:
pip install --upgrade "sprite-gen @ git+https://github.com/aldegad/sprite-gen.git@v4.0.0"The wheel, the source archive and SHA256SUMS are attached. Full notes in CHANGELOG.md.