V STUFF
Psych ↔ V-Slice
Tap to skip
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 overposition (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 mappingdefaultZoom 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 LuamakeLuaSprite/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 overcamera_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 automaticallycharacters/*.jsondata/characters/, stages/<name>.json + matching .luadata/stages/, each data/<songId>/<songId>.json (+ a sibling events.json if present, merged in) + meta.txtdata/songs/<songId>/<songId>-chart.json and -metadata.json, and weeks/*.jsondata/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 workcustom_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 overprops 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 overisPlayer 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 overstartCamPosX/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 reconstructablecameraOffsets 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 mappingcameraZoom 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 mappingisPlayer 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 overindices 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.