+static void async_skaterift_world_loaded( void *payload, u32 size )
+{
+ skaterift_end_op();
+}
+
+static void skaterift_world_changer_thread( void *data )
+{
+ const char *path = data;
+ world_load( 1, world_global.load_target );
+ vg_async_call( async_skaterift_world_loaded, NULL, 0 );
+}
+
+/* holding pattern before we can start loading the new world, since we might be
+ * waiting for audio to stop */
+static void skaterift_change_world_preupdate(void)
+{
+ for( u32 i=1; i<vg_list_size(world_global.worlds); i++ ){
+ world_instance *inst = &world_global.worlds[i];
+
+ if( inst->status == k_world_status_unloading ){
+ if( world_freeable( inst ) ){
+ world_free( inst );
+ }
+ return;
+ }
+ }
+
+ vg_info( "worlds cleared, begining load\n" );
+ skaterift_shift_op( k_async_op_world_loading );
+
+ /* finally can start the loader */
+ vg_loader_start( skaterift_world_changer_thread, NULL );
+}
+
+/* places all loaded worlds into unloading state */
+static void skaterift_change_world( const char *world_path )
+{
+ vg_info( "switching to %s\n", world_path );
+
+ if( world_global.active_world != 0 ){
+ vg_error( "Cannot change worlds while in non-root world\n" );
+ }
+ else{
+ skaterift_begin_op( k_async_op_world_preloading );
+
+ vg_linear_clear( vg_mem.scratch );
+ world_global.load_target = vg_linear_alloc( vg_mem.scratch, 1024 );
+ vg_strncpy( world_path, world_global.load_target,
+ 1024, k_strncpy_overflow_fatal );
+
+ vg_info( "unloading old worlds\n" );
+ world_unlink_nonlocal( &world_global.worlds[0] );
+
+ for( u32 i=1; i<vg_list_size(world_global.worlds); i++ ){
+ world_instance *inst = &world_global.worlds[i];
+
+ if( inst->status == k_world_status_loaded ){
+ inst->status = k_world_status_unloading;
+ world_fadeout_audio( inst );
+ }
+ }
+ }
+}
+
+static int skaterift_change_world_command( int argc, const char *argv[] )
+{
+ if( argc == 1 )
+ skaterift_change_world( argv[0] );
+
+ return 0;
+}
+
+#else
+
+#include "skaterift_imgui_dev.c"
+