Complete modernization of the floor-plan editor. Three layered
changes shipped together since they share state shapes and the
test infrastructure stubs.
1) React rewrite (state + hooks + views + tests)
Drops the FloorplanEditorContext singleton + legacy view
components and replaces them with a pure-React reducer
architecture:
- state/ — typed FloorplanState + FloorplanAction union,
pure reducer covering PAINT_TILE / ERASE_TILE /
ADJUST_HEIGHT / SET_DOOR / SET_DOOR_DIR / SET_THICKNESS /
SET_WALL_HEIGHT / BRUSH_SET / SELECT_RECT / SELECT_ALL /
CLEAR_SELECTION / SQUARE_SELECT_TOGGLE / IMPORT_STRING /
APPLY_REMOTE_DIFF / APPLY_REMOTE_SNAPSHOT. Source-tagged
('local' | 'remote') so the editor can distinguish user
edits from server pushes. Co-located encoding helpers
(parseTilemap / serializeTilemap) and area-counter
selectors.
- hooks/ — useFloorplanReducer (wraps useReducer with a
history stack + loadFromServer + undo/redo), useTool
(pointer events -> dispatch), usePointerToTile (screen
-> tile projection that respects the viewBox origin so
pan/zoom stays accurate).
- views/ — FloorplanCanvasSVG, FloorplanHeightPicker,
FloorplanToolbar, FloorplanOptionsPanel,
FloorplanImportExport, FloorplanTile,
FloorplanPreviewSVG (alternative iso preview kept as a
fallback view, not wired into the main layout).
- Co-located Vitest suites for every module above (encoding,
reducer, selectors, hooks, views, integration). 100+ new
test cases.
2) Live in-room preview (NEW capability)
useFloorplanLiveSync drives client-side preview of the edit
directly into the active room — every tile / door / wall
height / thickness change is applied through
GetRoomMessageHandler().applyFloorModelLocally (new public
method on the renderer, see paired renderer PR) with
zero server traffic during editing. The wire
UpdateFloorPropertiesMessageComposer is only sent when the
user explicitly clicks Save. Thickness slider additionally
calls RoomEngine.updateRoomInstancePlaneThickness for
zero-latency wall/floor-depth feedback while dragging.
Toggle 'Live preview ON / OFF' in the bottom strip (default
ON) lets the user opt out if they want to keep changes
contained to the editor's own preview until Save.
Revert button re-applies the original snapshot locally so
the room snaps back to where it was when the editor opened.
3) UX polish
- Undo / Redo (Ctrl+Z, Ctrl+Shift+Z / Ctrl+Y) backed by a
100-step history stack inside useFloorplanReducer. Local
mutating actions push history; brush/selection UI bumps
and remote dispatches bypass it; loadFromServer wipes the
stack.
- Zoom 40-600 % with Ctrl+wheel, +/- buttons, % label.
Shift+drag or middle-mouse drag pans the canvas.
- Auto-fit on first paint: computes the screen-space
bounding box of the painted (non-blocked) tiles, picks the
zoom that just contains them with a 5 % margin, pans so
the room sits in the viewport centre. Default view is now
'room fills the canvas' instead of 'room is a dot at the
top-centre of a huge empty canvas'. Clicking the % label
re-runs the fit; crosshair button keeps zoom and recentres
the pan only.
- Door direction control: arrows + door icon triplet
(8-way rotate by single click on prev/next, full cycle
forward on the icon itself). Wall and floor thickness
collapse from two 4-button rows into two compact
segmented selectors (active state in emerald). Saves
significant horizontal space.
- Habbo floor pattern tile (~186 B PNG, vendored from
habbofurni.com/images/furni_floor.png) tiled as the
canvas background with image-rendering: pixelated so the
texture stays crisp at every zoom level. Replaces the
solid black background.
Test infrastructure
nitro-renderer.mock grows constructors / proxies / functions
for everything the new floor-editor tests transitively
import (floor composers + events, RoomEngineEvent,
ILinkEventTracker, convertNumbersForSaving /
convertSettingToNumber, GetRoomMessageHandler,
GetTicker, GetRenderer, NitroTicker, RoomPreviewer with a
sufficiently real .updatePreviewModel / dispose surface,
and a TextureUtils.createRenderTexture that returns an
object with a no-op .destroy). test-setup adds a no-op
ResizeObserver polyfill (jsdom doesn't ship one and the
optional FloorplanRoomPreview observes its container) and
a draggable-windows-container portal root for tests that
mount NitroCardView.
Files: 44 changed (mostly new). yarn typecheck 0 errors,
yarn test 341/341 green.
Nitro V3
Prerequisites
- Git
- NodeJS >= 18
- If using NodeJS < 18 remove
--openssl-legacy-providerfrom the package.json scripts
- If using NodeJS < 18 remove
- Yarn
npm i yarn -g
Quick install (recommended)
The repository ships a cross-platform installer that performs the full setup in one go: prerequisites check, renderer clone & link, dependency install, config copy, JSON parsing mode selection, URL prompt with validation, and the production build.
After cloning Nitro V3, from its root run:
# Windows
install.bat
# Linux / macOS
./install.sh
Both wrappers just exec node install.mjs, so you can also invoke it directly:
node install.mjs
The installer walks through these steps:
[1/9] Check prerequisites (node >= 18, yarn, git)
[2/9] Clone Nitro_Render_V3
[3/9] Setup renderer (yarn install + yarn link)
[4/9] Setup client (yarn install + yarn link "@nitrots/nitro-renderer")
[5/9] Copy public/configuration/*.example -> *.json
[6/9] Choose JSON parsing mode (json5 recommended) -> writes .nitro-build.json
[7/9] Configure URLs (interactive, validated)
[8/9] Build (yarn build)
[9/9] Summary
Headless / CI runs
Every step can be driven from flags so the installer can be used in pipelines:
node install.mjs --non-interactive \
--json-mode=json5 \
--socket-url=wss://example.com/ws \
--api-url=https://example.com \
--asset-url=https://example.com/nitro-assets/ \
--image-library-url=https://example.com/c_images \
--hof-furni-url=https://example.com/hof_furni \
--camera-url=https://example.com/camera \
--thumbnails-url=https://example.com/thumbnails \
--habbopages-url=/habbopages \
--api-base-url=https://example.com \
--plain-config-base-url=https://example.com/configuration \
--plain-gamedata-base-url=https://example.com/gamedata \
--skip-link
Useful workflow flags:
--non-interactive/--skip-prompts— keep example defaults unless a URL override is passed--json-mode=<json5\|legacy\|auto>— pick the parser without the JSON mode prompt--skip-build,--skip-clone,--skip-link— re-runs without redoing those steps--help— full flag reference and per-key URL flags
install.mjs is idempotent: re-running it keeps any *.json config files
that already exist and only patches the URL keys you pass on the CLI.
Splitting gamedata
The renderer can load gamedata files (FigureData, FurnitureData, FigureMap,
EffectMap, ProductData, HabboAvatarActions, ExternalTexts, UITexts) either as
a single legacy JSON/JSON5 file or as a directory of small files organised
in three tiers: core/ (vendor baseline), custom/ (your additions / overrides),
seasonal/ (date-bound content such as Christmas or Easter).
The split layout is much easier to maintain — you edit a small focused file instead of a 43 MB FurnitureData.json — and lets you keep vendor and operator content cleanly separated.
Directory layout
nitro-assets/gamedata/furnidata/
manifest.json5 # { "tiers": ["core", "custom", "seasonal"] }
core/
manifest.json5 # { "files": ["floor-001.json5", ..., "wall-001.json5"] }
floor-001.json5
floor-002.json5
wall-001.json5
custom/ # OPTIONAL — created by you
manifest.json5 # { "files": ["my-rares.json5"] }
my-rares.json5
seasonal/ # OPTIONAL — created by you
manifest.json5
xmas-2026.json5
Each tier is loaded in order. Within a tier, files load in the order listed in
its manifest.json5. Items in later layers override items in earlier layers
when they share the same identifier (id, classname, name, or the
top-level key for flat dictionaries).
Generating the core/ tier from a legacy file
Use the bundled CLI splitter:
node scripts/split-gamedata.mjs \
--input ~/legacy-gamedata/FurnitureData.json \
--output ~/nitro-assets/gamedata/furnidata
It auto-detects the gamedata type from the file's top-level keys and applies the strategy that makes the most sense:
| Type | Split strategy |
|---|---|
| EffectMap | one file per effect type (dance, fx, ...) |
| FigureData | one palettes.json5 + one file per setType |
| FigureMap | chunks of libraries (default 500/file) |
| FurnitureData | floor / wall, chunks of furnitype (300) |
| HabboAvatarActions | grouped by state (or single file if ≤1) |
| ProductData | chunks of products (default 500) |
| ExternalTexts/UITexts | grouped by key prefix (e.g. gamecenter.*) |
Useful flags: --type=<name> to force the type, --chunk-size=N to override
the default chunk size, --json to emit standard JSON instead of JSON5,
--force to overwrite an existing output directory. Full reference:
node scripts/split-gamedata.mjs --help
We only ship the core/ tier with vendor baselines — custom/ and seasonal/
are operator-owned: create their manifests when you need them and the loader
picks them up automatically.
Pointing the renderer at a directory
In public/configuration/renderer-config.json, replace the legacy file URL
with the directory URL (note the trailing slash — that's how the loader
detects split mode):
{
// single file (legacy, still supported):
"furnidata.url": "https://example.com/nitro-assets/gamedata/FurnitureData.json",
// directory (split mode):
"furnidata.url": "https://example.com/nitro-assets/gamedata/furnidata/",
}
Both styles work; you can migrate one gamedata file at a time.
Installation (manual)
- First you should open terminal and navigate to the folder where you want to clone Nitro and Nitro-Renderer
- Clone Nitro (Expl. C:\Github)
git clone https://github.com/duckietm/Nitro-V3.git<== For now switch to Dev-RendererV2git clone https://github.com/duckietm/Nitro_Render_V3.git- Install the dependencies for the renderer : cd C:\Github\Nitro_Render_V3
yarn install
- Now we will create a Link for the Nitro Renderer :
yarn linkThis will give you a link addressyarn link "@nitrots/nitro-renderer" - Install the dependencies for Cool UI : cd C:\Github\Nitro-V3
yarn installyarn link "@nitrots/nitro-renderer"<== This will link the renderer in the project
- Rename a few files
- Copy
public/configuration/renderer-config.exampletopublic/configuration/renderer-config.json - Copy
public/configuration/ui-config.exampletopublic/configuration/ui-config.json - Copy
public/configuration/client-mode.exampletopublic/configuration/client-mode.json - Set your links
- Open
public/configuration/renderer-config.json- Update
socket.url, asset.url, image.library.url, & hof.furni.url
- Update
- Open
public/configuration/ui-config.json- Update
camera.url, thumbnails.url, url.prefix, habbopages.url
- Update
yarn build<== the final step to build the DIST folder this is where your browser needs to point / or upload this to your /client if you do the compile on a other machine (preferd)- You can override any variable by passing it to
NitroConfigin the index.html
- Copy
JSON / JSON5 configuration mode
Starting with this version of Nitro V3, you can choose how the client parses the
configuration files (renderer-config.json, ui-config.json, client-mode.json,
and the gamedata JSONs served by the renderer):
- JSON5 (recommended) — accepts comments, trailing commas, single quotes
and unquoted identifiers. Easier to maintain, especially in
ui-config.jsonwhere you may want inline notes. - JSON (legacy strict) — only valid standard JSON is accepted. Any comment or trailing comma will fail the load with a clear error.
Picking a mode
The first time you run yarn start or yarn build, an interactive prompt asks
which mode to use:
════════════════════════════════════════════════════════════
Nitro V3 — JSON mode configuration
════════════════════════════════════════════════════════════
1) JSON5 (recommended)
2) JSON (legacy strict)
Scelta [1=JSON5]:
Your choice is stored in .nitro-build.json at the project root (gitignored, so
each deployment keeps its own setting). Subsequent builds reuse it silently.
Changing the mode later
Run the prompt again at any time:
yarn configure
You can also set the mode without interaction (useful in CI / scripts):
# one-shot override for a single build
NITRO_JSON_MODE=legacy yarn build
NITRO_JSON_MODE=json5 yarn build
# write the choice persistently
echo '{"jsonMode":"legacy"}' > .nitro-build.json
The recognized values are legacy, json5, and auto (auto = try strict JSON
first, fall back to JSON5 — equivalent to the original Render V3 behaviour).
How it propagates
The chosen mode is injected at build time as the compile-time constant
__NITRO_JSON_MODE__. It is honoured by:
src/bootstrap.tswhen loadingclient-mode.json@nitrots/utils→JsonParser.tsin Render V3, used for every config file and every gamedata JSON loaded by the renderer
In legacy mode, an invalid file produces a clear error that suggests switching
to JSON5; nothing is silently coerced.
Usage
- To use Nitro you need
.nitroassets generated, see nitro-converter for instructions - See Morningstar Websockets for instructions on configuring websockets on your server
Development
Run Nitro in development mode when you are editing the files, this way you can see the changes in your browser instantly
yarn start
Production
To build a production version of Nitro just run the following command
yarn build:prod
- A
distfolder will be generated, these are the files that must be uploaded to your webserver - Consult your CMS documentation for compatibility with Nitro and how to add the production files