Google sign-in isn't set up on this deployment yet.
Your profile
Optional — just a display name for this browser, stored locally. It's never sent anywhere on its own.
Your name gets added to the pre-filled submission — you still post it yourself, nothing is automatic.
V Stuff
Psych → V-Slice
Converts a Psych Engine chart into the V-Slice chart and metadata format. Paste or upload a chart, fill in what Psych doesn't store, and get both output files.
FROM Psych Engine
One difficulty in, copied across easy / normal / hard on the way out.
No file selected
TO V-Slice
chart.json holds notes and events, metadata.json holds everything about the song.
Convert a chart to see the output here.
Event mapping — Play Animation and Add Camera Zoom convert directly; the target character becomes V-Slice's role name (opponent/player/girlfriend — confirmed against a real V-Slice chart, not just guessed). Flash Camera / flash become FlashCamera. Anything else (Philly Glow, Cinematics, custom stage events) passes through with its original name under v.p1 / v.p2 so nothing is silently dropped — check the console list after converting and wire those up in your engine fork as needed.
Metadata — alt charter, sprite artist, cutscene artist, and sticker pack are real fields in V-Slice metadata (confirmed against an actual file) but Psych has nowhere to store them, so fill them in on the left if you want them in the output — they're left out entirely when blank rather than written as empty strings.
Section length — some Psych charts store each section's length as lengthInSteps (16 = a standard 4-beat section) instead of sectionBeats; both are read correctly.
FROM Psych Engine
A character's per-animation offsets carry over exactly. A few Psych-only fields don't have a V-Slice home — see the note below.
No file selected
TO V-Slice
One file — the character definition V-Slice loads directly.
Convert a character to see the output here.
Field mapping — each animation's anim becomes name, its atlas name becomes prefix, loop becomes looped, and per-animation offsets/indices (as frameIndices) carry over exactly (no shared default is assumed). An animation whose id contains "death" is renamed to firstDeath/deathLoop/deathConfirm and a death block is added — Psych usually only has one death animation, so you'll likely need to create the loop/confirm states yourself. scale and sing_duration map directly to scale/singTime (confirmed real fields, not guesses), healthicon becomes healthIcon: {id: ...}, and startingAnimation is set to whichever animation comes first.
Not carried over — position (belongs in the stage file, not the character) and healthbar_colors (no field in this schema). Set these directly in the output if your fork supports them.
FROM Psych Engine
Stage JSON gives positions and camera offsets directly. The Lua script is optional — if you paste it, background props get pulled out of the makeLuaSprite / addLuaSprite calls, best-effort.
No file selected
No file selected
TO V-Slice
One stage.json — background props plus character positions and camera offsets.
Convert a stage to see the output here.
Field mapping — defaultZoom becomes cameraZoom, and boyfriend/opponent/girlfriend positions plus their camera_* offsets go into characters.bf/dad/gf. If hide_girlfriend is true, the gf entry is left out entirely. Character zIndex isn't stored by Psych, so it's set from the fields on the left (default 300/200/100, matching official stage layering).
Props from Lua — makeLuaSprite/makeAnimatedLuaSprite give name, asset path and position; addAnimationByPrefix fills in animations and sets startingAnimation to whichever one gets added first; addAnimationByIndices is picked up the same way but stores its frame list as frameIndices; setScrollFactor fills scroll; addLuaSprite('x', true) (the foreground flag) pushes zIndex above the characters, otherwise props stack below them in call order. atlasSettings is always null and per-animation flipX/flipY default to false since Lua doesn't expose either. This is regex parsing, not a Lua interpreter — loops, variables used as sprite names, and anything built programmatically won't be picked up, so check the result against the script.
Not carried over — camera_speed isn't part of the stage schema, it belongs in the song's metadata.json as cameraSpeed (see the Charts tab). Shaders, tweened event animations (like a custom spotlight event), and anything in onEvent/onTimerCompleted are code, not static stage data — you'll need to reimplement that logic directly in your target engine.
FROM Psych Engine
Upload a whole mod folder as a .zip. Runs the Charts/Characters/Stages converters above across every matching file automatically, copies audio and character/stage art into V-Slice's layout, and gives you a report of what needs manual attention.
No file selected
TO V-Slice
A ready-to-drop-in Polymod folder, zipped.
Convert a mod folder to see the report here.
Converted automatically — characters/*.json → data/characters/, stages/<name>.json + matching .lua → data/stages/, each data/<songId>/<songId>.json (+ a sibling events.json if present, merged in) + meta.txt → data/songs/<songId>/<songId>-chart.json and -metadata.json, and weeks/*.json → data/levels/ (same mapping as the Weeks tab — the song list and unlock flag come through, but the storymenu art doesn't exist in Psych's format, so those files still need props/titleAsset/background filled in by hand). songs/<songId>/*.ogg, images/characters/*, and top-level images/icons/*.png are copied straight across (V-Slice uses the same layout for these — confirmed against a real mod). Other images/<folder>/* subfolders are copied to shared/images/stages/<folder>/ as a best guess, skipping known UI-only folders. A basic _polymod_meta.json is generated from the zip's folder name — edit the description, version, and contributors by hand.
Not converted — needs manual work — custom_events/*, dialogue .txt files, per-song .lua scripts beyond stages (events.lua, script.lua — these are code, not data), notestyles, menu music/SFX, and Psych-specific config (characterList.txt, stageList.txt, weekList.txt, freeplayColors.txt, introText.txt, specialThanks.txt, main-view.xml). All of it is listed in the report so nothing just silently disappears.
FROM Psych Engine
Paste or upload a week JSON. Converts the song list and unlock flag directly — the story-menu character art, title image, and background color don't exist in Psych's format, so those stay editable placeholders on the right.
No file selected
TO V-Slice
Level JSON — save it as data/levels/<Name>.json (no spaces, matching case, e.g. "Vs Impostor" → VsImpostor.json).
Field mapping — each song entry's display name becomes a lowercase song id in songs (matching the data/songs/<id>/ folder convention — confirmed against a real mod, where "Winter-Horrorland" in the week file matches the winter-horrorland song folder exactly). weekName becomes name, and hideStoryMode inverts into visible.
Not carried over — props is left empty: V-Slice's story-menu characters are full sprite-sheet animations with offsets and dance timing, and Psych's weekCharacters is just plain strings with none of that. titleAsset and background are filled from the fields on the left (or sensible placeholders) since Psych has no equivalent image/color to pull from — replace them with real assets before shipping. storyName and weekBefore also have no field in this schema.
FROM Codename Engine
Paste or upload a Codename Engine chart (the file with "codenameChart": true and a strumLines array). Codename doesn't store BPM or song credits in the chart itself, so fill those in below.
No file selected
TO V-Slice
Two files: chart and metadata, same as the Charts tab.
Convert a chart to see the output here.
Field mapping — each strumLines entry with type: 1 is treated as the player's strumline (notes land on lanes 0-3, matching Codename's own id directly); every other strumline (type 0 — typically "opponent", but this also covers a separate "girlfriend" strumline if the chart has one, since Codename supports strumlines beyond just player/opponent) is merged onto lanes 4-7. sLen maps straight to l — both are already in milliseconds. A note's numeric type is looked up in the chart's noteTypes array and used as k when it resolves to something other than the default. The "Camera Movement" event (seen in real charts with a single 0/1 param) converts to FocusCamera with that param as the character index — everything else passes through with its original name, with each entry of its params array preserved as p1, p2, etc. so nothing is silently dropped.
Not carried over / lossy — if a chart has three or more non-player strumlines, they all collapse onto the same 4-7 lanes and become indistinguishable in the output; check the note count if that matters for your chart. BPM, song name, artist, and stage aren't stored in the Codename chart file at all (they likely live in a separate metadata file Codename keeps elsewhere) — the fields on the left are the only source for those. noteTypes entries beyond a name aren't inspected for further behavior. Codename → V-Slice only for now; the reverse direction and Characters/Stages support aren't built yet.
FROM Codename Engine
Paste or upload a Codename character XML (the file starting with <!DOCTYPE codename-engine-character>).
No file selected
TO V-Slice
Field mapping — each <anim>'s name becomes name, its anim attribute (the actual atlas prefix) becomes prefix, x/y become offsets, fps becomes frameRate, loop="true" becomes looped. The character's own y (and x, if present) becomes the top-level offsets, flipX carries straight over, icon becomes healthIcon: {id: ...}, and startingAnimation is set to whichever animation comes first — same mapping as the Characters tab. An animation whose name contains "death" gets the same death-state handling too.
Not carried over — isPlayer has no equivalent field (V-Slice decides player vs. opponent per-song, not per-character-file); color (health bar tint) has no field in this schema either.
FROM Codename Engine
Paste or upload a Codename stage XML (the file starting with <!DOCTYPE codename-engine-stage>).
No file selected
TO V-Slice
Field mapping — each <sprite> becomes a prop: name carries over, assetPath is the stage's folder joined with the sprite's own sprite attribute, x/y become position, and scroll (a single number in Codename) fills both axes of the [x, y] scroll pair. Any nested <anim> elements inside a sprite convert the same way as character animations. zoom becomes cameraZoom. Since Codename doesn't give explicit stacking numbers, zIndex is inferred from document order — each <sprite>, and the <girlfriend/>/<dad/>/<boyfriend/> placeholder tags wherever they appear in the file, get an incrementing value in the order they're written. If the file has no <girlfriend/> tag at all, the output marks her hidden (alpha: 0) the same way the Stages tab does for a Psych stage with hide_girlfriend.
Not carried over — startCamPosX/startCamPosY have no equivalent (V-Slice tracks camera position per-character via cameraOffsets, not a single stage-wide starting point); character position/cameraOffsets default to [0, 0] since the <girlfriend/>/<dad/>/<boyfriend/> tags carry no position data at all in Codename — fill those in by hand.
Codename → V-Slice
Not built yet for Codename Engine — Mod Folder and Weeks aren't wired up for Codename. Use Charts, Characters, or Stages above, or switch to a Psych → V-Slice direction.
FROM V-Slice
Pick which difficulty becomes the Psych chart. Metadata.json is optional but fills in song/character names and BPM properly.
No file selected
No file selected
TO Psych Engine
One chart.json, in Psych's section format.
Convert a chart to see the output here.
Section reconstruction — notes are grouped into fixed 4-beat sections walked forward from timeChanges (or a flat 100 BPM if no metadata is given). Each section's mustHitSection is set by majority vote of that section's notes, and directions are shifted back accordingly — this is the exact inverse of the Charts (forward) conversion, so a round trip should match. Trailing empty sections at the end of the original chart aren't reconstructed since there's no note data to anchor them. Not every difficulty needs notes — the dropdown auto-picks the first one (normal, then hard, then easy) that actually has any once you paste or upload a chart.
Events — PlayAnimation and ZoomCamera convert back directly (ZoomCamera reconstructs the delta from consecutive absolute zoom values, keyed by its mode field — the easing curve itself, whether it's one combined name like "expoOut" or split into ease + easeDir, doesn't carry over either way since Psych's "Add Camera Zoom" has no easing concept at all). PlayAnimation's role name — opponent/player/girlfriend — maps back to dad/boyfriend/gf. ScrollSpeed becomes "Change Scroll Speed" with the new speed and strumline (duration/easing are dropped for the same reason). FlashCamera always restores as "Flash Camera" — Psych's "flash" event name is indistinguishable from it once merged, so if your original used "flash" you'll want to rename it back by hand. FocusCamera and SwitchIdleSuffix aren't native Psych events, so they come through as-is with the target character guessed from its numeric index (0/1/2 → dad/boyfriend/girlfriend, inferred from typical ordering, not confirmed); FocusCamera's remaining data (x, y, duration, ease — whichever are actually present, since real files sometimes only send char) is packed as JSON in the second slot. You'll need a custom event handler for either. Any other event Psych can't natively represent — SetCameraBop and anything not covered above — comes through the same way: full data preserved as JSON rather than dropped, so check those events after converting.
FROM V-Slice
Fields Psych needs that V-Slice doesn't store are editable below.
No file selected
TO Psych Engine
One file, ready to drop in the characters folder.
Convert a character to see the output here.
Field mapping — each animation's name becomes anim, prefix becomes the atlas name, looped becomes loop, and offsets/frameRate/frameIndices (as indices) carry over exactly (missing fps falls back to the default above). scale, singTime, and healthIcon.id map straight to scale, sing_duration, and healthicon when present, falling back to the fields on the left otherwise. assetPath has its shared: prefix stripped to become image, unless you override it on the left.
Not reconstructable — cameraOffsets and the death block have no home in Psych's character schema (that data lives in the stage file instead, or isn't used at all), so they're dropped. deathLoop/deathConfirm states pass through as extra animation entries, but base Psych won't reference them as anything special.
FROM V-Slice
Props become a generated Lua onCreate() — check it against your actual assets before using it.
No file selected
TO Psych Engine
Both a stage.json and the Lua script it needs.
Convert a stage to see the output here.
Field mapping — cameraZoom becomes defaultZoom, each character's position/cameraOffsets go back into boyfriend/opponent/girlfriend and camera_*. hide_girlfriend is set to true if there's no gf entry at all, or if it's present with alpha: 0 — both show up in real stages.
Lua generation — props are sorted by zIndex (props missing it fall back to their position in the props array, since that's valid too) and turned into makeLuaSprite/makeAnimatedLuaSprite + addAnimationByPrefix (or addAnimationByIndices when an animation has frameIndices) + setScrollFactor + addLuaSprite, with the foreground flag set for any prop whose zIndex is above the highest character zIndex. A startingAnimation becomes an objectPlayAnimation call right after the sprite is built. scale works whether it's a single number or an [x,y] pair, but either way it can't be reproduced in Lua (Psych's setGraphicSize needs the sprite's real pixel dimensions, which isn't in the JSON) — flagged props are listed after converting. animType other than "sparrow" is generated the same way regardless, since Psych only understands sparrow atlases and single images — double check those manually. danceEvery, atlasSettings, blend modes, and any lowQuality gating aren't reconstructed since Psych stores none of that in the stage JSON either.
Mod Folder — V-Slice → Psych
Not built yet — the whole-folder converter only goes Psych → V-Slice for now. Use the Charts/Characters/Stages tabs above one file at a time for the reverse direction.
Weeks — V-Slice → Psych
Not built yet — level → week only goes Psych → V-Slice for now.
V-Slice → Codename
Not built yet — this direction isn't wired up. Codename support currently covers Codename → V-Slice (Charts/Characters/Stages) and Codename → Psych (Charts).
FROM Codename Engine
Paste or upload a Codename Engine chart. This reuses the same Codename → V-Slice mapping and then runs it through the V-Slice → Psych converter, so the results and caveats match those two tabs exactly — including the lossy 3+ non-player-strumline case.
No file selected
TO Psych Engine
Goes Codename → V-Slice → Psych internally (two already-tested converters chained), so the note lane mapping (player strumline → lanes 0-3, everything else → lanes 4-7), the "Camera Movement" → FocusCamera event mapping, and the 3+ non-player-strumline caveat are identical to the Codename → V-Slice Charts tab. From there it's the same V-Slice → Psych mapping as the regular Charts tab: sections rebuilt from the note data, difficulty picked from the field above, needsVoices left on. Only one difficulty comes out per conversion — Codename doesn't separate difficulties into different files the way Psych does, so run this once per difficulty label you need if your Codename chart actually has more than one.
FROM Codename Engine
Paste or upload a Codename character XML. This maps straight to Psych's schema — no V-Slice in between — so isPlayer and color come through natively, since Psych has fields for both and V-Slice doesn't.
No file selected
TO Psych Engine
Field mapping — isPlayer becomes _editor_isPlayer and color (hex) becomes healthbar_colors ([r,g,b]) directly, since Psych has real fields for both — going through V-Slice first would silently drop them, since V-Slice's character schema has neither. Each <anim>'s anim attribute (the atlas prefix) becomes the animation entry's name, its own name becomes anim, x/y become offsets, fps carries over, loop="true" becomes loop. The character's own y (and x, if present) becomes position, flipX becomes flip_x, icon becomes healthicon.
Not carried over — indices is always empty since Codename's animations don't carry frame-index data; sing_duration and scale have no Codename source, so they default to 4 and 1 (Psych has no field for a Codename-style singTime either, so this is a plain default, not a mapping).
FROM Codename Engine
Paste or upload a Codename stage XML. Same z-index-from-document-order inference as the Codename → V-Slice Stages tab, generating Lua directly (reusing the same Lua builder the Charts → Psych Stages tab uses) instead of going through a V-Slice stage.json first.
No file selected
Convert a stage to see the output here.
Same field mapping as the V-Slice → Psych Stages tab: zoom becomes defaultZoom, character positions/camera offsets default to [0, 0] since Codename's <girlfriend/>/<dad/>/<boyfriend/> tags carry no position data — fill those by hand. <sprite> elements become Lua makeLuaSprite/makeAnimatedLuaSprite calls, generated straight from the parsed props without a V-Slice stage.json round-trip in between.
Codename → Psych
Not built yet for this direction — Mod Folder and Weeks aren't wired up. Use Charts, Characters, or Stages above.
Not built yet
This direction doesn't have a converter yet. Currently built: Psych ↔ V-Slice (all 5 tabs), Codename → V-Slice (Charts/Characters/Stages), and Codename → Psych (Charts/Characters/Stages).
Pick two different engines
"Convert from" and "convert to" are set to the same engine right now — pick two different ones above to see a converter.
Chart Editor — V-Slice format, notes only
Drag on an empty lane to place a sustain note (a quick tap with no drag places a tap note instead). Drag an existing note to move it, drag its bottom edge to resize the sustain, tap it without moving (or hit the red × on its corner) to delete it. Works with touch on mobile — dragging on empty space still scrolls normally. Load audio to hear the song while editing — right-click the grid to seek. Ctrl+Z undoes the last change. Small gray diamonds on the right show events for reference (not editable here).
No file selected
No file selected
No file selected
0:00 / 0:00
Load a chart to see the grid.
Character Editor — spritesheet preview + JSON editing
Upload a spritesheet PNG and/or its atlas (Sparrow XML or TexturePacker JSON) — either one works alone (PNG shows the whole sheet, atlas alone lets you insert animations with no image), both together preview individual frames. Frames are grouped into animations by stripping the trailing frame number (e.g. "BF idle0000" → prefix "BF idle"). Edit the character.json on the right and use "Insert detected animations" to pull the atlas's prefixes straight in.
No file selected
No file selected
Preview background:
Loading…
No atlas loaded yet.
Stage Editor — spritesheet preview + JSON editing
Same idea as the Character Editor, but for stage props. Both files are optional and work independently: a PNG alone previews the whole spritesheet, an atlas alone (Sparrow XML or TexturePacker JSON) lets you insert its animations into a prop without any image, and both together let you preview individual frames.
No file selected
No file selected
Preview background:
Loading…
No atlas loaded yet.
Week Editor — build a V-Slice level.json
Fill in the fields below to build a level from scratch. Any change to a field regenerates the JSON on the right from scratch — so if you paste or upload an existing level.json into that box, hit "Load JSON into fields" right away, before touching any field above, or your paste will get overwritten the next time a field changes.
No file selected
Same schema as the Weeks converter tab: props stays empty here too, since story-menu character art has to be built and referenced by hand — this editor is for getting the song list, name, visibility, and placeholder title/background right without hand-editing JSON. Save the download as data/levels/<Name>.json (no spaces, matching case).
Community Showcase
Posts here are public GitHub issues on this project's repo, tagged "showcase" — there's no separate database, so anyone can read them and anyone with a GitHub account can post one. Hit Submit to open a pre-filled issue on GitHub, fill it in, and it'll show up here once it's posted.