Changelog
All notable changes to this project are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
[0.3.1] - 2026-09-19
Added
SFML::WindowBase,SFML::RenderWindowand theSFML::RenderTargetmodule, mirroring SFML 3's own hierarchy.WindowBasewrapssfWindowBase-- an OS window and event queue with no OpenGL context -- and is constructible, withWindowandRenderWindowderived from it.Windowkeeps its existingsfRenderWindowbehaviour;RenderWindowis the same renderable window under the name that also includesRenderTarget.RenderTextureincludesRenderTargettoo, so a drawable's#drawnow accepts aRenderWindowor aRenderTexturedirectly (the legacyTargetstill works). The shared window surface is generated once, inext/window/window_base.inc.- RBS signatures are now checked against the code.
sig/**/*.rbsdeclaresSFML::VERSION, and aSteepfileplus thesteepdevelopment gem addrake steepto type-checklib/against the signatures.check.yamlruns bothrake rbsandrake steep, onmainand onrelease/**PRs, which previously ran no workflow at all.
Changed
Mouse.position,Mouse.set_positionandTouch.positiondispatch to the*RenderWindowor*WindowBaseCSFML entry point depending on whether the argument is a Window/RenderWindow or a WindowBase, instead of passing ansfRenderWindow*where ansfWindowBase*is expected.- RBS constructors use
def initialize. Everydef self.newbecamedef initialize: (…) -> void, the form RubyMine links toFoo.newand Steep checks, and which matches the C extension, where every constructor is aninitialize.
[0.3.0] - 2026-09-19
Added
- Four more experimental binary-gem targets:
aarch64-linux-musl,arm-linux-gnu(ARMv7 hard-float),arm-linux-musl, andaarch64-mingw-ucrt(64-bit Windows on ARM). All four ride the same rake-compiler-dock cross-compilation this project already uses for its other targets; none has been run on its target hardware yet, so all areexperimentalinpublish.yamllikeaarch64-linux-gnu,x86_64-darwinandarm64-darwinalready were.script/provision.shgained matching cases for the two new glibc/musl Linux targets. rakelib/package.rake'sEXPECTED_ABISgained an entry foraarch64-mingw-ucrt: its rake-compiler-dock image carries no cross Ruby older than 3.4, so that gem has no floor below it — the same kind of upstream gapx86-mingw32already has at the top end.aarch64-linux-gnuis now additionally run — not just cross-compiled — by a newtest-arm64.yamlworkflow, on GitHub's hostedubuntu-24.04-armrunner. It staysexperimentalinpublish.yamlfor now; this is the evidence that will eventually justify dropping that flag.- ARMv6 (32-bit), RISC-V (rv64gc), PowerPC (ppc64le), and any BSD are deliberately not covered by a binary gem: rake-compiler-dock ships no cross-compilation image for any of them, and building custom cross-toolchain infrastructure for them is a much larger, separate undertaking. The source-gem fallback remains the only path there, unchanged.
Fixed
- A GC race in the audio bindings aborted the process on arm64.
Sound,MusicandSoundStreamreleased the GVL inside theirdfree, but their data types were markedRUBY_TYPED_FREE_IMMEDIATELY, so the free could run during garbage collection: another thread (Ruby'sTimeoutthread, in the test suite) could then allocate while GC was mid-cycle and Ruby aborted with "object allocation during garbage collection phase". Dropping the flag defers the free to a safe point; releasing the GVL is still what keepssf*_destroyfrom deadlocking against the audio thread.
Documentation
- Every public class, module, method and constant is documented, with the
result rendered on rubydoc.info. The
comments live beside each binding in
ext/**/*.c;rake yardbuilds them intodoc/. Previously the comments carriedcall-seqand@returntags but little prose, so rubydoc.info showed a signature with a blank description; every such method now has a sentence of its own. - RBS type signatures for the same surface ship in
sig/**/*.rbs, for IDE completion (Solargraph, RubyMine) and static checking (Sorbet, Steep);rake rbsvalidates them. .yardoptsships in the gem, so rubydoc.info generates with the same title, README and extra files asrake yard.rake doc:undocfails when a method is left without a description, so the coverage cannot regress silently (yard statsusesblank?and passes for tag-only comments, which is how the blanks went unnoticed).
[0.2.1]
Added
SFML::Transformis now a class, wrappingsfTransformby value. It previously exposed onlycombineandinverseas module functions over plain 9-element Arrays, leaving 11 of CSFML's 13 transform functions unbound: you could read a matrix out of aSpritebut not build one, apply one to a point, or compose one. It now hasTransform.identity/IDENTITY,Transform.from_a,#to_a/#matrix,#gl_matrix,#==,#translate!,#rotate!,#scale!(the last two taking an optional centre), non-mutating#translate/#rotate/#scale,#transform_point,#transform_rect,#combine!,#*,#inverseand#copy.map_pixel_to_coordsandmap_coords_to_pixelon both render targets. The screen-to-world conversion behind every click-to-select interaction; without it a zoomed, rotated or viewport-shiftedViewcould not be hit-tested from Ruby at all.SFML::Windowgained the render-target methods onlyRenderTexturehad:clear_stencil,clear_color_and_stencil,viewport,scissorandsrgb?. Both classes also gainedpush_gl_states,pop_gl_states,reset_gl_states,draw_primitivesanddraw_vertex_buffer_range. The shared surface is generated once fromext/graphics/render_target.inc, the same wayext/audio/sound_source.incalready serves the three sound sources.SFML::View.from_rect,View#scissorandView#scissor=.SFML::Transformable#inverse_transformand#copy— every subclass (Sprite,Text,Circle,RectangleShape,ConvexShape,Shape) already had both.SFML::Texture.srgb,.srgb_from_stream,.srgb_from_image,Texture#resize_srgbandTexture#swap.SFML::Shader#set_vec4_array,#set_mat3_arrayand#set_mat4_array, completing the array uniforms alongside the existing float/vec2/vec3 forms.SFML::Circle#point_count=,SFML::VertexBuffer#swap,SFML::Window.from_handleandSFML::Window#create_vulkan_surface.
Fixed
- Non-ASCII text and window titles were mangled.
Text#string=,Window#title=, window creation andFtpDirectoryResponse#directorypassed UTF-8 bytes to CSFML'sconst char*entry points, which hand them tosf::String's narrow-character constructor and decode them with the C locale:"héllo"came back asU+0068 U+FFFFFFFF U+FFFFFFFF U+006C U+006C U+006F. All four now go through the UTF-32 entry points and round-trip exactly, including astral-plane characters. The converters live inext/core/unicode.c. Mouse.position,Mouse.position=andTouch.positionreinterpret-castsfRenderWindow*tosfWindowBase*, relying on the base being at offset 0 — true of CSFML 3.0.0's layout but not guaranteed by it. They now callsfMouse_getPositionRenderWindow,sfMouse_setPositionRenderWindowandsfTouch_getPositionRenderWindow, which CSFML ships for exactly this reason. Ruby-visible behaviour is unchanged.Shader#set_float_array,#set_vec2_arrayand#set_vec3_arrayleaked their conversion buffer if an element raised part-way through. All six array setters are now generated from one macro built onALLOCV_N, whose buffer Ruby frees while unwinding.VertexBuffer#updatehad the same leak; it now sharesvertices_from_rb, which validates every element before allocating anything.- Audio-thread callbacks re-entered the VM from a foreign native thread. The
SoundStream#on_get_data/#on_seekcallbacks and the effect processor run on SFML's audio thread, which Ruby never created; calling back into Ruby from it (rb_thread_call_with_gvl) is a fatal VM error. Each now hands off to one shared Ruby-owned worker (ext/core/foreign_thread.c).SoundSource#stop,Sound#buffer=and theSound/Music/SoundStreamdestructors release the GVL while they wait on the audio thread, and a processor that times out degrades to a full buffer of silence rather than stalling playback.
Changed
Windowis created throughsfRenderWindow_createUnicoderather thansfRenderWindow_create, so a title given at construction is subject to the same UTF-8 fix as#title=.RenderTexture#viewportand#scissornow take the view as an optional argument; omitting it means the target's current view, matching CSFML'sNULL.
[0.2.0]
Added
- Full Audio binding.
SFML::Listener,SFML::SoundSourceCone,SFML::SoundBuffer,SFML::Sound,SFML::Music,SFML::SoundStream,SFML::SoundRecorderandSFML::SoundBufferRecorder, plus theSFML::SoundStatusandSFML::SoundChannelenums. The shared sound-source surface (play/pause/stop/status, pitch, pan, volume, spatialization, position/direction/velocity, cone, doppler and attenuation factors, distance/gain bounds and playing offset) is generated once fromext/audio/sound_source.incfor all three source classes.SoundStreamandSoundRecorderare subclassable through#on_get_data/#on_seekand#on_process/#on_start/#on_stop; their callbacks re-enter Ruby under the GVL and treat an exception as "stop", since they run on SFML's audio thread. - Full Network binding.
SFML::IpAddress(string/bytes/integer constructors,NONE,ANY,LOCAL_HOST,BROADCAST, local/public lookup),SFML::Packet(raw data plus every typed reader/writer),SFML::SocketSelector,SFML::TcpSocket,SFML::TcpListener,SFML::UdpSocket,SFML::Http/HttpRequest/HttpResponse,SFML::Ftpwith its response, directory-response and listing-response classes, and theSocketStatus,HttpMethod,HttpStatus,FtpStatusandFtpTransferModeenums. SFML::SoundSource#effect_processor=accepts a Ruby proc. BecausesfEffectProcessorcarries nouserData(so one C callback cannot tell which source invoked it), the binding dispatches through a bounded pool of C thunks, one per active source; exceeding the pool raises.
Changed
- The vendored CSFML/SFML archives are now linked by full path instead of
-l. mkmf puts the host's-L/usr/lib64ahead of the ports prefix, which made-lfreetype(and the new codec libraries) resolve to the host's shared objects and silently add runtime dependencies. The built extension again has nolibsfml/libcsfml/libfreetype/libvorbis/libFLAC/liboggshared dependency.
Build
ext/ports.rbnow builds Ogg 1.3.5, Vorbis 1.3.7 and FLAC 1.4.3 as pinned, checksum-verified, static and position-independent ports, alongside FreeType. SFML 3's audio backend is miniaudio, so OpenAL is not needed.SFML_BUILD_AUDIO,SFML_BUILD_NETWORK,CSFML_BUILD_AUDIOandCSFML_BUILD_NETWORKare enabled;ws2_32was added to the Windows system libraries and the CoreAudio/CoreFoundation frameworks to the macOS ones.CMakeLists.txtfinds and links theAudio/Networkcomponents.- Archive extraction accepts
.tar.xzas well as.tar.gz, since the FLAC release ships only as xz.
Known issues
- Audio playback and capture need an audio device; the CI suite stays headless, so
SoundStream#on_get_dataandSoundRecorderare exercised by construction and mapping only, never by actually playing or capturing. HttpandFtpperform real network I/O and are not exercised in CI beyond construction and enum mapping.
[0.1.1]
Added
- Full Graphics binding.
RectangleShape,ConvexShape, a callback-based customShape,Sprite,Texture,Image,Font,Text,RenderTexture,Shader,Vertex,VertexArray,VertexBuffer,Glyph, and the value typesBlendMode,StencilMode.Circlegained the outline/texture/bounds surface it was missing. - Full Window/input binding.
Keyboardreal-time queries (pressed?,scancode_pressed?,localize,delocalize,description), and theMouse,Joystick,Touch,Sensor,Clipboard,Cursor,Context,ContextSettingsandVulkanmodules/classes.Eventnow exposes every payload (text, mouse move/raw/button/wheel, joystick move/button/connect, touch, sensor). - System completeness.
SFML::Time(arithmetic, comparison, unit conversions),SFML.sleep,SFML::Buffer, andSFML::InputStream(wraps any#read-able object soFont/Texture/Image/Shadercan load from streams). - Value classes.
Vector2,Vector3,Color,Rectare now real Ruby classes; every setter still accepts a plain array, and getters return the class (which behaves like an array viato_ary/Enumerable). RenderTextureis a draw target alongsideWindow: both route through the sharedTargetdispatcher, so every drawable renders to either.- Predefined
Colorconstants,BlendMode/StencilModepredefined values,ContextSettings, andVideoMode.desktop_mode/.fullscreen_modes. - Initial project scaffolding: C extension wrapping CSFML (SFML 2 bindings).
- Windows, video modes, events, keyboard input, transforms, drawables (circle, rectangle, sprite, image), textures, views, clocks, and render targets.
- Unit tests (minitest) for Clock, Transformable, Circle, RenderState, VideoMode — headless, deterministic.
- RuboCop configuration and CI integration (test, check, publish workflows).
- Publish GitHub Action to build and upload the gem on release.
ext/ports.rb: downloads, checksum-verifies, and builds SFML 3.0.2 and CSFML 3.0.0 from source into a static, position-independent prefix. Ships inside the gem, sogem install sfml3-rbbuilds its own dependencies with no CSFML pre-installed. Shared byrake ports(development) andext/extconf.rb(install time).ext/extconf.rbthree-way dependency resolution:--enable-system-libraries(orSFML_USE_SYSTEM_LIBRARIES) links a system CSFML 3; otherwise an existingports/<host>prefix is reused, or built automatically.- A compile-time
CSFML_VERSION_MAJOR < 3guard (ext/ext/sfml.h) and anextconf.rbheader check, so a CSFML 2.x install fails with one actionable message instead of a wall of compiler errors. CMakeLists.txtrewritten into a real, buildable configuration (CLion/IDE use only —rake compileremains the build of record): resolves the vendoredports/<host>prefix, linksfind_package(SFML 3 ...)and the CSFML static archives, and links through the C++ driver, since SFML is C++.
Changed
- Rebuilt the drawing path around
Target.Targetnow carries a{type, handle}union and dispatches each draw call to the window or render-texture variant;Window#drawand every drawable's#drawgo through it.RenderStateis initialized fromsfRenderStates_defaultinstead of field-by-field, fixing thestencilMode/coordinateTypefields that were left uninitialized under CSFML 3, and it now exposesblend_mode,stencil_mode,coordinate_type,textureandshader. - Naming aligned with SFML 3, typos fixed.
angle/angle=→rotation/rotation=;escalate→scale!(the mutating operation;scaleremains the getter);matrix→transform(matrixkept as an alias).View#position/position=were removed: the old setter wrote the viewport rather than the center — usecenter/viewport. Clock#elapsed_time/#restart!now returnSFML::Timerather than bareFloatseconds.Window.newaccepts(video_mode, title, style = :default, state = :windowed, settings = nil)instead of hardcodingsfDefaultStyle/sfWindowed/no settings.- The C extension is now organised by SFML subsystem. The 24 sources no longer sit flat in
ext/with their headers in a doubledext/ext/tree; each binding's.cand.hlive together underext/core/,ext/system/,ext/window/orext/graphics/, matching how SFML and CSFML split themselves, so a binding's home follows from the CSFML header it wraps. Includes became subsystem-relative (#include "graphics/circle.h") and header guards path-derived (SFML_RB_GRAPHICS_CIRCLE_H), retiring threeCANDY2D_EXT_*guards left over from a previous project name.ext/extconf.rbsets$srcsfrom a recursive glob and extends$VPATH, because mkmf globs only the top level; objects stay flat, which is why every.cbasename must remain unique.Rakefilegainedsource_pattern = '**/*.{c,h}'— recursive so the new tree is seen at all, and including headers because editing one previously triggered no rebuild.CMakeLists.txtglobs recursively to match. ext/ext/klass/transform.hdeleted: a stale duplicate of the livemodule/transform.hthat nothing included, and which would have collided with it in the new tree. The five unbound placeholders (Image, Polygon, Rectangle, Sprite, Texture) were kept, so the layout still shows where they will land.CrossBuild#buildnow clears every staging directory underpkg/, not just the current platform's.rake native:<platform> gemalso runs the shared source-gem task, so a stalepkg/<name>-<version>/fails the cross build with... are not filesabout files that plainly exist — which is exactly what the restructuring triggered.- The C extension moved from the untyped
Data_Wrap_Structfamily toTypedData. All wrapped types now carry anrb_data_type_t.Data_*has been deprecated since Ruby 2.3 and warns on every use under Ruby 4.0, which was the extension's only compile-time portability problem — the build is now warning-clean under-Wall -Wextrafrom Ruby 3.1 to 4.0.TypedData_Get_Structalso type-checks, whereData_Get_Structsilently reinterpreted whatever pointer it was handed. ext/extconf.rbnow always compiles with-Wall -Wextra, and with-Werror=deprecated-declarationswhenSFML_STRICT=1. CI sets it, so a regression to the deprecated API fails the build rather than adding noise. It is opt-in because an unfamiliar compiler atgem installtime must never fail on a warning.- Minimum Ruby raised from 2.5.0 to 3.1, matched by
TargetRubyVersionin.rubocop.ymland by the binary gems, which is the point: there is now one floor rather than a source/binary split.>= 2.5.0had never been exercised — every workflow ran only Ruby 3.3. 3.1 is the oldest cross-ruby thex64-mingw-ucrtrake-compiler-dock image carries, so it is what lets a singleRUBY_CC_VERSIONserve every platform. test.yamlnow runs the suite across 3.1, 3.2, 3.3, 3.4 and 4.0 — exactly the ABI set the binary gems ship — so every Ruby that can receive a precompiled gem is also proven to build from source, withSFML_STRICT=1.publish.yamlgained averify-sourcegate that installs the freshly built source gem on the floor and requiressfml, so a release cannot ship a source gem that fails to build. Every supported Ruby now has a binary gem, but most platforms (macOS, ARM, the BSDs) still do not, and that path stays load-bearing.Gemfilegained a:testgroup pinningminitestto~> 5.0; minitest 6 requires Ruby >= 3.2, still above the floor. The cross-compile containers now install withBUNDLE_WITHOUT="development test"..rubocop.ymlexcludespkg/,tmp/andports/. Those hold staged copies oflib/andext/, so every offence in a real source file was being counted twice or more.- Packaging now produces precompiled binary gems alongside the source gem.
rake gem:nativecross-compiles inside rake-compiler-dock;rake platformslists the targets. On a covered platformgem install sfml3-rbno longer needs a toolchain, CMake, or a source build at all. The source gem remains the fallback everywhere else, unchanged in behaviour. - Binary gems now cover i686 as well as x86_64, on both glibc and musl Linux and on 32- and 64-bit
Windows:
x86-linux-gnu,x86-linux-muslandx86-mingw32joinx86_64-linux-gnu,x86_64-linux-muslandx64-mingw-ucrt, withaarch64-linux-gnu,x86_64-darwinandarm64-darwinstill experimental. Two gaps are upstream, not ours: RubyInstaller publishes no 32-bit Ruby 4.0, sox86-mingw32stops at 3.4; and 64-bit Windows before Ruby 3.1 is a different platform (x64-mingw32), which is not built. rakelib/package.rakeasserts the ABIs of each gem it builds againstEXPECTED_ABIS. rake-compiler only warns when an image lacks a cross-ruby it was asked for, so without this a gem could quietly ship fewer ABIs than intended.ext/ports.rbtargets gained amultiarchkey. i686 compiles withi686-linux-gnu-gccbut its libraries live in/usr/lib/i386-linux-gnu, and feeding the compiler triple toCMAKE_LIBRARY_ARCHITECTUREwould have pointed CMake at a directory that does not exist.- The extension build moved from a hand-rolled
rake compile(ruby extconf.rb && makeinsideext/, then a manual copy) torake-compiler'sRake::ExtensionTask. Builds are now out of tree intmp/, soext/no longer accumulates.ofiles, aMakefileandmkmf.logbeside its sources. lib/sfml.rbpreferssfml/<major.minor>/sfml_ext— the per-ABI layout binary gems use — and falls back tosfml/sfml_extfor a source build.ext/ports.rbis now cross-aware: prefixes and build trees are keyed by target (ports/<target>) rather than by host, and a CMake toolchain file is generated per target.SFML_TARGETselects one; a native build is unaffected.- Project renamed to sfml3.rb; migrated from SFML 2 to SFML 3 (via CSFML 3) — no longer just planned. Distro CSFML packages are frequently still 2.x and are no longer supported.
- Native extension renamed from the bare
exttosfml/sfml_ext, matching thesfmlgem namespace and whatrake-compilercross-compilation expects. event_name.candkeyboard.cname tables are now indexed by named enum constant (C99 designated initializers) instead of raw ordinal position, so a future upstream reorder is a compile error rather than a silently wrong event or key name.Window#clearnow takes a single[r, g, b, a]color array, matching every other color-setting method in the API, instead of three positional numbers.- Removed the dead
install-packageextconf hook and its accompanyingext/linux.sh(empty) andext/msys2.shstubs, and the unusedrun_script/Arguments/library_nofoundhelpers inext/auxlib.rb. - Removed the vendored
include/ruby/*.hstubs and the emptyinclude/SFML/CSFML headersplaceholder; nothing referenced them onceCMakeLists.txtwas fixed to query the real Ruby headers. sfml3-rb.gemspeccarries more information: adocumentation_urialongside the existing source/bug-tracker/changelog links, a summary that actually names SFML 3 and the precompiled binaries (it is the linegem searchshows), andextra_rdoc_files/rdoc_options. It also shipsCHANGELOG.mdandTODO.md— the latter being the module-by-module record of what is bound, which is what tells someone whether the gem covers their case. Comments now record what a reader cannot infer: that rake-compiler rewritesplatform,required_ruby_versionandextensionsfor binary gems, and thatrequired_rubygems_versionis deliberately unset because pinning>= 3.3.22here would lock Ruby 3.1.0 (RubyGems 3.3.3) out of the source gem. Reformatted to 2-space indentation. Nohomepage_uri: it would duplicatesource_code_uri, and RubyGems warns that only the first of a duplicated link is shown.sfml.gemspec→sfml3-rb.gemspec: description now describes the self-building install rather than a still-planned migration; license changed to0BSD, matchingLICENSE.md.- Gem package renamed from
sfmltosfml3-rb(gem install sfml3-rb).require 'sfml'and theSFMLRuby module are unchanged — only the RubyGems package name moved.
Fixed
Circle#scalereturned the position, not the scale.RenderStatewas missing admarkslot; the wrapped struct now holds its texture/shader Ruby references so a GC cannot collect them while the states are in use.View#position/position=wrote the viewport under a misleading name (removed).raise_method_no_implementedcrashed onNULLand never reached its message branch.Window#joystick_threshold=treated the threshold as a boolean.Circle#drawraised unconditionally: its second argument check testedrb_targetagainstRenderStateinstead ofrb_state(ext/circle.c), and a validTargetis never aRenderState, so no call could get past it.Target#drawunwrapped itsstateargument with no class check — the one remaining place a caller-supplied object reached a struct accessor unguarded. Combined with the untypedData_Get_Structit used to call, passing the wrong object there reinterpreted an arbitrary pointer; it now raises before that can happen.ext/ext/module.handext/ext/klass/transform.heach declared astatic VALUEat file scope in a header, giving every one of the ~10 including translation units its own zero-initialized copy.rb_mExtmoved intoext.c, its only user;rb_cTransformwas dead and is gone.- An unused
hashlocal inEvent_key(ext/event.c). - Gem metadata: summary typo, empty description, incorrect homepage.
- Double-free crash (
free(): double free detected in tcache) inCircle,Clock,Transformable, andWindow: their_freefunctions called both CSFML's ownsfX_destroyandfree()on the same pointer, thoughsfX_destroyalready releases it. Event#sizereadevent->size.heightfor both components, so a resize event always reported a square. Also fixes the CSFML 3 struct layout (sfSizeEvent.sizeis now a vector, not flatwidth/height).View#get_rotationandRenderTarget#drawwere missingreturnstatements, so both handed an uninitializedVALUEback to Ruby — the same crash class as the double-free above, just not yet triggered by the test suite.Transform.inversewas bound toTransform_combineinstead ofTransform_inverse— a copy-paste bug that made.inversesilently wrong (and unusable at 1 argument).Window#view=,#view, and#default_viewwere fully implemented but never registered — calling them raisedNoMethodError.- CSFML 3 API port: video mode fields,
sfRenderWindow_create's new window-state parameter,sfRenderWindow_waitEvent's new timeout parameter, andsfFloatRect'sposition/sizefields (was flatleft/top/width/height). - A
%ulprintf format-string bug inext/exceptions.cproduced garbled error messages (e.g. "given 3l"); corrected to%lu. test/sfml_test.rb:require 'minitest/unit'(removed from modern minitest) →require 'minitest/autorun'; classes referenced without theirSFML::namespace;assert_in_epsilonused directly on arrays, which fails because it calls.abson the expected value.
Known issues
- Classes that wrap
T_DATAnever callrb_define_alloc_func, so Ruby printsundefining the allocator of T_DATA class ...once per class on first construction. Harmless and long-standing; fixing it means moving construction out of the singletonnewinto an alloc func plusinitialize. - Of the nine binary-gem targets, only
x86_64-linux-gnuhas been verified end to end (built, installed, full suite passes).x64-mingw-ucrtcross-builds and links cleanly with correct PE imports but has not been run on Windows. The rest have not been run on their target hardware;aarch64-linux-gnu,x86_64-darwinandarm64-darwinare markedexperimentalinpublish.yaml, so a failure can't hold back a release. - The Windows gem imports
libwinpthread-1.dll, which RubyInstaller ships in itsruby_builtin_dllsdirectory. If that ever proves unreliable, add-lwinpthreadto the-Wl,-Bstaticgroup inPorts.cxx_runtime. bundle exec rubocopdoes not pass on the repository as a whole (55 offenses across 14 files, 40 autocorrectable, mostlyStyle/FrozenStringLiteralComment) — this predates the packaging work andcheck.yamlhas been failing on it. The gemspec's own remaining offence isGemspec/RequireMFA, left deliberately: settingrubygems_mfa_requiredrequires MFA on every owner's RubyGems account, andpublish.yamlpushes with an API token.- TODO.md tracks SFML 3 → Ruby API porting coverage, module by module.