Pārlūkot izejas kodu

new release (see changelog)

Nikolay Suslov 4 gadi atpakaļ
vecāks
revīzija
b1c3979a43
100 mainītis faili ar 15795 papildinājumiem un 1434 dzēšanām
  1. 248 124
      public/core/app.js
  2. 93 7
      public/core/helpers.js
  3. 240 175
      public/core/luminary.js
  4. 21 40
      public/core/reflectorClient.js
  5. 696 0
      public/core/virtualTime.js
  6. 6479 0
      public/core/vwf.js
  7. 67 70
      public/core/vwf/api/kernel.js
  8. 358 0
      public/core/vwf/api/model.js
  9. 358 0
      public/core/vwf/api/view.js
  10. 186 0
      public/core/vwf/fabric.js
  11. 736 0
      public/core/vwf/model.js
  12. 168 142
      public/core/vwf/model/javascript.js
  13. 46 32
      public/core/vwf/model/object.js
  14. 235 0
      public/core/vwf/model/ohm.js
  15. 65 0
      public/core/vwf/model/stage.js
  16. 249 0
      public/core/vwf/model/stage/log.js
  17. 0 0
      public/core/vwf/model/stage/map.js
  18. 88 0
      public/core/vwf/utility/kutility.js
  19. 146 153
      public/core/vwf/utility/logger.js
  20. 134 163
      public/core/vwf/utility/utility.js
  21. 491 0
      public/core/vwf/utility/xpath.js
  22. 303 0
      public/core/vwf/view.js
  23. 88 0
      public/core/vwf/view/document.js
  24. 114 0
      public/core/vwf/view/ohm.js
  25. BIN
      public/defaults/assets/webimg.jpg
  26. 29 3
      public/defaults/proxy/aframe/ascene.js
  27. 6 0
      public/defaults/proxy/aframe/ascene.vwf.json
  28. 38 5
      public/defaults/proxy/animation/animationNode.js
  29. 3 0
      public/defaults/proxy/animation/animationNode.vwf.json
  30. 167 0
      public/defaults/proxy/objects/legoboost.js
  31. 73 0
      public/defaults/proxy/objects/legoboost.vwf.json
  32. 7 4
      public/defaults/worlds/aframe-ar/index.vwf.config.json
  33. 0 21
      public/defaults/worlds/aframe-ar/index.vwf.html
  34. 1 1
      public/defaults/worlds/aframe-ar/index.vwf.json
  35. 4 2
      public/defaults/worlds/aframe/index.vwf.config.json
  36. 0 19
      public/defaults/worlds/aframe/index.vwf.html
  37. 1 1
      public/defaults/worlds/aframe/index.vwf.json
  38. 5 3
      public/defaults/worlds/aframe2/index.vwf.config.json
  39. 1 1
      public/defaults/worlds/aframe2/index.vwf.json
  40. 6 3
      public/defaults/worlds/gearvr/index.vwf.config.json
  41. 105 0
      public/defaults/worlds/lego-boost/appui.js
  42. 16 0
      public/defaults/worlds/lego-boost/index.vwf.config.json
  43. 121 0
      public/defaults/worlds/lego-boost/index.vwf.json
  44. 14 0
      public/defaults/worlds/lego-boost/info.json
  45. 5 3
      public/defaults/worlds/multipixel/index.vwf.config.json
  46. 1 43
      public/defaults/worlds/multipixel/index.vwf.json
  47. 5 3
      public/defaults/worlds/ohmlang-calc/index.vwf.config.json
  48. 5 3
      public/defaults/worlds/ohmlang-lsys/index.vwf.config.json
  49. 12 9
      public/defaults/worlds/ohmlang-lsys/index.vwf.js
  50. 5 3
      public/defaults/worlds/orchestra/index.vwf.config.json
  51. 7 3
      public/defaults/worlds/osc-example/index.vwf.config.json
  52. 5 3
      public/defaults/worlds/paint/index.vwf.config.json
  53. 15 0
      public/defaults/worlds/pure/appui.js
  54. 10 0
      public/defaults/worlds/pure/index.vwf.config.json
  55. 48 0
      public/defaults/worlds/pure/index.vwf.js
  56. 16 0
      public/defaults/worlds/pure/index.vwf.json
  57. 14 0
      public/defaults/worlds/pure/info.json
  58. 6 4
      public/defaults/worlds/webrtc/index.vwf.config.json
  59. 0 129
      public/domReady.js
  60. 1604 0
      public/drivers/model/aframe.js
  61. 0 0
      public/drivers/model/aframe/addon/BVHLoader.js
  62. 0 0
      public/drivers/model/aframe/addon/SkyShader.js
  63. 0 0
      public/drivers/model/aframe/addon/THREE.MeshLine.js
  64. 2 2
      public/drivers/model/aframe/addon/TransformControls.js
  65. 4 4
      public/drivers/model/aframe/addon/aframe-components.js
  66. 1 1
      public/drivers/model/aframe/addon/aframe-interpolation.js
  67. 0 0
      public/drivers/model/aframe/addon/aframe-sun-sky.js
  68. 0 0
      public/drivers/model/aframe/addon/aframe-sun-sky.min.js
  69. 0 0
      public/drivers/model/aframe/addon/aframe-teleport-controls.js
  70. 0 0
      public/drivers/model/aframe/addon/aframe-teleport-controls.min.js
  71. 0 0
      public/drivers/model/aframe/addon/three/BufferGeometryUtils.js
  72. 0 0
      public/drivers/model/aframe/addon/virtualgc/nipplejs.js
  73. 0 0
      public/drivers/model/aframe/addon/virtualgc/virtual-gamepad-controls.css
  74. 213 255
      public/drivers/model/aframe/aframe-master.js
  75. 0 0
      public/drivers/model/aframe/aframe-master.js.map
  76. 12 0
      public/drivers/model/aframe/aframe-master.min.js
  77. 0 0
      public/drivers/model/aframe/aframe-master.min.js.map
  78. 0 0
      public/drivers/model/aframe/extras/aframe-extras.controls.js
  79. 0 0
      public/drivers/model/aframe/extras/aframe-extras.controls.min.js
  80. 0 0
      public/drivers/model/aframe/extras/aframe-extras.js
  81. 0 0
      public/drivers/model/aframe/extras/aframe-extras.loaders.js
  82. 0 0
      public/drivers/model/aframe/extras/aframe-extras.loaders.min.js
  83. 0 0
      public/drivers/model/aframe/extras/aframe-extras.min.js
  84. 0 0
      public/drivers/model/aframe/extras/aframe-extras.misc.js
  85. 0 0
      public/drivers/model/aframe/extras/aframe-extras.misc.min.js
  86. 0 0
      public/drivers/model/aframe/extras/aframe-extras.pathfinding.js
  87. 0 0
      public/drivers/model/aframe/extras/aframe-extras.pathfinding.min.js
  88. 0 0
      public/drivers/model/aframe/extras/aframe-extras.primitives.js
  89. 0 0
      public/drivers/model/aframe/extras/aframe-extras.primitives.min.js
  90. 0 0
      public/drivers/model/aframe/extras/components/grab.js
  91. 0 0
      public/drivers/model/aframe/extras/components/grab.min.js
  92. 0 0
      public/drivers/model/aframe/extras/components/sphere-collider.js
  93. 0 0
      public/drivers/model/aframe/extras/components/sphere-collider.min.js
  94. 0 0
      public/drivers/model/aframe/fonts/custom-msdf.json
  95. 0 0
      public/drivers/model/aframe/fonts/custom.png
  96. 0 0
      public/drivers/model/aframe/kframe/aframe-aabb-collider-component.js
  97. 0 0
      public/drivers/model/aframe/kframe/aframe-aabb-collider-component.min.js
  98. 1453 0
      public/drivers/model/aframeComponent.js
  99. 146 0
      public/drivers/model/lego-boost.js
  100. 0 0
      public/drivers/model/math/closure/base.js

+ 248 - 124
public/app.js → public/core/app.js

@@ -1,17 +1,18 @@
 /*
 The MIT License (MIT)
-Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+Copyright (c) 2014-2020 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
 
 Virtual World Framework Apache 2.0 license  (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
 */
 
 import page from '/lib/page.mjs';
-import { log } from '/lib/utils/log.js';
-import { Helpers } from '/helpers.js';
+import { Helpers } from '/core/helpers.js';
+import { VWF } from '/core/vwf.js';
 import { WorldApp } from '/web/world-app.js';
-import { Widgets } from '/lib/widgets.js';
-import { ReflectorClient } from './reflector-client.js';
-import { Luminary } from '/luminary.js';
+import { Widgets } from '/lib/ui/widgets.js';
+
+import { createAdapter } from '/lib/fun/@most/adapter/dist/index.mjs';
+import *  as mostSubject from '/lib/fun/@most/subject/dist/index.all.js';
 
 class App {
   constructor() {
@@ -21,22 +22,32 @@ class App {
     window._app = this;
     window._cellWidgets = this.widgets;
 
+    //global functional objects M - for MostJS Core; R - for Ramda; L - for Partial Lenses  
+    window.M = mostCore;
+    M.createAdapter = createAdapter;
+    M.subject = mostSubject;
+    M.scheduler = mostScheduler;
+    M.prelude = mostPrelude;
+    M.e = mostDomEvent;
+    ///
+
     window._noty = new Noty;
     this.helpers = new Helpers;
-    this.log = log;
+    this.log = this.helpers.log;
     this.hashids = new Hashids.default();
 
     this.clearLocalStorage();
 
-    this.luminary = new Luminary;
-    this.reflectorClient = new ReflectorClient;
+    // this.luminary = new Luminary;
+
+
     this.config = {};
 
     this.initDB()
     new Promise(res => { this.initUser(); res });
 
 
-    import('/lib/polyglot/language.js').then(res => {
+    import('/lib/locale/locale.js').then(res => {
       window._LangManager = new res.default;
       return new Promise(r => r(_LangManager.setLanguage()))
     })
@@ -81,59 +92,46 @@ class App {
   }
 
   initDB() {
-
-    var config = JSON.parse(localStorage.getItem('lcs_config'));
-    if (!config) {
-      config = {
-        'luminary': false,
-        'luminaryPath': 'luminary',
-        'luminaryGlobalHBPath': 'server/heartbeat',
-        'luminaryGlobalHB': false,
-        'dbhost': window.location.origin + '/gun', // 'https://' + window.location.hostname + ':8080/gun', //'http://localhost:8080/gun',
-        'reflector': 'https://' + window.location.hostname + ':3002',
-        'webrtc': false,
-        'language': 'en',
-        'd3DoF': false,
-        'd6DoF': false
-      }
+    let configDefaults = {
+      'luminary': false,
+      'luminaryPath': 'https://localhost:8081',
+      'luminaryGlobalHBPath': 'server/heartbeat',
+      'luminaryGlobalHB': false,
+      'dbhost': window.location.origin + '/gun', // 'https://' + window.location.hostname + ':8080/gun', //'http://localhost:8080/gun',
+      'reflector': 'https://' + window.location.hostname + ':3002',
+      'webrtc': false,
+      'language': 'en',
+      'd3DoF': false,
+      'd6DoF': false,
+      'streamMsg': false,
+      'multisocket': false 
     }
 
-    //if old config on browser exists
+    let conf = JSON.parse(localStorage.getItem('lcs_config'));
+    let config = conf ? conf : {};
 
-    if (!config.luminaryPath) {
-      config.luminaryPath = 'luminary';
-    }
-
-    if (!config.luminaryGlobalHBPath) {
-      config.luminaryGlobalHBPath = 'server/heartbeat';
-    }
-
-    if (!config.luminaryGlobalHB) {
-      config.luminaryGlobalHB = false;
-    }
-
-    if (!config.webrtc) {
-      config.webrtc = false;
-    }
-
-    if (!config.d3DoF) {
-      config.d3DoF = false;
-    }
-
-    if (!config.d6DoF) {
-      config.d6DoF = false;
+    if(conf){
+      Object.keys(configDefaults).forEach(el => {
+        config[el] = config[el] ? config[el] : configDefaults[el]
+    })
+    } else {
+      Object.assign(config, configDefaults);
     }
 
     localStorage.setItem('lcs_config', JSON.stringify(config));
     this.config = config;
 
+
     let webrtcConnection = this.config.webrtc;
 
-    const opt = { peers: this.dbHost, localStorage: false, RTCPeerConnection: webrtcConnection, axe: false } //localStorage: false,
+    const opt = { peers: this.dbHost, localStorage: false, multicast: false, RTCPeerConnection: webrtcConnection, axe: false } //localStorage: false,
     //const opt = { peers: this.dbHost, localStorage: false, until: 1000, chunk: 5, axe: false} //until: 5000, chunk: 5
     //opt.store = RindexedDB(opt);
     this.db = Gun(opt);
 
+    //In production
+    //Gun.log.off = true;
+
     //window._LCS_SYS_USER = undefined;
     window._LCSDB = this.db;
     window._LCS_WORLD_USER = undefined;
@@ -144,7 +142,7 @@ class App {
 
       let noty = new Noty({
         text: msg,
-        timeout: 1000,
+        timeout: 100,
         theme: 'mint',
         layout: 'bottomRight',
         type: 'success'
@@ -162,13 +160,14 @@ class App {
       let msg = 'No connection to ' + peer.url;
       let noty = new Noty({
         text: msg,
-        timeout: 1000,
+        timeout: 100,
         theme: 'mint',
         layout: 'bottomRight',
         type: 'error'
       });
       noty.show();
       console.log(msg)
+
     })
 
   }
@@ -210,14 +209,6 @@ class App {
   }
 
 
-  async chooseConnection(data) {
-    if (this.isLuminary) {
-      return await _app.luminary.connect(data) //use Luminary
-    } else {
-      return data //use Reflector
-    }
-  }
-
   get isLuminary() {
 
     return this.config.luminary;
@@ -401,11 +392,13 @@ class App {
             "title": "Empty World"
           },
           "model": {
-            "vwf/model/aframe": null
+            "/drivers/model/aframe": null,
+            "/drivers/model/aframeComponent": null
           },
           "view": {
-            "vwf/view/aframe": null,
-            "vwf/view/editor-new": null
+            "/drivers/view/aframe": null,
+            "/drivers/view/aframeComponent": null,
+            "/drivers/view/editor": null
           }
         }, null, 4),
       "assets_json": JSON.stringify({}),
@@ -461,7 +454,9 @@ class App {
   HandleDebugIndex() {
 
     window._app.hideProgressBar();
-    window._app.hideUIControl();
+    //window._app.hideUIControl();
+
+    _app.generalIndex().then(r=>{
 
     let el = document.createElement("div");
     el.setAttribute("id", "appGUI");
@@ -469,18 +464,24 @@ class App {
 
     _cellWidgets.debugGUI();
 
+    })
+
   }
 
   HandleSettingsIndex() {
 
     window._app.hideProgressBar();
-    window._app.hideUIControl();
+    //window._app.hideUIControl();
+
+
+    _app.generalIndex().then(res=>{
 
     let el = document.createElement("div");
     el.setAttribute("id", "appGUI");
     document.body.appendChild(el);
 
     _cellWidgets.reflectorGUI();
+    })
 
   }
 
@@ -493,7 +494,10 @@ class App {
     let saveName = ctx.params.savename;
 
     window._app.hideProgressBar();
-    window._app.hideUIControl();
+    //window._app.hideUIControl();
+
+
+    _app.generalIndex().then(res=>{
 
     if (!_app.indexApp) {
       _app.indexApp = new IndexApp;
@@ -503,12 +507,13 @@ class App {
     _app.helpers.getUserPub(userAlias).then(res => {
       worldApp.makeGUI(res)
     })
+  })
 
   }
 
   HandleSetupIndex() {
     window._app.hideProgressBar();
-    window._app.hideUIControl();
+    //window._app.hideUIControl();
 
     _LCSDB.on('auth',
       function (ack) {
@@ -630,7 +635,9 @@ class App {
 
     console.log("USER INDEX");
     window._app.hideProgressBar();
-    window._app.hideUIControl();
+    //window._app.hideUIControl();
+
+    _app.generalIndex().then(r=>{
 
     import('/web/header.js').then(res => {
       let gui = new res.Header();
@@ -1023,6 +1030,11 @@ class App {
       $type: "div",
       $components: [userProfile]
     })
+
+
+  })
+
+
   }
 
   HandleUserWorlds(ctx) {
@@ -1033,13 +1045,17 @@ class App {
     let type = ctx.params.type;
 
     window._app.hideProgressBar();
-    window._app.hideUIControl();
+   // window._app.hideUIControl();
+
+    _app.generalIndex().then(r=>{
 
     if (!_app.indexApp) {
       _app.indexApp = new IndexApp;
     }
     _app.indexApp.allWorldsForUser(user)
 
+  })
+
   }
 
   HandleFileEdit(ctx) {
@@ -1052,8 +1068,9 @@ class App {
     let type = ctx.params.type;
 
     window._app.hideProgressBar();
-    window._app.hideUIControl();
+    //window._app.hideUIControl();
 
+    _app.generalIndex().then(r=>{
 
     _LCSDB.on('auth',
       async function (ack) {
@@ -1171,6 +1188,8 @@ class App {
           }
         }
       })
+
+    })
   }
 
 
@@ -1178,6 +1197,7 @@ class App {
 
     let infoEl = document.createElement("div");
     infoEl.setAttribute("id", "indexPage");
+    //infoEl.classList.add("mdc-typography");
 
     let lang = _LangManager.locale;
 
@@ -1187,17 +1207,49 @@ class App {
 
   }
 
+  async loadIndexLibs() {
+
+    return loadjs([
+      '/lib/ui/cell.js',
+      '/lib/ui/treeview/treeview.min.css',
+      '/lib/ui/treeview/treeview.min.js',
+      '/lib/ui/mdc/dist/material-components-web.min.css',
+      '/lib/ui/mdc/dist/material-components-web.min.js',
+      '/lib/ui/mdc.css',
+      '/lib/ui/ace/ace.js',
+      '/lib/ui/drag-drop.js',
+      '/lib/buffer5.6.0.min.js',
+    ], {
+      async: false,
+      returnPromise: true
+    })
+  }
+
+
+  generalIndex() {
+
+    let p = new Promise(res => res())
+    .then(res=>{
+      return _app.loadIndexLibs();
+    })
+    .then(res=>{
+      document.querySelector('body').classList.add("mdc-typography");
+      mdc.autoInit();
+    })
+    return p
+  }
+
   HandleIndex() {
 
     console.log("INDEX");
 
-    window._app.hideProgressBar();
-    window._app.hideUIControl();
-
-    (new Promise(res => res(_app.generateFrontPage()))).then(res => {
+    //window._app.hideUIControl();
+    _app.generateFrontPage();
 
+    _app.generalIndex().then(res=>{
       if (!_app.indexApp) {
         _app.indexApp = new IndexApp('index');
+        window._app.hideProgressBar();
       }
     })
 
@@ -1336,12 +1388,16 @@ class App {
         vwfApp.conf = config
       }
 
+      let infoFile = val['info_json'];
+      let infoSettings = infoFile ? JSON.parse(infoFile).info.settings: null;
+      if(infoSettings){
       let manualSettings = localStorage.getItem('lcs_app_manual_settings');
       if (manualSettings) {
         let manualConf = JSON.parse(manualSettings);
         vwfApp.conf.model = manualConf.model;
         vwfApp.conf.view = manualConf.view;
       }
+    } 
 
       //check & set default proxy for world
       vwfApp.proxy = val.proxy ? val.proxy : _LCS_WORLD_USER.pub
@@ -1365,67 +1421,129 @@ class App {
 
 
     }).then(res => {
-      //TODO: load libs for selected config drivers
-      return app.loadAppLibs()
-    }).then(res => {
-      return app.loadVWF()
+      //Load vwf_view Document
+      let dbPath = _app.helpers.appName + '_js';
+      vwfApp.doc = res[0][dbPath];
+      //Load libs for selected config drivers
+      let libs = app.getLibsForConfig(vwfApp.conf);
+      if(libs.length !== 0)
+          return app.loadAppLibs(libs); 
+      
+      return 'nodriver'
+
+    })
+    .then(res => {
+      //Load VWF libs
+        return app.loadVWF();
     }).then(res => {
-      var userLibraries = { model: {}, view: {} };
-      var application;
 
-      vwf.conf = vwfApp.conf;
-      vwf.proxy = vwfApp.proxy;
+      let connectionConf = {
+        luminary: _app.isLuminary,
+        luminaryGlobalHB: _app.isLuminaryGlobalHB,
+        luminaryGlobalHBPath: _app.luminaryGlobalHBPath
+      }
 
-      vwf.loadConfiguration(application, userLibraries, vwf.conf, compatibilityCheck);
+       //Load main VWF app
+      window.vwf = new VWF(vwfApp.conf, vwfApp.proxy, vwfApp.doc, connectionConf)
+      let userLibraries = { model: {}, view: {} };
+      let application = undefined;
 
+      vwf.loadConfiguration(application, userLibraries, null);
     })
 
   }
 
+  getLibsForConfig(conf){
+
+    //Load not ES6 standard libs before driver loads
+    //Use import in drivers by default
+
+    const confLibsDefaults = {
+      '/drivers/model/aframe':[
+          '/drivers/model/aframe/aframe-master.js',
+          '/drivers/model/aframe/addon/SkyShader.js',
+          '/drivers/model/aframe/addon/BVHLoader.js',
+          '/drivers/model/aframe/addon/TransformControls.js',
+          '/drivers/model/aframe/addon/THREE.MeshLine.js',
+          '/drivers/model/aframe/addon/three/BufferGeometryUtils.js',
+          '/drivers/model/aframe/addon/virtualgc/virtual-gamepad-controls.css',
+          '/drivers/model/aframe/addon/virtualgc/nipplejs.js',
+          '/drivers/model/aframe/addon/aframe-sun-sky.min.js',
+          '/drivers/model/aframe/extras/aframe-extras.loaders.min.js',
+          '/drivers/model/aframe/extras/aframe-extras.controls.min.js',
+          '/drivers/model/aframe/addon/aframe-teleport-controls.js',
+          '/drivers/model/aframe/kframe/aframe-aabb-collider-component.min.js',
+          '/drivers/model/aframe/addon/aframe-interpolation.js',
+          '/drivers/model/aframe/addon/aframe-components.js'
+      ],
+
+      '/drivers/view/webrtc': [
+        '/drivers/view/webrtc/adapter-latest.js'
+      ],
+      '/drivers/view/editor': [
+        '/lib/ui/cell.js',
+        '/lib/ui/treeview/treeview.min.css',
+        '/lib/ui/treeview/treeview.min.js',
+        '/lib/ui/mdc/dist/material-components-web.min.css',
+        '/lib/ui/mdc/dist/material-components-web.min.js',
+        '/lib/ui/mdc.css',
+        '/lib/ui/ace/ace.js',
+        '/lib/ui/screenfull/screenfull.min.js',
+        '/lib/ui/drag-drop.js',
+        '/lib/buffer5.6.0.min.js',
+        '/drivers/view/editor/draggabilly/draggabilly.pkgd.js',
+        '/drivers/view/editor/colorpicker/colorpicker.min.js',
+        '/drivers/view/editor/colorpicker/themes.css',
+        '/drivers/view/editor/editorLive.css'
+      ],
+        '/drivers/view/aframe-ar-driver':[
+          '/drivers/view/arjs/aframe-ar.js'
+      ],
+      '/drivers/view/lego-boost':[
+        '/drivers/view/lego-boost/bundle.js'
+      ],
+      '/drivers/view/osc':[
+        '/drivers/view/oscjs/osc-browser.min.js'
+      ]
+    }
+
+
+    var appLibs = [];
+
+    Object.keys(conf.model).concat(Object.keys(conf.view)).forEach(el => {
+      let driver = confLibsDefaults[el];
+      if(driver)
+        appLibs = appLibs.concat(confLibsDefaults[el])
+    });
+
+    return appLibs
+  }
+
+
   async loadVWF() {
 
     return loadjs([
-      '/lib/compatibilitycheck.js',
+      '/lib/async.min.js',
+      '/lib/ohm/ohm.min.js',
+      '/lib/qheap.js',
       '/lib/lively.vm_standalone.js',
-      '/lib/require.js',
       '/lib/crypto.js',
       '/lib/md5.js',
       '/lib/alea.js',
-      '/lib/mash.js',
-      '/vwf.js'
+      '/lib/mash.js'
     ], {
       async: false,
       returnPromise: true
     })
+  }
 
 
-  }
-  async loadAppLibs() {
+  async loadAppLibs(libs) {
 
-    //by default AFrame & WebRTC  driver libs loaded
-    return loadjs([
-      '/vwf/model/aframe/aframe-master.js',
-      '/vwf/model/aframe/addon/SkyShader.js',
-      '/vwf/model/aframe/addon/BVHLoader.js',
-      '/vwf/model/aframe/addon/TransformControls.js',
-      '/vwf/model/aframe/addon/THREE.MeshLine.js',
-      '/vwf/model/aframe/addon/three/BufferGeometryUtils.js',
-      '/vwf/model/aframe/addon/virtualgc/nipplejs.js',
-      '/vwf/model/aframe/addon/aframe-sun-sky.min.js',
-      '/vwf/model/aframe/extras/aframe-extras.loaders.min.js',
-      '/vwf/model/aframe/extras/aframe-extras.controls.min.js',
-      '/vwf/model/aframe/addon/aframe-teleport-controls.js',
-      '/vwf/model/aframe/kframe/aframe-aabb-collider-component.min.js',
-      '/vwf/model/aframe/addon/aframe-interpolation.js',
-      '/vwf/model/aframe/addon/aframe-components.js',
-      '/vwf/view/webrtc/adapter-latest.js',
-      '/lib/draggabilly/draggabilly.pkgd.js'
-      //'/vwf/view/arjs/aframe-ar.js' //load in aframe-ar-driver
-    ], {
+    return loadjs(libs, {
       async: false,
       returnPromise: true
     });
-
   }
 
   //get DB application state information for reflector (called from VWF)
@@ -1554,7 +1672,7 @@ class App {
 
   async saveWorld(wn) {
 
-    let name = wn ? wn : this.worldName;
+    let name = wn ? wn : _app.helpers.appPath;
 
     let proto = this.helpers.getWorldProto();
     _LCSDB.user().get('worlds').get(name).get('index_vwf_json').put(JSON.stringify(proto), res => {
@@ -1644,28 +1762,34 @@ class App {
 
   hideProgressBar() {
 
-    var progressbar = document.getElementById("load-progressbar");
-    if (progressbar) {
-      progressbar.classList.remove("visible");
-      progressbar.classList.remove("mdc-linear-progress--indeterminate");
+    NProgress.done();
+    NProgress.remove();
+    //TODO:
 
-      progressbar.classList.add("not-visible");
-      progressbar.classList.add("mdc-linear-progress--closed");
+    // var progressbar = document.getElementById("load-progressbar");
+    // if (progressbar) {
+    //   progressbar.classList.remove("visible");
+    //   progressbar.classList.remove("mdc-linear-progress--indeterminate");
 
-    }
+    //   progressbar.classList.add("not-visible");
+    //   progressbar.classList.add("mdc-linear-progress--closed");
+
+    // }
 
   }
 
   showProgressBar() {
 
-    let progressbar = document.getElementById("load-progressbar");
-    if (progressbar) {
-      progressbar.classList.remove("not-visible");
-      progressbar.classList.remove("mdc-linear-progress--closed");
+    //TODO:
+    // let progressbar = document.getElementById("load-progressbar");
+    // if (progressbar) {
+    //   progressbar.classList.remove("not-visible");
+    //   progressbar.classList.remove("mdc-linear-progress--closed");
+
+    //   progressbar.classList.add("visible");
+    //   progressbar.classList.add("mdc-linear-progress--indeterminate");
+    // }
 
-      progressbar.classList.add("visible");
-      progressbar.classList.add("mdc-linear-progress--indeterminate");
-    }
   }
 
   // SUPPORT of DELETE USER WORLDS & SAVE STATES (experimental)

+ 93 - 7
public/helpers.js → public/core/helpers.js

@@ -8,7 +8,7 @@ Virtual World Framework Apache 2.0 license  (https://github.com/NikolaySuslov/li
 class Helpers {
 
     constructor() {
-        console.log("helpers constructor");
+        //console.log("helpers constructor");
         // List of valid ID characters for use in an instance.
         this.ValidIDChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
         // List of valid extensions for VWF components.
@@ -16,6 +16,16 @@ class Helpers {
         this.applicationRoot = "/"; //app
     }
 
+
+   log(o) {
+        if(typeof o === "object" && "_" in o) {
+          const obj = {...o};
+          delete obj._;
+          return console.log(obj);
+        }
+        return console.log(o);
+      }
+
     reduceSaveObject(path) {
         let obj = Object.assign({}, path);
 
@@ -223,14 +233,31 @@ class Helpers {
         return text
     }
 
+    removePatches(obj) {
+
+        let rm = L.lazy(rec =>
+            L.ifElse(R.is(String),
+                L.when(x => x == 'patches'), [L.keysEverywhere, rec], L.optional)
+        );  //x == 'id' ||  
+
+        return L.remove(rm, obj)
+    };
+
+    replacePatchesWithIds(obj) {
+
+        let rm = L.lazy(rec =>
+            L.ifElse(R.is(String),
+                L.when(x => x == 'patches'), [L.keysEverywhere, rec], L.optional)
+        );
+        return L.modify(rm, (k) => 'id', obj)
+    };
+
+
     removeProps(obj) {
 
         let rm = L.lazy(rec =>
             L.ifElse(R.is(String),
-                L.when(x => x == 'id'
-                    || x == 'patches'
-                    || x == 'random'
-                    || x == 'sequence'), [L.keysEverywhere, rec], L.optional)
+                L.when(x => x == 'random' || x == 'sequence' || x === 'id' || x === 'patches' || x === 'childrenDeleted'), [L.keysEverywhere, rec], L.optional)
         );
 
         return L.remove(rm, obj)
@@ -288,6 +315,7 @@ class Helpers {
     }
 
     getWorldProto() {
+
         let worldID = vwf.application();
         let nodeDef = this.getNodeDef(worldID);
 
@@ -304,10 +332,14 @@ class Helpers {
         let node = vwf.getNode(nodeID, true);
         let nodeDefPure = this.removeProps(node);
         let nodeDef = this.removeGrammarObj(nodeDefPure);
-
         let finalDef = this.replaceFloatArraysInNodeDef(nodeDef);
 
         return finalDef
+        // if(param == 'withID'){
+        //     return this.replacePatchesWithIds(finalDef);
+        // }
+       
+        // return this.removePatches(finalDef)
     }
 
     replaceFloatArraysInNodeDef(state) {
@@ -342,7 +374,7 @@ class Helpers {
         };
 
 
-        let value = require("vwf/utility").transform(
+        let value = vwf.utility.transform(
             state, transitTransformation
         );
 
@@ -488,6 +520,60 @@ class Helpers {
         noty.show();
     }
 
+    remap(inMin, inMax, outMin, outMax) {
+        return function remaper(x) {
+            return (x - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
+        }
+    }
+
+    get appPath (){
+        return JSON.parse(localStorage.getItem('lcs_app')).path.public_path
+    } 
+    
+    get appName() {
+        return JSON.parse(localStorage.getItem('lcs_app')).path.application.split(".").join("_")
+    } 
+    
+    // Changing this function significantly from the GLGE code
+    // Will search hierarchy down until encountering a matching child
+    // Will look into nodes that don't match.... this might not be desirable
+        findChildByName(obj, childName, childType, recursive) {
+                    let self = this;
+                    var child = undefined;
+                    if (recursive) {
+
+                        // TODO: If the obj itself has the child name, the object will be returned by this function
+                        //       I don't think this this desirable.
+
+                        if (self.nameTest.call(this, obj, childName)) {
+                            child = obj;
+                        } else if (obj.children && obj.children.length > 0) {
+                            for (var i = 0; i < obj.children.length && child === undefined; i++) {
+                                child = FindChildByName(obj.children[i], childName, childType, true);
+                            }
+                        }
+                    } else {
+                        if (obj.children) {
+                            for (var i = 0; i < obj.children.length && child === undefined; i++) {
+                                if (self.nameTest.call(this, obj.children[i], childName)) {
+                                    child = obj.children[i];
+                                }
+                            }
+                        }
+                    }
+                    return child;
+
+                }
+
+        nameTest(obj, name) {
+                    if (obj.name == "") {
+                        return (obj.parent.name + "Child" == name);
+                    } else {
+                        return (obj.name == name || obj.id == name || obj.vwfID == name);
+                    }
+                }
+
+
 }
 
 export { Helpers } 

+ 240 - 175
public/luminary.js → public/core/luminary.js

@@ -1,6 +1,6 @@
 /*
 The MIT License (MIT)
-Copyright (c) 2014-2019 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+Copyright (c) 2014-2020 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
 */
 
 //import { Helpers } from '/helpers.js';
@@ -11,13 +11,60 @@ class Luminary {
         this.helpers = _app.helpers; //new Helpers;
         this.info = {};
         this.pendingList = [];
-        this.status = { pending: true, initialized: false, trials: 3 };
+        this.status = {
+            pending: true,
+            initialized: false
+        };
         this.clients = {};
         this.heartbeat = {}
         this.clientID = undefined;
         this.namespace = undefined;
     }
 
+    createStream() {
+        let self = this;
+
+        this.streamScheduler = M.scheduler.newDefaultScheduler();
+        const [induce, events] = M.createAdapter();
+
+        this.streamAdapter = {
+            induce: induce,
+            events: events,
+        };
+
+        const eventsStream = M.multicast(events);
+
+
+        const clientDelete = M.tap(el => {
+            console.log("Check for deletion: ", el);
+            self.deleteClient();
+        }, M.constant('delete', M.periodic(5000)));
+
+        const clientLive = M.tap(el => {
+            //console.log("LIVE: ", el);
+            _lum.get(this.namespace).get('clients').get(self.clientID).get('live').put('tick');
+        }, M.constant('live', M.periodic(500)));
+
+        const hb = M.tap(el => {
+            if (self.hbInit) {
+                self.checkForHB();
+            }
+        }, M.constant('heartbeat', M.periodic(50)));
+
+        const allStreams = M.mergeArray([eventsStream, hb, clientLive, clientDelete]);
+
+        const tapFunction = function (el) {
+            //console.log('FINAL TAP: ', el)
+        }
+
+        const finalStream = M.tap((res) => {
+            tapFunction(res);
+        }, allStreams);
+
+        M.runEffects(finalStream, this.streamScheduler);
+
+    }
+
     unsubscribeFromHeartbeat() {
         //TODO
     }
@@ -26,7 +73,9 @@ class Luminary {
 
         let self = this;
 
-        heartbeat.on(resp => {
+        heartbeat.put({
+            'tick': 0
+        }).on(resp => {
 
             var res = Gun.obj.copy(resp);
             if (res.tick) {
@@ -52,7 +101,7 @@ class Luminary {
     subscribeOnMessages() {
 
         let self = this;
-        let instance = _LCSDB.get(this.namespace);
+        let instance = _lum.get(this.namespace);
 
         instance.get('message').on(resp => {
             var res = Gun.obj.copy(resp);
@@ -85,15 +134,17 @@ class Luminary {
 
     stamp(source) {
 
+        let self = this;
+
         var message = source.tick
-        if(typeof message == "string"){
-            message  = JSON.parse(source.tick);
+        if (typeof message == "string") {
+            message = JSON.parse(source.tick);
         }
-        
 
-        // if(message.sender){
-        //     console.log("HEARTBEAT FROM: " + message.sender);
-        // }
+        if (message.sender) {
+            //console.log("HEARTBEAT FROM: " + message.sender);
+            self.heartbeat.sender = message.sender;
+        }
 
         message.state = Gun.state.is(source, 'tick');
         message.start_time = this.start_time; //Gun.state.is(source, 'start_time');
@@ -115,12 +166,12 @@ class Luminary {
         return message
     }
 
-    stampExternalMessage(msg) {
+    async stampExternalMessage(msg) {
 
         let message = Object.assign({}, msg);
         message.client = this.clientID;
 
-        let instance = _LCSDB.get(this.namespace)//_LCSDB.get(meta.namespace);
+        let instance = _lum.get(this.namespace) //_LCSDB.get(meta.namespace);
 
         if (message.result === undefined) {
 
@@ -128,7 +179,7 @@ class Luminary {
 
         } else if (message.action == "getState") {
 
-            let state = message.result;//JSON.stringify(message.result);
+            let state = message.result; //JSON.stringify(message.result);
             let toClient = message.parameters[0];
 
             let newMsg =
@@ -139,9 +190,13 @@ class Luminary {
                     explicit: toClient
                 })
 
-            instance.get('message')
-                .get('tick')
-                .put(newMsg)
+            await (new Promise(res => {
+                instance.get('message')
+                    .get('tick')
+                    .put(newMsg).once(res)
+            })).then(res => {
+                console.log("Set state")
+            })
 
         } else if (message.action === "execute") {
             console.log("!!!! execute ", message)
@@ -155,17 +210,21 @@ class Luminary {
 
             var fields = Object.assign({}, message);
 
-            vwf.private.queue.insert(fields, !fields.action); // may invoke dispatch(), so call last before returning to the host
+            if (_app.config.streamMsg) {
+                vwf.virtualTime.streamAdapter.induce(fields);
+            } else {
+                vwf.virtualTime.insert(fields, !fields.action);
+            }
+
 
         } catch (e) {
 
             vwf.logger.warn(fields.action, fields.node, fields.member, fields.parameters,
-                "exception performing action:", require("vwf/utility").exceptionMessage(e));
+                "exception performing action:", vwf.utility.exceptionMessage(e));
 
         }
     }
 
-
     async connect(path) {
 
         let self = this;
@@ -175,7 +234,7 @@ class Luminary {
         this.clientID = Gun.text.random();
         this.namespace = this.helpers.GetNamespace(path.path);
 
-        //vwf.moniker_ = clientID;  
+        //vwf.moniker_ = self.clientID;  
 
         this.info = {
             pathname: window.location.pathname.slice(1,
@@ -186,10 +245,29 @@ class Luminary {
         }
 
         //set an instance with namespace
-        let luminaryPath = _app.luminaryPath;
-        let lum = _LCSDB.get(luminaryPath);
 
-        let instance = _LCSDB.get(this.namespace);
+
+        if (_app.config.multisocket) {
+            let luminaryPath = _app.luminaryPath
+
+            if (luminaryPath) {
+                window._lum = Gun({
+                    peers: [luminaryPath + "/" + path.path.instance],
+                    musticast: false,
+                    localStorage: false,
+                    radisk: false,
+                    file: false
+                });
+            }
+
+        } else {
+            window._lum = _LCSDB;
+        }
+
+        self.createStream();
+
+        let instance = _lum.get(this.namespace);
+        //_lum.get('instances').set(instance);
 
         instance.not(function (res) {
             instance
@@ -197,25 +275,38 @@ class Luminary {
                 .put({
                     'start_time': 'start_time',
                     'rate': 1
+                    //'message':{}
                 });
-            lum.get('instances').set(instance);
+            _lum.get('instances').set(instance);
             self.status.initialized = "first";
         });
 
-        await instance.once(res => {
+
+        await (new Promise(res => {
+            instance.once(res)
+        })).then(res => {
             self.start_time = Gun.state.is(res, 'start_time');
             self.rate = res.rate;
-        }).promOnce();
+        })
+
+        let client = _lum.get(self.clientID).put({});
+        await (new Promise(res => {
+            instance.get('clients').set(client).once(res)
+        })).then(r => {
+            instance.get('clients').get(self.clientID).put({
+                id: self.clientID,
+                instance: self.namespace,
+                user: path.user
+            });
+        });
 
+        _lum.get('allclients').set(client);
 
-        let client = _LCSDB.get(self.clientID).put({ id: self.clientID, instance: self.namespace, user: path.user }).once(res => {
+        await (new Promise(res => {
+            _lum.get(self.clientID).once(res)
+        })).then(res => {
             self.setStateTime = Gun.state.is(res, 'id');
-            setInterval(function () {
-                client.get('live').put('tick');
-            }, 500);
-        });
-        instance.get('clients').set(client);
-        lum.get('allclients').set(client);
+        })
 
         instance.get('clients').map().on(res => {
             if (res) {
@@ -235,7 +326,7 @@ class Luminary {
                         self.clients[res.id].live = clientTime
                     }
 
-                    if (self.status.initialized == "first"  && self.setStateTime) {
+                    if (self.status.initialized == "first" && self.setStateTime) {
 
                         self.status.initialized = true;
                         instance
@@ -246,67 +337,41 @@ class Luminary {
                                 self.start_time = Gun.state.is(res, 'start_time');
                                 self.rate = res.rate;
 
-                                if (!_app.isLuminaryGlobalHB) {
-                                    let tickMsg = {
-                                        parameters: "[]",
-                                        time: 'tick', //hb
-                                        sender: self.clientID
-                                    };
-                                    instance.get('heartbeat').get('tick').put(tickMsg);
-                                    self.initHeartBeat();
+                                if (!vwf.isLuminaryGlobalHB) {
+                                    self.hbInit = true;
                                 }
-
                                 self.initFirst(res);
-                                self.initDeleteClient();
-
-
-
                             });
 
-                        let noty = new Noty({
-                            text: "FIRST CLIENT",
-                            timeout: 1000,
-                            theme: 'mint',
-                            layout: 'bottomRight',
-                            type: 'success'
-                        });
+                        _app.helpers.notyOK("FIRST CLIENT");
 
-                        noty.show();
                     } else if (!self.status.initialized && self.setStateTime) {
 
-                        if (res.id == self.clientID && self.status.trials > 0) {
-                            self.status.trials = self.status.trials - 1;
-                            console.log("CONNECTION TRIALS FOR: " + res.id + ' - ' + self.status.trials);
-                        } else if (res.id !== self.clientID && self.clients[res.id].live - self.clients[res.id].old < 1000) {
+                        // if (self.clients.length == 1 && res.id == self.clientID){
+                        //     console.log("THAT'S ME and ONLY ONE");
+                        //             //request for the new instance 
+                        //     let path = JSON.parse(self.info.path);
+                        //     window.location.pathname = path.user + path.path["public_path"];
+
+                        // }
+                        // else 
+                        if (res.id == self.clientID) {
+                            console.log("THAT'S ME");
+                        } else if (self.clients[res.id].live - self.clients[res.id].old == 0 || self.clients[res.id].live - self.clients[res.id].old > 1000) {
+                            console.log("OLD CLIENT!");
+                        } else {
                             console.log("REQUEST STATE FROM: " + res.id);
 
                             self.status.initialized = true;
 
-                            if (!_app.isLuminaryGlobalHB) {
-                                self.initHeartBeat();
+                            if (!vwf.isLuminaryGlobalHB) {
+                                self.hbInit = true;
                             }
 
                             self.initOtherClient(res);
-                            self.initDeleteClient();
-
-
-                            let noty = new Noty({
-                                text: "CONNECTING TO EXISTED CLIENT...",
-                                timeout: 1000,
-                                theme: 'mint',
-                                layout: 'bottomRight',
-                                type: 'success'
-                            });
-
-                            noty.show();
-
 
-                        } else if (res.id == self.clientID && self.status.trials == 0) {
-                            console.log("INITIALIZE WORLD FOR: " + res.id);
+                            _app.helpers.notyOK("CONNECTING TO EXISTED CLIENT...");
 
-                            //request for the new instance 
-                            let path = JSON.parse(self.info.path);
-                            window.location.pathname = path.user + path.path["public_path"];
                         }
                     }
                 }
@@ -332,26 +397,26 @@ class Luminary {
     }
 
 
-    clientsMessage() {
+    clientMessage() {
 
         let self = this;
 
-        let clientDescriptor = { extends: "proxy/client.vwf" };
-        let clientNodeMessage =
-        {
+        let clientDescriptor = {
+            extends: "proxy/client.vwf"
+        };
+        let clientNodeMessage = {
             action: "createChild",
             parameters: ["proxy/clients.vwf", self.clientID, clientDescriptor],
             time: 'tick'
         }
 
         return clientNodeMessage
-
     }
 
-    initFirst(ack) {
+    async initFirst(ack) {
 
         let self = this;
-        let instance = _LCSDB.get(self.namespace);
+        let instance = _lum.get(self.namespace);
 
         let clientMsg =
             JSON.stringify({
@@ -374,34 +439,36 @@ class Luminary {
                 explicit: self.clientID
             })
 
+        await (new Promise(res => {
+            instance.get('message').put({})
+                .get('tick')
+                .put(clientMsg).once(res)
+        })).then(res => {
+            return new Promise(r => instance.get('message')
+                .get('tick')
+                .put(appMsg).once(r))
+        }).then(r => {
 
-        instance.get('message')
-            .get('tick')
-            .put(clientMsg);
-
-        instance.get('message')
-            .get('tick')
-            .put(appMsg, res => {
+            self.status.pending = false;
 
-                self.status.pending = false;
+            let clientMessage = self.clientMessage();
+            instance.get('message')
+                .get('tick')
+                .put(JSON.stringify(clientMessage), res => {
+                    console.log("CREATE CLIENT: - " + res);
+                })
 
-                let clientsMessage = self.clientsMessage();
-                instance.get('message')
-                    .get('tick')
-                    .put(JSON.stringify(clientsMessage), res => {
-                        console.log("CREATE CLIENT: - " + res);
-                    })
-            });
+        })
 
     }
 
 
-    initOtherClient(ack) {
+    async initOtherClient(ack) {
 
         console.log('new other client');
 
         let self = this;
-        let instance = _LCSDB.get(self.namespace);
+        let instance = _lum.get(self.namespace);
 
         let masterID = ack.id;
 
@@ -414,104 +481,102 @@ class Luminary {
                 parameters: [self.clientID]
             })
 
-        instance.get('message')
-            .get('tick')
-            .put(msg);
 
-        let clientsMessage = self.clientsMessage();
+        await (new Promise(res => {
+            instance.get('message')
+                .get('tick')
+                .put(msg).once(res)
+        })).then(res => {
 
-        instance.get('message')
-            .get('tick').put(JSON.stringify(clientsMessage), res=>{
-              
-                    console.log("CREATE CLIENT: - " + res);
-            });
+            let clientMessage = JSON.stringify(self.clientMessage());
 
-    }
+            return new Promise(r =>
+                instance.get('message')
+                .get('tick')
+                .put(clientMessage).once(r))
+        }).then(r => {
 
+            console.log("CREATE CLIENT: - " + r);
+
+        })
+
+
+    }
 
-    initHeartBeat() {
 
+    makeHB() {
         let self = this;
-        let instance = _LCSDB.get(self.namespace);
 
-        setInterval(function () {
+        //console.log("HeartBeat");
 
-            let message = {
-                parameters: "[]",
-                time: 'tick', //hb
-                sender: self.clientID
+        let message = {
+            parameters: "[]",
+            time: 'tick', //hb
+            sender: self.clientID
 
-            };
+        };
 
-            instance.get('heartbeat').get('tick').once(data => {
-                if (data) {
+        _lum.get(self.namespace).get('heartbeat').get('tick').put(JSON.stringify(message), function (ack) {
+            if (ack.err) {
+                //console.log('ERROR: ' + ack.err)
+            }
+        });
 
-                    //let res = JSON.parse(data);
+    }
 
-                    var res = data
-                    if(typeof res == "string"){
-                        res = JSON.parse(data);
-                    }
+    checkForHB() {
 
-                    if (res.sender) {
+        let self = this;
 
-                        let now = Gun.time.is();
-                        let diff = now - self.heartbeat.lastTick;
-                        if ((Object.keys(self.clients).length == 1)
-                            || (res.sender == self.clientID && diff < 1000)
-                            || (res.sender !== self.clientID && diff > 1000)) {
+        let sender = self.heartbeat.sender;
 
-                            //console.log("TICK FROM" + self.clientID);    
-                            instance.get('heartbeat').get('tick').put(message, function (ack) {
-                                if (ack.err) {
-                                    console.log('ERROR: ' + ack.err)
-                                }
-                            });
-                        }
-                    }
-                }
-            })
-        }, 50);
-    }
+        let now = Gun.time.is();
+        let diff = now - self.heartbeat.lastTick;
 
 
-    initDeleteClient() {
+        if ((Object.keys(self.clients).length == 1) ||
+            (sender == self.clientID && diff < 1000) ||
+            (sender !== self.clientID && diff > 1000)) {
+
+            self.makeHB()
+        }
+    }
+
+    deleteClient() {
 
         let self = this;
-        let instance = _LCSDB.get(self.namespace);
+        let instance = _lum.get(self.namespace);
 
-        setInterval(function () {
-            Object.keys(self.clients).forEach(el => {
-                let current = Gun.time.is();
+        Object.keys(self.clients).forEach(el => {
+            let current = Gun.time.is();
 
-                if (el !== self.clientID) {
-                    if (current - self.clients[el].live > 10000) {
-                        console.log("CLIENT DISCONECTED : " + el);
+            if (el !== self.clientID) {
+                if (current - self.clients[el].live > 10000) {
+                    console.log("CLIENT DISCONECTED : " + el);
 
-                        let clientDeleteMessage =
-                        {
-                            action: "deleteChild",
-                            parameters: ["proxy/clients.vwf", el],
-                            time: 'tick'
-                        };
+                    let clientDeleteMessage = {
+                        action: "deleteChild",
+                        parameters: ["proxy/clients.vwf", el],
+                        time: 'tick'
+                    };
 
+                    new Promise(res => {
                         instance.get('message')
-                            .get('tick').once(res => {
-                                instance.get('message')
-                                    .get('tick')
-                                    .put(JSON.stringify(clientDeleteMessage), res => {
-
-                                        instance.get('clients').get(el).put(null);
-                                        delete self.clients[el];
-                                    })
-                            })
-                    }
-                }
-            })
-        }, 5000);
-    }
+                            .get('tick')
+                            .put(JSON.stringify(clientDeleteMessage)).once(res)
+                    }).then(res => {
 
+                        instance.get('clients').get(el).put(null);
+                        delete self.clients[el];
 
+                    })
+                }
+            }
+
+        })
+    }
 }
 
-export { Luminary }
+export {
+    Luminary
+}

+ 21 - 40
public/reflector-client.js → public/core/reflectorClient.js

@@ -5,12 +5,12 @@ Copyright (c) 2014-2019 Nikolai Suslov and the Krestianstvo.org project contribu
 Virtual World Framework Apache 2.0 license  (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
 */
 
-//import { Helpers } from '/helpers.js';
+import { Helpers } from '/core/helpers.js';
 
 class ReflectorClient {
     constructor() {
         console.log("reflector client constructor");
-        this.helpers = _app.helpers; //new Helpers;
+        this.helpers = new Helpers;
         this.socket = undefined;
     }
 
@@ -63,34 +63,7 @@ class ReflectorClient {
         this.socket = io.connect( host, options );
                 
 
-            } else {  // Ruby Server -- only supports socket.io 0.6
-
-                io.util.merge( options, {
-
-                    // For socket.io 0.6, specify the port since the default isn't correct when
-                    // using https.
-
-                    port: window.location.port ||
-                        ( window.location.protocol === "https:" ? 443 : 80 ),
-
-                    // The ruby socket.io server only supports WebSockets. Don't try the others.
-
-                    transports: [
-                        'websocket',
-                    ],
-
-                    // Increase the timeout because of starvation while loading the scene. The
-                    // server timeout must also be increased. (For socket.io 0.7+, the client
-                    // timeout is controlled by the server.)
-
-                    transportOptions: {
-                        "websocket": { timeout: 90000 },
-                    },
-
-                } );
-
-             this.socket = io.connect( undefined, options );
-            }
+            } 
 
         } catch ( e ) {
 
@@ -99,18 +72,18 @@ class ReflectorClient {
             // Start a timer to monitor the incoming queue and dispatch the messages as though
             // they were received from the server.
 
-            vwf.dispatch();
+        //    this.dispatch();
 
-            setInterval( function() {
+        //     setInterval( function() {
 
-                var fields = {
-                    time: vwf.now + 0.010, // TODO: there will be a slight skew here since the callback intervals won't be exactly 10 ms; increment using the actual delta time; also, support play/pause/stop and different playback rates as with connected mode.
-                    origin: "reflector",
-                };
+        //         var fields = {
+        //             time: vwf.now + 0.010, // TODO: there will be a slight skew here since the callback intervals won't be exactly 10 ms; increment using the actual delta time; also, support play/pause/stop and different playback rates as with connected mode.
+        //             origin: "reflector",
+        //         };
 
-                vwf.private.queue.insert( fields, true ); // may invoke dispatch(), so call last before returning to the host
+        //         _app.virtualTime.insert( fields, true ); // may invoke dispatch(), so call last before returning to the host
 
-            }, 10 );
+        //     }, 10 );
 
         }
 
@@ -164,7 +137,15 @@ class ReflectorClient {
                     // Update the queue.  Messages in the queue are ordered by time, then by order of arrival.
                     // Time is only advanced if the message has no action, meaning it is a tick.
 
-                    vwf.private.queue.insert( fields, !fields.action ); // may invoke dispatch(), so call last before returning to the host
+                    if(_app.config.streamMsg) {
+                        vwf.virtualTime.streamAdapter.induce(fields);
+                    } else {
+                        vwf.virtualTime.insert( fields, !fields.action ); 
+                    }
+                    
+                    //
+                    
+                    // may invoke dispatch(), so call last before returning to the host
 
                     // Each message from the server allows us to move time forward. Parse the
                     // timestamp from the message and call dispatch() to execute all queued
@@ -177,7 +158,7 @@ class ReflectorClient {
                 } catch ( e ) {
 
                     vwf.logger.warn( fields.action, fields.node, fields.member, fields.parameters,
-                        "exception performing action:", require( "vwf/utility" ).exceptionMessage( e ) );
+                        "exception performing action:", vwf.utility.exceptionMessage( e ) );
 
                 }
 

+ 696 - 0
public/core/virtualTime.js

@@ -0,0 +1,696 @@
+/*
+The MIT License (MIT)
+Copyright (c) 2014-2020 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+
+Virtual World Framework Apache 2.0 license  (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
+*/
+
+
+import { Helpers } from '/core/helpers.js';
+import { Utility } from '/core/vwf/utility/utility.js';
+
+
+class VirtualTime {
+
+    constructor() {
+        console.log("Virtual Time constructor");
+        this.helpers = new Helpers;
+        this.utility = new Utility;
+
+        /// The simulation clock, which contains the current time in seconds. Time is controlled by
+        /// the reflector and updates here as we receive control messages.
+        /// 
+        /// @name module:vwf.now
+        /// 
+        /// @private
+
+        this.now = 0;
+
+        /// The queue's sequence number for the currently executing action.
+        /// 
+        /// The queue enumerates actions in order of arrival, which is distinct from execution order
+        /// since actions may be scheduled to run in the future. `sequence_` can be used to
+        /// distinguish between actions that were previously placed on the queue for execution at a
+        /// later time, and those that arrived after the current action, regardless of their
+        /// scheduled time.
+        /// 
+        /// @name module:vwf.sequence_
+        /// 
+        /// @private
+
+        this.sequence_ = undefined
+
+        /// The moniker of the client responsible for the currently executing action. `client_` will
+        /// be falsy for actions originating in the server, such as time ticks.
+        /// 
+        /// @name module:vwf.client_
+        /// 
+        /// @private
+
+        this.client_ = undefined
+
+        ///// From queue props
+
+             /// Current time as provided by the reflector. Messages to be executed at this time or
+            /// earlier are available from #pull.
+            /// 
+            /// @name module:vwf~queue.time
+
+        this.time = 0
+
+        /// Suspension count. Queue processing is suspended when suspension is greater than 0.
+        /// 
+        /// @name module:vwf~queue.suspension
+
+        this.suspension = 0
+
+        /// Sequence counter for tagging messages by order of arrival. Messages are sorted by
+        /// time, origin, then by arrival order.
+        /// 
+        /// @name module:vwf~queue.sequence
+
+        this.sequence = 0
+
+        /// Array containing the messages in the queue.
+        /// 
+        /// @name module:vwf~queue.queue
+
+        this.queue = new Heap({
+            compar: this.queueSort
+        }) //[]
+
+        //Add Stream support
+        this.initReflectorStream();
+
+    }
+
+    initReflectorStream() {
+        const self = this;
+
+        this.streamDelay = 0;
+        this.streamDefaultScheduler = M.scheduler.newDefaultScheduler();
+        //this.streamDefaultScheduler = M.scheduler.schedulerRelativeTo(self.startTime, this.streamDefaultS);
+        //this.streamScheduler = M.scheduler.newDefaultScheduler();
+        const [induce, events] = M.createAdapter();
+
+        this.streamAdapter = {
+            induce: induce,
+            events: events,
+        };
+
+        const tapFunction = function (res) {
+            let mostTime =
+                M.scheduler.currentTime(self.streamDefaultScheduler) / 1000;
+
+            if (_app.config.streamMsg) {
+                console.log('STREAM: ', res, ' TIME: ', mostTime);
+                self.insert(res, !res.action);
+            }
+
+        };
+
+        this.reflectorStream = M.multicast(events); //M.concatMap((x) => M.fromPromise(x()), events);
+        const resultStream = M.concatMap(el => {
+            return M.delay(this.streamDelay, M.now(el))
+        }, this.reflectorStream);
+
+        // M.delay(5000, this.reflectorStream)
+
+        this.eventsStream = M.tap((res) => {
+            tapFunction(res);
+        }, resultStream);
+        M.runEffects(this.eventsStream, this.streamDefaultScheduler);
+    }
+
+
+         /// Insert a message or messages into the queue. Optionally execute the simulation
+            /// through the time marked on the message.
+            /// 
+            /// When chronic (chron-ic) is set, vwf#dispatch is called to execute the simulation up
+            /// through the indicated time. To prevent actions from executing out of order, insert
+            /// should be the caller's last operation before returning to the host when invoked with
+            /// chronic.
+            /// 
+            /// @name module:vwf~queue.insert
+            /// 
+            /// @param {Object|Object[]} fields
+            /// @param {Boolean} [chronic]
+
+
+    insert(fields, chronic) {
+
+        var messages = fields instanceof Array ? fields : [fields];
+
+        messages.forEach(function (fields) {
+
+            // if ( fields.action ) {  // TODO: don't put ticks on the queue but just use them to fast-forward to the current time (requires removing support for passing ticks to the drivers and nodes)
+
+            fields.sequence = ++this.sequence; // track the insertion order for use as a sort key
+            this.queue.insert(fields);
+
+            // }
+
+            if (chronic) {
+                this.time = Math.max(this.time, fields.time); // save the latest allowed time for suspend/resume
+            }
+
+        }, this);
+
+        //Sort here (now in Heap)
+
+        if (chronic) {
+            this.dispatch();
+        }
+
+    }
+
+
+         /// Pull the next message from the queue.
+            /// 
+            /// @name module:vwf~queue.pull
+            /// 
+            /// @returns {Object|undefined} The next message if available, otherwise undefined.
+
+    pull() {
+
+        if (this.suspension == 0 && this.queue.length > 0 && this.queue.peek().time <= this.time) {
+            return this.queue.shift();
+        }
+
+    }
+
+         /// Update the queue to include only the messages selected by a filtering function.
+            /// 
+            /// @name module:vwf~queue.filter
+            /// 
+            /// @param {Function} callback
+            ///   `filter` calls `callback( fields )` once for each message in the queue. If
+            ///   `callback` returns a truthy value, the message will be retained. Otherwise it will
+            ///   be removed from the queue.
+
+
+    filter(callback /* fields */ ) {
+
+        // this.queue = this.queue.filter( callback );
+        let filtered = this.queue._list.slice().filter(callback);
+        //this.queue._list = this.queue._list.filter(callback);
+        this.queue = new Heap({
+            compar: this.queueSort
+        });
+        filtered.map(el => {
+            this.queue.insert(el);
+        });
+
+    }
+
+    filterQueue() {
+        this.filter(function (fields) {
+
+            if ((fields.origin === "reflector") && fields.sequence > vwf.virtualTime.sequence_) {
+                return true;
+            } else {
+                vwf.logger.debugx("setState", function () {
+                    return ["removing", JSON.stringify(loggableFields(fields)), "from queue"];
+                });
+            }
+
+        })
+    }
+
+      /// Suspend message execution.
+            /// 
+            /// @name module:vwf~_app.virtualTime.suspend
+            /// 
+            /// @returns {Boolean} true if the queue was suspended by this call.
+
+    suspend(why) {
+
+        if (this.suspension++ == 0) {
+            vwf.logger.infox("-queue#suspend", "suspending queue at time", this.now, why ? why : "");
+            return true;
+        } else {
+            vwf.logger.debugx("-queue#suspend", "further suspending queue at time", this.now, why ? why : "");
+            return false;
+        }
+
+    }
+
+     /// Resume message execution.
+            ///
+            /// vwf#dispatch may be called to continue the simulation. To prevent actions from
+            /// executing out of order, resume should be the caller's last operation before
+            /// returning to the host.
+            /// 
+            /// @name module:vwf~_app.virtualTime.resume
+            /// 
+            /// @returns {Boolean} true if the queue was resumed by this call.
+
+
+    resume(why) {
+
+        if (--this.suspension == 0) {
+            vwf.logger.infox("-queue#resume", "resuming queue at time", this.now, why ? why : "");
+            this.dispatch();
+            return true;
+        } else {
+            vwf.logger.debugx("-queue#resume", "partially resuming queue at time", this.now, why ? why : "");
+            return false;
+        }
+
+    }
+
+      //     /// Return the ready state of the queue.
+        //     /// 
+        //     /// @name module:vwf~queue.ready
+        //     /// 
+        //     /// @returns {Boolean}
+
+
+    ready() {
+        return this.suspension == 0;
+    }
+
+    queueSort(a, b) {
+
+        // Sort by time, then future messages ahead of reflector messages, then by sequence.  // TODO: we probably want a priority queue here for better performance
+        // 
+        // The sort by origin ensures that the queue is processed in a well-defined order
+        // when future messages and reflector messages share the same time, even if the
+        // reflector message has not arrived at the client yet.
+        // 
+        // The sort by sequence number ensures that the messages remain in their arrival
+        // order when the earlier sort keys don't provide the order.
+
+        // Execute the simulation through the new time.
+
+        // To prevent actions from executing out of order, callers should immediately return
+        // to the host after invoking insert with chronic set.
+
+
+        if (a.time != b.time) {
+            return a.time - b.time;
+        } else if (a.origin != "reflector" && b.origin == "reflector") {
+            return -1;
+        } else if (a.origin == "reflector" && b.origin != "reflector") {
+            return 1;
+        } else {
+            return a.sequence - b.sequence;
+        }
+
+    }
+
+
+    // -- queueTransitTransformation -----------------------------------------------------------
+
+        /// vwf/utility/transform() transformation function to convert the message queue for proper
+        /// JSON serialization.
+        /// 
+        /// queue: [ { ..., parameters: [ [ arguments ] ], ... }, { ... }, ... ]
+        /// 
+        /// @name module:vwf~queueTransitTransformation
+
+
+    queueTransitTransformation(object, names, depth) {
+
+        let self = this
+
+        if (depth == 0) {
+
+            // Omit any private direct messages for this client, then sort by arrival order
+            // (rather than by time) so that messages will retain the same arrival order when
+            // reinserted.
+
+            return object.filter(el => el !== 0).filter(function (fields) {
+                return !(fields.origin === "reflector" && fields.sequence > vwf.virtualTime.sequence_) && fields.action; // TODO: fields.action is here to filter out tick messages  // TODO: don't put ticks on the queue but just use them to fast-forward to the current time (requires removing support for passing ticks to the drivers and nodes)
+            }).sort(function (fieldsA, fieldsB) {
+                return fieldsA.sequence - fieldsB.sequence;
+            });
+
+        } else if (depth == 1) {
+
+            // Remove the sequence fields since they're just local annotations used to keep
+            // messages ordered by insertion order and aren't directly meaniful outside of this
+            // client.
+
+            var filtered = {};
+
+            Object.keys(object).filter(function (key) {
+                return key != "sequence";
+            }).forEach(function (key) {
+                filtered[key] = object[key];
+            });
+
+            return filtered;
+
+        }
+
+        return object;
+    }
+
+
+    get stateQueue() {
+        return {
+            time: this.time,
+            queue: this.utility.transform(this.queue._list, this.queueTransitTransformation),
+        }
+
+    }
+
+    // -- dispatch -----------------------------------------------------------------------------
+
+    /// Dispatch incoming messages waiting in the queue. "currentTime" specifies the current
+    /// simulation time that we should advance to and was taken from the time stamp of the last
+    /// message received from the reflector.
+    /// 
+    /// @name module:vwf.dispatch
+
+    dispatch() {
+
+        var fields;
+
+        // Actions may use receive's ready function to suspend the queue for asynchronous
+        // operations, and to resume it when the operation is complete.
+
+        while (fields = /* assignment! */ this.pull()) {
+
+            // Advance time to the message time.
+
+            if (this.now != fields.time) {
+                this.sequence_ = undefined; // clear after the previous action
+                this.client_ = undefined; // clear after the previous action
+                this.now = fields.time;
+                this.tock();
+            }
+
+            // Perform the action.
+
+            if (fields.action) { // TODO: don't put ticks on the queue but just use them to fast-forward to the current time (requires removing support for passing ticks to the drivers and nodes)
+                this.sequence_ = fields.sequence; // note the message's queue sequence number for the duration of the action
+                this.client_ = fields.client; // ... and note the originating client
+                this.receive(fields.node, fields.action, fields.member, fields.parameters, fields.respond, fields.origin);
+            } else {
+                this.tick();
+            }
+
+        }
+
+        // Advance time to the most recent time received from the server. Tick if the time
+        // changed.
+
+        if (this.ready() && this.now != this.time) {
+            this.sequence_ = undefined; // clear after the previous action
+            this.client_ = undefined; // clear after the previous action
+            this.now = this.time;
+            this.tock();
+        }
+
+    }
+
+    // -- plan ---------------------------------------------------------------------------------
+
+    /// @name module:vwf.plan
+
+    plan(nodeID, actionName, memberName, parameters, when, callback_async /* ( result ) */ ) {
+
+        vwf.logger.debuggx("plan", nodeID, actionName, memberName,
+            parameters && parameters.length, when, callback_async && "callback");
+
+        var time = when > 0 ? // absolute (+) or relative (-)
+            Math.max(this.now, when) :
+            this.now + (-when);
+
+        var fields = {
+            time: time,
+            node: nodeID,
+            action: actionName,
+            member: memberName,
+            parameters: parameters,
+            client: this.client_, // propagate originating client
+            origin: "future",
+            // callback: callback_async,  // TODO
+        };
+
+        this.insert(fields);
+
+        vwf.logger.debugu();
+    }
+
+    // -- send ---------------------------------------------------------------------------------
+
+    /// Send a message to the reflector. The message will be reflected back to all participants
+    /// in the instance.
+    /// 
+    /// @name module:vwf.send
+
+    send(nodeID, actionName, memberName, parameters, when, callback_async /* ( result ) */ ) {
+
+        vwf.logger.debuggx("send", nodeID, actionName, memberName,
+            parameters && parameters.length, when, callback_async && "callback"); // TODO: loggableParameters()
+
+        var time = when > 0 ? // absolute (+) or relative (-)
+            Math.max(this.now, when) :
+            this.now + (-when);
+
+        // Attach the current simulation time and pack the message as an array of the arguments.
+
+        var fields = {
+            time: time,
+            node: nodeID,
+            action: actionName,
+            member: memberName,
+            parameters: this.utility.transform(parameters, this.utility.transforms.transit),
+            // callback: callback_async,  // TODO: provisionally add fields to queue (or a holding queue) then execute callback when received back from reflector
+        };
+
+        if (vwf.isLuminary) {
+
+            vwf.luminary.stampExternalMessage(fields);
+
+        } else if (vwf.reflectorClient.socket) {
+
+            // Send the message.
+            var message = JSON.stringify(fields);
+            vwf.reflectorClient.socket.send(message);
+
+        }
+        // else {
+
+        //     // In single-user mode, loop the message back to the incoming queue.
+
+        //     fields.client =  vwf.moniker_; // stamp with the originating client like the reflector does
+        //     fields.origin = "reflector";
+
+        //     _app.virtualTime.insert( fields );
+
+        // }
+
+        vwf.logger.debugu();
+    }
+
+    // get queue () { // vwf.private.queue
+
+    // }
+
+    // -- respond ------------------------------------------------------------------------------
+
+    /// Return a result for a function invoked by the server.
+    /// 
+    /// @name module:vwf.respond
+
+    respond(nodeID, actionName, memberName, parameters, result) {
+
+        vwf.logger.debuggx("respond", nodeID, actionName, memberName,
+            parameters && parameters.length, "..."); // TODO: loggableParameters(), loggableResult()
+
+        // Attach the current simulation time and pack the message as an array of the arguments.
+
+        var fields = {
+            // sequence: undefined,  // TODO: use to identify on return from reflector?
+            time: this.now,
+            node: nodeID,
+            action: actionName,
+            member: memberName,
+            parameters: this.utility.transform(parameters, this.utility.transforms.transit),
+            result: this.utility.transform(result, this.utility.transforms.transit)
+        };
+
+        if (vwf.isLuminary) {
+
+            vwf.luminary.stampExternalMessage(fields);
+
+        } else if (vwf.reflectorClient.socket) {
+
+            // Send the message.
+
+            var message = JSON.stringify(fields);
+            vwf.reflectorClient.socket.send(message);
+
+        } else {
+
+            // Nothing to do in single-user mode.
+
+        }
+
+        vwf.logger.debugu();
+    }
+
+    // -- receive ------------------------------------------------------------------------------
+
+    /// Handle receipt of a message. Unpack the arguments and call the appropriate handler.
+    /// 
+    /// @name module:vwf.receive
+
+    receive(nodeID, actionName, memberName, parameters, respond, origin) {
+
+        // origin == "reflector" ?
+        //     this.logger.infogx( "receive", nodeID, actionName, memberName,
+        //         parameters && parameters.length, respond, origin ) :
+        //     this.logger.debuggx( "receive", nodeID, actionName, memberName,
+        //         parameters && parameters.length, respond, origin );
+
+        // TODO: delegate parsing and validation to each action.
+
+        // Look up the action handler and invoke it with the remaining parameters.
+
+        // Note that the message should be validated before looking up and invoking an arbitrary
+        // handler.
+
+        var args = [],
+            result;
+
+        if (nodeID || nodeID === 0) args.push(nodeID);
+        if (memberName) args.push(memberName);
+        if (parameters) args = args.concat(parameters); // flatten
+
+        if (actionName == 'createChild') {
+            console.log("create child!");
+            // args.push(function(childID)
+            // {
+            //     //when creating over the reflector, call ready on heirarchy after create.
+            //     //nodes from setState are readied in createNode
+            //     // vwf.decendants(childID).forEach(function(i){
+            //     //     vwf.callMethod(i,'ready',[]);
+            //     // });
+            //     // vwf.callMethod(childID,'ready',[]);
+            //     console.log("create child!");
+            // });
+        }
+
+        // Invoke the action.
+
+        // if (environment(actionName, parameters)) {
+        //     require("vwf/configuration").environment = environment(actionName, parameters);
+        // } else 
+        if (origin !== "reflector" || !nodeID || vwf.private.nodes.existing[nodeID]) {
+            result = vwf[actionName] && vwf[actionName].apply(vwf, args);
+        } else {
+            vwf.logger.debugx("receive", "ignoring reflector action on non-existent node", nodeID);
+            result = undefined;
+        }
+
+        // Return the result.
+
+        respond && this.respond(nodeID, actionName, memberName, parameters, result);
+
+        // origin == "reflector" ?
+        //     this.logger.infou() : this.logger.debugu();
+
+
+        /// The reflector sends a `setState` action as part of the application launch to pass
+        /// the server's execution environment to the client. A `setState` action isn't really
+        /// appropriate though since `setState` should be the last part of the launch, whereas
+        /// the environment ought to be set much earlier--ideally before the kernel loads.
+        /// 
+        /// Executing the `setState` as received would overwrite any configuration settings
+        /// already applied by the application. So instead, we detect this particular message
+        /// and only use it to update the environment in the configuration object.
+        /// 
+        /// `environment` determines if a message is the reflector's special pre-launch
+        /// `setState` action, and if so, and if the application hasn't been created yet,
+        /// returns the execution environment property.
+
+        // function environment(actionName, param) {
+
+        //     if (actionName === "setState" && !vwf.application()) {
+
+        //         var parameters = param;
+
+        //         if (parameters[0].init) {
+        //             parameters = [JSON.parse(localStorage.getItem('lcs_app')).saveObject]
+        //         }
+
+        //         var applicationState = parameters && parameters[0];
+
+        //         if (applicationState && Object.keys(applicationState).length === 1 &&
+        //             applicationState.configuration && Object.keys(applicationState.configuration).length === 1) {
+        //             return applicationState.configuration.environment;
+        //         }
+
+        //     }
+
+        //     return undefined;
+        // }
+
+    }
+
+
+    // -- tick ---------------------------------------------------------------------------------
+
+    /// Tick each tickable model, view, and node. Ticks are sent on each reflector idle message.
+    /// 
+    /// @name module:vwf.tick
+
+    // TODO: remove, in favor of drivers and nodes exclusively using future scheduling;
+    // TODO: otherwise, all clients must receive exactly the same ticks at the same times.
+
+    tick() {
+
+        // Call ticking() on each model.
+
+        vwf.models.forEach(function (model) {
+            model.ticking && model.ticking(this.now); // TODO: maintain a list of tickable models and only call those
+        }, vwf);
+
+        // Call ticked() on each view.
+
+        vwf.views.forEach(function (view) {
+            view.ticked && view.ticked(this.now); // TODO: maintain a list of tickable views and only call those
+        }, vwf);
+
+        // Call tick() on each tickable node.
+
+        vwf.tickable.nodeIDs.forEach(function (nodeID) {
+            vwf.callMethod(nodeID, "tick", [this.now]);
+        }, vwf);
+
+    };
+
+    // -- tock ---------------------------------------------------------------------------------
+
+    /// Notify views of a kernel time change. Unlike `tick`, `tock` messages are sent each time
+    /// that time moves forward. Only view drivers are notified since the model state should be
+    /// independent of any particular sequence of idle messages.
+    /// 
+    /// @name module:vwf.tock
+
+    tock() {
+
+        // Call tocked() on each view.
+
+        vwf.views.forEach(function (view) {
+            view.tocked && view.tocked(this.now);
+        }, vwf);
+
+    }
+
+
+    get getNow() {
+        return this.now
+    }
+
+
+}
+
+export {
+    VirtualTime
+}

+ 6479 - 0
public/core/vwf.js

@@ -0,0 +1,6479 @@
+/*
+The MIT License (MIT)
+Copyright (c) 2014-2020 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+
+Virtual World Framework Apache 2.0 license  (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
+*/
+
+/// @module vwf
+
+/// vwf.js is the main Virtual World Framework manager. It is constructed as a JavaScript ES6 module
+/// to isolate it from the rest of the  page's JavaScript environment. The vwf module self-creates its own instance when loaded and
+/// attaches to the global window object as window.vwf. Nothing else should affect the global environment.
+
+import { Helpers } from '/core/helpers.js';
+import { VirtualTime } from '/core/virtualTime.js';
+import { ReflectorClient } from '/core/reflectorClient.js';
+import { Luminary } from '/core/luminary.js';
+
+import { Logger } from '/core/vwf/utility/logger.js';
+import { KUtility } from '/core/vwf/utility/kutility.js';
+import { Utility } from '/core/vwf/utility/utility.js';
+import { XPath } from '/core/vwf/utility/xpath.js';
+
+import { Fabric } from '/core/vwf/fabric.js';
+import { ViewKernel } from '/core/vwf/view.js';
+import { ModelKernel } from '/core/vwf/model.js';
+import { Log } from '/core/vwf/model/stage/log.js';
+
+class VWF {
+
+    constructor(driverConf, proxy, doc, connectionConf) {
+        console.log("VWF constructor");
+
+        let self = this;
+
+        this.helpers = new Helpers;
+
+        this.driverConfiguration = driverConf || {};
+        this.proxy = proxy;
+        this.doc = doc;
+
+        this.isLuminary = connectionConf.luminary;
+        this.isLuminaryGlobalHB = connectionConf.luminaryHB;
+        this.luminaryGlobalHBPath = connectionConf.luminaryGlobalHBPath;
+        
+        
+        this.luminary = new Luminary;
+        this.reflectorClient = new ReflectorClient;
+        this.virtualTime = new VirtualTime;
+        window._Time = this.virtualTime; //vwf.virtualTime = app.virtualTime;
+
+        this.applicationLoad = undefined;
+
+        // == Public variables =====================================================================
+
+        /// The runtime environment (production, development, testing) and other configuration
+        /// settings appear here.
+        /// 
+        /// @name module:vwf.configuration
+        /// 
+        /// @private
+
+        /// Default configuration for all environments.
+
+        this.configuration =  {
+            "log-level": "info",                  // logger threshold
+            "random-seed": +new Date,             // pseudorandom number generator seed
+            "randomize-ids": true,               // randomize IDs to discourage assumptions about ID allocation
+            "humanize-ids": true,                // append recognizable strings to node IDs
+            "preserve-script-closures": false,    // retain method/event closures by not serializing functions (breaks replication, persistence)
+            "load-timeout": 30                   // resource load timeout in seconds
+        }
+
+        
+        //undefined; // require( "vwf/configuration" ).active; // "active" updates in place and changes don't invalidate the reference  // TODO: assign here after converting vwf.js to a RequireJS module and listing "vwf/configuration" as a dependency
+
+        /// Kernel utility functions and objects.
+        /// 
+        /// @name module:vwf.utility
+        /// 
+        /// @private
+
+        this.kutility = new KUtility;
+        //this.kutility = undefined; // require( "vwf/kernel/utility" );  // TODO: assign here after converting vwf.js to a RequireJS module and listing "vwf/kernel/utility" as a dependency
+
+
+        this.xpath = new XPath;
+        this.utility = new Utility;
+
+        this.viewModule = new Fabric({
+            id:"vwf/view"
+        }, 'View');
+
+        this.modelModule = new Fabric({
+            id:"vwf/model"
+        }, 'Model');
+
+
+        /// The kernel logger.
+        /// 
+        /// @name module:vwf.logger
+        /// 
+        /// @private
+
+        this.logger = (new Logger).for("vwf", this, this.configuration["log-level"] );
+
+        //undefined; // require( "logger" ).for( undefined, this );  // TODO: for( "vwf", ... ), and update existing calls  // TODO: assign here after converting vwf.js to a RequireJS module and listing "vwf/logger" as a dependency
+
+        /// Each model and view module loaded by the main page registers itself here.
+        /// 
+        /// @name module:vwf.modules
+        /// 
+        /// @private
+
+        this.modules = [];
+
+        /// vwf.initialize() creates an instance of each model and view module configured on the main
+        /// page and attaches them here.
+        /// 
+        /// @name module:vwf.models
+        /// 
+        /// @private
+
+        this.models = [];
+
+        /// vwf.initialize() creates an instance of each model and view module configured on the main
+        /// page and attaches them here.
+        /// 
+        /// @name module:vwf.views
+        /// 
+        /// @private
+
+        this.views = [];
+
+        /// this.models is a list of references to the head of each driver pipeline. Define an
+        /// `actual` property that evaluates to a list of references to the pipeline tails. This is
+        /// a list of the actual drivers after any intermediate stages and is useful for debugging.
+        /// 
+        /// @name module:vwf.models.actual
+
+        Object.defineProperty(this.models, "actual", {
+
+            get: function () {
+
+                // Map the array to the result.
+
+                var actual = this.map(function (model) {
+                    return last(model);
+                });
+
+                // Map the non-integer properties too.
+
+                for (var propertyName in this) {
+                    if (isNaN(Number(propertyName))) {
+                        actual[propertyName] = last(this[propertyName]);
+                    }
+                }
+
+                // Follow a pipeline to the last stage.
+
+                function last(model) {
+                    while (model.model) model = model.model;
+                    return model;
+                }
+
+                return actual;
+            }
+
+        });
+
+        /// this.views is a list of references to the head of each driver pipeline. Define an
+        /// `actual` property that evaluates to a list of references to the pipeline tails. This is
+        /// a list of the actual drivers after any intermediate stages and is useful for debugging.
+        /// 
+        /// @name module:vwf.views.actual
+
+        Object.defineProperty(this.views, "actual", {
+
+            get: function () {
+
+                // Map the array to the result.
+
+                var actual = this.map(function (model) {
+                    return last(model);
+                });
+
+                // Map the non-integer properties too.
+
+                for (var propertyName in this) {
+                    if (isNaN(Number(propertyName))) {
+                        actual[propertyName] = last(this[propertyName]);
+                    }
+                }
+
+                // Follow a pipeline to the last stage.
+
+                function last(model) {
+                    while (model.model) model = model.model;
+                    return model;
+                }
+
+                return actual;
+            }
+
+        });
+
+        /// The identifer assigned to the client by the server.
+        /// 
+        /// @name module:vwf.moniker_
+        /// 
+        /// @private
+
+        this.moniker_ = undefined;
+
+        /// Nodes that are receiving ticks.
+        /// 
+        /// @name module:vwf.tickable
+        /// 
+        /// @private
+
+        this.tickable = {
+            // models: [],
+            // views: [],
+            nodeIDs: [],
+        };
+
+        // == Private variables ====================================================================
+
+        /// @name module:vwf.private
+        /// 
+        /// @private
+
+        this.private = {}; // for debugging
+
+        /// Components describe the objects that make up the simulation. They may also serve as
+        /// prototype objects for further derived components. External components are identified by
+        /// URIs. Once loaded, we save a mapping here from its URI to the node ID of its prototype so
+        /// that we can find it if it is reused. Components specified internally as object literals
+        /// are anonymous and are not indexed here.
+        /// 
+        /// @name module:vwf~components
+
+        this.components = this.private.components = {}; // maps component node ID => component specification
+
+        // Each node is assigned an ID as it is created. This is the most recent ID assigned.
+
+        // Communication between the manager and the models and views uses these IDs to refer to the
+        // nodes. The manager doesn't maintain any particular state for the nodes and knows them
+        // only as their IDs. The models work in federation to provide the meaning to each node.
+
+        // var lastID = 0;
+
+        /// Callback functions defined in this scope use this local "vwf" to locate the manager.
+        /// 
+        /// @name module:vwf~vwf
+
+        window.vwf = this;
+
+
+        //////////
+        this.setProperty.entrants = {}; // maps ( nodeID + '-' + propertyName ) => { index: i, value: v }
+        this.getProperty.entrants = {}; // maps ( nodeID + '-' + propertyName ) => { index: i, value: v }
+
+
+        // == Private variables ====================================================================
+
+        /// Prototype for name-based, unordered collections in the node registry, including
+        /// `node.properties`, `node.methods`, and `node.events`.
+
+        this.keyedCollectionPrototype = {
+
+            /// Record that a property, method or event has been created.
+            /// 
+            /// @param {String} name
+            ///   The member name.
+            /// @param {Boolean} changes
+            ///   For patchable nodes, record changes so that `kernel.getNode` may create a patch
+            ///   when retrieving the node.
+            /// @param [value]
+            ///   An optional value to assign to the record. If `value` is omitted, the record will
+            ///   exist in the collection but have the value `undefined`.
+            /// 
+            /// @returns {Boolean}
+            ///   `true` if the member was successfully added. `false` if a member by that name
+            ///   already exists.
+
+            create: function (name, changes, value) {
+
+                if (!this.hasOwn(name)) {
+
+                    this.makeOwn("existing");
+
+                    // Add the member. `Object.defineProperty` is used instead of
+                    // `this.existing[name] = ...` since the prototype may be a behavior proxy, and
+                    // the accessor properties would prevent normal assignment.
+
+                    Object.defineProperty(this.existing, name,
+                        self.configurable(value ? value : undefined));
+
+                    if (changes) {
+
+                        this.makeOwn("changes");
+
+                        if (this.changes[name] !== "removed") {
+                            this.changes[name] = "added";
+                        } else {
+                            this.changes[name] = "changed"; // previously removed, then added
+                        }
+
+                        if (this.container && this.containerMember) {
+                            this.container.change(this.containerMember);
+                        }
+
+                    }
+
+                    return true;
+                }
+
+                return false;
+            },
+
+            /// Record that a member has been deleted.
+            /// 
+            /// @param {String} name
+            ///   The member name.
+            /// 
+            /// @returns {Boolean}
+            ///   `true` if the member was successfully removed. `false` if a member by that name
+            ///   does not exist.
+
+            delete: function (name, changes) {
+
+                if (this.hasOwn(name)) {
+
+                    delete this.existing[name];
+
+                    if (changes) {
+
+                        this.makeOwn("changes");
+
+                        if (this.changes[name] !== "added") {
+                            this.changes[name] = "removed";
+                        } else {
+                            delete this.changes[name]; // previously added, then removed
+                        }
+
+                        if (this.container && this.containerMember) {
+                            this.container.change(this.containerMember);
+                        }
+
+                    }
+
+                    return true;
+                }
+
+                return false;
+            },
+
+            /// Record that a member has changed.
+            /// 
+            /// @param {String} name
+            ///   The member name.
+            /// 
+            /// @returns {Boolean}
+            ///   `true` if the change was successfully recorded. `false` if a member by that name
+            ///   does not exist.
+
+            change: function (name, value) {
+
+                if (this.hasOwn(name)) {
+
+                    this.makeOwn("changes");
+
+                    if (this.changes[name] !== "added") {
+                        this.changes[name] = value ?
+                            value : this.changes[name] || "changed";
+                    }
+
+                    if (this.container && this.containerMember) {
+                        this.container.change(this.containerMember);
+                    }
+
+                    return true;
+                }
+
+                return false;
+            },
+
+            /// Determine if a node has a member with the given name, either directly on the node or
+            /// inherited from a prototype.
+            /// 
+            /// @param {String} name
+            ///   The member name.
+            /// 
+            /// @returns {Boolean}
+
+            has: function (name) {
+                return name in this.existing;
+            },
+
+            /// Determine if a node has a member with the given name. The node's prototypes are not
+            /// considered.
+            /// 
+            /// @param {String} name
+            ///   The member name.
+            /// 
+            /// @returns {Boolean}
+
+            // Since prototypes of the collection objects mirror the node's prototype chain,
+            // collection objects for the proto-prototype `node.vwf` intentionally don't inherit
+            // from `Object.prototype`. Otherwise the Object members `hasOwnProperty`,
+            // `isPrototypeOf`, etc. would be mistaken as members of a VWF node.
+
+            // Instead of using the simpler `this.existing.hasOwnProperty( name )`, we must reach
+            // `hasOwnProperty through `Object.prototype`.
+
+            hasOwn: function (name) {
+                return Object.prototype.hasOwnProperty.call(this.existing, name);
+            },
+
+            /// Hoist a field from a prototype to the collection in preparation for making local
+            /// changes.
+            /// 
+            /// If the field in the prototype is an object, create a new object with that field as
+            /// its prototype. If the field in the prototype is an array, clone the field since
+            /// arrays can't readily serve as prototypes for other arrays. In other cases, copy the
+            /// field from the prototype. Only objects, arrays and primitive values are supported.
+            /// 
+            /// @param {String} fieldName
+            ///   The name of a field to hoist from the collection's prototype.
+
+            makeOwn: function (fieldName) {
+
+                if (!this.hasOwnProperty(fieldName)) {
+
+                    if (this[fieldName] instanceof Array) {
+                        this[fieldName] = this[fieldName].slice(); // clone arrays
+                    } else if (typeof this[fieldName] === "object" && this[fieldName] !== null) {
+                        this[fieldName] = Object.create(this[fieldName]); // inherit from objects
+                    } else {
+                        this[fieldName] = this[fieldName]; // copy primitives
+                    }
+
+                }
+
+            },
+
+            /// The property, method, or event members defined in this collection.
+            /// 
+            /// `existing` is an unordered collection of elements and optional values. The keys are
+            /// the primary data. Existence on the object is significant regardless of the value.
+            /// Some collections store data in the element when the kernel owns additional details
+            /// about the member. Values will be `undefined` in other collections.
+            /// 
+            /// For each collection, `existing` is the authoritative list of the node's members. Use
+            /// `collection.hasOwn( memberName )` to determine if the node defines a property,
+            /// method or event by that name.
+            /// 
+            /// The prototype of each `existing` object will be the `existing` object of the node's
+            /// prototype (or a proxy to the top behavior for nodes with behaviors). Use
+            /// `collection.has( memberName )` to determine if a property, method or event is
+            /// defined on the node or its prototypes.
+
+            existing: Object.create(null
+                // name: undefined,
+                // name: { ... } -- details
+                // ...
+            ),
+
+            /// The change list for members in this collection.
+            /// 
+            /// For patchable nodes, `changes` records the members that have been added, removed, or
+            /// changed since the node was first initialized. `changes` is not created in the
+            /// collection until the first change occurs. Only the change is recorded here. The
+            /// state behind the change is retrieved from the drivers when needed.
+
+            changes: {
+                // name: "added"
+                // name: "removed"
+                // name: "changed"
+                // name: { ... } -- changed, with details
+                // ...
+            },
+
+            /// The parent collection if this collection is a member of another. Changes applied to
+            /// members of this collection will call `container.change( containerMember )` to also
+            /// set the change flag for the containing member.
+            /// 
+            /// For example, members of the `node.events` collection contain listener collections at
+            /// `node.events.existing[name].listeners`. Each listener collection knows its event
+            /// name and points back to `node.events`. Changing a listener will call
+            /// `node.events.change( name )` to mark the event as changed.
+
+            container: undefined,
+
+            /// This collection's name in the parent if this collection is a member of another
+            /// collection. Changes to members of this collection will mark that member changed in
+            /// the containing collection.
+
+            containerMember: undefined,
+
+        }
+
+        /// Prototype for index-based, ordered collections in the node registry, including
+        /// `event.listeners`.
+
+        this.indexedCollectionPrototype = {
+
+            /// Record that a member has been created.
+            /// 
+            /// @param {string|number|boolean|null} id
+            ///   The member's unique id.
+            /// @param {Boolean} changes
+            ///   For patchable nodes, record changes so that `kernel.getNode` may create a patch
+            ///   when retrieving the node.
+            /// 
+            /// @returns {Boolean}
+            ///   `true` if the member was successfully added. `false` if a member with that id
+            ///   already exists.
+
+            create: function (id, changes) {
+
+                if (!this.hasOwn(id)) {
+
+                    this.makeOwn("existing");
+                    this.existing.push(id);
+
+                    if (changes) {
+
+                        this.makeOwn("changes");
+
+                        var removedIndex = this.changes.removed ?
+                            this.changes.removed.indexOf(id) : -1;
+
+                        if (removedIndex < 0) {
+                            this.changes.added = this.changes.added || [];
+                            this.changes.added.push(id);
+                        } else {
+                            this.changes.removed.splice(removedIndex, 1);
+                            this.changes.changed = this.changes.changed || [];
+                            this.changes.changed.push(id);
+                        }
+
+                        if (this.container && this.containerMember) {
+                            this.container.change(this.containerMember);
+                        }
+
+                    }
+
+                    return true;
+                }
+
+                return false;
+            },
+
+            /// Record that a member has been deleted.
+            /// 
+            /// @param {string|number|boolean|null} id
+            ///   The member's unique id.
+            /// 
+            /// @returns {Boolean}
+            ///   `true` if the member was successfully removed. `false` if a member with that id
+            ///   does not exist.
+
+            delete: function (id, changes) {
+
+                if (this.hasOwn(id)) {
+
+                    this.existing.splice(this.existing.indexOf(id), 1);
+
+                    if (changes) {
+
+                        this.makeOwn("changes");
+
+                        var addedIndex = this.changes.added ?
+                            this.changes.added.indexOf(id) : -1;
+
+                        if (addedIndex < 0) {
+                            this.changes.removed = this.changes.removed || [];
+                            this.changes.removed.push(id);
+                        } else {
+                            this.changes.added.splice(addedIndex, 1);
+                        }
+
+                        if (this.container && this.containerMember) {
+                            this.container.change(this.containerMember);
+                        }
+
+                    }
+
+                    return true;
+                }
+
+                return false;
+            },
+
+            /// Record that a member has changed.
+            /// 
+            /// @param {string|number|boolean|null} id
+            ///   The member's unique id.
+            /// 
+            /// @returns {Boolean}
+            ///   `true` if the change was successfully recorded. `false` if a member with that id
+            ///   does not exist.
+
+            change: function (id) {
+
+                if (this.hasOwn(id)) {
+
+                    this.makeOwn("changes");
+
+                    var addedIndex = this.changes.added ?
+                        this.changes.added.indexOf(id) : -1;
+
+                    var changedIndex = this.changes.changed ?
+                        this.changes.changed.indexOf(id) : -1;
+
+                    if (addedIndex < 0 && changedIndex < 0) {
+                        this.changes.changed = this.changes.changed || [];
+                        this.changes.changed.push(id);
+                    }
+
+                    if (this.container && this.containerMember) {
+                        this.container.change(this.containerMember);
+                    }
+
+                    return true;
+                }
+
+                return false;
+            },
+
+            /// Determine if a node has a member with the given id.
+            /// 
+            /// `has` is the same as `hasOwn` for `indexedCollectionPrototype` since index-based
+            /// collections don't automatically inherit from their prototypes.
+            /// 
+            /// @param {string|number|boolean|null} id
+            ///   The member's unique id.
+            /// 
+            /// @returns {Boolean}
+
+            has: function (id) {
+                return this.hasOwn(id);
+            },
+
+            /// Determine if a node has a member with the given id. The node's prototypes are not
+            /// considered.
+            /// 
+            /// @param {string|number|boolean|null} id
+            ///   The member's unique id.
+            /// 
+            /// @returns {Boolean}
+
+            hasOwn: function (id) {
+                return this.existing ? this.existing.indexOf(id) >= 0 : false;
+            },
+
+            /// Hoist a field from a prototype to the collection in preparation for making local
+            /// changes.
+            /// 
+            /// If the field in the prototype is an object, create a new object with that field as
+            /// its prototype. If the field in the prototype is an array, clone the field since
+            /// arrays can't readily serve as prototypes for other arrays. In other cases, copy the
+            /// field from the prototype. Only objects, arrays and primitive values are supported.
+            /// 
+            /// @param {String} fieldName
+            ///   The name of a field to hoist from the collection's prototype.
+
+            makeOwn: function (fieldName) {
+
+                if (!this.hasOwnProperty(fieldName)) {
+
+                    if (this[fieldName] instanceof Array) {
+                        this[fieldName] = this[fieldName].slice(); // clone arrays
+                    } else if (typeof this[fieldName] === "object" && this[fieldName] !== null) {
+                        this[fieldName] = Object.create(this[fieldName]); // inherit from objects
+                    } else {
+                        this[fieldName] = this[fieldName]; // copy primitives
+                    }
+
+                }
+
+            },
+
+            /// IDs of the members defined in this collection.
+            /// 
+            /// `existing` is an ordered list of IDs, which much be unique within the collection.
+            /// The IDs retain the order in which they were originally added.
+            /// 
+            /// For each collection, `existing` is the authoritative list of the node's members. Use
+            /// `collection.hasOwn( memberID )` to determine if the collection contains a member
+            /// with that id. Unlike `keyedCollectionPrototype` collections,
+            /// `indexedCollectionPrototype` collections aren't connected in parallel with their
+            /// containers' prototype chains.
+
+            existing: [
+                // id,
+                // id,
+                // ...
+            ],
+
+            /// The change list for members in this collection.
+            /// 
+            /// For patchable nodes, `changes` records the members that have been added, removed, or
+            /// changed since the node was first initialized. Changes are recorded in separate
+            /// `added`, `removed`, and `changed` arrays, respectively. The `added` array retains
+            /// the order in which the members were added. Although `removed` and `changed` are also
+            /// arrays, the order of removals and changes is not significant.
+            /// 
+            /// `changes` is not created in the collection until the first change occurs. Only the
+            /// change is recorded here. The state behind the change is retrieved from the drivers
+            /// when needed.
+
+            changes: {
+                // added: [ id, ... ],
+                // removed: [ id, ... ],
+                // changed: [ id, ... ],
+            },
+
+            /// The parent collection if this collection is a member of another. Changes applied to
+            /// members of this collection will call `container.change( containerMember )` to also
+            /// set the change flag for the containing member.
+            /// 
+            /// For example, members of the `node.events` collection contain listener collections at
+            /// `node.events.existing[name].listeners`. Each listener collection knows its event
+            /// name and points back to `node.events`. Changing a listener will call
+            /// `node.events.change( name )` to mark the event as changed.
+
+            container: undefined,
+
+            /// This collection's name in the parent if this collection is a member of another
+            /// collection. Changes to members of this collection will mark that member changed in
+            /// the containing collection.
+
+            containerMember: undefined,
+
+        }
+
+        // Prototype for the `events` collection in the `nodes` objects.
+
+        this.eventCollectionPrototype = Object.create(self.keyedCollectionPrototype, {
+
+            create: {
+                value: function (name, changes, parameters) {
+                    var value = parameters ? {
+                        parameters: parameters.slice(), // clone
+                    } : {};
+
+                    value.listeners = Object.create(self.indexedCollectionPrototype, {
+                        container: self.enumerable(this),
+                        containerMember: self.enumerable(name),
+                    });
+
+                    return self.keyedCollectionPrototype.create.call(this, name, changes, value);
+                }
+
+            },
+
+        })
+
+        /// The application's nodes, indexed by ID.
+        /// 
+        /// The kernel defines an application as:
+        /// 
+        ///   * A tree of nodes,
+        ///   * Extending prototypes and implementing behaviors,
+        ///   * Publishing properties, and
+        ///   * Communicating using methods and events.
+        /// 
+        /// This definition is as abstract as possible to avoid imposing unnecessary policy on the
+        /// application. The concrete realization of these concepts lives in the hearts and minds of
+        /// the drivers configured for the application. `nodes` contains the kernel's authoritative
+        /// data about this arrangement.
+        /// 
+        /// @name module:vwf~nodes
+
+        // Note: this is a first step towards moving authoritative data out of the vwf/model/object
+        // and vwf/model/javascript drivers and removing the kernel's dependency on them as special
+        // cases. Only `nodes.existing[id].properties` is currently implemented this way.
+
+        this.nodes = this.private.nodes = {
+
+            /// Register a node as it is created.
+            /// 
+            /// @param {ID} nodeID
+            ///   The ID assigned to the new node. The node will be indexed in `nodes` by this ID.
+            /// @param {ID} prototypeID
+            ///   The ID of the node's prototype, or `undefined` if this is the proto-prototype,
+            ///   `node.vwf`.
+            /// @param {ID[]} behaviorIDs
+            ///   An array of IDs of the node's behaviors. `behaviorIDs` should be an empty array if
+            ///   the node doesn't have any behaviors.
+            /// @param {String} nodeURI
+            ///   The node's URI. `nodeURI` should be the component URI if this is the root node of
+            ///   a component loaded from a URI, and undefined in all other cases.
+            /// @param {String} nodeName
+            ///   The node's name.
+            /// @param {ID} parentID
+            ///   The ID of the node's parent, or `undefined` if this is the application root node
+            ///   or another global, top-level node.
+            /// 
+            /// @returns {Object} 
+            ///   The kernel `node` object if the node was successfully added. `undefined` if a node
+            ///   identified by `nodeID` already exists.
+
+            create: function (nodeID, prototypeID, behaviorIDs, nodeURI, nodeName, parentID) {
+
+                // if ( ! this.existing[nodeID] ) {
+
+                let selfNode = this;
+                var prototypeNode = behaviorIDs.reduce(function (prototypeNode, behaviorID) {
+                    return selfNode.proxy(prototypeNode, selfNode.existing[behaviorID]);
+                }, this.existing[prototypeID]);
+
+                // Look up the parent.
+
+                var parentNode = this.existing[parentID];
+
+                // If this is the global root of a new tree, add it to the `globals` set.
+
+                if (!parentNode) {
+                    this.globals[nodeID] = undefined;
+                }
+
+                // Add the node to the registry.
+
+                return this.existing[nodeID] = {
+
+                    // id: ...,
+
+                    // Inheritance. -- not implemented here yet; still using vwf/model/object
+
+                    // prototype: ...,
+                    // behaviors: [],
+
+                    // Intrinsic state. -- not implemented here yet.
+
+                    // source: ...,
+                    // type: ...,
+
+                    uri: nodeURI,
+                    name: nodeName,
+
+                    // Internal state. The change flags are omitted until needed. -- not implemented here yet; still using vwf/model/object
+
+                    // sequence: ...,
+                    // sequenceChanged: true / false,
+
+                    // prng: ...,
+                    // prngChanged: true / false,
+
+                    // Tree. -- not implemented here yet; still using vwf/model/object
+
+                    // parent: ...,
+                    // children: [],
+
+                    // Property, Method and Event members defined on the node.
+
+                    properties: Object.create(self.keyedCollectionPrototype, {
+                        existing: self.enumerable(Object.create(prototypeNode ?
+                            prototypeNode.properties.existing : null)),
+                    }),
+
+                    methods: Object.create(self.keyedCollectionPrototype, {
+                        existing: self.enumerable(Object.create(prototypeNode ?
+                            prototypeNode.methods.existing : null)),
+                    }),
+
+                    events: Object.create(self.eventCollectionPrototype, {
+                        existing: self.enumerable(Object.create(prototypeNode ?
+                            prototypeNode.events.existing : null)),
+                    }),
+
+                    // Is this node patchable? Nodes are patchable if they were loaded from a
+                    // component.
+
+                    patchable: !!(nodeURI ||
+                        parentNode && !parentNode.initialized && parentNode.patchable),
+
+                    // Has this node completed initialization? For applications, visibility to
+                    // ancestors from uninitialized nodes is blocked. Change tracking starts
+                    // after initialization.
+
+                    initialized: false,
+
+                    childsDeleted: {}
+
+                };
+
+                // } else {
+
+                //     return undefined;
+
+                // }
+
+            },
+
+            /// Record that a node has initialized.
+
+            initialize: function (nodeID) {
+
+                if (this.existing[nodeID]) {
+
+                    this.existing[nodeID].initialized = true;
+
+                    return true;
+                }
+
+                return false;
+            },
+
+            /// Unregister a node as it is deleted.
+
+            delete: function (nodeID, parentID) {
+
+                if (this.existing[nodeID] && this.existing[parentID]) {
+
+                    this.existing[parentID].childsDeleted[this.existing[nodeID].name] = nodeID;
+
+                    delete this.existing[nodeID];
+                    delete this.globals[nodeID];
+
+                    return true;
+                }
+
+                return false;
+            },
+
+            /// Create a proxy node in the form of the nodes created by `nodes.create` to represent
+            /// a behavior node in another node's prototype chain. The `existing` objects of the
+            /// proxy's collections link to the prototype collection's `existing` objects, just as
+            /// with a regular prototype. The proxy's members delegate to the corresponding members
+            /// in the behavior.
+
+            proxy: function (prototypeNode, behaviorNode) {
+
+                return {
+
+                    properties: {
+                        existing: Object.create(
+                            prototypeNode ? prototypeNode.properties.existing : null,
+                            propertyDescriptorsFor(behaviorNode.properties.existing)
+                        ),
+                    },
+
+                    methods: {
+                        existing: Object.create(
+                            prototypeNode ? prototypeNode.methods.existing : null,
+                            propertyDescriptorsFor(behaviorNode.methods.existing)
+                        ),
+                    },
+
+                    events: {
+                        existing: Object.create(
+                            prototypeNode ? prototypeNode.events.existing : null,
+                            propertyDescriptorsFor(behaviorNode.events.existing)
+                        ),
+                    },
+
+                };
+
+                /// Return an `Object.create` properties object for a proxy object for the provided
+                /// collection's `existing` object.
+
+                function propertyDescriptorsFor(collectionExisting) {
+
+                    return Object.keys(collectionExisting).reduce(
+
+                        function (propertiesObject, memberName) {
+
+                            propertiesObject[memberName] = {
+                                get: function () {
+                                    return collectionExisting[memberName]
+                                },
+                                enumerable: true,
+                            };
+
+                            return propertiesObject;
+                        },
+
+                        {}
+
+                    );
+
+                }
+
+            },
+
+            /// Registry of all nodes, indexed by ID. Each is an object created by `nodes.create`.
+
+            existing: {
+
+                // id: {
+                //     id: ...,
+                //     uri: ...,
+                //     name: ...,
+                //     ...
+                // }
+
+            },
+
+            /// Global root nodes. Each of these is the root of a tree.
+            /// 
+            /// The `globals` object is a set: the keys are the data, and only existence on the
+            /// object is significant.
+
+            globals: {
+                // id: undefined,
+            }
+
+        }
+
+    }
+
+    // == Public functions =====================================================================
+
+    // -- loadConfiguration ---------------------------------------------------------------------------
+
+    // The main page only needs to call vwf.loadConfiguration() to launch the application. Use
+    // require.ready() or jQuery(document).ready() to call loadConfiguration() once the page has
+    // loaded. loadConfiguration() accepts three parameters.
+    // 
+    // A component specification identifies the application to be loaded. modelInitializers and 
+    // viewInitializers identify the model and view libraries that were parsed out of the URL that 
+    // should be attached to the simulation. Each is specified as an object with each library 
+    // name as a property of the object with any arguments as the value of the property.
+    // Arguments may be specified as an array [1], as a single value if there is only one [2], or as 
+    // undefined if there are none[3].
+    // 
+    //     [1] vwf.loadConfiguration( ..., { "vwf/model/glge": [ "#scene, "second param" ] }, { ... } )
+    //     [2] vwf.loadConfiguration( ..., { "vwf/model/glge": "#scene" }, { ... } )
+    //     [3] vwf.loadConfiguration( ..., { "vwf/model/javascript": undefined }, { ... } )
+
+    loadConfiguration(app, userLib, cb
+
+        /* [ componentURI|componentObject ] { modelInitializers }
+                    { viewInitializers } */
+    ) {
+        let self = this;
+
+        let applicationLoad = app;
+        let userLibraries = userLib || {};
+
+        var applicationConfig = {};
+        let callback = cb;
+
+        var initializers = {
+            model: [
+                 {
+                    library: "/core/vwf/model/ohm",
+                    active: true
+                },
+                {
+                    library: "/core/vwf/model/javascript",
+                    active: true
+                },
+                {
+                    library: "/core/vwf/model/object",
+                    active: true
+                }
+
+            ],
+            view: [
+
+                {
+                    library: "/core/vwf/view/ohm",
+                    active: true
+                },
+
+                {
+                    library: "/core/vwf/view/document",
+                    active: true
+                },
+
+
+            ]
+        };
+        //mapLibraryName(requireArray);
+        mapLibraryName(initializers["model"]);
+        mapLibraryName(initializers["view"]);
+
+        function mapLibraryName(array) {
+            for (var i = 0; i < array.length; i++) {
+                array[array[i].library] = array[i];
+            }
+        }
+
+        function getActiveLibraries(libraryList, includeParameters) {
+            var activeLibraryList = [];
+            for (var i = 0; i < libraryList.length; i++) {
+                if (libraryList[i].active) {
+                    if (includeParameters) {
+                        var activeLibrary = {};
+                        activeLibrary[libraryList[i].library] = libraryList[i].parameters;
+                        activeLibraryList.push(activeLibrary);
+                    } else {
+                        activeLibraryList.push(libraryList[i].library);
+                    }
+                }
+            }
+            return activeLibraryList;
+        }
+
+
+        // let confPromise = new Promise((resolve, reject) => {
+        //     resolve(this.driverConfiguration);
+        // });
+
+            let configLibraries = this.driverConfiguration;
+
+            if (configLibraries && typeof configLibraries == "object") {
+                if (typeof configLibraries.configuration == "object") {
+                    applicationConfig = configLibraries.configuration;
+                }
+                Object.keys(configLibraries).forEach(function (libraryType) {
+                    if (libraryType == 'info' && configLibraries[libraryType]["title"]) {
+                        //jQuery('title').html(configLibraries[libraryType]["title"]);
+                        document.querySelector('title').innerHTML = configLibraries[libraryType]["title"]
+                    }
+                    if (!userLibraries[libraryType]) {
+                        userLibraries[libraryType] = {};
+                    }
+                    // Merge libraries from config file and URL together. Check for incompatible
+                    // libraries, and disable them.
+                    Object.keys(configLibraries[libraryType]).forEach(function (libraryName) {
+                        var disabled = false;
+                        if (!disabled) {
+                            if (userLibraries[libraryType][libraryName] == undefined) {
+                                userLibraries[libraryType][libraryName] = configLibraries[libraryType][libraryName];
+                            } else if (typeof userLibraries[libraryType][libraryName] == "object" && typeof configLibraries[libraryType][libraryName] == "object") {
+                                userLibraries[libraryType][libraryName] = Object.assign({}, configLibraries[libraryType][libraryName], userLibraries[libraryType][libraryName]);
+                            }
+                        }
+                    });
+                });
+            }
+
+
+            Object.keys(userLibraries).forEach(function (libraryType) {
+                if (initializers[libraryType]) {
+                    Object.keys(userLibraries[libraryType]).forEach(function (libraryName) {
+                        //if (requireArray[libraryName]) {
+                           // requireArray[libraryName].active = true;
+
+                            if(!initializers[libraryType][libraryName]){
+
+                                initializers[libraryType].unshift({'library': libraryName});
+                                initializers[libraryType][libraryName] = initializers[libraryType][0];
+
+                            }
+                            initializers[libraryType][libraryName].active = true;
+                            if (userLibraries[libraryType][libraryName] && userLibraries[libraryType][libraryName] != "") {
+                                if (typeof initializers[libraryType][libraryName].parameters == "object") {
+
+                                    initializers[libraryType][libraryName].parameters = Object.assign({}, initializers[libraryType][libraryName].parameters, userLibraries[libraryType][libraryName]);
+
+                                } else {
+                                    initializers[libraryType][libraryName].parameters = userLibraries[libraryType][libraryName];
+                                }
+                            }
+                    });
+                }
+            })
+
+
+            // Load default renderer if no other librarys specified
+            // if (Object.keys(userLibraries["model"]).length == 0 && Object.keys(userLibraries["view"]).length == 0) {
+            //     // requireArray["vwf/model/threejs"].active = true;
+            // }
+
+                    // With the scripts loaded, we must initialize the framework. vwf.initialize()
+                    // accepts three parameters: a world specification, model configuration parameters,
+                    // and view configuration parameters.
+
+        self.initialize(self.applicationLoad, getActiveLibraries(initializers["model"], true), getActiveLibraries(initializers["view"], true), callback);
+
+
+
+    }
+
+
+
+    // -- initialize ---------------------------------------------------------------------------
+
+    /// The main page only needs to call vwf.initialize() to launch the application. Use
+    /// require.ready() or jQuery(document).ready() to call initialize() once the page has
+    /// loaded. initialize() accepts three parameters.
+    /// 
+    /// A component specification identifies the application to be loaded. If a URI is provided,
+    /// the specification is loaded from there [1]. Alternately, a JavaScript object literal
+    /// containing the specfication may be provided [2]. Since a component can extend and
+    /// specialize a prototype, using a simple object literal allows existing component to be
+    /// configured for special uses [3].
+    /// 
+    ///     [1] vwf.initialize( "http://vwf.example.com/applications/sample12345", ... )
+    ///
+    ///     [2] vwf.initialize( { source: "model.dae", type: "model/vnd.collada+xml",
+    ///             properties: { "p1": ... }, ... }, ... )
+    ///
+    ///     [3] vwf.initialize( { extends: "http://vwf.example.com/applications/sample12345",
+    ///             source: "alternate-model.dae", type: "model/vnd.collada+xml" }, ... )
+    /// 
+    /// modelInitializers and viewInitializers identify the model and view modules that should be
+    /// attached to the simulation. Each is specified as an array of objects that map the name of
+    /// a model or view to construct to the set of arguments to pass to its constructor. Modules
+    /// without parameters may be specified as a string [4]. Arguments may be specified as an
+    /// array [5], or as a single value if there is only one [6].
+    /// 
+    ///     [4] vwf.initialize( ..., [ "vwf/model/javascript" ], [ ... ] )
+    ///     [5] vwf.initialize( ..., [ { "vwf/model/glge": [ "#scene, "second param" ] } ], [ ... ] )
+    ///     [6] vwf.initialize( ..., [ { "vwf/model/glge": "#scene" } ], [ ... ] )
+    /// 
+    /// @name module:vwf.initialize
+
+    async initialize(
+        /* [ componentURI|componentObject ] [ modelInitializers ]
+                   [ viewInitializers ] */
+    ) {
+
+        let self = this;
+
+        var args = Array.prototype.slice.call(arguments);
+        //var application;
+
+        // Load the runtime configuration. We start with the factory defaults. The reflector may
+        // provide additional settings when we connect.
+
+        //this.configuration = require("vwf/configuration").active; // "active" updates in place and changes don't invalidate the reference
+
+        // Load the kernel utilities.
+
+        //this.kutility =  new KUtility //require("vwf/kernel/utility");
+
+        // Create the logger.
+
+        //this.logger = require("logger").for("vwf", this); // TODO: for( "vwf", ... ), and update existing calls
+
+        // Get the jQuery reference. This also happens in `loadConfiguration`, but the tests
+        // initialize using `initialize` and don't call `loadConfiguration` first.
+
+        //  jQuery = require("jquery");
+
+        // Parse the function parameters. If the first parameter is not an array, then treat it
+        // as the application specification. Otherwise, fall back to the "application" parameter
+        // in the query string.
+
+        if (typeof args[0] != "object" || !(args[0] instanceof Array)) {
+            this.applicationLoad = args.shift();
+        }
+
+        // Shift off the parameter containing the model list and initializer arguments.
+
+        var modelInitializers = args.shift() || [];
+
+        // Shift off the parameter containing the view list and initializer arguments.
+
+        var viewInitializers = args.shift() || [];
+
+        var callback = args.shift();
+        var compatibilityStatus = {
+            compatible: true,
+            errors: {}
+        };
+
+        // Create the model interface to the kernel. Models can make direct calls that execute
+        // immediately or future calls that are placed on the queue and executed when removed.
+
+        let modelKernel = new ModelKernel({
+            id:"vwf/kernel/model"
+        }).factory();
+        this.models.kernel = modelKernel.create(vwf);
+
+        //this.models.kernel = require("vwf/kernel/model").create(vwf);
+
+        // Create and attach each configured model.
+
+        for (let modelInitializer of modelInitializers) {
+        //modelInitializers.forEach(function (modelInitializer) {
+
+            // Skip falsy values to allow initializers to be conditionally included by the
+            // loader.
+
+            if (modelInitializer) {
+
+                // Accept either { "vwf/model/name": [ arguments] } or "vwf/model/name".
+
+                if (typeof modelInitializer == "object" && modelInitializer != null) {
+                    var modelName = Object.keys(modelInitializer)[0];
+                    var modelArguments = modelInitializer[modelName];
+                } else {
+                    var modelName = modelInitializer;
+                    var modelArguments = undefined;
+                }
+
+                let log = new Log({
+                    id:"vwf/model/stage/log"
+                }).factory();
+
+                //
+                    var modelMod = undefined;
+                    await import(modelName+'.js').then(m=>{
+                        modelMod = (new m.default({
+                            id: modelName
+                        }).factory())
+                    });
+                    
+
+                    var model = modelMod.create(
+                        this.models.kernel, // model's kernel access
+                        [log],
+                        //[require("vwf/model/stage/log")], // stages between the kernel and model
+                        {}, // state shared with a paired view
+                        [].concat(modelArguments || []) // arguments for initialize()
+                    );
+
+
+                if (model) {
+                    this.models.push(model);
+                    this.models[modelName] = model; // also index by id  // TODO: this won't work if multiple model instances are allowed
+
+                    if (modelName == "/core/vwf/model/javascript") { // TODO: need a formal way to follow prototype chain from vwf.js; this is peeking inside of vwf-model-javascript
+                        this.models.javascript = model;
+                        while (this.models.javascript.model) this.models.javascript = this.models.javascript.model;
+                    }
+
+                    if (modelName == "/core/vwf/model/object") { // TODO: this is peeking inside of vwf-model-object
+                        this.models.object = model;
+                        while (this.models.object.model) this.models.object = this.models.object.model;
+                    }
+
+                    if (model.model.compatibilityStatus) {
+                        if (!model.model.compatibilityStatus.compatible) {
+                            compatibilityStatus.compatible = false;
+                            Object.assign(compatibilityStatus.errors, model.model.compatibilityStatus.errors);
+                            //jQuery.extend(compatibilityStatus.errors, model.model.compatibilityStatus.errors);
+                        }
+                    }
+                }
+
+            }
+
+        }
+        //, this);
+
+        // Create the view interface to the kernel. Views can only make replicated calls which
+        // bounce off the reflection server, are placed on the queue when received, and executed
+        // when removed.
+
+        let viewKernel = new ViewKernel({
+            id:"vwf/kernel/view"
+        }).factory();
+
+        this.views.kernel = viewKernel.create(vwf);
+
+        //this.views.kernel = require("vwf/kernel/view").create(vwf);
+
+        // Create and attach each configured view.
+
+        for( let viewInitializer of viewInitializers) {
+        //viewInitializers.forEach(function (viewInitializer) {
+
+            // Skip falsy values to allow initializers to be conditionally included by the
+            // loader.
+
+            if (viewInitializer) {
+
+                // Accept either { "vwf/view/name": [ arguments] } or "vwf/view/name".
+
+                if (typeof viewInitializer == "object" && viewInitializer != null) {
+                    var viewName = Object.keys(viewInitializer)[0];
+                    var viewArguments = viewInitializer[viewName];
+                } else {
+                    var viewName = viewInitializer;
+                    var viewArguments = undefined;
+                }
+
+                // if (!viewName.match("^vwf/view/")) { // old way
+
+                //     var view = this.modules[viewName];
+
+                //     if (view) {
+                //         var instance = new view();
+                //         instance.state = this.models.actual["vwf/model/" + viewName] && this.models.actual["vwf/model/" + viewName].state || {}; // state shared with a paired model
+                //         view.apply(instance, [vwf].concat(viewArguments || []));
+                //         this.views.push(instance);
+                //         this.views[viewName] = instance; // also index by id  // TODO: this won't work if multiple view instances are allowed
+
+                //         if (view.compatibilityStatus) {
+                //             if (!view.compatibilityStatus.compatible) {
+                //                 compatibilityStatus.compatible = false;
+                //                 Object.assign(compatibilityStatus.errors, view.compatibilityStatus.errors);
+                //                 // jQuery.extend(compatibilityStatus.errors, view.compatibilityStatus.errors);
+                //             }
+                //         }
+                //     }
+
+                // } else { 
+                    // new way
+
+                    var modelPeer = this.models.actual[viewName.replace("view/", "model/")]; // TODO: this.model.actual() is kind of heavy, but it's probably OK to use just a few times here at start-up
+
+                   
+                    var viewMod = undefined;
+                    await import(viewName +'.js').then(m=>{
+                        viewMod = (new m.default({
+                            id: viewName
+                        }).factory())
+                    })
+
+                    var view = viewMod.create(
+                        this.views.kernel, // view's kernel access
+                        [], // stages between the kernel and view
+                        modelPeer && modelPeer.state || {}, // state shared with a paired model
+                        [].concat(viewArguments || []) // arguments for initialize()
+                    );
+
+
+
+                    if (view) {
+                        this.views.push(view);
+                        this.views[viewName] = view; // also index by id  // TODO: this won't work if multiple view instances are allowed
+
+                        if (view.compatibilityStatus) {
+                            if (!view.compatibilityStatus.compatible) {
+                                compatibilityStatus.compatible = false;
+                                Object.assign(compatibilityStatus.errors, view.compatibilityStatus.errors);
+                                // jQuery.extend(compatibilityStatus.errors, view.compatibilityStatus.errors);
+                            }
+                        }
+                    }
+
+                //}
+
+            }
+
+        }
+        //, this);
+
+
+        if (callback) {
+            callback(compatibilityStatus);
+        }
+
+        //await _app.getApplicationState();
+        await _app.getApplicationState()
+            .then(res => {
+                return self.chooseConnection(res)
+            })
+            .then(res => {
+                self.ready(self.application, res)
+            })
+
+    }
+
+    // -- ready --------------------------------------------------------------------------------
+
+    /// @name module:vwf.ready
+
+    ready(component_uri_or_json_or_object, path) {
+
+        // Connect to the reflector. This implementation uses the socket.io library, which
+        // communicates using a channel back to the server that provided the client documents.
+
+        if (this.isLuminary) {
+            //Use Luminary for connection
+
+            this.namespace_ = this.luminary.namespace; //this.helpers.GetNamespace(path.path); //.split(".").join("_");
+            this.moniker_ = this.luminary.clientID;
+            console.log('namespace: ' + this.namespace_, ' for client: ' + this.moniker_);
+
+            //let heartbeat = _LCSDB.get('server').get('heartbeat'); 
+            var heartbeat = _lum.get(this.namespace_).get('heartbeat'); //_LCSDB.get(vwf.namespace_).get('heartbeat');
+
+            if (this.isLuminaryGlobalHB && this.luminaryGlobalHBPath) {
+                let hbPath = this.luminaryGlobalHBPath.split('/');
+                var heartbeat = _LCSDB;
+                hbPath.forEach(el => {
+                    heartbeat = heartbeat.get(el);
+                })
+            }
+
+           this.luminary.subscribeOnHeartbeat(heartbeat);
+           this.luminary.subscribeOnMessages();
+
+
+        } else {
+            //Use Reflector for connection
+
+            this.reflectorClient.connect(component_uri_or_json_or_object, path);
+
+        }
+
+    }
+
+    // -- log ----------------------------------------------------------------------------------
+
+    /// Send a log message to the reflector.
+    /// 
+    /// @name module:vwf.log
+
+    log() {
+
+        this.respond(undefined, "log", undefined, undefined, arguments);
+
+    }
+
+
+    // -- setState -----------------------------------------------------------------------------
+
+    /// setState may complete asynchronously due to its dependence on createNode. To prevent
+    /// actions from executing out of order, queue processing must be suspended while setState is
+    /// in progress. createNode suspends the queue when necessary, but additional calls to
+    /// suspend and resume the queue may be needed if other async operations are added.
+    /// 
+    /// @name module:vwf.setState
+    /// 
+    /// @see {@link module:vwf/api/kernel.setState}
+
+    setState(appState, callback_async /* () */ ) {
+        let self = this;
+
+        this.logger.debuggx("setState"); // TODO: loggableState
+
+        // Set the runtime configuration.
+
+        var applicationState = appState;
+
+        if (applicationState.init) {
+            applicationState = JSON.parse(localStorage.getItem('lcs_app')).saveObject
+        }
+
+        if (applicationState.configuration) {
+            this.configuration = applicationState.configuration;
+            //require("vwf/configuration").instance = applicationState.configuration;
+        }
+
+        // Update the internal kernel state.
+
+        if (applicationState.kernel) {
+            if (applicationState.kernel.time !== undefined) self.virtualTime.now = applicationState.kernel.time;
+        }
+
+        // Create or update global nodes and their descendants.
+
+        var nodes = applicationState.nodes || [];
+        var annotations = applicationState.annotations || {};
+
+        var nodeIndex = 0;
+
+        async.forEachSeries(nodes, function (nodeComponent, each_callback_async /* ( err ) */ ) {
+
+            // Look up a possible annotation for this node. For backward compatibility, if the
+            // state has exactly one node and doesn't contain an annotations object, assume the
+            // node is the application.
+
+            var nodeAnnotation = self.nodes.length > 1 || applicationState.annotations ?
+                annotations[nodeIndex] : "application";
+
+            self.createNode(nodeComponent, nodeAnnotation, function (nodeID) /* async */ {
+                each_callback_async(undefined);
+            });
+
+            nodeIndex++;
+
+        }, function (err) /* async */ {
+
+            // Clear the message queue, except for reflector messages that arrived after the
+            // current action.
+
+            self.virtualTime.filterQueue();
+
+            // Set the queue time and add the incoming items to the queue.
+
+            if (applicationState.queue) {
+                self.virtualTime.time = applicationState.queue.time;
+                self.virtualTime.insert(applicationState.queue.queue || []);
+            }
+
+            callback_async && callback_async();
+
+        });
+
+        this.logger.debugu();
+    };
+
+    // -- getState -----------------------------------------------------------------------------
+
+    /// @name module:vwf.getState
+    /// 
+    /// @see {@link module:vwf/api/kernel.getState}
+
+    getState(full, normalize) {
+
+        let self = this;
+        this.logger.debuggx("getState", full, normalize);
+
+        // Get the application nodes and queue.
+
+        var applicationState = {
+
+            // Runtime configuration.
+
+            configuration: self.configuration, //require("vwf/configuration").active,
+
+            // Internal kernel state.
+
+            kernel: {
+                time: self.virtualTime.now,
+            },
+
+            // Global node and descendant deltas.
+
+            nodes: [ // TODO: all global objects
+                this.getNode("proxy/clients.vwf", full),
+                this.getNode(this.application(), full),
+            ],
+
+            // `createNode` annotations, keyed by `nodes` indexes.
+
+            annotations: {
+                1: "application",
+            },
+
+            // Message queue.
+
+            queue: self.virtualTime.stateQueue
+
+
+        };
+
+        // Normalize for consistency.
+
+        if (normalize) {
+            applicationState = self.utility.transform(
+                applicationState, self.utility.transforms.hash);
+        }
+
+        this.logger.debugu();
+
+        return applicationState;
+    };
+
+    // -- hashState ----------------------------------------------------------------------------
+
+    /// @name module:vwf.hashState
+    /// 
+    /// @see {@link module:vwf/api/kernel.hashState}
+
+    hashState() {
+
+        this.logger.debuggx("hashState");
+
+        var applicationState = this.getState(true, true);
+
+        // Hash the nodes.
+
+        var hashn = this.hashNode(applicationState.nodes[0]); // TODO: all global objects
+
+        // Hash the queue.
+
+        var hashq = "q" + Crypto.MD5(JSON.stringify(applicationState.queue)).toString().substring(0, 16);
+
+        // Hash the other kernel properties.
+
+        var hashk = "k" + Crypto.MD5(JSON.stringify(applicationState.kernel)).toString().substring(0, 16);
+
+        this.logger.debugu();
+
+        // Generate the combined hash.
+
+        return hashn + ":" + hashq + ":" + hashk;
+    }
+
+
+
+
+    // -- createNode ---------------------------------------------------------------------------
+
+    /// Create a node from a component specification. Construction may require loading data from
+    /// multiple remote documents. This function returns before construction is complete. A
+    /// callback is invoked once the node has fully loaded.
+    /// 
+    /// A simple node consists of a set of properties, methods and events, but a node may
+    /// specialize a prototype component and may also contain multiple child nodes, any of which
+    /// may specialize a prototype component and contain child nodes, etc. So components cover a
+    /// vast range of complexity. The application definition for the overall simulation is a
+    /// single component instance.
+    /// 
+    /// A node is a component instance--a single, anonymous specialization of its component.
+    /// Nodes specialize components in the same way that any component may specialize a prototype
+    /// component. The prototype component is made available as a base, then new or modified
+    /// properties, methods, events, child nodes and scripts are attached to modify the base
+    /// implemenation.
+    /// 
+    /// To create a node, we first make the prototoype available by loading it (if it has not
+    /// already been loaded). This is a recursive call to createNode() with the prototype
+    /// specification. Then we add new, and modify existing, properties, methods, and events
+    /// according to the component specification. Then we load and add any children, again
+    /// recursively calling createNode() for each. Finally, we attach any new scripts and invoke
+    /// an initialization function.
+    /// 
+    /// createNode may complete asynchronously due to its dependence on setNode, createChild and
+    /// loadComponent. To prevent actions from executing out of order, queue processing must be
+    /// suspended while createNode is in progress. setNode, createChild and loadComponent suspend
+    /// the queue when necessary, but additional calls to suspend and resume the queue may be
+    /// needed if other async operations are added.
+    /// 
+    /// @name module:vwf.createNode
+    /// 
+    /// @see {@link module:vwf/api/kernel.createNode}
+
+    createNode (nodeComponent, nodeAnnotation, baseURI, callback_async /* ( nodeID ) */ ) {
+
+        let self = this;
+
+        // Interpret `createNode( nodeComponent, callback )` as
+        // `createNode( nodeComponent, undefined, undefined, callback )` and
+        // `createNode( nodeComponent, nodeAnnotation, callback )` as
+        // `createNode( nodeComponent, nodeAnnotation, undefined, callback )`. `nodeAnnotation`
+        // was added in 0.6.12, and `baseURI` was added in 0.6.25.
+
+        if (typeof nodeAnnotation == "function" || nodeAnnotation instanceof Function) {
+            callback_async = nodeAnnotation;
+            baseURI = undefined;
+            nodeAnnotation = undefined;
+        } else if (typeof baseURI == "function" || baseURI instanceof Function) {
+            callback_async = baseURI;
+            baseURI = undefined;
+        }
+
+        this.logger.debuggx("createNode", function () {
+            return [JSON.stringify(self.loggableComponent(nodeComponent)), nodeAnnotation];
+        });
+
+        var nodePatch;
+
+        if (self.componentIsDescriptor(nodeComponent) && nodeComponent.patches) {
+            nodePatch = nodeComponent;
+            nodeComponent = nodeComponent.patches; // TODO: possible sync errors if the patched node is a URI component and the kernel state (time, random) is different from when the node was created on the originating client
+        }
+
+        // nodeComponent may be a URI, a descriptor, or an ID. While being created, it will
+        // transform from a URI to a descriptor to an ID (depending on its starting state).
+        // nodeURI, nodeDescriptor, and nodeID capture the intermediate states.
+
+        var nodeURI, nodeDescriptor, nodeID;
+
+        async.series([
+
+            // If `nodeComponent` is a URI, load the descriptor. `nodeComponent` may be a URI, a
+            // descriptor or an ID here.
+
+            function (series_callback_async /* ( err, results ) */ ) {
+
+                if (self.componentIsURI(nodeComponent)) { // URI  // TODO: allow non-vwf URIs (models, images, etc.) to pass through to stage 2 and pass directly to createChild()
+
+                    // Resolve relative URIs, but leave host-relative, locally-absolute
+                    // references intact.
+
+                    nodeURI = nodeComponent
+                    //nodeComponent[0] == "/" ? nodeComponent : require( "vwf/utility" ).resolveURI( nodeComponent, baseURI );
+
+                    // Load the document if we haven't seen this URI yet. Mark the components
+                    // list to indicate that this component is loading.
+
+                    if (!self.components[nodeURI]) { // uri is not loading (Array) or is loaded (id)
+
+                        self.components[nodeURI] = []; // [] => array of callbacks while loading => true
+
+                        self.loadComponent(nodeURI, undefined, function (nodeDescriptor) /* async */ {
+                            nodeComponent = nodeDescriptor;
+                            series_callback_async(undefined, undefined);
+                        }, function (errorMessage) {
+                            nodeComponent = undefined;
+                            series_callback_async(errorMessage, undefined);
+                        });
+
+                        // If we've seen this URI, but it is still loading, just add our callback to
+                        // the list. The original load's completion will call our callback too.
+
+                    } else if (self.components[nodeURI] instanceof Array) { // uri is loading
+
+                        callback_async && self.components[nodeURI].push(callback_async); // TODO: is this leaving a series callback hanging if we don't call series_callback_async?
+
+                        // If this URI has already loaded, skip to the end and call the callback
+                        // with the ID.
+
+                    } else { // uri is loaded
+
+                        if (nodePatch) {
+                            self.setNode(self.components[nodeURI], nodePatch, function (nodeID) /* async */ {
+                                callback_async && callback_async(self.components[nodeURI]); // TODO: is this leaving a series callback hanging if we don't call series_callback_async?
+                            });
+                        } else {
+                            callback_async && callback_async(self.components[nodeURI]); // TODO: is this leaving a series callback hanging if we don't call series_callback_async?
+                        }
+
+                    }
+
+                } else { // descriptor, ID or error
+                    series_callback_async(undefined, undefined);
+                }
+
+            },
+
+            // Rudimentary support for `{ includes: prototype }`, which absorbs a prototype
+            // descriptor into the node descriptor before creating the node.
+
+            // Notes:
+            // 
+            //   - Only supports one level, so `{ includes: prototype }` won't work if the
+            //     prototype also contains a `includes` directive).
+            //   - Only works with prototype URIs, so `{ includes: { ... descriptor ... } }`
+            //     won't work.
+            //   - Loads the prototype on each reference, so unlike real prototypes, multiple
+            //     references to the same prototype cause multiple network loads.
+            // 
+            // Also see the `mergeDescriptors` limitations.
+
+            function (series_callback_async /* ( err, results ) */ ) {
+
+                if (self.componentIsDescriptor(nodeComponent) && nodeComponent.includes && self.componentIsURI(nodeComponent.includes)) { // TODO: for "includes:", accept an already-loaded component (which componentIsURI exludes) since the descriptor will be loaded again
+
+                    var prototypeURI = nodeComponent.includes //require( "vwf/utility" ).resolveURI( nodeComponent.includes, nodeURI || baseURI );
+
+                    self.loadComponent(prototypeURI, undefined, function (prototypeDescriptor) /* async */ {
+                        prototypeDescriptor = self.resolvedDescriptor(prototypeDescriptor, prototypeURI);
+                        nodeComponent = self.mergeDescriptors(nodeComponent, prototypeDescriptor); // modifies prototypeDescriptor
+                        series_callback_async(undefined, undefined);
+                    }, function (errorMessage) {
+                        nodeComponent = undefined;
+                        series_callback_async(errorMessage, undefined);
+                    });
+
+                } else {
+                    series_callback_async(undefined, undefined);
+                }
+
+            },
+
+            // If `nodeComponent` is a descriptor, construct and get the ID. `nodeComponent` may
+            // be a descriptor or an ID here.
+
+            function (series_callback_async /* ( err, results ) */ ) {
+
+                if (self.componentIsDescriptor(nodeComponent)) { // descriptor  // TODO: allow non-vwf URIs (models, images, etc.) to pass through to stage 2 and pass directly to createChild()
+
+                    nodeDescriptor = nodeComponent;
+
+                    // Create the node as an unnamed child global object.
+
+                    self.createChild(0, nodeAnnotation, nodeDescriptor, nodeURI, function (nodeID) /* async */ {
+                        nodeComponent = nodeID;
+                        series_callback_async(undefined, undefined);
+                    });
+
+                } else { // ID or error
+                    series_callback_async(undefined, undefined);
+                }
+
+            },
+
+            // nodeComponent is an ID here.
+
+            function (series_callback_async /* ( err, results ) */ ) {
+
+                if (self.componentIsID(nodeComponent) || self.components[nodeComponent] instanceof Array) { // ID
+
+                    nodeID = nodeComponent;
+
+                    if (nodePatch) {
+                        self.setNode(nodeID, nodePatch, function (nodeID) /* async */ {
+                            series_callback_async(undefined, undefined);
+                        });
+                    } else {
+                        series_callback_async(undefined, undefined);
+                    }
+
+                } else { // error
+                    series_callback_async(undefined, undefined); // TODO: error
+                }
+
+            },
+
+        ], function (err, results) /* async */ {
+
+            // If this node derived from a URI, save the list of callbacks waiting for
+            // completion and update the component list with the ID.
+
+            if (nodeURI) {
+                var callbacks_async = self.components[nodeURI];
+                self.components[nodeURI] = nodeID;
+            }
+
+            // Pass the ID to our callback.
+
+            callback_async && callback_async(nodeID); // TODO: handle error if invalid id
+
+            // Call the other callbacks.
+
+            if (nodeURI) {
+                callbacks_async.forEach(function (callback_async) {
+                    callback_async && callback_async(nodeID);
+                });
+            }
+
+        });
+
+        this.logger.debugu();
+    }
+
+    // -- deleteNode ---------------------------------------------------------------------------
+
+    /// @name module:vwf.deleteNode
+    /// 
+    /// @see {@link module:vwf/api/kernel.deleteNode}
+
+    deleteNode (nodeID) {
+        let self = this;
+
+        this.logger.debuggx("deleteNode", nodeID);
+
+        // Send the meta event into the application. We send it before deleting the child so
+        // that the child will still be available for review.
+
+        var parentID = this.parent(nodeID);
+
+        if (parentID !== 0) {
+
+            var nodeIndex = this.children(parentID).indexOf(nodeID);
+
+            if (nodeIndex < 0) {
+                nodeIndex = undefined;
+            }
+
+            if (this.models.kernel.enabled()) {
+                this.fireEvent(parentID, ["children", "removed"],
+                    [nodeIndex, this.kutility.nodeReference(nodeID)]);
+            }
+
+        }
+
+        // Remove the entry in the components list if this was the root of a component loaded
+        // from a URI.
+
+        Object.keys(self.components).some(function (nodeURI) { // components: nodeURI => nodeID
+            if (self.components[nodeURI] == nodeID) {
+                delete self.components[nodeURI];
+                return true;
+            }
+        });
+
+        //Delete childs nodes
+        self.children(nodeID).forEach(function(child)
+        {
+            self.deleteNode(child);
+        });
+
+
+        // Call deletingNode() on each model. The node is considered deleted after all models
+        // have run.
+
+        this.models.forEach(function (model) {
+            model.deletingNode && model.deletingNode(nodeID);
+        });
+
+        // Unregister the node.
+
+        this.nodes.delete(nodeID, parentID);
+
+        // Call deletedNode() on each view. The view is being notified that a node has been
+        // deleted.
+
+        this.views.forEach(function (view) {
+            view.deletedNode && view.deletedNode(nodeID);
+        });
+
+        this.logger.debugu();
+    }
+
+    // -- setNode ------------------------------------------------------------------------------
+
+    /// setNode may complete asynchronously due to its dependence on createChild. To prevent
+    /// actions from executing out of order, queue processing must be suspended while setNode is
+    /// in progress. createChild suspends the queue when necessary, but additional calls to
+    /// suspend and resume the queue may be needed if other async operations are added.
+    /// 
+    /// @name module:vwf.setNode
+    /// 
+    /// @see {@link module:vwf/api/kernel.setNode}
+
+    setNode (nodeID, nodeComponent, callback_async /* ( nodeID ) */ ) { // TODO: merge with createChild?
+
+        let self = this;
+        self.logger.debuggx("setNode", function () {
+            return [nodeID, JSON.stringify(self.loggableComponent(nodeComponent))];
+        });
+
+        var node = self.nodes.existing[nodeID];
+
+        // Set the internal state.
+
+        self.models.object.internals(nodeID, nodeComponent);
+
+        // Suppress kernel reentry so that we can write the state without coloring from
+        // any scripts.
+
+        self.models.kernel.disable();
+
+        // Create the properties, methods, and events. For each item in each set, invoke
+        // createProperty(), createMethod(), or createEvent() to create the field. Each
+        // delegates to the models and views as above.
+
+        // Properties.
+
+        nodeComponent.properties && Object.keys(nodeComponent.properties).forEach(function (propertyName) { // TODO: setProperties should be adapted like this to be used here
+            var propertyValue = nodeComponent.properties[propertyName];
+
+            // Is the property specification directing us to create a new property, or
+            // initialize a property already defined on a prototype?
+
+            // Create a new property if the property is not defined on a prototype.
+            // Otherwise, initialize the property.
+
+            var creating = !node.properties.has(propertyName); // not defined on node or prototype
+
+            // Translate node references in the descriptor's form `{ node: nodeID }` into kernel
+            // node references.
+
+            if (self.valueHasAccessors(propertyValue) && propertyValue.node) {
+                propertyValue = self.kutility.nodeReference(propertyValue.node);
+            }
+
+            // Create or initialize the property.
+
+            if (creating) {
+                self.createProperty(nodeID, propertyName, propertyValue);
+            } else {
+                self.setProperty(nodeID, propertyName, propertyValue);
+            } // TODO: delete when propertyValue === null in patch
+
+        });
+
+        // Methods.
+
+        nodeComponent.methods && Object.keys(nodeComponent.methods).forEach(function (methodName) {
+            var methodHandler = nodeComponent.methods[methodName];
+
+            var creating = !node.methods.has(methodName); // not defined on node or prototype
+
+            // Create or initialize the method.
+
+            if (creating) {
+                self.createMethod(nodeID, methodName, methodHandler.parameters, methodHandler.body);
+            } else {
+                self.setMethod(nodeID, methodName, methodHandler);
+            } // TODO: delete when methodHandler === null in patch
+
+        });
+
+        // Events.
+
+        nodeComponent.events && Object.keys(nodeComponent.events).forEach(function (eventName) {
+            var eventDescriptor = nodeComponent.events[eventName];
+
+            var creating = !node.events.has(eventName); // not defined on node or prototype
+
+            // Create or initialize the event.
+
+            if (creating) {
+                self.createEvent(nodeID, eventName, eventDescriptor.parameters);
+                self.setEvent(nodeID, eventName, eventDescriptor); // set the listeners since `createEvent` can't do it yet
+            } else {
+                self.setEvent(nodeID, eventName, eventDescriptor);
+            } // TODO: delete when eventDescriptor === null in patch
+
+        });
+
+        // Restore kernel reentry.
+
+        self.models.kernel.enable();
+
+
+        async.series([
+
+            function (series_callback_async /* ( err, results ) */ ) {
+
+                // Create and attach the children. For each child, call createChild() with the
+                // child's component specification. createChild() delegates to the models and
+                // views as before.
+
+                async.forEach(Object.keys(nodeComponent.children || {}), function (childName, each_callback_async /* ( err ) */ ) {
+
+                    var creating = !self.nodeHasOwnChild.call(self, nodeID, childName);
+                    if (creating) {
+                        self.createChild(nodeID, childName, nodeComponent.children[childName], undefined, function (childID) /* async */ { // TODO: add in original order from nodeComponent.children  // TODO: ensure id matches nodeComponent.children[childName].id  // TODO: propagate childURI + fragment identifier to children of a URI component?
+                            each_callback_async(undefined);
+                        });
+                    } else {
+                        self.setNode(nodeComponent.children[childName].id || nodeComponent.children[childName].patches,
+                            nodeComponent.children[childName],
+                            function (childID) /* async */ {
+                                each_callback_async(undefined);
+                            });
+                    } // TODO: delete when nodeComponent.children[childName] === null in patch
+                }, function (err) /* async */ {
+                        let deletedProtoNodes = nodeComponent.childrenDeleted;
+
+                        if (deletedProtoNodes) {
+                            let childNames = Object.keys(nodeComponent.childrenDeleted)
+                            if (childNames) {
+                                childNames.forEach(el => {
+                                    console.log("DELETE CHILD HERE!: ", el);
+                                    self.deleteChild(nodeID, el);
+                                });
+                            }
+                        }
+                    series_callback_async(err, undefined);
+                });
+
+            },
+
+            function (series_callback_async /* ( err, results ) */ ) {
+
+                // Attach the scripts. For each script, load the network resource if the script
+                // is specified as a URI, then once loaded, call execute() to direct any model
+                // that manages scripts of this script's type to evaluate the script where it
+                // will perform any immediate actions and retain any callbacks as appropriate
+                // for the script type.
+
+                var scripts = nodeComponent.scripts ? [].concat(nodeComponent.scripts) : []; // accept either an array or a single item
+
+                var baseURI = self.uri(nodeID, true);
+
+                async.map(scripts, function (script, map_callback_async /* ( err, result ) */ ) {
+
+                    if (self.valueHasType(script)) {
+                        if (script.source) {
+                            self.loadScript(script.source, baseURI, function (scriptText) /* async */ { // TODO: this load would be better left to the driver, which may want to ignore it in certain cases, but that would require a completion callback from kernel.execute()
+                                map_callback_async(undefined, {
+                                    text: scriptText,
+                                    type: script.type
+                                });
+                            }, function (errorMessage) {
+                                map_callback_async(errorMessage, undefined);
+                            });
+                        } else {
+                            map_callback_async(undefined, {
+                                text: script.text,
+                                type: script.type
+                            });
+                        }
+                    } else {
+                        map_callback_async(undefined, {
+                            text: script,
+                            type: undefined
+                        });
+                    }
+
+                }, function (err, scripts) /* async */ {
+
+                    // Suppress kernel reentry so that initialization functions don't make any
+                    // changes during replication.
+
+                    self.models.kernel.disable();
+
+                    // Create each script.
+
+                    scripts.forEach(function (script) {
+                        self.execute(nodeID, script.text, script.type); // TODO: callback
+                    });
+
+                    // Restore kernel reentry.
+
+                    self.models.kernel.enable();
+
+                    series_callback_async(err, undefined);
+                });
+
+            },
+
+        ], function (err, results) /* async */ {
+
+            callback_async && callback_async(nodeID);
+
+        });
+
+        this.logger.debugu();
+
+        return nodeComponent;
+    }
+
+    // -- getNode ------------------------------------------------------------------------------
+
+    /// @name module:vwf.getNode
+    /// 
+    /// @see {@link module:vwf/api/kernel.getNode}
+
+    getNode(nodeID, full, normalize, proto) { // TODO: options to include/exclude children, prototypes
+
+        this.logger.debuggx("getNode", nodeID, full);
+
+        var node = this.nodes.existing[nodeID];
+
+        // Start the descriptor.
+
+        var nodeComponent = {};
+
+        // Arrange the component as a patch if the node originated in a URI component. We want
+        // to refer to the original URI but apply any changes that have been made to the node
+        // since it was loaded.
+
+        var patches = this.models.object.patches(nodeID),
+            patched = false;
+
+        if (node.patchable) {
+            nodeComponent.patches = node.uri || nodeID;
+        } else {
+            nodeComponent.id = nodeID;
+        }
+
+        // Intrinsic state. These don't change once created, so they can be omitted if we're
+        // patching.
+
+        if (full || !node.patchable) {
+
+            var intrinsics = this.intrinsics(nodeID); // source, type
+
+            var prototypeID = this.prototype(nodeID);
+
+            if (prototypeID === undefined) {
+                nodeComponent.extends = null;
+            } else if (prototypeID !== this.kutility.protoNodeURI) {
+                nodeComponent.extends = this.getNode(prototypeID); // TODO: move to vwf/model/object and get from intrinsics
+            }
+
+            nodeComponent.implements = this.behaviors(nodeID).map(function (behaviorID) {
+                return this.getNode(behaviorID); // TODO: move to vwf/model/object and get from intrinsics
+            }, this);
+
+            nodeComponent.implements.length || delete nodeComponent.implements;
+
+            if (intrinsics.source !== undefined) nodeComponent.source = intrinsics.source;
+            if (intrinsics.type !== undefined) nodeComponent.type = intrinsics.type;
+
+        }
+
+        // Internal state.
+        //patches = patches ? patches : {}
+        if (full || !node.patchable || patches.internals) {
+
+            var internals = this.models.object.internals(nodeID); // sequence and random
+
+            nodeComponent.sequence = internals.sequence;
+            nodeComponent.random = internals.random;
+
+        }
+
+        // Suppress kernel reentry so that we can read the state without coloring from any
+        // scripts.
+
+        this.models.kernel.disable();
+
+        // Properties.
+
+        if (full || !node.patchable) {
+
+            // Want everything, or only want patches but the node is not patchable.
+
+            nodeComponent.properties = this.getProperties(nodeID);
+
+            // Description of Getters & Setters
+            let jsProps = this.models.javascript.nodes[nodeID];
+            let getters = jsProps.private.getters;
+            let setters = jsProps.private.setters;
+
+            Object.keys(nodeComponent.properties).forEach(el => {
+
+                if (Object.keys(getters).includes(el) || Object.keys(setters).includes(el)) {
+
+                    let prop = {
+                        value: nodeComponent.properties[el]
+                    }
+
+                    if (typeof getters[el] == 'function') {
+                        let fn = getters[el].toString()
+                        prop.get = fn.slice(fn.indexOf("{") + 1, fn.lastIndexOf("}"));
+
+                    } else {
+                        prop.get = ""
+                    }
+
+                    if (typeof setters[el] == 'function') {
+                        let fn = setters[el].toString()
+                        prop.set = fn.slice(fn.indexOf("{") + 1, fn.lastIndexOf("}"));
+                    } else {
+                        prop.set = ""
+                    }
+
+                    nodeComponent.properties[el] = prop
+                }
+            })
+            ///end of Getters & Setters
+
+            for (var propertyName in nodeComponent.properties) {
+                var propertyValue = nodeComponent.properties[propertyName];
+
+                if (propertyValue === undefined) {
+                    delete nodeComponent.properties[propertyName];
+                } else if (this.kutility.valueIsNodeReference(propertyValue)) {
+                    // Translate kernel node references into descriptor node references.
+                    nodeComponent.properties[propertyName] = {
+                        node: propertyValue.id
+                    };
+                }
+
+            }
+
+        } else if (node.properties.changes) {
+
+            // The node is patchable and properties have changed.
+
+            nodeComponent.properties = {};
+
+            Object.keys(node.properties.changes).forEach(function (propertyName) {
+
+                if (node.properties.changes[propertyName] !== "removed") { // TODO: handle delete
+
+                    var propertyValue = this.getProperty(nodeID, propertyName);
+
+                    if (this.kutility.valueIsNodeReference(propertyValue)) {
+                        // Translate kernel node references into descriptor node references.
+                        nodeComponent.properties[propertyName] = {
+                            node: propertyValue.id
+                        };
+                    } else {
+                        nodeComponent.properties[propertyName] = propertyValue;
+                    }
+
+                }
+
+            }, this);
+
+        }
+
+        if (Object.keys(nodeComponent.properties).length == 0) {
+            delete nodeComponent.properties;
+        } else {
+            patched = true;
+        }
+
+        // Methods.
+
+        if (full || !node.patchable) {
+
+            Object.keys(node.methods.existing).forEach(function (methodName) {
+                nodeComponent.methods = nodeComponent.methods || {};
+                nodeComponent.methods[methodName] = this.getMethod(nodeID, methodName);
+                patched = true;
+            }, this);
+
+        } else if (node.methods.changes) {
+
+            Object.keys(node.methods.changes).forEach(function (methodName) {
+                if (node.methods.changes[methodName] !== "removed") { // TODO: handle delete
+                    nodeComponent.methods = nodeComponent.methods || {};
+                    nodeComponent.methods[methodName] = this.getMethod(nodeID, methodName);
+                    patched = true;
+                }
+            }, this);
+
+        }
+
+        // Events.
+
+        var events = full || !node.patchable ?
+            node.events.existing : node.events.changes;
+
+        if (events) {
+            Object.keys(events).forEach(function (eventName) {
+                nodeComponent.events = nodeComponent.events || {};
+                nodeComponent.events[eventName] = this.getEvent(nodeID, eventName);
+                patched = true;
+            }, this);
+        }
+
+        // Restore kernel reentry.
+
+        this.models.kernel.enable();
+
+        // Children.
+
+        nodeComponent.children = {};
+
+        this.children(nodeID).forEach(function (childID) {
+            nodeComponent.children[this.name(childID)] = this.getNode(childID, full);
+        }, this);
+
+        for (var childName in nodeComponent.children) { // TODO: distinguish add, change, remove
+            if (nodeComponent.children[childName] === undefined) {
+                delete nodeComponent.children[childName];
+            }
+        }
+        
+        ////CHECK FOR DELETED PROTO NODES
+
+        nodeComponent.childrenDeleted = {};
+
+        Object.keys(node.childsDeleted).forEach((childName) => {
+            nodeComponent.childrenDeleted[childName] = null;
+            patched = true;
+        });
+
+        ////
+
+        if (Object.keys(nodeComponent.children).length == 0) {
+            delete nodeComponent.children;
+        } else {
+            patched = true;
+        }
+
+        // Scripts.
+
+        // TODO: scripts
+
+        if (node.scripts) {
+            console.log("SCRIPTS: ", node.scripts)
+        }
+
+
+        // Normalize for consistency.
+
+        if (normalize) {
+            nodeComponent = self.utility.transform(
+                nodeComponent, self.utility.transforms.hash);
+        }
+
+        this.logger.debugu();
+
+        // Return the descriptor created, unless it was arranged as a patch and there were no
+        // changes. Otherwise, return the URI if this is the root of a URI component.
+
+        if (full || !node.patchable || patched) {
+            return nodeComponent;
+        } else if (node.uri) {
+            return node.uri;
+        } else {
+            return undefined;
+        }
+
+    };
+
+    // -- hashNode -----------------------------------------------------------------------------
+
+    /// @name module:vwf.hashNode
+    /// 
+    /// @see {@link module:vwf/api/kernel.hashNode}
+
+    hashNode(nodeID) { // TODO: works with patches?  // TODO: only for nodes from getNode( , , true )
+
+        this.logger.debuggx("hashNode", typeof nodeID == "object" ? nodeID.id : nodeID);
+
+        var nodeComponent = typeof nodeID == "object" ? nodeID : this.getNode(nodeID, true, true);
+
+        // Hash the intrinsic state.
+
+        var internal = {
+            id: nodeComponent.id,
+            source: nodeComponent.source,
+            type: nodeComponent.type
+        }; // TODO: get subset same way as getNode() puts them in without calling out specific field names
+
+        internal.source === undefined && delete internal.source;
+        internal.type === undefined && delete internal.type;
+
+        var hashi = "i" + Crypto.MD5(JSON.stringify(internal)).toString().substring(0, 16);
+
+        // Hash the properties.
+
+        var properties = nodeComponent.properties || {};
+
+        var hashp = Object.keys(properties).length ?
+            "p" + Crypto.MD5(JSON.stringify(properties)).toString().substring(0, 16) : undefined;
+
+        // Hash the children.
+
+        var children = nodeComponent.children || {};
+
+        var hashc = Object.keys(children).length ?
+            "c" + Crypto.MD5(JSON.stringify(children)).toString().substring(0, 16) : undefined;
+
+        this.logger.debugu();
+
+        // Generate the combined hash.
+
+        return hashi + (hashp ? "." + hashp : "") + (hashc ? "/" + hashc : "");
+    }
+
+
+    // -- createChild --------------------------------------------------------------------------
+
+    /// When we arrive here, we have a prototype node in hand (by way of its ID) and an object
+    /// containing a component specification. We now need to create and assemble the new node.
+    /// 
+    /// The VWF manager doesn't directly manipulate any node. The various models act in
+    /// federation to create the greater model. The manager simply routes messages within the
+    /// system to allow the models to maintain the necessary data. Additionally, the views
+    /// receive similar messages that allow them to keep their interfaces current.
+    ///
+    /// To create a node, we simply assign a new ID, then invoke a notification on each model and
+    /// a notification on each view.
+    /// 
+    /// createChild may complete asynchronously due to its dependence on createNode and the
+    /// creatingNode and createdNode driver calls. To prevent actions from executing out of
+    /// order, queue processing must be suspended while createChild is in progress. createNode
+    /// and the driver callbacks suspend the queue when necessary, but additional calls to
+    /// suspend and resume the queue may be needed if other async operations are added.
+    /// 
+    /// @name module:vwf.createChild
+    /// 
+    /// @see {@link module:vwf/api/kernel.createChild}
+
+    createChild(nodeID, childName, childComponent, childURI, callback_async /* ( childID ) */ ) {
+
+        let self = this;
+
+        this.logger.debuggx("createChild", function () {
+            return [nodeID, childName, JSON.stringify(self.loggableComponent(childComponent)), childURI];
+        });
+
+        childComponent = self.normalizedComponent(childComponent);
+
+        var child, childID, childIndex, childPrototypeID, childBehaviorIDs = [],
+            deferredInitializations = {};
+
+        var resolvedSource; // resolved `childComponent.source` for the drivers.
+
+        // Determine if we're replicating previously-saved state, or creating a fresh object.
+
+        var replicating = !!childComponent.id;
+
+        // Allocate an ID for the node. IDs must be unique and consistent across all clients
+        // sharing the same instance regardless of the component load order. Each node maintains
+        // a sequence counter, and we allocate the ID based on the parent's sequence counter and
+        // ID. Top-level nodes take the ID from their origin URI when available or from a hash
+        // of the descriptor. An existing ID is used when synchronizing to state drawn from
+        // another client or to a previously-saved state.
+
+        if (childComponent.id) { // incoming replication: pre-calculated id
+            childID = childComponent.id;
+            childIndex = this.children(nodeID).length;
+        } else if (nodeID === 0) { // global: component's URI or hash of its descriptor
+            childID = childURI ||
+                Crypto.MD5(JSON.stringify(childComponent)).toString(); // TODO: MD5 may be too slow here
+            childIndex = childURI;
+        } else { // descendant: parent id + next from parent's sequence
+            childID = nodeID + ":" + this.sequence(nodeID) +
+                (this.configuration["randomize-ids"] ? "-" + ("0" + Math.floor(this.random(nodeID) * 100)).slice(-2) : "") +
+                (this.configuration["humanize-ids"] ? "-" + childName.replace(/[^0-9A-Za-z_-]+/g, "-") : "");
+            childIndex = this.children(nodeID).length;
+        }
+
+        // Register the node.
+
+        child = self.nodes.create(childID, childPrototypeID, childBehaviorIDs, childURI, childName, nodeID);
+
+        // Register the node in vwf/model/object. Since the kernel delegates many node
+        // information functions to vwf/model/object, this serves to register it with the
+        // kernel. The node must be registered before any async operations occur to ensure that
+        // the parent's child list is correct when following siblings calculate their index
+        // numbers.
+
+        this.models.object.creatingNode(nodeID, childID, childPrototypeID, childBehaviorIDs,
+            childComponent.source, childComponent.type, childIndex, childName); // TODO: move node metadata back to the kernel and only use vwf/model/object just as a property store?
+
+        // The base URI for relative references is the URI of this node or the closest ancestor.
+
+        var baseURI = this.uri(childID, true);
+
+        // Construct the node.
+
+        async.series([
+
+            function (series_callback_async /* ( err, results ) */ ) {
+
+                // Rudimentary support for `{ includes: prototype }`, which absorbs a prototype
+                // descriptor into the child descriptor before creating the child. See the notes
+                // in `createNode` and the `mergeDescriptors` limitations.
+
+                // This first task always completes asynchronously (even if it doesn't perform
+                // an async operation) so that the stack doesn't grow from node to node while
+                // createChild() recursively traverses a component. If this task is moved,
+                // replace it with an async stub, or make the next task exclusively async.
+
+                if (self.componentIsDescriptor(childComponent) && childComponent.includes && self.componentIsURI(childComponent.includes)) { // TODO: for "includes:", accept an already-loaded component (which componentIsURI exludes) since the descriptor will be loaded again
+
+                    var prototypeURI = childComponent.includes //require( "vwf/utility" ).resolveURI( childComponent.includes, baseURI );
+
+                    var sync = true; // will loadComponent() complete synchronously?
+
+                    self.loadComponent(prototypeURI, undefined, function (prototypeDescriptor) /* async */ {
+
+                        // Resolve relative references with respect to the included component.
+
+                        prototypeDescriptor = self.resolvedDescriptor(prototypeDescriptor, prototypeURI);
+
+                        // Merge the child descriptor onto the `includes` descriptor.
+
+                        childComponent = self.mergeDescriptors(childComponent, prototypeDescriptor); // modifies prototypeDescriptor
+
+                        if (sync) {
+
+                            self.virtualTime.suspend("before beginning " + childID); // suspend the queue
+
+                            async.nextTick(function () {
+                                series_callback_async(undefined, undefined);
+                                self.virtualTime.resume("after beginning " + childID); // resume the queue; may invoke dispatch(), so call last before returning to the host
+                            });
+
+                        } else {
+                            series_callback_async(undefined, undefined);
+                        }
+
+                    }, function (errorMessage) {
+                        childComponent = undefined;
+                        series_callback_async(errorMessage, undefined);
+                    });
+
+                    sync = false; // not if we got here first
+
+                } else {
+
+                    self.virtualTime.suspend("before beginning " + childID); // suspend the queue
+
+                    async.nextTick(function () {
+                        series_callback_async(undefined, undefined);
+                        self.virtualTime.resume("after beginning " + childID); // resume the queue; may invoke dispatch(), so call last before returning to the host
+                    });
+
+                }
+
+            },
+
+            function (series_callback_async /* ( err, results ) */ ) {
+
+                // Create the prototype and behavior nodes (or locate previously created
+                // instances).
+
+                async.parallel([
+
+                    function (parallel_callback_async /* ( err, results ) */ ) {
+
+                        // Create or find the prototype and save the ID in childPrototypeID.
+
+                        if (childComponent.extends !== null) { // TODO: any way to prevent node loading node as a prototype without having an explicit null prototype attribute in node?
+
+                            var prototypeComponent = childComponent.extends || self.kutility.protoNodeURI;
+
+                            self.createNode(prototypeComponent, undefined, baseURI, function (prototypeID) /* async */ {
+                                childPrototypeID = prototypeID;
+
+                                // TODO: the GLGE driver doesn't handle source/type or properties in prototypes properly; as a work-around pull those up into the component when not already defined
+                                if (!childComponent.source) {
+                                    var prototype_intrinsics = self.intrinsics(prototypeID);
+                                    if (prototype_intrinsics.source) {
+                                        var prototype_uri = self.uri(prototypeID);
+                                        var prototype_properties = self.getProperties(prototypeID);
+                                        childComponent.source = prototype_intrinsics.source //require( "vwf/utility" ).resolveURI( prototype_intrinsics.source, prototype_uri );
+                                        childComponent.type = prototype_intrinsics.type;
+                                        childComponent.properties = childComponent.properties || {};
+                                        Object.keys(prototype_properties).forEach(function (prototype_property_name) {
+                                            if (childComponent.properties[prototype_property_name] === undefined && prototype_property_name != "transform") {
+                                                childComponent.properties[prototype_property_name] = prototype_properties[prototype_property_name];
+                                            }
+                                        });
+                                    }
+                                }
+                                parallel_callback_async(undefined, undefined);
+                            });
+                        } else {
+                            childPrototypeID = undefined;
+                            parallel_callback_async(undefined, undefined);
+                        }
+
+                    },
+
+                    function (parallel_callback_async /* ( err, results ) */ ) {
+
+                        // Create or find the behaviors and save the IDs in childBehaviorIDs.
+
+                        var behaviorComponents = childComponent.implements ? [].concat(childComponent.implements) : []; // accept either an array or a single item
+
+                        async.map(behaviorComponents, function (behaviorComponent, map_callback_async /* ( err, result ) */ ) {
+                            self.createNode(behaviorComponent, undefined, baseURI, function (behaviorID) /* async */ {
+                                map_callback_async(undefined, behaviorID);
+                            });
+                        }, function (err, behaviorIDs) /* async */ {
+                            childBehaviorIDs = behaviorIDs;
+                            parallel_callback_async(err, undefined);
+                        });
+
+                    },
+
+                ], function (err, results) /* async */ {
+                    series_callback_async(err, undefined);
+                });
+
+            },
+
+            function (series_callback_async /* ( err, results ) */ ) {
+
+                // Re-register the node now that we have the prototypes and behaviors.
+
+                child = self.nodes.create(childID, childPrototypeID, childBehaviorIDs, childURI, childName, nodeID);
+
+                // For the proto-prototype node `node.vwf`, register the meta events.
+
+                if (childID === self.kutility.protoNodeURI) {
+                    child.events.create(self.namespaceEncodedName(["properties", "created"]));
+                    child.events.create(self.namespaceEncodedName(["properties", "initialized"]));
+                    child.events.create(self.namespaceEncodedName(["properties", "deleted"]));
+                    child.events.create(self.namespaceEncodedName(["methods", "created"]));
+                    child.events.create(self.namespaceEncodedName(["methods", "deleted"]));
+                    child.events.create(self.namespaceEncodedName(["events", "created"]));
+                    child.events.create(self.namespaceEncodedName(["events", "deleted"]));
+                    child.events.create(self.namespaceEncodedName(["children", "added"]));
+                    child.events.create(self.namespaceEncodedName(["children", "removed"]));
+                }
+
+                // Re-register the node in vwf/model/object now that we have the prototypes and
+                // behaviors. vwf/model/object knows that we call it more than once and only
+                // updates the new information.
+
+                self.models.object.creatingNode(nodeID, childID, childPrototypeID, childBehaviorIDs,
+                    childComponent.source, childComponent.type, childIndex, childName); // TODO: move node metadata back to the kernel and only use vwf/model/object just as a property store?
+
+                // Resolve the asset source URL for the drivers.
+
+                resolvedSource = childComponent.source && childComponent.source
+                //require( "vwf/utility" ).resolveURI( childComponent.source, baseURI );
+
+                // Call creatingNode() on each model. The node is considered to be constructed
+                // after all models have run.
+
+                async.forEachSeries(self.models, function (model, each_callback_async /* ( err ) */ ) {
+
+                    var driver_ready = true;
+                    var timeoutID;
+
+                    // TODO: suppress kernel reentry here (just for childID?) with kernel/model showing a warning when breached; no actions are allowed until all drivers have seen creatingNode()
+
+                    model.creatingNode && model.creatingNode(nodeID, childID, childPrototypeID, childBehaviorIDs,
+                        resolvedSource, childComponent.type, childIndex, childName,
+                        function (ready) /* async */ {
+
+                            if (driver_ready && !ready) {
+                                suspend();
+                            } else if (!driver_ready && ready) {
+                                resume();
+                            }
+
+                            function suspend() {
+                                self.virtualTime.suspend("while loading " + childComponent.source + " for " + childID + " in creatingNode"); // suspend the queue
+                                timeoutID = window.setTimeout(function () {
+                                    resume("timeout loading " + childComponent.source)
+                                }, self.configuration["load-timeout"] * 1000);
+                                driver_ready = false;
+                            }
+
+                            function resume(err) {
+                                window.clearTimeout(timeoutID);
+                                driver_ready = true;
+                                err && self.logger.warnx("createChild", nodeID, childName + ":", err);
+                                each_callback_async(err); // resume createChild()
+                                self.virtualTime.resume("after loading " + childComponent.source + " for " + childID + " in creatingNode"); // resume the queue; may invoke dispatch(), so call last before returning to the host
+                            }
+
+                        });
+
+                    // TODO: restore kernel reentry here
+
+                    driver_ready && each_callback_async(undefined);
+
+                }, function (err) /* async */ {
+                    series_callback_async(err, undefined);
+                });
+
+            },
+
+            function (series_callback_async /* ( err, results ) */ ) {
+
+                // Call createdNode() on each view. The view is being notified of a node that has
+                // been constructed.
+
+                async.forEach(self.views, function (view, each_callback_async /* ( err ) */ ) {
+
+                    var driver_ready = true;
+                    var timeoutID;
+
+                    view.createdNode && view.createdNode(nodeID, childID, childPrototypeID, childBehaviorIDs,
+                        resolvedSource, childComponent.type, childIndex, childName,
+                        function (ready) /* async */ {
+
+                            if (driver_ready && !ready) {
+                                suspend();
+                            } else if (!driver_ready && ready) {
+                                resume();
+                            }
+
+                            function suspend() {
+                                self.virtualTime.suspend("while loading " + childComponent.source + " for " + childID + " in createdNode"); // suspend the queue
+                                timeoutID = window.setTimeout(function () {
+                                    resume("timeout loading " + childComponent.source)
+                                }, self.configuration["load-timeout"] * 1000);
+                                driver_ready = false;
+                            }
+
+                            function resume(err) {
+                                window.clearTimeout(timeoutID);
+                                driver_ready = true;
+                                err && self.logger.warnx("createChild", nodeID, childName + ":", err);
+                                each_callback_async(err); // resume createChild()
+                                self.virtualTime.resume("after loading " + childComponent.source + " for " + childID + " in createdNode"); // resume the queue; may invoke dispatch(), so call last before returning to the host
+                            }
+
+                        });
+
+                    driver_ready && each_callback_async(undefined);
+
+                }, function (err) /* async */ {
+                    series_callback_async(err, undefined);
+                });
+
+            },
+
+            function (series_callback_async /* ( err, results ) */ ) {
+
+                // Set the internal state.
+
+                self.models.object.internals(childID, childComponent);
+
+                // Suppress kernel reentry so that we can read the state without coloring from
+                // any scripts.
+
+                replicating && self.models.kernel.disable();
+
+                // Create the properties, methods, and events. For each item in each set, invoke
+                // createProperty(), createMethod(), or createEvent() to create the field. Each
+                // delegates to the models and views as above.
+
+                childComponent.properties && Object.keys(childComponent.properties).forEach(function (propertyName) {
+                    var propertyValue = childComponent.properties[propertyName];
+
+                    var value = propertyValue,
+                        get, set, create;
+
+                    if (self.valueHasAccessors(propertyValue)) {
+                        value = propertyValue.node ? self.kutility.nodeReference(propertyValue.node) : propertyValue.value;
+                        get = propertyValue.get;
+                        set = propertyValue.set;
+                        create = propertyValue.create;
+                    }
+
+                    // Is the property specification directing us to create a new property, or
+                    // initialize a property already defined on a prototype?
+
+                    // Create a new property if an explicit getter or setter are provided or if
+                    // the property is not defined on a prototype. Initialize the property when
+                    // the property is already defined on a prototype and no explicit getter or
+                    // setter are provided.
+
+                    var creating = create || // explicit create directive, or
+                        get !== undefined || set !== undefined || // explicit accessor, or
+                        !child.properties.has(propertyName); // not defined on prototype
+
+                    // Are we assigning the value here, or deferring assignment until the node
+                    // is constructed because setters will run?
+
+                    var assigning = value === undefined || // no value, or
+                        set === undefined && (creating || !self.nodePropertyHasSetter.call(self, childID, propertyName)) || // no setter, or
+                        replicating; // replicating previously-saved state (setters never run during replication)
+
+                    if (!assigning) {
+                        deferredInitializations[propertyName] = value;
+                        value = undefined;
+                    }
+
+                    // Create or initialize the property.
+
+                    if (creating) {
+                        self.createProperty(childID, propertyName, value, get, set);
+                    } else {
+                        self.setProperty(childID, propertyName, value);
+                    }
+
+                });
+
+                childComponent.methods && Object.keys(childComponent.methods).forEach(function (methodName) {
+                    var methodValue = childComponent.methods[methodName];
+
+                    if (self.valueHasBody(methodValue)) {
+                        self.createMethod(childID, methodName, methodValue.parameters, methodValue.body);
+                    } else {
+                        self.createMethod(childID, methodName, undefined, methodValue);
+                    }
+
+                });
+
+                childComponent.events && Object.keys(childComponent.events).forEach(function (eventName) {
+                    var eventValue = childComponent.events[eventName];
+
+                    if (self.valueHasBody(eventValue)) {
+                        self.createEvent(childID, eventName, eventValue.parameters);
+                        self.setEvent(childID, eventName, eventValue); // set the listeners since `createEvent` can't do it yet
+                    } else {
+                        self.createEvent(childID, eventName, undefined);
+                    }
+
+                });
+
+                // Restore kernel reentry.
+
+                replicating && self.models.kernel.enable();
+
+                // Create and attach the children. For each child, call createChild() with the
+                // child's component specification. createChild() delegates to the models and
+                // views as before.
+
+                async.forEach(Object.keys(childComponent.children || {}), function (childName, each_callback_async /* ( err ) */ ) {
+                    var childValue = childComponent.children[childName];
+
+                    self.createChild(childID, childName, childValue, undefined, function (childID) /* async */ { // TODO: add in original order from childComponent.children  // TODO: propagate childURI + fragment identifier to children of a URI component?
+                        each_callback_async(undefined);
+                    });
+
+                }, function (err) /* async */ {
+                    series_callback_async(err, undefined);
+                });
+
+            },
+
+            function (series_callback_async /* ( err, results ) */ ) {
+
+                // Attach the scripts. For each script, load the network resource if the script is
+                // specified as a URI, then once loaded, call execute() to direct any model that
+                // manages scripts of this script's type to evaluate the script where it will
+                // perform any immediate actions and retain any callbacks as appropriate for the
+                // script type.
+
+                var scripts = childComponent.scripts ? [].concat(childComponent.scripts) : []; // accept either an array or a single item
+
+                async.map(scripts, function (script, map_callback_async /* ( err, result ) */ ) {
+
+                    if (self.valueHasType(script)) {
+                        if (script.source) {
+                            self.loadScript(script.source, baseURI, function (scriptText) /* async */ { // TODO: this load would be better left to the driver, which may want to ignore it in certain cases, but that would require a completion callback from kernel.execute()
+                                map_callback_async(undefined, {
+                                    text: scriptText,
+                                    type: script.type
+                                });
+                            }, function (errorMessage) {
+                                map_callback_async(errorMessage, undefined);
+                            });
+                        } else {
+                            map_callback_async(undefined, {
+                                text: script.text,
+                                type: script.type
+                            });
+                        }
+                    } else {
+                        map_callback_async(undefined, {
+                            text: script,
+                            type: undefined
+                        });
+                    }
+
+                }, function (err, scripts) /* async */ {
+
+                    // Watch for any async kernel calls generated as we run the scripts and wait
+                    // for them complete before completing the node.
+
+                    self.models.kernel.capturingAsyncs(function () {
+
+                        // Suppress kernel reentry so that initialization functions don't make
+                        // any changes during replication.
+
+                        replicating && self.models.kernel.disable();
+
+                        // Create each script.
+
+                        scripts.forEach(function (script) {
+                            self.execute(childID, script.text, script.type); // TODO: callback
+                        });
+
+                        // Perform initializations for properties with setter functions. These
+                        // are assigned here so that the setters run on a fully-constructed node.
+
+                        Object.keys(deferredInitializations).forEach(function (propertyName) {
+                            self.setProperty(childID, propertyName, deferredInitializations[propertyName]);
+                        });
+
+                        // TODO: Adding the node to the tickable list here if it contains a tick() function in JavaScript at initialization time. Replace with better control of ticks on/off and the interval by the node.
+
+                        if (self.execute(childID, "Boolean( this.tick )")) {
+                            self.tickable.nodeIDs.push(childID);
+                        }
+
+                        // Restore kernel reentry.
+                        replicating && self.models.kernel.enable();
+
+                    }, function () {
+
+                        // This function is called when all asynchronous calls from the previous
+                        // function have returned.
+
+                        // Call initializingNode() on each model and initializedNode() on each
+                        // view to indicate that the node is fully constructed.
+
+                        // Since nodes don't (currently) inherit their prototypes' children,
+                        // for each prototype also call initializingNodeFromPrototype() to allow
+                        // model drivers to apply the prototypes' initializers to the node.
+
+                        async.forEachSeries(self.prototypes(childID, true).reverse().concat(childID),
+                            function (childInitializingNodeID, each_callback_async /* err */ ) {
+
+                                // Call initializingNode() on each model.
+
+                                self.models.kernel.capturingAsyncs(function () {
+
+                                    self.models.forEach(function (model) {
+
+                                        // Suppress kernel reentry so that initialization functions
+                                        // don't make any changes during replication.
+                                        replicating && self.models.kernel.disable();
+
+                                        // For a prototype, call `initializingNodeFromPrototype` to
+                                        // run the prototype's initializer on the node. For the
+                                        // node, call `initializingNode` to run its own initializer.
+                                        // 
+                                        // `initializingNodeFromPrototype` is separate from
+                                        // `initializingNode` so that `initializingNode` remains a
+                                        // single call that indicates that the node is fully
+                                        // constructed. Existing drivers, and any drivers that don't
+                                        // care about prototype initializers will by default ignore
+                                        // the intermediate initializations.
+                                        // (`initializingNodeFromPrototype` was added in 0.6.23.)
+
+                                        if (childInitializingNodeID !== childID) {
+                                            model.initializingNodeFromPrototype &&
+                                                model.initializingNodeFromPrototype(nodeID, childID, childInitializingNodeID);
+                                        } else {
+                                            model.initializingNode &&
+                                                model.initializingNode(nodeID, childID, childPrototypeID, childBehaviorIDs,
+                                                    resolvedSource, childComponent.type, childIndex, childName);
+                                        }
+
+                                        // Restore kernel reentry.
+                                        replicating && self.models.kernel.enable();
+
+                                    });
+
+                                }, function () {
+                                    each_callback_async(undefined);
+                                });
+
+                            },
+                            function (err) /* async */ {
+
+                                // Call initializedNode() on each view.
+
+                                self.views.forEach(function (view) {
+                                    view.initializedNode && view.initializedNode(nodeID, childID, childPrototypeID, childBehaviorIDs,
+                                        resolvedSource, childComponent.type, childIndex, childName);
+                                });
+
+                                // Mark the node as initialized.
+
+                                self.nodes.initialize(childID);
+
+                                // Send the meta event into the application.
+
+                                if (!replicating && nodeID !== 0) {
+                                    self.fireEvent(nodeID, ["children", "added"],
+                                        [childIndex, self.kutility.nodeReference(childID)]);
+                                }
+
+                                // Dismiss the loading spinner
+                                if (childID === self.application()) {
+
+                                    NProgress.done();
+                                    NProgress.remove();
+
+                                    //TODO: "LOADING SPINNER"
+
+                                    // var progressbar = document.getElementById("load-progressbar");
+                                    // if (progressbar) {
+                                    //     //document.querySelector('body').removeChild(progressbar);
+                                    //     progressbar.classList.remove("visible");
+                                    //     progressbar.classList.add("not-visible");
+                                    //     progressbar.classList.add("mdc-linear-progress--closed");
+
+                                    // }
+                                    
+                                    // var spinner = document.getElementById( "vwf-loading-spinner" );
+                                    // spinner && spinner.classList.remove( "pace-active" );
+                                }
+
+                                series_callback_async(err, undefined);
+                            });
+                    });
+                });
+            },
+
+        ], function (err, results) /* async */ {
+
+            // The node is complete. Invoke the callback method and pass the new node ID and the
+            // ID of its prototype. If this was the root node for the application, the
+            // application is now fully initialized.
+
+            // Always complete asynchronously so that the stack doesn't grow from node to node
+            // while createChild() recursively traverses a component.
+
+            if (callback_async) {
+
+                self.virtualTime.suspend("before completing " + childID); // suspend the queue
+
+                async.nextTick(function () {
+                    callback_async(childID);
+                    self.virtualTime.resume("after completing " + childID); // resume the queue; may invoke dispatch(), so call last before returning to the host
+                });
+
+            }
+
+        });
+
+        this.logger.debugu();
+    }
+
+    // -- deleteChild --------------------------------------------------------------------------
+
+    /// @name module:vwf.deleteChild
+    /// 
+    /// @see {@link module:vwf/api/kernel.deleteChild}
+
+    deleteChild(nodeID, childName) {
+
+        var childID = this.children(nodeID).filter(function (childID) {
+            return this.name(childID) === childName;
+        }, this)[0];
+
+        if (childID !== undefined) {
+            return this.deleteNode(childID);
+        }
+
+    }
+
+    // -- addChild -----------------------------------------------------------------------------
+
+    /// @name module:vwf.addChild
+    /// 
+    /// @see {@link module:vwf/api/kernel.addChild}
+
+    addChild(nodeID, childID, childName) {
+
+        this.logger.debuggx("addChild", nodeID, childID, childName);
+
+        // Call addingChild() on each model. The child is considered added after all models have
+        // run.
+
+        this.models.forEach(function (model) {
+            model.addingChild && model.addingChild(nodeID, childID, childName);
+        });
+
+        // Call addedChild() on each view. The view is being notified that a child has been
+        // added.
+
+        this.views.forEach(function (view) {
+            view.addedChild && view.addedChild(nodeID, childID, childName);
+        });
+
+        this.logger.debugu();
+    }
+
+    // -- removeChild --------------------------------------------------------------------------
+
+    /// @name module:vwf.removeChild
+    /// 
+    /// @see {@link module:vwf/api/kernel.removeChild}
+
+    removeChild(nodeID, childID) {
+
+        this.logger.debuggx("removeChild", nodeID, childID);
+
+        // Call removingChild() on each model. The child is considered removed after all models
+        // have run.
+
+        this.models.forEach(function (model) {
+            model.removingChild && model.removingChild(nodeID, childID);
+        });
+
+        // Call removedChild() on each view. The view is being notified that a child has been
+        // removed.
+
+        this.views.forEach(function (view) {
+            view.removedChild && view.removedChild(nodeID, childID);
+        });
+
+        this.logger.debugu();
+    }
+
+    // -- setProperties ------------------------------------------------------------------------
+
+    /// Set all of the properties for a node.
+    /// 
+    /// @name module:vwf.setProperties
+    /// 
+    /// @see {@link module:vwf/api/kernel.setProperties}
+
+    setProperties(nodeID, properties) { // TODO: rework as a cover for setProperty(), or remove; passing all properties to each driver is impractical since initializing and setting are different, and reentry can't be controlled when multiple sets are in progress.
+
+        let self = this;
+        this.logger.debuggx("setProperties", nodeID, properties);
+
+        var node = self.nodes.existing[nodeID];
+
+        var entrants = this.setProperty.entrants;
+
+        // Call settingProperties() on each model.
+
+        properties = this.models.reduceRight(function (intermediate_properties, model, index) { // TODO: note that we can't go left to right and stop after the first that accepts the set since we are setting all of the properties as a batch; verify that this creates the same result as calling setProperty individually on each property and that there are no side effects from setting through a driver after the one that handles the set.
+
+            var model_properties = {};
+
+            if (model.settingProperties) {
+
+                model_properties = model.settingProperties(nodeID, properties);
+
+            } else if (model.settingProperty) {
+
+                Object.keys(node.properties.existing).forEach(function (propertyName) {
+
+                    if (properties[propertyName] !== undefined) {
+
+                        var reentry = entrants[nodeID + '-' + propertyName] = {
+                            index: index
+                        }; // the active model number from this call  // TODO: need unique nodeID+propertyName hash
+
+                        model_properties[propertyName] =
+                            model.settingProperty(nodeID, propertyName, properties[propertyName]);
+                        if (self.models.kernel.blocked()) {
+                            model_properties[propertyName] = undefined; // ignore result from a blocked setter
+                        }
+
+                        delete entrants[nodeID + '-' + propertyName];
+
+                    }
+
+                });
+
+            }
+
+            Object.keys(node.properties.existing).forEach(function (propertyName) {
+                if (model_properties[propertyName] !== undefined) { // copy values from this model
+                    intermediate_properties[propertyName] = model_properties[propertyName];
+                } else if (intermediate_properties[propertyName] === undefined) { // as well as recording any new keys
+                    intermediate_properties[propertyName] = undefined;
+                }
+            });
+
+            return intermediate_properties;
+
+        }, {});
+
+        // Record the change.
+
+        if (node.initialized && node.patchable) {
+            Object.keys(properties).forEach(function (propertyName) {
+                node.properties.change(propertyName);
+            });
+        }
+
+        // Call satProperties() on each view.
+
+        this.views.forEach(function (view) {
+
+            if (view.satProperties) {
+                view.satProperties(nodeID, properties);
+            } else if (view.satProperty) {
+                for (var propertyName in properties) {
+                    view.satProperty(nodeID, propertyName, properties[propertyName]); // TODO: be sure this is the value actually set, not the incoming value
+                }
+            }
+
+        });
+
+        this.logger.debugu();
+
+        return properties;
+    }
+
+    // -- getProperties ------------------------------------------------------------------------
+
+    /// Get all of the properties for a node.
+    /// 
+    /// @name module:vwf.getProperties
+    /// 
+    /// @see {@link module:vwf/api/kernel.getProperties}
+
+    getProperties(nodeID) { // TODO: rework as a cover for getProperty(), or remove; passing all properties to each driver is impractical since reentry can't be controlled when multiple gets are in progress.
+
+        let self = this;
+        this.logger.debuggx("getProperties", nodeID);
+
+        var node = self.nodes.existing[nodeID];
+
+        var entrants = this.getProperty.entrants;
+
+        // Call gettingProperties() on each model.
+
+        var properties = this.models.reduceRight(function (intermediate_properties, model, index) { // TODO: note that we can't go left to right and take the first result since we are getting all of the properties as a batch; verify that this creates the same result as calling getProperty individually on each property and that there are no side effects from getting through a driver after the one that handles the get.
+
+            var model_properties = {};
+
+            if (model.gettingProperties) {
+
+                model_properties = model.gettingProperties(nodeID, properties);
+
+            } else if (model.gettingProperty) {
+
+                Object.keys(node.properties.existing).forEach(function (propertyName) {
+
+                    var reentry = entrants[nodeID + '-' + propertyName] = {
+                        index: index
+                    }; // the active model number from this call  // TODO: need unique nodeID+propertyName hash
+
+                    model_properties[propertyName] =
+                        model.gettingProperty(nodeID, propertyName, intermediate_properties[propertyName]);
+                    if (self.models.kernel.blocked()) {
+                        model_properties[propertyName] = undefined; // ignore result from a blocked getter
+                    }
+
+                    delete entrants[nodeID + '-' + propertyName];
+
+                });
+
+            }
+
+            Object.keys(node.properties.existing).forEach(function (propertyName) {
+                if (model_properties[propertyName] !== undefined) { // copy values from this model
+                    intermediate_properties[propertyName] = model_properties[propertyName];
+                } else if (intermediate_properties[propertyName] === undefined) { // as well as recording any new keys
+                    intermediate_properties[propertyName] = undefined;
+                }
+            });
+
+            return intermediate_properties;
+
+        }, {});
+
+        // Call gotProperties() on each view.
+
+        this.views.forEach(function (view) {
+
+            if (view.gotProperties) {
+                view.gotProperties(nodeID, properties);
+            } else if (view.gotProperty) {
+                for (var propertyName in properties) {
+                    view.gotProperty(nodeID, propertyName, properties[propertyName]); // TODO: be sure this is the value actually gotten and not an intermediate value from above
+                }
+            }
+
+        });
+
+        this.logger.debugu();
+
+        return properties;
+    }
+
+    // -- createProperty -----------------------------------------------------------------------
+
+    /// Create a property on a node and assign an initial value.
+    /// 
+    /// @name module:vwf.createProperty
+    /// 
+    /// @see {@link module:vwf/api/kernel.createProperty}
+
+    createProperty(nodeID, propertyName, propertyValue, propertyGet, propertySet) {
+        let self = this;
+        this.logger.debuggx("createProperty", function () {
+            return [nodeID, propertyName, JSON.stringify(self.loggableValue(propertyValue)),
+                self.loggableScript(propertyGet), self.loggableScript(propertySet)
+            ];
+        });
+
+        var node = self.nodes.existing[nodeID];
+
+        // Register the property.
+
+        node.properties.create(propertyName, node.initialized && node.patchable);
+
+        // Call creatingProperty() on each model. The property is considered created after all
+        // models have run.
+
+        this.models.forEach(function (model) {
+            model.creatingProperty && model.creatingProperty(nodeID, propertyName, propertyValue,
+                propertyGet, propertySet);
+        });
+
+        // Call createdProperty() on each view. The view is being notified that a property has
+        // been created.
+
+        this.views.forEach(function (view) {
+            view.createdProperty && view.createdProperty(nodeID, propertyName, propertyValue,
+                propertyGet, propertySet);
+        });
+
+        // Send the meta event into the application.
+
+        if (this.models.kernel.enabled()) {
+            this.fireEvent(nodeID, ["properties", "created"], [propertyName]);
+        }
+
+        this.logger.debugu();
+
+        return propertyValue;
+    }
+
+    // -- setProperty --------------------------------------------------------------------------
+
+    /// Set a property value on a node.
+    /// 
+    /// @name module:vwf.setProperty
+    /// 
+    /// @see {@link module:vwf/api/kernel.setProperty}
+
+    setProperty(nodeID, propertyName, propertyValue) {
+        let self = this;
+        this.logger.debuggx("setProperty", function () {
+            return [nodeID, propertyName, JSON.stringify(self.loggableValue(propertyValue))];
+        });
+
+        var node = self.nodes.existing[nodeID];
+
+        // Record calls into this function by nodeID and propertyName so that models may call
+        // back here (directly or indirectly) to delegate responses further down the chain
+        // without causing infinite recursion.
+
+        var entrants = this.setProperty.entrants;
+
+        var entry = entrants[nodeID + '-' + propertyName] || {}; // the most recent call, if any  // TODO: need unique nodeID+propertyName hash
+        var reentry = entrants[nodeID + '-' + propertyName] = {}; // this call
+
+        // Select the actual driver calls. Create the property if it doesn't exist on this node
+        // or its prototypes. Initialize it if it exists on a prototype but not on this node.
+        // Set it if it already exists on this node.
+
+        if (!node.properties.has(propertyName) || entry.creating) {
+            reentry.creating = true;
+            var settingPropertyEtc = "creatingProperty";
+            var satPropertyEtc = "createdProperty";
+            node.properties.create(propertyName, node.initialized && node.patchable);
+        } else if (!node.properties.hasOwn(propertyName) || entry.initializing) {
+            reentry.initializing = true;
+            var settingPropertyEtc = "initializingProperty";
+            var satPropertyEtc = "initializedProperty";
+            node.properties.create(propertyName, node.initialized && node.patchable);
+        } else {
+            var settingPropertyEtc = "settingProperty";
+            var satPropertyEtc = "satProperty";
+        }
+
+        // Keep track of the number of assignments made by this `setProperty` call and others
+        // invoked indirectly by it, starting with the outermost call.
+
+        var outermost = entrants.assignments === undefined;
+
+        if (outermost) {
+            entrants.assignments = 0;
+        }
+
+        // Have we been called for the same property on the same node for a property still being
+        // assigned (such as when a setter function assigns the property to itself)? If so, then
+        // the inner call should skip drivers that the outer call has already invoked, and the
+        // outer call should complete without invoking drivers that the inner call will have
+        // already called.
+
+        var reentered = (entry.index !== undefined);
+
+        // We'll need to know if the set was delegated to other properties or actually assigned
+        // here.
+
+        var delegated = false,
+            assigned = false;
+
+        // Call settingProperty() on each model. The first model to return a non-undefined value
+        // has performed the set and dictates the return value. The property is considered set
+        // after all models have run.
+
+        this.models.some(function (model, index) {
+
+            // Skip initial models that an outer call has already invoked for this node and
+            // property (if any). If an inner call completed for this node and property, skip
+            // the remaining models.
+
+            if ((!reentered || index > entry.index) && !reentry.completed) {
+
+                // Record the active model number.
+
+                reentry.index = index;
+
+                // Record the number of assignments made since the outermost call. When
+                // `entrants.assignments` increases, a driver has called `setProperty` to make
+                // an assignment elsewhere.
+
+                var assignments = entrants.assignments;
+
+                // Make the call.
+
+                if (!delegated && !assigned) {
+                    var value = model[settingPropertyEtc] && model[settingPropertyEtc](nodeID, propertyName, propertyValue);
+                } else {
+                    model[settingPropertyEtc] && model[settingPropertyEtc](nodeID, propertyName, undefined);
+                }
+
+                // Ignore the result if reentry is disabled and the driver attempted to call
+                // back into the kernel. Kernel reentry is disabled during replication to 
+                // prevent coloring from accessor scripts.
+
+                if (this.models.kernel.blocked()) { // TODO: this might be better handled wholly in vwf/kernel/model by converting to a stage and clearing blocked results on the return
+                    value = undefined;
+                }
+
+                // The property was delegated if the call made any assignments.
+
+                if (entrants.assignments !== assignments) {
+                    delegated = true;
+                }
+
+                // Otherwise if the call returned a value, the property was assigned here.
+                else if (value !== undefined) {
+                    entrants.assignments++;
+                    assigned = true;
+                }
+
+                // Record the value actually assigned. This may differ from the incoming value
+                // if it was range limited, quantized, etc. by the model. This is the value
+                // passed to the views.
+
+                if (value !== undefined) {
+                    propertyValue = value;
+                }
+
+                // If we are setting, exit from the this.models.some() iterator once the value
+                // has been set. Don't exit early if we are creating or initializing since every
+                // model needs the opportunity to register the property.
+
+                return settingPropertyEtc == "settingProperty" && (delegated || assigned);
+            }
+
+        }, this);
+
+        // Record the change if the property was assigned here.
+
+        if (assigned && node.initialized && node.patchable) {
+            node.properties.change(propertyName);
+        }
+
+        // Call satProperty() on each view. The view is being notified that a property has
+        // been set. Only call for value properties as they are actually assigned. Don't call
+        // for accessor properties that have delegated to other properties. Notifying when
+        // setting an accessor property would be useful, but since that information is
+        // ephemeral, and views on late-joining clients would never see it, it's best to never
+        // send those notifications.
+
+        if (assigned) {
+            this.views.forEach(function (view) {
+                view[satPropertyEtc] && view[satPropertyEtc](nodeID, propertyName, propertyValue);
+            });
+        }
+
+        if (reentered) {
+
+            // For a reentrant call, restore the previous state and move the index forward to
+            // cover the models we called.
+
+            entrants[nodeID + '-' + propertyName] = entry;
+            entry.completed = true;
+
+        } else {
+
+            // Delete the call record if this is the first, non-reentrant call here (the normal
+            // case).
+
+            delete entrants[nodeID + '-' + propertyName];
+
+            // If the property was created or initialized, send the corresponding meta event
+            // into the application.
+
+            if (this.models.kernel.enabled()) {
+                if (settingPropertyEtc === "creatingProperty") {
+                    this.fireEvent(nodeID, ["properties", "created"], [propertyName]);
+                } else if (settingPropertyEtc === "initializingProperty") {
+                    this.fireEvent(nodeID, ["properties", "initialized"], [propertyName]);
+                }
+            }
+
+        }
+
+        // Clear the assignment counter when the outermost `setProperty` completes.
+
+        if (outermost) {
+            delete entrants.assignments;
+        }
+
+        this.logger.debugu();
+
+        return propertyValue;
+    }
+
+
+
+
+    // -- getProperty --------------------------------------------------------------------------
+
+    /// Get a property value for a node.
+    /// 
+    /// @name module:vwf.getProperty
+    /// 
+    /// @see {@link module:vwf/api/kernel.getProperty}
+
+    getProperty(nodeID, propertyName, ignorePrototype) {
+
+        this.logger.debuggx("getProperty", nodeID, propertyName);
+
+        var propertyValue = undefined;
+
+        // Record calls into this function by nodeID and propertyName so that models may call
+        // back here (directly or indirectly) to delegate responses further down the chain
+        // without causing infinite recursion.
+
+        var entrants = this.getProperty.entrants;
+
+        var entry = entrants[nodeID + '-' + propertyName] || {}; // the most recent call, if any  // TODO: need unique nodeID+propertyName hash
+        var reentry = entrants[nodeID + '-' + propertyName] = {}; // this call
+
+        // Keep track of the number of retrievals made by this `getProperty` call and others
+        // invoked indirectly by it, starting with the outermost call.
+
+        var outermost = entrants.retrievals === undefined;
+
+        if (outermost) {
+            entrants.retrievals = 0;
+        }
+
+        // Have we been called for the same property on the same node for a property still being
+        // retrieved (such as when a getter function retrieves the property from itself)? If so,
+        // then the inner call should skip drivers that the outer call has already invoked, and
+        // the outer call should complete without invoking drivers that the inner call will have
+        // already called.
+
+        var reentered = (entry.index !== undefined);
+
+        // We'll need to know if the get was delegated to other properties or actually retrieved
+        // here.
+
+        var delegated = false,
+            retrieved = false;
+
+        // Call gettingProperty() on each model. The first model to return a non-undefined value
+        // dictates the return value.
+
+        this.models.some(function (model, index) {
+
+            // Skip initial models that an outer call has already invoked for this node and
+            // property (if any). If an inner call completed for this node and property, skip
+            // the remaining models.
+
+            if ((!reentered || index > entry.index) && !reentry.completed) {
+
+                // Record the active model number.
+
+                reentry.index = index;
+
+                // Record the number of retrievals made since the outermost call. When
+                // `entrants.retrievals` increases, a driver has called `getProperty` to make
+                // a retrieval elsewhere.
+
+                var retrievals = entrants.retrievals;
+
+                // Make the call.
+
+                var value = model.gettingProperty &&
+                    model.gettingProperty(nodeID, propertyName, propertyValue); // TODO: probably don't need propertyValue here
+
+                // Ignore the result if reentry is disabled and the driver attempted to call
+                // back into the kernel. Kernel reentry is disabled during replication to 
+                // prevent coloring from accessor scripts.
+
+                if (this.models.kernel.blocked()) { // TODO: this might be better handled wholly in vwf/kernel/model by converting to a stage and clearing blocked results on the return
+                    value = undefined;
+                }
+
+                // The property was delegated if the call made any retrievals.
+
+                if (entrants.retrievals !== retrievals) {
+                    delegated = true;
+                }
+
+                // Otherwise if the call returned a value, the property was retrieved here.
+                else if (value !== undefined) {
+                    entrants.retrievals++;
+                    retrieved = true;
+                }
+
+                // Record the value retrieved.
+
+                if (value !== undefined) {
+                    propertyValue = value;
+                }
+
+                // Exit from the this.models.some() iterator once we have a return value.
+
+                return delegated || retrieved;
+            }
+
+        }, this);
+
+        if (reentered) {
+
+            // For a reentrant call, restore the previous state, move the index forward to cover
+            // the models we called.
+
+            entrants[nodeID + '-' + propertyName] = entry;
+            entry.completed = true;
+
+        } else {
+
+            // Delete the call record if this is the first, non-reentrant call here (the normal
+            // case).
+
+            delete entrants[nodeID + '-' + propertyName];
+
+            // Delegate to the behaviors and prototype if we didn't get a result from the
+            // current node.
+
+            if (propertyValue === undefined && !ignorePrototype) {
+
+                this.behaviors(nodeID).reverse().concat(this.prototype(nodeID)).
+                some(function (prototypeID, prototypeIndex, prototypeArray) {
+
+                    if (prototypeIndex < prototypeArray.length - 1) {
+                        propertyValue = this.getProperty(prototypeID, propertyName, true); // behavior node only, not its prototypes
+                    } else if (prototypeID !== this.kutility.protoNodeURI) {
+                        propertyValue = this.getProperty(prototypeID, propertyName); // prototype node, recursively
+                    }
+
+                    return propertyValue !== undefined;
+
+                }, this);
+
+            }
+
+            // Call gotProperty() on each view.
+
+            this.views.forEach(function (view) {
+                view.gotProperty && view.gotProperty(nodeID, propertyName, propertyValue); // TODO: be sure this is the value actually gotten and not an intermediate value from above
+            });
+
+        }
+
+        // Clear the retrieval counter when the outermost `getProperty` completes.
+
+        if (outermost) {
+            delete entrants.retrievals;
+        }
+
+        this.logger.debugu();
+
+        return propertyValue;
+    }
+
+
+
+    // -- createMethod -------------------------------------------------------------------------
+
+    /// @name module:vwf.createMethod
+    /// 
+    /// @see {@link module:vwf/api/kernel.createMethod}
+
+    createMethod(nodeID, methodName, methodParameters, methodBody) {
+        let self = this;
+        this.logger.debuggx("createMethod", function () {
+            return [nodeID, methodName, methodParameters, self.loggableScript(methodBody)];
+        });
+
+        var node = self.nodes.existing[nodeID];
+
+        // Register the method.
+
+        node.methods.create(methodName, node.initialized && node.patchable);
+
+        // Call `creatingMethod` on each model. The method is considered created after all
+        // models have run.
+
+        this.models.forEach(function (model) {
+            model.creatingMethod && model.creatingMethod(nodeID, methodName, methodParameters,
+                methodBody);
+        });
+
+        // Call `createdMethod` on each view. The view is being notified that a method has been
+        // created.
+
+        this.views.forEach(function (view) {
+            view.createdMethod && view.createdMethod(nodeID, methodName, methodParameters,
+                methodBody);
+        });
+
+        // Send the meta event into the application.
+
+        if (this.models.kernel.enabled()) {
+            this.fireEvent(nodeID, ["methods", "created"], [methodName]);
+        }
+
+        this.logger.debugu();
+    }
+
+    // -- setMethod ----------------------------------------------------------------------------
+
+    /// @name module:vwf.setMethod
+    /// 
+    /// @see {@link module:vwf/api/kernel.setMethod}
+
+    setMethod(nodeID, methodName, methodHandler) {
+
+        let self = this;
+
+        this.logger.debuggx("setMethod", function () {
+            return [nodeID, methodName]; // TODO loggable methodHandler
+        });
+
+        var node = this.nodes.existing[nodeID];
+
+        methodHandler = this.normalizedHandler(methodHandler);
+
+        if (!node.methods.hasOwn(methodName)) {
+
+            // If the method doesn't exist on this node, delegate to `kernel.createMethod` to
+            // create and assign the method.
+
+            this.createMethod(nodeID, methodName, methodHandler.parameters, methodHandler.body); // TODO: result
+
+        } else {
+
+            // Call `settingMethod` on each model. The first model to return a non-undefined
+            // value dictates the return value.
+
+            this.models.some(function (model) {
+
+                // Call the driver.
+
+                var handler = model.settingMethod && model.settingMethod(nodeID, methodName, methodHandler);
+
+                // Update the value to the value assigned if the driver handled it.
+
+                if (handler !== undefined) {
+                    methodHandler = self.utility.merge({}, handler); // omit `undefined` values
+                }
+
+                // Exit the iterator once a driver has handled the assignment.
+
+                return handler !== undefined;
+
+            });
+
+            // Record the change.
+
+            if (node.initialized && node.patchable) {
+                node.methods.change(methodName);
+            }
+
+            // Call `satMethod` on each view.
+
+            this.views.forEach(function (view) {
+                view.satMethod && view.satMethod(nodeID, methodName, methodHandler);
+            });
+
+        }
+
+        this.logger.debugu();
+
+        return methodHandler;
+    }
+
+    // -- getMethod ----------------------------------------------------------------------------
+
+    /// @name module:vwf.getMethod
+    /// 
+    /// @see {@link module:vwf/api/kernel.getMethod}
+
+    getMethod(nodeID, methodName) {
+
+        let self = this;
+        this.logger.debuggx("getMethod", function () {
+            return [nodeID, methodName];
+        });
+
+        var node = this.nodes.existing[nodeID];
+
+        // Call `gettingMethod` on each model. The first model to return a non-undefined value
+        // dictates the return value.
+
+        var methodHandler = {};
+
+        this.models.some(function (model) {
+
+            // Call the driver.
+
+            var handler = model.gettingMethod && model.gettingMethod(nodeID, methodName);
+
+            // Update the value to the value assigned if the driver handled it.
+
+            if (handler !== undefined) {
+                methodHandler = self.utility.merge({}, handler); // omit `undefined` values
+            }
+
+            // Exit the iterator once a driver has handled the retrieval.
+
+            return handler !== undefined;
+
+        });
+
+        // Call `gotMethod` on each view.
+
+        this.views.forEach(function (view) {
+            view.gotMethod && view.gotMethod(nodeID, methodName, methodHandler);
+        });
+
+        this.logger.debugu();
+
+        return methodHandler;
+    }
+
+    // -- callMethod ---------------------------------------------------------------------------
+
+    /// @name module:vwf.callMethod
+    /// 
+    /// @see {@link module:vwf/api/kernel.callMethod}
+
+    callMethod(nodeID, methodName, methodParameters) {
+        let self = this;
+        this.logger.debuggx("callMethod", function () {
+            return [nodeID, methodName, JSON.stringify(self.loggableValues(methodParameters))];
+        });
+
+        // Call callingMethod() on each model. The first model to return a non-undefined value
+        // dictates the return value.
+
+        var methodValue = undefined;
+
+        this.models.some(function (model) {
+            methodValue = model.callingMethod && model.callingMethod(nodeID, methodName, methodParameters);
+            return methodValue !== undefined;
+        });
+
+        // Call calledMethod() on each view.
+
+        this.views.forEach(function (view) {
+            view.calledMethod && view.calledMethod(nodeID, methodName, methodParameters, methodValue);
+        });
+
+        this.logger.debugu();
+
+        return methodValue;
+    }
+
+    // -- createEvent --------------------------------------------------------------------------
+
+    /// @name module:vwf.createEvent
+    /// 
+    /// @see {@link module:vwf/api/kernel.createEvent}
+
+    createEvent(nodeID, eventName, eventParameters) { // TODO: parameters (used? or just for annotation?)  // TODO: allow a handler body here and treat as this.*event* = function() {} (a self-targeted handler); will help with ui event handlers
+
+        this.logger.debuggx("createEvent", nodeID, eventName, eventParameters);
+
+        var node = this.nodes.existing[nodeID];
+
+        // Encode any namespacing into the name. (Namespaced names were added in 0.6.21.)
+
+        var encodedEventName = this.namespaceEncodedName(eventName);
+
+        // Register the event.
+
+        node.events.create(encodedEventName, node.initialized && node.patchable, eventParameters);
+
+        // Call `creatingEvent` on each model. The event is considered created after all models
+        // have run.
+
+        this.models.forEach(function (model) {
+            model.creatingEvent && model.creatingEvent(nodeID, encodedEventName, eventParameters);
+        });
+
+        // Call `createdEvent` on each view. The view is being notified that a event has been
+        // created.
+
+        this.views.forEach(function (view) {
+            view.createdEvent && view.createdEvent(nodeID, encodedEventName, eventParameters);
+        });
+
+        // Send the meta event into the application.
+
+        if (this.models.kernel.enabled()) {
+            this.fireEvent(nodeID, ["events", "created"], [eventName]);
+        }
+
+        this.logger.debugu();
+    }
+
+    // -- setEvent -----------------------------------------------------------------------------
+
+    /// @name module:vwf.setEvent
+    /// 
+    /// @see {@link module:vwf/api/kernel.setEvent}
+
+    setEvent(nodeID, eventName, eventDescriptor) {
+
+        this.logger.debuggx("setEvent", function () {
+            return [nodeID, eventName]; // TODO: loggable eventDescriptor
+        });
+
+        var node = this.nodes.existing[nodeID];
+
+        // eventDescriptor = normalizedHandler( eventDescriptor );  // TODO
+
+        // Encode any namespacing into the name.
+
+        var encodedEventName = this.namespaceEncodedName(eventName);
+
+        if (!node.events.hasOwn(encodedEventName)) {
+
+            // If the event doesn't exist on this node, delegate to `kernel.createEvent` to
+            // create and assign the event.
+
+            this.createEvent(nodeID, eventName, eventDescriptor.parameters); // TODO: result
+
+            (eventDescriptor.listeners || []).forEach(function (listener) {
+                return this.addEventListener(nodeID, eventName, listener, listener.context, listener.phases);
+            }, this);
+
+        } else {
+
+            // Locate the event in the registry.
+
+            var event = node.events.existing[encodedEventName];
+
+            // xxx
+
+            eventDescriptor = {
+
+                parameters: event.parameters ?
+                    event.parameters.slice() : [], // TODO: note: we're ignoring eventDescriptor.parameters in the set
+
+                listeners: (eventDescriptor.listeners || []).map(function (listener) {
+
+                    if (event.listeners.hasOwn(listener.id)) {
+                        return this.setEventListener(nodeID, eventName, listener.id, listener);
+                    } else {
+                        return this.addEventListener(nodeID, eventName, listener, listener.context, listener.phases);
+                    }
+
+                }, this),
+
+            };
+
+        }
+
+        this.logger.debugu();
+
+        return eventDescriptor;
+    }
+
+    // -- getEvent -----------------------------------------------------------------------------
+
+    /// @name module:vwf.getEvent
+    /// 
+    /// @see {@link module:vwf/api/kernel.getEvent}
+
+    getEvent(nodeID, eventName) {
+
+        this.logger.debuggx("getEvent", function () {
+            return [nodeID, eventName];
+        });
+
+        var node = this.nodes.existing[nodeID];
+
+        // Encode any namespacing into the name.
+
+        var encodedEventName = this.namespaceEncodedName(eventName);
+
+        // Locate the event in the registry.
+
+        var event = node.events.existing[encodedEventName];
+
+        // Build the result descriptor. Omit the `parameters` and `listeners` fields when the
+        // parameters or listeners are missing or empty, respectively.
+
+        var eventDescriptor = {};
+
+        if (event.parameters) {
+            eventDescriptor.parameters = event.parameters.slice();
+        }
+
+        if (event.listeners.existing.length) {
+            eventDescriptor.listeners = event.listeners.existing.map(function (eventListenerID) {
+                var listener = this.getEventListener(nodeID, eventName, eventListenerID);
+                listener.id = eventListenerID;
+                return listener;
+            }, this);
+        }
+
+        this.logger.debugu();
+
+        return eventDescriptor;
+    }
+
+    // -- addEventListener ---------------------------------------------------------------------
+
+    /// @name module:vwf.addEventListener
+    /// 
+    /// @see {@link module:vwf/api/kernel.addEventListener}
+
+    addEventListener(nodeID, eventName, eventHandler, eventContextID, eventPhases) {
+        let self = this;
+        this.logger.debuggx("addEventListener", function () {
+            return [nodeID, eventName, self.loggableScript(eventHandler),
+                eventContextID, eventPhases
+            ];
+        });
+
+        var node = self.nodes.existing[nodeID];
+
+        // Encode any namespacing into the name.
+
+        var encodedEventName = this.namespaceEncodedName(eventName);
+
+        // Register the event if this is the first listener added to an event on a prototype.
+
+        if (!node.events.hasOwn(encodedEventName)) {
+            node.events.create(encodedEventName, node.initialized && node.patchable);
+        }
+
+        // Locate the event in the registry.
+
+        var event = node.events.existing[encodedEventName];
+
+        // Normalize the descriptor.
+
+        eventHandler = this.normalizedHandler(eventHandler, event.parameters);
+
+        // Register the listener.
+
+        var eventListenerID = eventHandler.id || this.sequence(nodeID);
+
+        event.listeners.create(eventListenerID, node.initialized && node.patchable);
+
+        // Call `addingEventListener` on each model.
+
+        this.models.forEach(function (model) {
+            model.addingEventListener &&
+                model.addingEventListener(nodeID, encodedEventName, eventListenerID,
+                    eventHandler, eventContextID, eventPhases);
+        });
+
+        // Call `addedEventListener` on each view.
+
+        this.views.forEach(function (view) {
+            view.addedEventListener &&
+                view.addedEventListener(nodeID, encodedEventName, eventListenerID,
+                    eventHandler, eventContextID, eventPhases);
+        });
+
+        this.logger.debugu();
+
+        return eventListenerID;
+    }
+
+    // -- removeEventListener ------------------------------------------------------------------
+
+    /// @name module:vwf.removeEventListener
+    /// 
+    /// @see {@link module:vwf/api/kernel.removeEventListener}
+
+    removeEventListener(nodeID, eventName, eventListenerID) {
+        let self = this;
+        this.logger.debuggx("removeEventListener", function () {
+            return [nodeID, eventName, self.loggableScript(eventListenerID)];
+        });
+
+        var node = this.nodes.existing[nodeID];
+
+        // Encode any namespacing into the name.
+
+        var encodedEventName = this.namespaceEncodedName(eventName);
+
+        // Locate the event in the registry.
+
+        var event = node.events.existing[encodedEventName];
+
+        // Unregister the listener.
+
+        event.listeners.delete(eventListenerID, node.initialized && node.patchable);
+
+        // Call `removingEventListener` on each model.
+
+        this.models.forEach(function (model) {
+            model.removingEventListener &&
+                model.removingEventListener(nodeID, encodedEventName, eventListenerID);
+        });
+
+        // Call `removedEventListener` on each view.
+
+        this.views.forEach(function (view) {
+            view.removedEventListener &&
+                view.removedEventListener(nodeID, encodedEventName, eventListenerID);
+        });
+
+        this.logger.debugu();
+
+        return eventListenerID;
+    }
+
+    // -- setEventListener ---------------------------------------------------------------------
+
+    /// @name module:vwf.setEventListener
+    /// 
+    /// @see {@link module:vwf/api/kernel.setEventListener}
+
+    setEventListener(nodeID, eventName, eventListenerID, eventListener) {
+
+        let self = this;
+        this.logger.debuggx("setEventListener", function () {
+            return [nodeID, eventName, eventListenerID]; // TODO: loggable eventListener
+        });
+
+        var node = this.nodes.existing[nodeID];
+
+        // Encode any namespacing into the name.
+
+        var encodedEventName = this.namespaceEncodedName(eventName);
+
+        // Locate the event in the registry.
+
+        var event = node.events.existing[encodedEventName];
+
+        // Normalize the descriptor.
+
+        eventListener = this.normalizedHandler(eventListener, event.parameters);
+
+        // Record the change.
+
+        if (node.initialized && node.patchable) {
+            event.listeners.change(eventListenerID);
+        }
+
+        // Call `settingEventListener` on each model. The first model to return a non-undefined
+        // value dictates the return value.
+
+        this.models.some(function (model) {
+
+            // Call the driver.
+
+            var listener = model.settingEventListener &&
+                model.settingEventListener(nodeID, encodedEventName, eventListenerID, eventListener);
+
+            // Update the value to the value assigned if the driver handled it.
+
+            if (listener !== undefined) {
+                eventListener = self.utility.merge({}, listener); // omit `undefined` values
+            }
+
+            // Exit the iterator once a driver has handled the assignment.
+
+            return listener !== undefined;
+
+        });
+
+        // Call `satEventListener` on each view.
+
+        this.views.forEach(function (view) {
+            view.satEventListener &&
+                view.satEventListener(nodeID, encodedEventName, eventListenerID, eventListener);
+        });
+
+        this.logger.debugu();
+
+        return eventListener;
+    }
+
+    // -- getEventListener ---------------------------------------------------------------------
+
+    /// @name module:vwf.getEventListener
+    /// 
+    /// @see {@link module:vwf/api/kernel.getEventListener}
+
+    getEventListener(nodeID, eventName, eventListenerID) {
+
+        let self = this;
+        this.logger.debuggx("getEventListener", function () {
+            return [nodeID, eventName, eventListenerID];
+        });
+
+        // Encode any namespacing into the name.
+
+        var encodedEventName = this.namespaceEncodedName(eventName);
+
+        // Call `gettingEventListener` on each model. The first model to return a non-undefined
+        // value dictates the return value.
+
+        var eventListener = {};
+
+        this.models.some(function (model) {
+
+            // Call the driver.
+
+            var listener = model.gettingEventListener &&
+                model.gettingEventListener(nodeID, encodedEventName, eventListenerID);
+
+            // Update the value to the value assigned if the driver handled it.
+
+            if (listener !== undefined) {
+                eventListener = self.utility.merge({}, listener); // omit `undefined` values
+            }
+
+            // Exit the iterator once a driver has handled the assignment.
+
+            return listener !== undefined;
+
+        });
+
+        // Call `gotEventListener` on each view.
+
+        this.views.forEach(function (view) {
+            view.gotEventListener &&
+                view.gotEventListener(nodeID, encodedEventName, eventListenerID, eventListener);
+        });
+
+        this.logger.debugu();
+
+        return eventListener;
+    }
+
+    // -- flushEventListeners ------------------------------------------------------------------
+
+    /// @name module:vwf.flushEventListeners
+    /// 
+    /// @see {@link module:vwf/api/kernel.flushEventListeners}
+
+    flushEventListeners(nodeID, eventName, eventContextID) {
+
+        this.logger.debuggx("flushEventListeners", nodeID, eventName, eventContextID);
+
+        // Encode any namespacing into the name.
+
+        var encodedEventName = this.namespaceEncodedName(eventName);
+
+        // Retrieve the event in case we need to remove listeners from it
+
+        var node = this.nodes.existing[nodeID];
+        var event = node.events.existing[encodedEventName];
+
+        // Call `flushingEventListeners` on each model.
+
+        this.models.forEach(function (model) {
+            var removedIds = model.flushingEventListeners &&
+                model.flushingEventListeners(nodeID, encodedEventName, eventContextID);
+
+            // If the model driver returned an array of the ids of event listeners that it 
+            // removed, remove their references in the kernel
+
+            if (removedIds && removedIds.length) {
+
+                // Unregister the listeners that were flushed
+                removedIds.forEach(function (removedId) {
+                    event.listeners.delete(removedId, node.initialized && node.patchable);
+                });
+            }
+        });
+
+        // Call `flushedEventListeners` on each view.
+
+        this.views.forEach(function (view) {
+            view.flushedEventListeners &&
+                view.flushedEventListeners(nodeID, encodedEventName, eventContextID);
+        });
+
+        // TODO: `flushEventListeners` can be interpreted by the kernel now and handled with a
+        // `removeEventListener` call instead of separate `flushingEventListeners` and
+        // `flushedEventListeners` calls to the drivers.
+
+        this.logger.debugu();
+    }
+
+    // -- fireEvent ----------------------------------------------------------------------------
+
+    /// @name module:vwf.fireEvent
+    /// 
+    /// @see {@link module:vwf/api/kernel.fireEvent}
+
+    fireEvent(nodeID, eventName, eventParameters) {
+        let self = this;
+        this.logger.debuggx("fireEvent", function () {
+            return [nodeID, eventName, JSON.stringify(self.loggableValues(eventParameters))];
+        });
+
+        // Encode any namespacing into the name. (Namespaced names were added in 0.6.21.)
+
+        var encodedEventName = this.namespaceEncodedName(eventName);
+
+        // Call `firingEvent` on each model.
+
+        var handled = this.models.reduce(function (handled, model) {
+            return model.firingEvent && model.firingEvent(nodeID, encodedEventName, eventParameters) || handled;
+        }, false);
+
+        // Call `firedEvent` on each view.
+
+        this.views.forEach(function (view) {
+            view.firedEvent && view.firedEvent(nodeID, encodedEventName, eventParameters);
+        });
+
+        this.logger.debugu();
+
+        // TODO: `fireEvent` needs to tell the drivers to invoke each listener individually
+        // now that listeners can be spread across multiple drivers.
+
+        return handled;
+    }
+
+    // -- dispatchEvent ------------------------------------------------------------------------
+
+    /// Dispatch an event toward a node. Using fireEvent(), capture (down) and bubble (up) along
+    /// the path from the global root to the node. Cancel when one of the handlers returns a
+    /// truthy value to indicate that it has handled the event.
+    /// 
+    /// @name module:vwf.dispatchEvent
+    /// 
+    /// @see {@link module:vwf/api/kernel.dispatchEvent}
+
+    dispatchEvent(nodeID, eventName, eventParameters, eventNodeParameters) {
+        let self = this;
+        this.logger.debuggx("dispatchEvent", function () {
+            return [nodeID, eventName, JSON.stringify(self.loggableValues(eventParameters)),
+                JSON.stringify(self.loggableIndexedValues(eventNodeParameters))
+            ];
+        });
+
+        // Defaults for the parameters to send with the events. Values from `eventParameters`
+        // are sent to each node. `eventNodeParameters` contains additional values to send to
+        // specific nodes.
+
+        eventParameters = eventParameters || [];
+        eventNodeParameters = eventNodeParameters || {};
+
+        // Find the inheritance path from the node to the root.
+
+        var ancestorIDs = this.ancestors(nodeID);
+        var lastAncestorID = "";
+
+        // Make space to record the parameters sent to each node. Parameters provided for upper
+        // nodes cascade down until another definition is found for a lower node. We'll remember
+        // these on the way down and replay them on the way back up.
+
+        var cascadedEventNodeParameters = {
+            "": eventNodeParameters[""] || [] // defaults come from the "" key in eventNodeParameters
+        };
+
+        // Parameters passed to the handlers are the concatention of the eventParameters array,
+        // the eventNodeParameters for the node (cascaded), and the phase.
+
+        var targetEventParameters = undefined;
+
+        var phase = undefined;
+        var handled = false;
+
+        // Capturing phase.
+
+        phase = "capture"; // only handlers tagged "capture" will be invoked
+
+        handled = handled || ancestorIDs.reverse().some(function (ancestorID) { // TODO: reverse updates the array in place every time and we'd rather not
+
+            cascadedEventNodeParameters[ancestorID] = eventNodeParameters[ancestorID] ||
+                cascadedEventNodeParameters[lastAncestorID];
+
+            lastAncestorID = ancestorID;
+
+            targetEventParameters =
+                eventParameters.concat(cascadedEventNodeParameters[ancestorID], phase);
+
+            targetEventParameters.phase = phase; // smuggle the phase across on the parameters array  // TODO: add "phase" as a fireEvent() parameter? it isn't currently needed in the kernel public API (not queueable, not called by the drivers), so avoid if possible
+
+            return this.fireEvent(ancestorID, eventName, targetEventParameters);
+
+        }, this);
+
+        // At target.
+
+        phase = undefined; // invoke all handlers
+
+        cascadedEventNodeParameters[nodeID] = eventNodeParameters[nodeID] ||
+            cascadedEventNodeParameters[lastAncestorID];
+
+        targetEventParameters =
+            eventParameters.concat(cascadedEventNodeParameters[nodeID], phase);
+
+        handled = handled || this.fireEvent(nodeID, eventName, targetEventParameters);
+
+        // Bubbling phase.
+
+        phase = undefined; // invoke all handlers
+
+        handled = handled || ancestorIDs.reverse().some(function (ancestorID) { // TODO: reverse updates the array in place every time and we'd rather not
+
+            targetEventParameters =
+                eventParameters.concat(cascadedEventNodeParameters[ancestorID], phase);
+
+            return this.fireEvent(ancestorID, eventName, targetEventParameters);
+
+        }, this);
+
+        this.logger.debugu();
+    }
+
+    // -- execute ------------------------------------------------------------------------------
+
+    /// @name module:vwf.execute
+    /// 
+    /// @see {@link module:vwf/api/kernel.execute}
+
+    execute(nodeID, scriptText, scriptType, callback_async /* result */ ) {
+
+        let self = this;
+
+        this.logger.debuggx("execute", function () {
+            return [nodeID, self.loggableScript(scriptText), scriptType];
+        });
+
+        // Assume JavaScript if the type is not specified and the text is a string.
+
+        if (!scriptType && (typeof scriptText == "string" || scriptText instanceof String)) {
+            scriptType = "application/javascript";
+        }
+
+        // Call executing() on each model. The script is considered executed after all models
+        // have run.
+
+        var scriptValue = undefined;
+
+        // Watch for any async kernel calls generated as we execute the scriptText and wait for
+        // them to complete before calling the callback.
+
+        self.models.kernel.capturingAsyncs(function () {
+            self.models.some(function (model) {
+                scriptValue = model.executing &&
+                    model.executing(nodeID, scriptText, scriptType);
+                return scriptValue !== undefined;
+            });
+
+            // Call executed() on each view to notify view that a script has been executed.
+
+            self.views.forEach(function (view) {
+                view.executed && view.executed(nodeID, scriptText, scriptType);
+            });
+
+        }, function () {
+            callback_async && callback_async(scriptValue);
+        });
+
+        this.logger.debugu();
+
+        return scriptValue;
+    }
+
+    // -- random -------------------------------------------------------------------------------
+
+    /// @name module:vwf.random
+    /// 
+    /// @see {@link module:vwf/api/kernel.random}
+
+    random(nodeID) {
+        return this.models.object.random(nodeID);
+    }
+
+    // -- seed ---------------------------------------------------------------------------------
+
+    /// @name module:vwf.seed
+    /// 
+    /// @see {@link module:vwf/api/kernel.seed}
+
+    seed(nodeID, seed) {
+        return this.models.object.seed(nodeID, seed);
+    }
+
+    // -- time ---------------------------------------------------------------------------------
+
+    /// The current simulation time.
+    /// 
+    /// @name module:vwf.time
+    /// 
+    /// @see {@link module:vwf/api/kernel.time}
+
+    now() {
+        return this.virtualTime.now;
+    }
+
+    time() {
+        return this.virtualTime.now;
+    }
+
+    // -- client -------------------------------------------------------------------------------
+
+    /// The moniker of the client responsible for the current action. Will be falsy for actions
+    /// originating in the server, such as time ticks.
+    /// 
+    /// @name module:vwf.client
+    /// 
+    /// @see {@link module:vwf/api/kernel.client}
+
+    client() {
+        return this.virtualTime.client_;
+    }
+
+    // -- moniker ------------------------------------------------------------------------------
+
+    /// The identifer the server assigned to this client.
+    /// 
+    /// @name module:vwf.moniker
+    /// 
+    /// @see {@link module:vwf/api/kernel.moniker}
+
+    moniker() {
+        return this.moniker_;
+    }
+
+    // -- application --------------------------------------------------------------------------
+
+    /// @name module:vwf.application
+    /// 
+    /// @see {@link module:vwf/api/kernel.application}
+
+    application(initializedOnly) {
+        let self = this;
+        var applicationID;
+
+        Object.keys(self.nodes.globals).forEach(function (globalID) {
+            var global = self.nodes.existing[globalID];
+            if ((!initializedOnly || global.initialized) && global.name === "application") {
+                applicationID = globalID;
+            }
+        }, this);
+
+        return applicationID;
+    }
+
+    // -- intrinsics ---------------------------------------------------------------------------
+
+    /// @name module:vwf.intrinsics
+    /// 
+    /// @see {@link module:vwf/api/kernel.intrinsics}
+
+    intrinsics(nodeID, result) {
+        return this.models.object.intrinsics(nodeID, result);
+    }
+
+    // -- uri ----------------------------------------------------------------------------------
+
+    /// @name module:vwf.uri
+    /// 
+    /// @see {@link module:vwf/api/kernel.uri}
+
+    uri(nodeID, searchAncestors, initializedOnly) {
+
+        var uri = this.models.object.uri(nodeID);
+
+        if (searchAncestors) {
+            while (!uri && (nodeID = this.parent(nodeID, initializedOnly))) {
+                uri = this.models.object.uri(nodeID);
+            }
+        }
+
+        return uri;
+    }
+
+    // -- name ---------------------------------------------------------------------------------
+
+    /// @name module:vwf.name
+    /// 
+    /// @see {@link module:vwf/api/kernel.name}
+
+    name(nodeID) {
+        return this.models.object.name(nodeID);
+    }
+
+    // -- prototype ----------------------------------------------------------------------------
+
+    /// @name module:vwf.prototype
+    /// 
+    /// @see {@link module:vwf/api/kernel.prototype}
+
+    prototype(nodeID) {
+        return this.models.object.prototype(nodeID);
+    }
+
+    // -- prototypes ---------------------------------------------------------------------------
+
+    /// @name module:vwf.prototypes
+    /// 
+    /// @see {@link module:vwf/api/kernel.prototypes}
+
+    prototypes(nodeID, includeBehaviors) {
+
+        var prototypes = [];
+
+        do {
+
+            // Add the current node's behaviors.
+            if (includeBehaviors) {
+                var b = [].concat(this.behaviors(nodeID));
+                Array.prototype.push.apply(prototypes, b.reverse());
+            }
+
+            // Get the next prototype.
+            nodeID = this.prototype(nodeID);
+
+            // Add the prototype.
+            if (nodeID) {
+                prototypes.push(nodeID);
+            }
+
+        } while (nodeID);
+
+        return prototypes;
+    }
+
+    // -- behaviors ----------------------------------------------------------------------------
+
+    /// @name module:vwf.behaviors
+    /// 
+    /// @see {@link module:vwf/api/kernel.behaviors}
+
+    behaviors(nodeID) {
+        return this.models.object.behaviors(nodeID);
+    }
+
+    // -- globals ------------------------------------------------------------------------------
+
+    /// @name module:vwf.globals
+    /// 
+    /// @see {@link module:vwf/api/kernel.globals}
+
+    globals(initializedOnly) {
+        let self = this;
+        var globals = {};
+
+        Object.keys(self.nodes.globals).forEach(function (globalID) {
+            if (!initializedOnly || self.nodes.existing[globalID].initialized) {
+                globals[globalID] = undefined;
+            }
+        }, this);
+
+        return globals;
+    }
+
+    // -- global -------------------------------------------------------------------------------
+
+    /// @name module:vwf.global
+    /// 
+    /// @see {@link module:vwf/api/kernel.global}
+
+    global(globalReference, initializedOnly) {
+
+        let self = this;
+        var globals = this.globals(initializedOnly);
+
+        // Look for a global node whose URI matches `globalReference`. If there is no match by
+        // URI, then search again by name.
+
+        return matches("uri") || matches("name");
+
+        // Look for a global node where the field named by `field` matches `globalReference`.
+
+        function matches(field) {
+
+            var matchingID;
+
+            Object.keys(globals).some(function (globalID) {
+                if (self.nodes.existing[globalID][field] === globalReference) {
+                    matchingID = globalID;
+                    return true;
+                }
+            });
+
+            return matchingID;
+        }
+
+    }
+
+
+
+    // -- root ---------------------------------------------------------------------------------
+
+    /// @name module:vwf.root
+    /// 
+    /// @see {@link module:vwf/api/kernel.root}
+
+    root(nodeID, initializedOnly) {
+
+        var rootID;
+
+        // Walk the ancestors to the top of the tree. Stop when we reach the pseudo-node at the
+        // global root, which unlike all other nodes has a falsy ID, or `undefined` if we could
+        // not reach the top because `initializedOnly` is set and we attempted to cross between
+        // nodes that have and have not completed initialization.
+
+        do {
+            rootID = nodeID;
+            nodeID = this.parent(nodeID, initializedOnly);
+        } while (nodeID);
+
+        // Return the root ID, or `undefined` when `initializedOnly` is set and the node can't
+        // see the root.
+
+        return nodeID === undefined ? undefined : rootID;
+    }
+
+    // -- ancestors ----------------------------------------------------------------------------
+
+    /// @name module:vwf.ancestors
+    /// 
+    /// @see {@link module:vwf/api/kernel.ancestors}
+
+    ancestors(nodeID, initializedOnly) {
+
+        var ancestors = [];
+
+        nodeID = this.parent(nodeID, initializedOnly);
+
+        while (nodeID) {
+            ancestors.push(nodeID);
+            nodeID = this.parent(nodeID, initializedOnly);
+        }
+
+        return ancestors;
+    }
+
+    // -- parent -------------------------------------------------------------------------------
+
+    /// @name module:vwf.parent
+    /// 
+    /// @see {@link module:vwf/api/kernel.parent}
+
+    parent(nodeID, initializedOnly) {
+        return this.models.object.parent(nodeID, initializedOnly);
+    }
+
+    // -- children -----------------------------------------------------------------------------
+
+    /// @name module:vwf.children
+    /// 
+    /// @see {@link module:vwf/api/kernel.children}
+
+    children(nodeID, initializedOnly) {
+
+        if (nodeID === undefined) {
+            this.logger.errorx("children", "cannot retrieve children of nonexistent node");
+            return;
+        }
+
+        return this.models.object.children(nodeID, initializedOnly);
+    }
+
+    // -- child --------------------------------------------------------------------------------
+
+    /// @name module:vwf.child
+    /// 
+    /// @see {@link module:vwf/api/kernel.child}
+
+    child(nodeID, childReference, initializedOnly) {
+
+        var children = this.children(nodeID, initializedOnly);
+
+        if (typeof childReference === "number" || childReference instanceof Number) {
+            return children[childReference];
+        } else {
+            return children.filter(function (childID) {
+                return childID && this.name(childID) === childReference;
+            }, this)[0];
+        }
+
+    }
+
+    // -- descendants --------------------------------------------------------------------------
+
+    /// @name module:vwf.descendants
+    /// 
+    /// @see {@link module:vwf/api/kernel.descendants}
+
+    descendants(nodeID, initializedOnly) {
+
+        if (nodeID === undefined) {
+            this.logger.errorx("descendants", "cannot retrieve children of nonexistent node");
+            return;
+        }
+
+        var descendants = [];
+
+        this.children(nodeID, initializedOnly).forEach(function (childID) {
+            descendants.push(childID);
+            childID && Array.prototype.push.apply(descendants, this.descendants(childID, initializedOnly));
+        }, this);
+
+        return descendants;
+    }
+
+    // -- sequence -----------------------------------------------------------------------------
+
+    /// @name module:vwf.sequence
+    /// 
+    /// @see {@link module:vwf/api/kernel.sequence}
+
+    sequence(nodeID) {
+        return this.models.object.sequence(nodeID);
+    }
+
+
+    /// Locate nodes matching a search pattern. See vwf.api.kernel#find for details.
+    /// 
+    /// @name module:vwf.find
+    ///
+    /// @param {ID} nodeID
+    ///   The reference node. Relative patterns are resolved with respect to this node. `nodeID`
+    ///   is ignored for absolute patterns.
+    /// @param {String} matchPattern
+    ///   The search pattern.
+    /// @param {Boolean} [initializedOnly]
+    ///   Interpret nodes that haven't completed initialization as though they don't have
+    ///   ancestors. Drivers that manage application code should set `initializedOnly` since
+    ///   applications should never have access to uninitialized parts of the application graph.
+    /// @param {Function} [callback]
+    ///   A callback to receive the search results. If callback is provided, find invokes
+    ///   callback( matchID ) for each match. Otherwise the result is returned as an array.
+    /// 
+    /// @returns {ID[]|undefined}
+    ///   If callback is provided, undefined; otherwise an array of the node ids of the result.
+    /// 
+    /// @see {@link module:vwf/api/kernel.find}
+
+    find(nodeID, matchPattern, initializedOnly, callback /* ( matchID ) */ ) {
+
+        // Interpret `find( nodeID, matchPattern, callback )` as
+        // `find( nodeID, matchPattern, undefined, callback )`. (`initializedOnly` was added in
+        // 0.6.8.)
+
+        if (typeof initializedOnly == "function" || initializedOnly instanceof Function) {
+            callback = initializedOnly;
+            initializedOnly = undefined;
+        }
+
+        // Run the query.
+
+        var matchIDs = find.call(this, nodeID, matchPattern, initializedOnly);
+
+        // Return the result. Invoke the callback if one was provided. Otherwise, return the
+        // array directly.
+
+        if (callback) {
+
+            matchIDs.forEach(function (matchID) {
+                callback(matchID);
+            });
+
+        } else { // TODO: future iterator proxy
+
+            return matchIDs;
+        }
+
+    }
+
+    // -- findClients ------------------------------------------------------------------------------
+
+    /// Locate client nodes matching a search pattern. 
+    ///
+    /// @name module:vwf.findClients
+    ///
+    /// @param {ID} nodeID
+    ///   The reference node. Relative patterns are resolved with respect to this node. `nodeID`
+    ///   is ignored for absolute patterns.
+    /// @param {String} matchPattern
+    ///   The search pattern.
+    /// @param {Function} [callback]
+    ///   A callback to receive the search results. If callback is provided, find invokes
+    ///   callback( matchID ) for each match. Otherwise the result is returned as an array.
+    /// 
+    /// @returns {ID[]|undefined}
+    ///   If callback is provided, undefined; otherwise an array of the node ids of the result.
+    /// 
+    /// @deprecated in version 0.6.21. Instead of `kernel.findClients( reference, "/pattern" )`,
+    ///   use `kernel.find( reference, "doc('http://vwf.example.com/clients.vwf')/pattern" )`.
+    /// 
+    /// @see {@link module:vwf/api/kernel.findClients}
+
+    findClients(nodeID, matchPattern, callback /* ( matchID ) */ ) {
+
+        this.logger.warn("`kernel.findClients` is deprecated. Use " +
+            "`kernel.find( nodeID, \"doc('proxy/clients.vwf')/pattern\" )`" +
+            " instead.");
+
+        var clientsMatchPattern = "doc('proxy/clients.vwf')" +
+            (matchPattern[0] === "/" ? "" : "/") + matchPattern;
+
+        return this.find(nodeID || this.application(), clientsMatchPattern, callback);
+    }
+
+    /// Test a node against a search pattern. See vwf.api.kernel#test for details.
+    /// 
+    /// @name module:vwf.test
+    /// 
+    /// @param {ID} nodeID
+    ///   The reference node. Relative patterns are resolved with respect to this node. `nodeID`
+    ///   is ignored for absolute patterns.
+    /// @param {String} matchPattern
+    ///   The search pattern.
+    /// @param {ID} testID
+    ///   A node to test against the pattern.
+    /// @param {Boolean} [initializedOnly]
+    ///   Interpret nodes that haven't completed initialization as though they don't have
+    ///   ancestors. Drivers that manage application code should set `initializedOnly` since
+    ///   applications should never have access to uninitialized parts of the application graph.
+    /// 
+    /// @returns {Boolean}
+    ///   true when testID matches the pattern.
+    /// 
+    /// @see {@link module:vwf/api/kernel.test}
+
+    test(nodeID, matchPattern, testID, initializedOnly) {
+
+        // Run the query.
+
+        var matchIDs = this.find.call(this, nodeID, matchPattern, initializedOnly);
+
+        // Search for the test node in the result.
+
+        return matchIDs.some(function (matchID) {
+            return matchID == testID;
+        });
+
+    }
+
+
+    // == Private functions ====================================================================
+
+    // -- loadComponent ------------------------------------------------------------------------
+
+    /// @name module:vwf~loadComponent
+
+    loadComponent(nodeURI, baseURI, callback_async /* nodeDescriptor */ , errback_async /* errorMessage */ ) { // TODO: turn this into a generic xhr loader exposed as a kernel function?
+
+        let self = this;
+
+        if (nodeURI == self.kutility.protoNodeURI) {
+
+            callback_async(self.kutility.protoNodeDescriptor);
+
+        } else if (nodeURI.match(RegExp("^data:application/json;base64,"))) {
+
+            // Primarly for testing, parse one specific form of data URIs. We need to parse
+            // these ourselves since Chrome can't load data URIs due to cross origin
+            // restrictions.
+
+            callback_async(JSON.parse(atob(nodeURI.substring(29)))); // TODO: support all data URIs
+
+        } else {
+
+            self.virtualTime.suspend("while loading " + nodeURI); // suspend the queue
+
+            let fetchUrl = self.remappedURI(nodeURI, baseURI);
+            let dbName = fetchUrl.split(".").join("_");
+
+            const parseComp = function (f) {
+
+                let result = JSON.parse(f);
+
+                let nativeObject = result;
+                // console.log(nativeObject);
+
+                if (nativeObject) {
+                    callback_async(nativeObject);
+                    self.virtualTime.resume("after loading " + nodeURI); // resume the queue; may invoke dispatch(), so call last before returning to the host
+
+                } else {
+
+                    self.logger.warnx("loadComponent", "error loading", nodeURI + ":", error);
+                    errback_async(error);
+                    self.virtualTime.resume("after loading " + nodeURI); // resume the queue; may invoke dispatch(), so call last before returning to the host
+                }
+
+            }
+
+
+            //var fileName = "";
+            // let userDB = _LCSDB.user(_LCS_WORLD_USER.pub);    
+            if (dbName.includes("proxy")) {
+                //userDB = await window._LCS_SYS_USER.get('proxy').then();
+                let proxyDB = self.proxy ? _LCSDB.user(self.proxy) : _LCSDB.user(_LCS_WORLD_USER.pub);
+                let fileName = dbName + '_json';
+                let dbNode = proxyDB.get('proxy').get(fileName);
+                let nodeProm = new Promise(res => dbNode.once(res));
+
+                nodeProm.then(comp => {
+                    parseComp(comp);
+                })
+
+                //    (function(r){
+                //        //console.log(r);
+                //        parseComp(r);
+
+                //    });
+
+            } else {
+                let worldName = dbName.split('/')[1];
+                let fileName = dbName.split('/')[2] + '_json';
+                let dbNode = _LCSDB.user(_LCS_WORLD_USER.pub).get('worlds').path(worldName).get(fileName);
+
+                let nodeProm = new Promise(res => dbNode.once(res))
+                nodeProm.then(comp => {
+                    parseComp(comp);
+                })
+
+                //    (function(r){
+                //     //console.log(r);
+                //     parseComp(r);
+
+                // });
+            }
+
+            //console.log(source);
+
+            //userDB.get(fileName).once(async function(res) {
+
+
+
+
+            // }, {wait: 1000})
+        }
+
+    }
+
+    // -- loadScript ---------------------------------------------------------------------------
+
+    /// @name module:vwf~loadScript
+
+    loadScript(scriptURI, baseURI, callback_async /* scriptText */ , errback_async /* errorMessage */ ) {
+
+        let self = this;
+        if (scriptURI.match(RegExp("^data:application/javascript;base64,"))) {
+
+            // Primarly for testing, parse one specific form of data URIs. We need to parse
+            // these ourselves since Chrome can't load data URIs due to cross origin
+            // restrictions.
+
+            callback_async(atob(scriptURI.substring(35))); // TODO: support all data URIs
+
+        } else {
+
+            self.virtualTime.suspend("while loading " + scriptURI); // suspend the queue
+
+            let fetchUrl = self.remappedURI(scriptURI, baseURI);
+            let dbName = fetchUrl.split(".").join("_")
+
+            const parseComp = function (res) {
+
+                let scriptText = res;
+
+                try {
+                    callback_async(scriptText);
+                    self.virtualTime.resume("after loading " + scriptURI); // resume the queue; may invoke dispatch(), so call last before returning to the host
+
+                } catch (e) {
+
+                    self.logger.warnx("loadScript", "error loading", scriptURI + ":", error);
+                    errback_async(error);
+                    self.virtualTime.resume("after loading " + scriptURI); // resume the queue; may invoke dispatch(), so call last before returning to the host
+                }
+            }
+
+
+
+            let worldName = dbName.split('/')[1];
+
+            //let userDB = _LCSDB.user(_LCS_WORLD_USER.pub);  
+
+
+            if (dbName.includes("proxy")) {
+                let proxyDB = self.proxy ? _LCSDB.user(self.proxy) : _LCSDB.user(_LCS_WORLD_USER.pub);
+
+                //userDB = window._LCS_SYS_USER.get('proxy');
+                let fileName = dbName;
+                let dbNode = proxyDB.get('proxy').get(fileName);
+                let nodeProm = new Promise(res => dbNode.once(res))
+
+                nodeProm.then(comp => {
+                    parseComp(comp);
+                })
+                // window._LCS_SYS_USER.get('proxy').get(fileName).get('file').once(function(r){
+                //     //console.log(r);
+                //     parseComp(r);
+                // });
+
+            } else {
+                let fileName = dbName.split('/')[2]; //dbName.replace(worldName + '/', "");
+
+                let dbNode = _LCSDB.user(_LCS_WORLD_USER.pub).get('worlds').path(worldName).get(fileName);
+                let nodeProm = new Promise(res => dbNode.once(res))
+                nodeProm.then(comp => {
+                    parseComp(comp);
+                })
+                // userDB.get('worlds').path(worldName).get(fileName).get('file').once(function(r){
+                //     //console.log(r);
+                //     parseComp(r);
+
+                // });
+            }
+
+            // userDB.get(fileName).once(, {wait: 1000})
+        }
+
+    }
+
+
+
+    /// Determine if a given property of a node has a setter function, either directly on the
+    /// node or inherited from a prototype.
+    /// 
+    /// This function must run as a method of the kernel. Invoke as: nodePropertyHasSetter.call(
+    ///   kernel, nodeID, propertyName ).
+    /// 
+    /// @name module:vwf~nodePropertyHasSetter
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} propertyName
+    /// 
+    /// @returns {Boolean}
+
+    nodePropertyHasSetter(nodeID, propertyName) { // invoke with the kernel as "this"  // TODO: this is peeking inside of vwf-model-javascript; need to delegate to all script drivers
+        var node = this.models.javascript.nodes[nodeID];
+        var setter = node.private.setters && node.private.setters[propertyName];
+        return typeof setter == "function" || setter instanceof Function;
+    }
+
+    /// Determine if a given property of a node has a setter function. The node's prototypes are
+    /// not considered.
+    /// 
+    /// This function must run as a method of the kernel. Invoke as:
+    ///   nodePropertyHasOwnSetter.call( kernel, nodeID, propertyName ).
+    /// 
+    /// @name module:vwf~nodePropertyHasOwnSetter
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} propertyName
+    /// 
+    /// @returns {Boolean}
+
+    nodePropertyHasOwnSetter(nodeID, propertyName) { // invoke with the kernel as "this"  // TODO: this is peeking inside of vwf-model-javascript; need to delegate to all script drivers
+        var node = this.models.javascript.nodes[nodeID];
+        var setter = node.private.setters && node.private.setters.hasOwnProperty(propertyName) && node.private.setters[propertyName];
+        return typeof setter == "function" || setter instanceof Function;
+    }
+
+    /// Determine if a node has a child with the given name, either directly on the node or
+    /// inherited from a prototype.
+    /// 
+    /// This function must run as a method of the kernel. Invoke as: nodeHasChild.call(
+    ///   kernel, nodeID, childName ).
+    /// 
+    /// @name module:vwf~nodeHasChild
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} childName
+    /// 
+    /// @returns {Boolean}
+
+    nodeHasChild(nodeID, childName) { // invoke with the kernel as "this"  // TODO: this is peeking inside of vwf-model-javascript
+        var node = this.models.javascript.nodes[nodeID];
+        return childName in node.children;
+    }
+
+    /// Determine if a node has a child with the given name. The node's prototypes are not
+    /// considered.
+    /// 
+    /// This function must run as a method of the kernel. Invoke as: nodeHasOwnChild.call(
+    ///   kernel, nodeID, childName ).
+    /// 
+    /// @name module:vwf~nodeHasOwnChild
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} childName
+    /// 
+    /// @returns {Boolean}
+
+    nodeHasOwnChild(nodeID, childName) { // invoke with the kernel as "this"  // TODO: this is peeking inside of vwf-model-javascript
+        var node = this.models.javascript.nodes[nodeID];
+        var hasChild = false;
+        if (parseInt(childName).toString() !== childName) {
+            hasChild = node.children.hasOwnProperty(childName); // TODO: this is peeking inside of vwf-model-javascript
+        } else {
+            // Children with numeric names do not get added as properties of the children array, so loop over the children
+            // to check manually
+            for (var i = 0, il = node.children.length; i < il; i++) {
+                if (childName === node.children[i].name) {
+                    hasChild = true;
+                }
+            }
+        }
+        return hasChild;
+    }
+
+    /// Determine if a component specifier is a URI.
+    /// 
+    /// A component may be specified as the URI of a resource containing a descriptor (string),
+    /// a descriptor (object), or the ID of a previously-created node (primitive).
+    /// 
+    /// @name module:vwf~componentIsURI
+    /// 
+    /// @param {String|Object} candidate
+    /// 
+    /// @returns {Boolean}
+
+    componentIsURI(candidate) {
+        return (typeof candidate == "string" || candidate instanceof String) && !this.componentIsID(candidate);
+    }
+
+    /// Determine if a component specifier is a descriptor.
+    /// 
+    /// A component may be specified as the URI of a resource containing a descriptor (string),
+    /// a descriptor (object), or the ID of a previously-created node (primitive).
+    /// 
+    /// @name module:vwf~componentIsDescriptor
+    /// 
+    /// @param {String|Object} candidate
+    /// 
+    /// @returns {Boolean}
+
+    componentIsDescriptor(candidate) {
+        return typeof candidate == "object" && candidate != null && !this.isPrimitive(candidate);
+    }
+
+    /// Determine if a component specifier is an ID.
+    /// 
+    /// A component may be specified as the URI of a resource containing a descriptor (string),
+    /// a descriptor (object), or the ID of a previously-created node (primitive).
+    /// 
+    /// @name module:vwf~componentIsID
+    /// 
+    /// @param {String|Object} candidate
+    /// 
+    /// @returns {Boolean}
+
+    componentIsID(candidate) {
+        return this.isPrimitive(candidate) && this.models.object.exists(candidate) &&
+            !(this.components[candidate] instanceof Array);
+    }
+
+    /// Determine if a value is a JavaScript primitive, or the boxed version of a JavaScript
+    /// primitive.
+    /// 
+    /// Node IDs are JavaScript primitives. This function may be used to determine if a value
+    /// has the correct type to be a node ID.
+    /// 
+    /// @name module:vwf~isPrimitive
+    /// 
+    /// @param candidate
+    /// 
+    /// @returns {Boolean}
+
+    isPrimitive(candidate) {
+
+        switch (typeof candidate) {
+
+            case "string":
+            case "number":
+            case "boolean":
+                return true;
+
+            case "object":
+                return candidate instanceof String || candidate instanceof Number ||
+                    candidate instanceof Boolean;
+
+            default:
+                return false;
+
+        }
+
+    }
+
+    /// Determine if an object is a component descriptor. Detect the type by searching for
+    /// descriptor keys in the candidate object.
+    /// 
+    /// @name module:vwf~objectIsComponent
+    /// 
+    /// @param {Object} candidate
+    /// 
+    /// @returns {Boolean}
+
+    objectIsComponent(candidate) {
+
+        var componentAttributes = [
+            "extends",
+            "implements",
+            "source",
+            "type",
+            "properties",
+            "methods",
+            "events",
+            "children",
+            "scripts",
+        ];
+
+        var isComponent = false;
+
+        if (typeof candidate == "object" && candidate != null) {
+
+            isComponent = componentAttributes.some(function (attributeName) {
+                return candidate.hasOwnProperty(attributeName);
+            });
+
+        }
+
+        return isComponent;
+    }
+
+    /// Determine if a property initializer is a detailed initializer containing explicit
+    /// accessor and value parameters (rather than a simple value specification). Detect the
+    /// type by searching for property initializer keys in the candidate object.
+    /// 
+    /// @name module:vwf~valueHasAccessors
+    /// 
+    /// @param {Object} candidate
+    /// 
+    /// @returns {Boolean}
+
+    valueHasAccessors(candidate) {
+
+        var accessorAttributes = [
+            "get",
+            "set",
+            "value",
+            "node",
+            "create",
+            "undefined",
+        ];
+
+        var hasAccessors = false;
+
+        if (typeof candidate == "object" && candidate != null) {
+
+            hasAccessors = accessorAttributes.some(function (attributeName) {
+                return candidate.hasOwnProperty(attributeName);
+            });
+
+        }
+
+        return hasAccessors;
+    }
+
+    /// Determine if a method or event initializer is a detailed initializer containing a
+    /// parameter list along with the body text (method initializers only). Detect the type by
+    /// searching for method and event initializer keys in the candidate object.
+    /// 
+    /// @name module:vwf~valueHasBody
+    /// 
+    /// @param {Object} candidate
+    /// 
+    /// @returns {Boolean}
+
+    valueHasBody(candidate) { // TODO: refactor and share with valueHasAccessors and possibly objectIsComponent  // TODO: unlike a property initializer, we really only care if it's an object vs. text; text == use as body; object == presume o.parameters and o.body  // TODO: except that a script in the unnamed-list format would appear as an object but should be used as the body
+
+        var bodyAttributes = [
+            "parameters",
+            "body",
+            "listeners",
+        ];
+
+        var hasBody = false; // TODO: "body" term is confusing, but that's the current terminology used in vwf/model/javascript
+
+        if (typeof candidate == "object" && candidate != null) {
+
+            hasBody = bodyAttributes.some(function (attributeName) {
+                return candidate.hasOwnProperty(attributeName);
+            });
+
+        }
+
+        return hasBody;
+    }
+
+    /// Determine if a script initializer is a detailed initializer containing explicit text and
+    /// type parameters (rather than being a simple text specification). Detect the type by
+    /// searching for the script initializer keys in the candidate object.
+    /// 
+    /// @name module:vwf~valueHasType
+    /// 
+    /// @param {Object} candidate
+    /// 
+    /// @returns {Boolean}
+
+    valueHasType(candidate) { // TODO: refactor and share with valueHasBody, valueHasAccessors and possibly objectIsComponent
+
+        var typeAttributes = [
+            "source",
+            "text",
+            "type",
+        ];
+
+        var hasType = false;
+
+        if (typeof candidate == "object" && candidate != null) {
+
+            hasType = typeAttributes.some(function (attributeName) {
+                return candidate.hasOwnProperty(attributeName);
+            });
+
+        }
+
+        return hasType;
+    }
+
+    /// Convert a potentially-namespaced member name into a string such that a namespaced name
+    /// will be distinct from an encoded name in any other namespace, or from any simple name
+    /// not having a namespace.
+    /// 
+    /// Simple names are strings such as `"name"`. Namespaced names are arrays of strings, such
+    /// as `[ "ns", "name" ]` or `[ "outer", "inner", "name" ]`. An array containing a single
+    /// string, such as `[ "name" ]`, is not namespaced and is the same name as `"name"`.
+    /// 
+    /// Each of the following encodes into a distinct value:
+    /// 
+    ///   `"name"` or `[ "name" ]`
+    ///   `[ "a", "name" ]`
+    ///   `[ "b", "name" ]`
+    ///   `[ "a", "a", "name" ]`
+    ///   `[ "a", "b", "name" ]`
+    ///   `[ "b", "b", "name" ]`
+    ///   *etc.*
+    /// 
+    /// @name module:vwf~namespaceEncodedName
+    /// 
+    /// @param {String|String[]} memberName
+    ///   A string, or an array of strings containing a name preceded by any number of namespace
+    ///   names. In an array, each element defines a unique space for the member name and for
+    ///   any intermediate namespaces.
+    /// 
+    /// @returns {String}
+
+    namespaceEncodedName(memberName) {
+
+        if (typeof memberName === "object" && memberName instanceof Array) {
+            return (memberName.length !== 1) ? "vwf$" + memberName.join("$") : memberName[0];
+        } else {
+            return memberName;
+        }
+
+    }
+
+    /// Convert a (potentially-abbreviated) component specification to a descriptor parsable by
+    /// vwf.createChild. The following forms are accepted:
+    /// 
+    ///   - Descriptor: { extends: component, source: ..., type: ..., ... }
+    ///   - Component URI: http://host/path/to/component.vwf
+    ///   - Asset URI: http://host/ath/to/asset.type
+    ///   - Node ID
+    /// 
+    /// They are converted as follows:
+    /// 
+    ///   - Descriptor: unchanged [1]
+    ///   - Component URI: a component that extends the component identified by the URI
+    ///   - Asset URI: a component having the asset identified by the URI as its source
+    ///   - Node ID: a component that extends the previously-created node identified by the ID
+    /// 
+    /// [1] As a special case, missing MIME types are filled in for assets matcching the
+    /// patterns *.unity3d and *.dae, and components having assets of those types but no
+    /// prototype declared will be upgraded to extend scene.vwf and navscene.vwf, respectively.
+    /// 
+    /// @name module:vwf~normalizedComponent
+    /// 
+    /// @param {String|Object} component
+    /// 
+    /// @returns {Object}
+
+    normalizedComponent(component) {
+
+        // Convert a component URI to an instance of that type or an asset reference to an
+        // untyped reference to that asset. Convert a component ID to an instance of that
+        // prototype.
+
+        if (this.componentIsURI(component)) {
+            if (component.match(/\.vwf$/)) { // TODO: detect component from mime-type instead of extension?
+                component = {
+                    extends: component
+                };
+            } else {
+                component = {
+                    source: component
+                };
+            }
+        } else if (this.componentIsID(component)) {
+            component = {
+                extends: component
+            };
+        }
+
+        // Fill in the mime type from the source specification if not provided.
+
+        if (component.source && !component.type) { // TODO: validate component
+
+            var match = component.source.match(/\.([^.]*)$/); // TODO: get type from mime-type (from server if remote, from os if local, or (?) from this internal table otherwise)
+
+            if (match) {
+
+                switch (match[1]) {
+                    case "unity3d":
+                        component.type = "application/vnd.unity";
+                        break;
+                    case "dae":
+                        component.type = "model/vnd.collada+xml";
+                        break;
+                }
+
+            }
+
+        }
+
+        // Fill in the component type from the mime type if not provided.
+
+        if (component.type && !component.extends) { // TODO: load from a server configuration file
+
+            switch (component.type) {
+                case "application/vnd.unity":
+                    component.extends = "http://vwf.example.com/scene.vwf";
+                    break;
+                case "model/vnd.collada+xml":
+                    component.extends = "http://vwf.example.com/navscene.vwf";
+                    break;
+            }
+
+        }
+
+        return component;
+    }
+
+    /// Convert a `Handler` specification into the standard form of an object containing
+    /// `parameters`, `body` and `type` fields.
+    /// 
+    /// @name module:vwf~normalizedHandler
+    /// 
+    /// @param {Handler|string}
+    /// @param {string[]} [defaultParameters]
+    /// 
+    /// @returns {Handler}
+
+    normalizedHandler(handler, defaultParameters) {
+
+        // Convert abbreviated forms to the explict `Handler` form.
+
+        if (typeof handler !== "object" || handler instanceof Array) {
+            handler = {
+                body: handler
+            }; //if (require("vwf/configuration").active["preserve-script-closures"]
+        } else if (this.configuration["preserve-script-closures"] && (typeof handler == "function" || handler instanceof Function)) {
+            handler = {
+                body: handler
+            };
+        }
+
+        // Use a default parameter list if the handler doesn't provide its own and if defaults
+        // were provided.
+
+        if (!handler.parameters && defaultParameters) {
+            handler.parameters = defaultParameters;
+        }
+
+        // Fill in a default media type if `type` is not provided. A `body` of type `string` is
+        // taken to be `application/javascript`.
+
+        if (handler.type === undefined) {
+            if (typeof handler.body === "string" || handler.body instanceof String) {
+                handler.type = "application/javascript";
+            }
+        }
+
+        return handler;
+    }
+
+    /// Convert a `fields` object as passed between the client and reflector and stored in the
+    /// message queue into a form suitable for writing to a log.
+    /// 
+    /// @name module:vwf~loggableFields
+    /// 
+    /// @param {Object} fields
+    /// 
+    /// @returns {Object}
+
+    loggableFields(fields) {
+        return this.utility.transform(fields, this.utility.transforms.transit);
+    }
+
+    /// Convert a component URI, descriptor or ID into a form suitable for writing to a log.
+    /// 
+    /// @name module:vwf~loggableComponent
+    /// 
+    /// @param {String|Object} component
+    /// 
+    /// @returns {String|Object}
+
+    loggableComponent(component) {
+        return this.utility.transform(component, this.loggableComponentTransformation);
+    }
+
+    /// Convert an arbitrary JavaScript value into a form suitable for writing to a log.
+    /// 
+    /// @name module:vwf~loggableValue
+    /// 
+    /// @param {Object} value
+    /// 
+    /// @returns {Object}
+
+    loggableValue(value) {
+        let self = this;
+        return this.utility.transform(value, function (object, names, depth) {
+            object = self.utility.transforms.transit(object, names, depth);
+            return typeof object == "number" ? Number(object.toPrecision(5)) : object; // reduce numeric precision to remove visual noise
+        });
+    }
+
+    /// Convert an array of arbitrary JavaScript values into a form suitable for writing to a
+    /// log.
+    /// 
+    /// @name module:vwf~loggableValues
+    /// 
+    /// @param {Object[]|undefined} values
+    /// 
+    /// @returns {Object[]|undefined}
+
+    loggableValues(values) {
+        return this.loggableValue(values);
+    }
+
+    /// Convert an object indexing arrays of arbitrary JavaScript values into a form suitable
+    /// for writing to a log.
+    /// 
+    /// @name module:vwf~loggableIndexedValues
+    /// 
+    /// @param {Object|undefined} values
+    /// 
+    /// @returns {Object|undefined}
+
+    loggableIndexedValues(values) {
+        return this.loggableValue(values);
+    }
+
+    /// Convert script text into a form suitable for writing to a log.
+    /// 
+    /// @name module:vwf~loggableScript
+    /// 
+    /// @param {String|undefined} script
+    /// 
+    /// @returns {String}
+
+    loggableScript(script) {
+        return (script || "").replace(/\s+/g, " ").substring(0, 100);
+    }
+
+    // -- remappedURI --------------------------------------------------------------------------
+
+    /// Remap a component URI to its location in a local cache.
+    /// 
+    /// http://vwf.example.com/component.vwf => http://localhost/proxy/vwf.example.com/component.vwf
+    /// 
+    /// @name module:vwf~remappedURI
+
+    remappedURI(uri, baseURI) {
+
+        // var match = uri.match( RegExp( "http://(vwf.example.com)/(.*)" ) );
+
+        // if ( match ) {
+        //     uri = window.location.protocol + "//" + window.location.host +
+        //         "/proxy/" + match[1] + "/" + match[2];
+        // }
+        if (baseURI && uri[0] !== "/") {
+
+            let newURI = "";
+            let parts = baseURI.split('/');
+            parts[parts.length - 1] = uri;
+            parts.map(el => {
+                newURI = newURI + el + '/';
+            })
+
+            return newURI.slice(0, -1)
+
+        }
+        return uri
+
+    }
+
+    // -- resolvedDescriptor -------------------------------------------------------------------
+
+    /// Resolve relative URIs in a component descriptor.
+    /// 
+    /// @name module:vwf~resolvedDescriptor
+
+    resolvedDescriptor(component, baseURI) {
+
+        return this.utility.transform(component, resolvedDescriptorTransformationWithBaseURI);
+
+        function resolvedDescriptorTransformationWithBaseURI(object, names, depth) {
+            return resolvedDescriptorTransformation.call(this, object, names, depth, baseURI);
+        }
+
+    }
+
+    // -- loggableComponentTransformation ------------------------------------------------------
+
+    /// vwf/utility/transform() transformation function to truncate the verbose bits of a
+    /// component so that it may be written to a log.
+    /// 
+    /// @name module:vwf~loggableComponentTransformation
+
+    loggableComponentTransformation(object, names, depth) {
+
+        // Get our bearings at the current recusion level.
+
+        var markers = this.descriptorMarkers(object, names, depth);
+
+        // Transform the object here.
+
+        switch (markers.containerName) {
+
+            case "extends":
+
+                // Omit a component descriptor for the prototype.
+
+                if (markers.memberIndex == 0 && this.componentIsDescriptor(object)) {
+                    return {};
+                }
+
+                break;
+
+            case "implements":
+
+                // Omit component descriptors for the behaviors.
+
+                if (markers.memberIndex == 0 && this.componentIsDescriptor(object)) {
+                    return {};
+                }
+
+                break;
+
+            case "properties":
+
+                // Convert property values to a loggable version, and omit getter and setter
+                // text.
+
+                if (markers.memberIndex == 0 && !this.valueHasAccessors(object) ||
+                    markers.memberIndex == 1 && names[0] == "value") {
+                    return this.loggableValue(object);
+                } else if (markers.memberIndex == 1 && (names[0] == "get" || names[0] == "set")) {
+                    return "...";
+                }
+
+                break;
+
+            case "methods":
+
+                // Omit method body text.
+
+                if (markers.memberIndex == 0 && !this.valueHasBody(object) ||
+                    markers.memberIndex == 1 && names[0] == "body") {
+                    return "...";
+                }
+
+                break;
+
+            case "events":
+
+                // Nothing for events.
+
+                break;
+
+            case "children":
+
+                // Omit child component descriptors.
+
+                if (markers.memberIndex == 0 && this.componentIsDescriptor(object)) {
+                    return {};
+                }
+
+                break;
+
+            case "scripts":
+
+                // Shorten script text.
+
+                if (markers.memberIndex == 0 && !this.valueHasType(object) ||
+                    markers.memberIndex == 1 && names[0] == "text") {
+                    return "...";
+                }
+
+                break;
+
+        }
+
+        return object;
+    }
+
+    // -- resolvedDescriptorTransformation -----------------------------------------------------
+
+    /// vwf/utility/transform() transformation function to resolve relative URIs in a component
+    /// descriptor.
+    /// 
+    /// @name module:vwf~resolvedDescriptorTransformation
+
+    resolvedDescriptorTransformation(object, names, depth, baseURI) {
+
+        // Get our bearings at the current recusion level.
+
+        var markers = this.descriptorMarkers(object, names, depth);
+
+        // Resolve all the URIs.
+
+        switch (markers.containerName) {
+
+            case "extends":
+            case "implements":
+            case "source":
+            case "children":
+
+                if (markers.memberIndex == 0 && this.componentIsURI(object)) {
+                    return object //require( "vwf/utility" ).resolveURI( object, baseURI );
+                }
+
+                break;
+
+            case "scripts":
+
+                if (markers.memberIndex == 1 && names[0] == "source") {
+                    return object //require( "vwf/utility" ).resolveURI( object, baseURI );
+                }
+
+                break;
+
+        }
+
+        return object;
+    }
+
+    // -- descriptorMarkers --------------------------------------------------------------------
+
+    /// Locate the closest container (`properties`, `methods`, `events`, `children`) and
+    /// contained member in a `vwf/utility/transform` iterator call on a component descriptor.
+    /// 
+    /// @name module:vwf~descriptorMarkers
+
+    descriptorMarkers(object, names, depth) {
+
+        // Find the index of the lowest nested component in the names list.
+
+        var componentIndex = names.length;
+
+        while (componentIndex > 2 && names[componentIndex - 1] == "children") {
+            componentIndex -= 2;
+        }
+
+        // depth                                                  names  notes
+        // -----                                                  -----  -----
+        // 0:                                                        []  the component
+        // 1:                                          [ "properties" ]  its properties object
+        // 2:                          [ "propertyName", "properties" ]  one property
+        // 1:                                            [ "children" ]  the children object
+        // 2:                               [ "childName", "children" ]  one child
+        // 3:                 [ "properties", "childName", "children" ]  the child's properties
+        // 4: [ "propertyName", "properties", "childName", "children" ]  one child property
+
+        if (componentIndex > 0) {
+
+            // Locate the container ("properties", "methods", "events", etc.) below the
+            // component in the names list.
+
+            var containerIndex = componentIndex - 1;
+            var containerName = names[containerIndex];
+
+            // Locate the member as appropriate for the container.
+
+            if (containerName == "extends") {
+
+                var memberIndex = containerIndex;
+                var memberName = names[memberIndex];
+
+            } else if (containerName == "implements") {
+
+                if (containerIndex > 0) {
+                    if (typeof names[containerIndex - 1] == "number") {
+                        var memberIndex = containerIndex - 1;
+                        var memberName = names[memberIndex];
+                    } else {
+                        var memberIndex = containerIndex;
+                        var memberName = undefined;
+                    }
+                } else if (typeof object != "object" || !(object instanceof Array)) {
+                    var memberIndex = containerIndex;
+                    var memberName = undefined;
+                }
+
+            } else if (containerName == "source" || containerName == "type") {
+
+                var memberIndex = containerIndex;
+                var memberName = names[memberIndex];
+
+            } else if (containerName == "properties" || containerName == "methods" || containerName == "events" ||
+                containerName == "children") {
+
+                if (containerIndex > 0) {
+                    var memberIndex = containerIndex - 1;
+                    var memberName = names[memberIndex];
+                }
+
+            } else if (containerName == "scripts") {
+
+                if (containerIndex > 0) {
+                    if (typeof names[containerIndex - 1] == "number") {
+                        var memberIndex = containerIndex - 1;
+                        var memberName = names[memberIndex];
+                    } else {
+                        var memberIndex = containerIndex;
+                        var memberName = undefined;
+                    }
+                } else if (typeof object != "object" || !(object instanceof Array)) {
+                    var memberIndex = containerIndex;
+                    var memberName = undefined;
+                }
+
+            } else {
+
+                containerIndex = undefined;
+                containerName = undefined;
+
+            }
+
+        }
+
+        return {
+            containerIndex: containerIndex,
+            containerName: containerName,
+            memberIndex: memberIndex,
+            memberName: memberName,
+        };
+
+    }
+
+    /// Locate nodes matching a search pattern. {@link module:vwf/api/kernel.find} describes the
+    /// supported patterns.
+    /// 
+    /// This is the internal implementation used by {@link module:vwf.find} and
+    /// {@link module:vwf.test}.
+    /// 
+    /// This function must run as a method of the kernel. Invoke it as:
+    ///   `find.call( kernel, nodeID, matchPattern, initializedOnly )`.
+    /// 
+    /// @name module:vwf~find
+    /// 
+    /// @param {ID} nodeID
+    ///   The reference node. Relative patterns are resolved with respect to this node. `nodeID`
+    ///   is ignored for absolute patterns.
+    /// @param {String} matchPattern
+    ///   The search pattern.
+    /// @param {Boolean} [initializedOnly]
+    ///   Interpret nodes that haven't completed initialization as though they don't have
+    ///   ancestors. Drivers that manage application code should set `initializedOnly` since
+    ///   applications should never have access to uninitialized parts of the application graph.
+    /// 
+    /// @returns {ID[]|undefined}
+    ///   An array of the node ids of the result.
+
+    find(nodeID, matchPattern, initializedOnly) {
+
+        // Evaluate the expression using the provided node as the reference. Take the root node
+        // to be the root of the reference node's tree. If a reference node is not provided, use
+        // the application as the root.
+
+        var rootID = nodeID ? this.root(nodeID, initializedOnly) :
+            this.application(initializedOnly);
+
+        return this.xpath.resolve(matchPattern, rootID, nodeID,
+            resolverWithInitializedOnly, this);
+
+
+        // Wrap `xpathResolver` to pass `initializedOnly` through.
+
+        function resolverWithInitializedOnly(step, contextID, resolveAttributes) {
+            return this.xpathResolver.call(this, step, contextID, resolveAttributes, initializedOnly);
+        }
+
+    }
+
+    // -- xpathResolver ------------------------------------------------------------------------
+
+    /// Interpret the steps of an XPath expression being resolved. Use with
+    /// vwf.utility.xpath#resolve.
+    ///
+    /// @name module:vwf~xpathResolver
+    /// 
+    /// @param {Object} step
+    /// @param {ID} contextID
+    /// @param {Boolean} [resolveAttributes]
+    /// @param {Boolean} [initializedOnly]
+    ///   Interpret nodes that haven't completed initialization as though they don't have
+    ///   ancestors. Drivers that manage application code should set `initializedOnly` since
+    ///   applications should never have access to uninitialized parts of the application graph.
+    /// 
+    /// @returns {ID[]}
+
+    xpathResolver(step, contextID, resolveAttributes, initializedOnly) {
+        let self = this;
+
+        var resultIDs = [];
+
+        switch (step.axis) {
+
+            // case "preceding":  // TODO
+            // case "preceding-sibling":  // TODO
+
+            case "ancestor-or-self":
+                resultIDs.push(contextID);
+                Array.prototype.push.apply(resultIDs, this.ancestors(contextID, initializedOnly));
+                break;
+
+            case "ancestor":
+                Array.prototype.push.apply(resultIDs, this.ancestors(contextID, initializedOnly));
+                break;
+
+            case "parent":
+                var parentID = this.parent(contextID, initializedOnly);
+                parentID && resultIDs.push(parentID);
+                break;
+
+            case "self":
+                resultIDs.push(contextID);
+                break;
+
+            case "child":
+                Array.prototype.push.apply(resultIDs,
+                    this.children(contextID, initializedOnly).filter(function (childID) {
+                        return childID;
+                    }, this)
+                );
+                break;
+
+            case "descendant":
+                Array.prototype.push.apply(resultIDs,
+                    this.descendants(contextID, initializedOnly).filter(function (descendantID) {
+                        return descendantID;
+                    }, this)
+                );
+                break;
+
+            case "descendant-or-self":
+                resultIDs.push(contextID);
+                Array.prototype.push.apply(resultIDs,
+                    this.descendants(contextID, initializedOnly).filter(function (descendantID) {
+                        return descendantID;
+                    }, this)
+                );
+                break;
+
+                // case "following-sibling":  // TODO
+                // case "following":  // TODO
+
+            case "attribute":
+                if (resolveAttributes) {
+                    resultIDs.push("@" + contextID); // TODO: @?
+                }
+                break;
+
+                // n/a: case "namespace":
+                // n/a:   break;
+
+        }
+
+        switch (step.kind) {
+
+            // Name test.
+
+            case undefined:
+
+                resultIDs = resultIDs.filter(function (resultID) {
+                    if (resultID[0] != "@") { // TODO: @?
+                        return self.xpathNodeMatchesStep.call(this, resultID, step.name);
+                    } else {
+                        return self.xpathPropertyMatchesStep.call(this, resultID.slice(1), step.name); // TODO: @?
+                    }
+                }, this);
+
+                break;
+
+                // Element test.
+
+            case "element":
+
+                // Cases: kind(node,type)
+
+                // element()
+                // element(name)
+                // element(,type)
+                // element(name,type)
+
+                resultIDs = resultIDs.filter(function (resultID) {
+                    return resultID[0] != "@" && self.xpathNodeMatchesStep.call(this, resultID, step.name, step.type); // TODO: @?
+                }, this);
+
+                break;
+
+                // Attribute test.
+
+            case "attribute":
+
+                resultIDs = resultIDs.filter(function (resultID) {
+                    return resultID[0] == "@" && self.xpathPropertyMatchesStep.call(this, resultID.slice(1), step.name); // TODO: @?
+                }, this);
+
+                break;
+
+                // The `doc()` function for referencing globals outside the current tree.
+                // http://www.w3.org/TR/xpath-functions/#func-doc.
+
+            case "doc":
+
+                if (this.root(contextID, initializedOnly)) {
+                    var globalID = this.global(step.name, initializedOnly);
+                    resultIDs = globalID ? [globalID] : [];
+                } else {
+                    resultIDs = [];
+                }
+
+                break;
+
+                // Any-kind test.
+
+            case "node":
+
+                break;
+
+                // Unimplemented test.
+
+            default:
+
+                resultIDs = [];
+
+                break;
+
+        }
+
+        return resultIDs;
+    }
+
+    // -- xpathNodeMatchesStep -----------------------------------------------------------------
+
+    /// Determine if a node matches a step of an XPath expression being resolved.
+    ///
+    /// @name module:vwf~xpathNodeMatchesStep
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} [name]
+    /// @param {String} [type]
+    /// 
+    /// @returns {Boolean}
+
+    xpathNodeMatchesStep(nodeID, name, type) {
+
+        if (name && this.name(nodeID) != name) {
+            return false;
+        }
+
+        var matches_type = !type || this.uri(nodeID) == type ||
+            this.prototypes(nodeID, true).some(function (prototypeID) {
+                return this.uri(prototypeID) == type;
+            }, this);
+
+        return matches_type;
+    }
+
+    // -- xpathPropertyMatchesStep -------------------------------------------------------------
+
+    /// Determine if a property matches a step of an XPath expression being resolved.
+    ///
+    /// @name module:vwf~xpathPropertyMatchesStep
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} [name]
+    /// 
+    /// @returns {Boolean}
+
+    xpathPropertyMatchesStep(nodeID, name) {
+
+        var properties = this.models.object.properties(nodeID);
+
+        if (name) {
+            return properties[name];
+        } else {
+            return Object.keys(properties).some(function (propertyName) {
+                return properties[propertyName];
+            }, this);
+        }
+
+    }
+
+    /// Merge two component descriptors into a single descriptor for a combined component. A
+    /// component created from the combined descriptor will behave in the same way as a
+    /// component created from `nodeDescriptor` that extends a component created from
+    /// `prototypeDescriptor`.
+    ///
+    /// Warning: this implementation modifies `prototypeDescriptor`.
+    ///
+    /// @name module:vwf~mergeDescriptors
+    ///
+    /// @param {Object} nodeDescriptor
+    ///   A descriptor representing a node extending `prototypeDescriptor`.
+    /// @param {Object} prototypeDescriptor
+    ///   A descriptor representing a prototype for `nodeDescriptor`.
+
+    // Limitations:
+    // 
+    //   - Doesn't merge children from the prototype with like-named children in the node.
+    //   - Doesn't merge property setters and getters from the prototype when the node provides
+    //     an initializing value.
+    //   - Methods from the prototype descriptor are lost with no way to invoke them if the node
+    //     overrides them.
+    //   - Scripts from both the prototype and the node are retained, but if both define an
+    //     `initialize` function, the node's `initialize` will overwrite `initialize` in
+    //     the prototype.
+    //   - The prototype doesn't carry its location with it, so relative paths will load with
+    //     respect to the location of the node.
+
+    mergeDescriptors(nodeDescriptor, prototypeDescriptor) {
+
+        if (nodeDescriptor.implements) {
+            prototypeDescriptor.implements = (prototypeDescriptor.implements || []).
+            concat(nodeDescriptor.implements);
+        }
+
+        if (nodeDescriptor.source) {
+            prototypeDescriptor.source = nodeDescriptor.source;
+            prototypeDescriptor.type = nodeDescriptor.type;
+        }
+
+        if (nodeDescriptor.properties) {
+
+            prototypeDescriptor.properties = prototypeDescriptor.properties || {};
+
+            for (var propertyName in nodeDescriptor.properties) {
+                prototypeDescriptor.properties[propertyName] = nodeDescriptor.properties[propertyName];
+            }
+
+        }
+
+        if (nodeDescriptor.methods) {
+
+            prototypeDescriptor.methods = prototypeDescriptor.methods || {};
+
+            for (var methodName in nodeDescriptor.methods) {
+                prototypeDescriptor.methods[methodName] = nodeDescriptor.methods[methodName];
+            }
+
+        }
+
+        if (nodeDescriptor.events) {
+
+            prototypeDescriptor.events = prototypeDescriptor.events || {};
+
+            for (var eventName in nodeDescriptor.events) {
+                prototypeDescriptor.events[eventName] = nodeDescriptor.events[eventName];
+            }
+
+        }
+
+        if (nodeDescriptor.children) {
+
+            prototypeDescriptor.children = prototypeDescriptor.children || {};
+
+            for (var childName in nodeDescriptor.children) {
+                prototypeDescriptor.children[childName] = nodeDescriptor.children[childName];
+            }
+
+        }
+
+        if (nodeDescriptor.scripts) {
+            prototypeDescriptor.scripts = (prototypeDescriptor.scripts || []).
+            concat(nodeDescriptor.scripts);
+        }
+
+        return prototypeDescriptor;
+    }
+
+    /// Return an {@link external:Object.defineProperty} descriptor for a property that is to be
+    /// enumerable, but not writable or configurable. 
+    /// 
+    /// @param value
+    ///   A value to wrap in a descriptor.
+    /// 
+    /// @returns
+    ///   An {@link external:Object.defineProperty} descriptor.
+
+    enumerable(value) {
+        return {
+            value: value,
+            enumerable: true,
+            writable: false,
+            configurable: false,
+        };
+    }
+
+    /// Return an {@link external:Object.defineProperty} descriptor for a property that is to be
+    /// enumerable and writable, but not configurable. 
+    /// 
+    /// @param value
+    ///   A value to wrap in a descriptor.
+    /// 
+    /// @returns
+    ///   An {@link external:Object.defineProperty} descriptor.
+
+    writable(value) {
+        return {
+            value: value,
+            enumerable: true,
+            writable: true,
+            configurable: false,
+        };
+    }
+
+    /// Return an {@link external:Object.defineProperty} descriptor for a property that is to be
+    /// enumerable, writable, and configurable. 
+    /// 
+    /// @param value
+    ///   A value to wrap in a descriptor.
+    /// 
+    /// @returns
+    ///   An {@link external:Object.defineProperty} descriptor.
+
+    configurable(value) {
+        return {
+            value: value,
+            enumerable: true,
+            writable: true,
+            configurable: true,
+        };
+    }
+
+
+    async chooseConnection(data) {
+        if (this.isLuminary) {
+          return await this.luminary.connect(data) //use Luminary
+        } else {
+          return data //use Reflector
+        }
+      }
+
+
+}
+
+export {
+    VWF
+}

+ 67 - 70
public/vwf/api/kernel.js → public/core/vwf/api/kernel.js

@@ -1,25 +1,18 @@
-"use strict";
-
-// Copyright 2012 United States Government, as represented by the Secretary of Defense, Under
-// Secretary of Defense (Personnel & Readiness).
-// 
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
-// in compliance with the License. You may obtain a copy of the License at
-// 
-//   http://www.apache.org/licenses/LICENSE-2.0
-// 
-// Unless required by applicable law or agreed to in writing, software distributed under the License
-// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
-// or implied. See the License for the specific language governing permissions and limitations under
-// the License.
+/*
+The MIT License (MIT)
+Copyright (c) 2014-2020 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+
+Virtual World Framework Apache 2.0 license  (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
+*/
 
 /// Kernel API.
 /// 
 /// @module vwf/api/kernel
 
-define( function() {
+class Kernel {
 
-    var exports = {
+    constructor() {
+        //console.log("Kernel constructor");
 
         // TODO: setState
         // TODO: getState
@@ -57,7 +50,7 @@ define( function() {
         /// 
         /// @returns {}
 
-        createNode: [ /* nodeComponent, nodeAnnotation, baseURI, callback( nodeID ) */ ],
+        this.createNode = [ /* nodeComponent, nodeAnnotation, baseURI, callback( nodeID ) */ ]
 
         /// Delete a node.
         /// 
@@ -69,7 +62,7 @@ define( function() {
         /// 
         /// @returns {}
 
-        deleteNode: [ /* nodeID */ ],
+        this.deleteNode = [ /* nodeID */ ]
 
         /// Set node will set the properties of the node specified by the given id and component.
         /// 
@@ -81,7 +74,7 @@ define( function() {
         /// 
         /// @returns {}
 
-        setNode: [ /* nodeID, nodeComponent, callback( nodeID ) */ ],
+        this.setNode = [ /* nodeID, nodeComponent, callback( nodeID ) */ ]
 
         /// Get node will retrieve the component of the node specified by the given id.
         /// 
@@ -93,7 +86,7 @@ define( function() {
         /// 
         /// @returns {Object}
 
-        getNode: [ /* nodeID, full, normalize */ ],
+        this.getNode = [ /* nodeID, full, normalize */ ]
 
         // TODO: hashNode
 
@@ -107,7 +100,7 @@ define( function() {
         /// 
         /// @returns {}
 
-        createChild: [ /* nodeID, childName, childComponent, childURI, callback( childID ) */ ],
+        this.createChild = [ /* nodeID, childName, childComponent, childURI, callback( childID ) */ ]
 
         /// @function
         /// 
@@ -116,7 +109,7 @@ define( function() {
         /// 
         /// @returns {}
 
-        deleteChild: [ /* nodeID, childName */ ],
+        this.deleteChild = [ /* nodeID, childName */ ]
 
         /// addChild calls addingChild() on each model. The child is considered added after each model has
         /// run.  Additionally, it calls addedChild() on each view. The view is being notified that a 
@@ -130,7 +123,7 @@ define( function() {
         /// 
         /// @returns {}
 
-        addChild: [ /* nodeID, childID, childName */ ],
+        this.addChild = [ /* nodeID, childID, childName */ ]
 
         /// removeChild calls removingChild() on each model. The child is considered removed after each model
         /// has run.  Additionally, it calls removedChild() on each view. The view is being notified that a 
@@ -143,7 +136,7 @@ define( function() {
         /// 
         /// @returns {}
 
-        removeChild: [ /* nodeID, childID */ ],
+        this.removeChild = [ /* nodeID, childID */ ]
 
         /// setProperties sets all of the properties for a node.  It will call settingProperties() 
         /// on each model and satProperties() on each view.
@@ -155,7 +148,7 @@ define( function() {
         /// 
         /// @returns {Object}
 
-        setProperties: [ /* nodeID, properties */ ],
+        this.setProperties = [ /* nodeID, properties */ ]
 
         /// getProperties will get all of the properties for a node.  It will call 
         /// gettingProperties() on each model and gotProperties() on each view.
@@ -166,7 +159,7 @@ define( function() {
         /// 
         /// @returns {Object}
 
-        getProperties: [ /* nodeID */ ],
+        this.getProperties = [ /* nodeID */ ]
 
         /// createProperty will create a property on a node and assign an initial value.
         /// It will call creatingProperty() on each model. The property is considered created after each
@@ -183,7 +176,7 @@ define( function() {
         /// 
         /// @returns {} propertyValue
 
-        createProperty: [ /* nodeID, propertyName, propertyValue, propertyGet, propertySet */ ],
+        this.createProperty = [ /* nodeID, propertyName, propertyValue, propertyGet, propertySet */ ]
 
         // TODO: deleteProperty
 
@@ -201,7 +194,7 @@ define( function() {
         /// 
         /// @returns {Value} propertyValue
 
-        setProperty: [ /* nodeID, propertyName, propertyValue */ ],
+        this.setProperty = [ /* nodeID, propertyName, propertyValue */ ]
 
         /// getProperty will retrive a property value for a node.  It will call gettingProperty() 
         /// on each model. The first model to return a non-undefined value dictates the return value.
@@ -214,7 +207,7 @@ define( function() {
         /// 
         /// @returns {Value} propertyValue
 
-        getProperty: [ /* nodeID, propertyName */ ],
+        this.getProperty = [ /* nodeID, propertyName */ ]
 
         /// Create a method on a node. Methods are incoming function calls made to a node.
         /// 
@@ -233,7 +226,7 @@ define( function() {
         /// 
         /// @returns {Handler} methodHandler
 
-        createMethod: [ /* nodeID, methodName, methodParameters, methodBody */ ],
+        this.createMethod = [ /* nodeID, methodName, methodParameters, methodBody */ ]
 
         // TODO: deleteMethod
 
@@ -250,7 +243,7 @@ define( function() {
         /// 
         /// @returns {Handler} methodHandler
 
-        setMethod: [ /* nodeID, methodName, methodHandler */ ],
+        this.setMethod = [ /* nodeID, methodName, methodHandler */ ]
 
         /// Get the handler for a method on a node.
         /// 
@@ -263,7 +256,7 @@ define( function() {
         /// 
         /// @returns {Handler} methodHandler
 
-        getMethod: [ /* nodeID, methodName */ ],
+        this.getMethod = [ /* nodeID, methodName */ ]
 
         /// Invoke a method on a node.
         /// 
@@ -278,7 +271,7 @@ define( function() {
         /// 
         /// @returns {Value} returnValue
 
-        callMethod: [ /* nodeID, methodName, methodParameters */ ],
+        this.callMethod = [ /* nodeID, methodName, methodParameters */ ]
 
         /// Create an event on a node.
         /// 
@@ -300,7 +293,7 @@ define( function() {
         /// 
         /// @returns {}
 
-        createEvent: [ /* nodeID, eventName, eventParameters */ ],
+        this.createEvent = [ /* nodeID, eventName, eventParameters */ ]
 
         // TODO: deleteEvent
 
@@ -317,7 +310,7 @@ define( function() {
         /// 
         /// @returns {Event}
 
-        setEvent: [ /* nodeID, eventName, eventDescriptor */ ],
+        this.setEvent = [ /* nodeID, eventName, eventDescriptor */ ]
 
         /// Get a node's event and its listeners.
         /// 
@@ -330,7 +323,7 @@ define( function() {
         /// 
         /// @returns {Event}
 
-        getEvent: [ /* nodeID, eventName */ ],
+        this.getEvent = [ /* nodeID, eventName */ ]
 
         /// Add a function to a node's event to be called when the event fires.
         /// 
@@ -378,7 +371,7 @@ define( function() {
         /// 
         /// @returns {ListenerID}
 
-        addEventListener: [ /* nodeID, eventName, eventHandler, eventContextID, eventPhases */ ],
+        this.addEventListener = [ /* nodeID, eventName, eventHandler, eventContextID, eventPhases */ ]
 
         /// Remove a listener function from a node's event. The handler will no longer be called
         /// when the event fires.
@@ -396,7 +389,7 @@ define( function() {
         /// @returns {ListenerID}
         ///   `eventListenerID` if the listener was removed successfully. Otherwise, a falsy value.
 
-        removeEventListener: [ /* nodeID, eventName, eventListenerID */ ],
+        this.removeEventListener = [ /* nodeID, eventName, eventListenerID */ ]
 
         /// Set the handler for a listener on a node's event.
         /// 
@@ -416,7 +409,7 @@ define( function() {
         /// 
         /// @returns {Listener}
 
-        setEventListener: [ /* nodeID, eventName, eventListenerID, eventListener */ ],
+        this.setEventListener = [ /* nodeID, eventName, eventListenerID, eventListener */ ]
 
         /// Get the handler for a listener on a node's event.
         /// 
@@ -432,7 +425,7 @@ define( function() {
         /// 
         /// @returns {Listener}
 
-        getEventListener: [ /* nodeID, eventName, eventListenerID */ ],
+        this.getEventListener = [ /* nodeID, eventName, eventListenerID */ ]
 
         /// Remove all listener functions from a node's event that are associated with a particular
         /// context.
@@ -449,7 +442,7 @@ define( function() {
         /// 
         /// @returns {}
 
-        flushEventListeners: [ /* nodeID, eventName, eventContextID */ ],
+        this.flushEventListeners = [ /* nodeID, eventName, eventContextID */ ]
 
         /// It will call firingEvent() on each model and firedEvent() on each view.
         /// 
@@ -464,7 +457,7 @@ define( function() {
         /// 
         /// @returns {}
 
-        fireEvent: [ /* nodeID, eventName, eventParameters */ ],
+        this.fireEvent = [ /* nodeID, eventName, eventParameters */ ]
 
         /// Dispatch an event toward a node. Using fireEvent(), capture (down) and bubble (up) along
         /// the path from the global root to the node. Cancel when one of the handlers returns a
@@ -485,7 +478,7 @@ define( function() {
         /// 
         /// @returns {}
 
-        dispatchEvent: [ /* nodeID, eventName, eventParameters, eventNodeParameters */ ],
+        this.dispatchEvent = [ /* nodeID, eventName, eventParameters, eventNodeParameters */ ]
 
         /// It will call executing() on each model. The script is considered executed after each model
         /// has run and all asynchronous calls made inside them have returned.
@@ -502,7 +495,7 @@ define( function() {
         /// 
         /// @returns {Value} returnValue
 
-        execute: [ /* nodeID, scriptText, scriptType, callback( returnValue ) */ ],
+        this.execute =[ /* nodeID, scriptText, scriptType, callback( returnValue ) */ ]
 
         /// @function
         /// 
@@ -510,14 +503,14 @@ define( function() {
         /// 
         /// @returns {Number}
 
-        random: [ /* nodeID */ ],
+        this.random = [ /* nodeID */ ]
 
         /// @function
         /// 
         /// @param {ID} nodeID
         /// @param {String} seed
 
-        seed: [ /* nodeID, seed */ ],
+        this.seed =  [ /* nodeID, seed */ ]
 
         /// It will return the current simulation time.
         /// 
@@ -525,7 +518,7 @@ define( function() {
         /// 
         /// @returns {Number}
 
-        time: [],
+        this.time = []
 
         /// It will return the moniker of the client responsible for the current action. Will be 
         /// falsy for actions originating in the server, such as time ticks.
@@ -534,7 +527,7 @@ define( function() {
         /// 
         /// @returns {String}
 
-        client: [],
+        this.client = []
 
         /// It will return the identifer the server assigned to this client.
         /// 
@@ -542,7 +535,7 @@ define( function() {
         /// 
         /// @returns {String}
 
-        moniker: [],
+        this.moniker = []
 
         /// Return the application root node. `kernel.application( initializedOnly )` is equivalent
         /// to `kernel.global( "application", initializedOnly )`.
@@ -559,7 +552,7 @@ define( function() {
         ///   The ID of the application root. `application` may return `undefined` if the entire
         ///   application has been deleted.
 
-        application: [ /* initializedOnly */ ],
+        this.application = [ /* initializedOnly */ ]
 
         /// Return the node's intrinsic state. This consists of:
         /// 
@@ -579,7 +572,7 @@ define( function() {
         /// 
         /// @returns {Object}
 
-        intrinsics: [ /* nodeID, result */ ],
+        this.intrinsics = [ /* nodeID, result */ ]
 
         /// Return the node's URI. This value will be the component URI for the root node of a component
         /// loaded from a URI, and undefined in all other cases.
@@ -595,7 +588,7 @@ define( function() {
         /// 
         /// @returns {String}
 
-        uri: [ /* nodeID, searchAncestors, initializedOnly */ ],
+        this.uri = [ /* nodeID, searchAncestors, initializedOnly */ ]
 
         /// Name calls naming() on each model. The first model to return a non-undefined value dictates
         /// the return value.
@@ -606,7 +599,7 @@ define( function() {
         /// 
         /// @returns {String}
 
-        name: [ /* nodeID */ ],
+        this.name = [ /* nodeID */ ]
 
         /// Return a node's prototype.
         /// 
@@ -618,7 +611,7 @@ define( function() {
         ///   The ID of the node's prototype, or `undefined` if called on the proto-prototype,
         ///   `node.vwf`.
 
-        prototype: [ /* nodeID */ ],
+        this.prototype = [ /* nodeID */ ]
 
         /// Return a node's prototype, its prototype, etc. up to and including `node.vwf`.
         /// 
@@ -631,7 +624,7 @@ define( function() {
         /// @returns {ID[]}
         ///   An array of IDs of the node's prototypes.
 
-        prototypes: [ /* nodeID, includeBehaviors */ ],
+        this.prototypes = [ /* nodeID, includeBehaviors */ ]
 
         /// Return a node's behaviors.
         /// 
@@ -643,7 +636,7 @@ define( function() {
         ///   An array of IDs of the node's behaviors. An empty array is returned if the node
         ///   doesn't have any behaviors.
 
-        behaviors: [ /* nodeID */ ],
+        this.behaviors = [ /* nodeID */ ]
 
         /// Return the set of global root nodes. Each global node is the root of a tree.
         /// 
@@ -661,7 +654,7 @@ define( function() {
         ///   on the result to get an array of IDs. The global trees are not ordered, and the order
         ///   of the IDs is not significant.
 
-        globals: [ /* initializedOnly */ ],
+        this.globals = [ /* initializedOnly */ ]
 
         /// Return a global root node selected by its URI or annotation.
         /// 
@@ -683,7 +676,7 @@ define( function() {
         ///   doesn't match any root or if `initializedOnly` is set and the selected tree has not
         ///   completed initialization.
 
-        global: [ /* globalReference, initializedOnly */ ],
+        this.global = [ /* globalReference, initializedOnly */ ]
 
         /// Return the node at the root of the tree containing a node.
         /// 
@@ -701,7 +694,7 @@ define( function() {
         ///   `initializedOnly` is set and the node or one of its ancestors has not completed
         ///   initialization.
 
-        root: [ /* nodeID, initializedOnly */ ],
+        this.root = [ /* nodeID, initializedOnly */ ]
 
         /// Return a node's parent, grandparent, its parent, etc.
         /// 
@@ -720,7 +713,7 @@ define( function() {
         ///   An array of IDs of the node's ancestors. An empty array is returned for global,
         ///   top-level nodes that don't have a parent.
 
-        ancestors: [ /* nodeID, initializedOnly */ ],
+        this.ancestors = [ /* nodeID, initializedOnly */ ]
 
         /// Return a node's parent.
         /// 
@@ -736,7 +729,7 @@ define( function() {
         ///   The ID of the node's parent, or `undefined` for the application root node or other
         ///   global, top-level nodes.
 
-        parent: [ /* nodeID, initializedOnly */ ],
+        this.parent = [ /* nodeID, initializedOnly */ ]
 
         /// Return a node's children.
         /// 
@@ -755,7 +748,7 @@ define( function() {
         ///   regardless of their initialization state and whether `initializedOnly` is set.
         ///   However, `initializedOnly` will cause uninitialized children to appear as `undefined`.
 
-        children: [ /* nodeID, initializedOnly */ ],
+        this.children = [ /* nodeID, initializedOnly */ ]
 
         /// Return a node's child selected by index or name.
         /// 
@@ -776,7 +769,7 @@ define( function() {
         ///   or if `initializedOnly` is set and the selected child has not completed
         ///   initialization.
 
-        child: [ /* nodeID, childReference, initializedOnly */ ],
+        this.child = [ /* nodeID, childReference, initializedOnly */ ]
 
         /// Return a node's children, grandchildren, their children, etc.
         /// 
@@ -795,7 +788,7 @@ define( function() {
         ///   doesn't have any descendants. The result may contain `undefined` elements when
         ///   `initializedOnly` is set and some descendants have not completed initialization.
 
-        descendants: [ /* nodeID, initializedOnly */ ],
+        this.descendants = [ /* nodeID, initializedOnly */ ]
 
         /// Locate nodes matching a search pattern. matchPattern supports an XPath subset consisting of
         /// the following:
@@ -872,7 +865,7 @@ define( function() {
         /// @returns {ID[]|undefined}
         ///   If callback is provided, undefined; otherwise an array of the node ids of the result.
 
-        find: [ /* nodeID, matchPattern, initializedOnly, callback( matchID ) */ ],
+        this.find = [ /* nodeID, matchPattern, initializedOnly, callback( matchID ) */ ]
 
         /// Test a node against a search pattern. See vwf.api.kernel#find for details of the query
         /// syntax.
@@ -894,7 +887,7 @@ define( function() {
         /// @returns {Boolean}
         ///   true when testID matches the pattern.
 
-        test: [ /* nodeID, matchPattern, testID, initializedOnly */ ],
+        this.test = [ /* nodeID, matchPattern, testID, initializedOnly */ ]
 
         /// Return client object matching the given search pattern.
         /// 
@@ -915,7 +908,7 @@ define( function() {
         /// @deprecated in version 0.6.21. Instead of `kernel.findClients( reference, "/pattern" )`,
         ///   use `kernel.find( reference, "doc('http://vwf.example.com/clients.vwf')/pattern" )`.
 
-        findClients: [ /* nodeID, matchPattern, callback( matchID ) */ ],
+        this.findClients = [ /* nodeID, matchPattern, callback( matchID ) */ ]
 
         /// Description.
         /// 
@@ -1009,8 +1002,12 @@ define( function() {
         ///   An array of listeners to be invoked when the event is fired. Listeners will be invoked
         ///   in the order provided.
 
-    };
 
-    return exports;
+    }
+}
+
+export {
+    Kernel
+  }
+
 
-} );

+ 358 - 0
public/core/vwf/api/model.js

@@ -0,0 +1,358 @@
+/*
+The MIT License (MIT)
+Copyright (c) 2014-2020 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+
+Virtual World Framework Apache 2.0 license  (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
+*/
+/// Model API.
+/// 
+/// @module vwf/api/model
+
+class ModelApi {
+
+    constructor() {
+        //console.log("Model constructor");
+
+
+                /// Description.
+        /// 
+        /// @function
+        /// 
+        /// @param {ID} nodeID
+        /// @param {ID} childID
+        /// @param {String} childExtendsID
+        /// @param {String[]} childImplementsIDs
+        /// @param {String} childSource
+        /// @param {String} childType
+        /// @param {String} childIndex
+        ///   When `nodeID` is falsy, the URI of the component, or `undefined` if the component
+        ///   wasn't loaded from a URI. When `nodeID` is truthy, the numerical index of the child's
+        ///   position in the parent's array, starting at `0`. When child order is significant to
+        ///   the driver, the child should be placed at the given position in the parent's array.
+        ///   Nodes won't necessarily arrive in numerical order since varying dependencies cause
+        ///   nodes to become ready at indeterminate times.
+        /// @param {String} childName
+        /// @param {module:vwf/api/model~readyCallback} callback
+        /// 
+        /// @returns {}
+
+        this.creatingNode = [ /* nodeID, childID, childExtendsID, childImplementsIDs, childSource, childType,
+        childIndex, childName, callback( ready ) */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {ID} childID
+    /// @param {String} childExtendsID
+    /// @param {String[]} childImplementsIDs
+    /// @param {String} childSource
+    /// @param {String} childType
+    /// @param {String} childIndex
+    ///   When `nodeID` is falsy, the URI of the component, or `undefined` if the component
+    ///   wasn't loaded from a URI. When `nodeID` is truthy, the numerical index of the child's
+    ///   position in the parent's array, starting at `0`. When child order is significant to
+    ///   the driver, the child should be placed at the given position in the parent's array.
+    ///   Nodes won't necessarily arrive in numerical order since varying dependencies cause
+    ///   nodes to become ready at indeterminate times.
+    /// @param {String} childName
+    /// 
+    /// @returns {}
+
+    this.initializingNode = [ /* nodeID, childID, childExtendsID, childImplementsIDs, childSource, childType,
+        childIndex, childName */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {ID} childID
+    /// @param {ID} childInitializingNodeID
+    /// 
+    /// @returns {}
+
+    this.initializingNodeFromPrototype = [ /* nodeID, childID, childInitializingNodeID */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// 
+    /// @returns {}
+
+    this.deletingNode = [ /* nodeID */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {}
+    /// 
+    /// @returns {}
+
+    this.addingChild = []
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {}
+    /// 
+    /// @returns {}
+
+    this.removingChild = []
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {}
+    /// 
+    /// @returns {}
+
+    this.settingProperties = []
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {}
+    /// 
+    /// @returns {}
+
+    this.gettingProperties = []
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {}
+    /// 
+    /// @returns {}
+
+    this.creatingProperty = []
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {}
+    /// 
+    /// @returns {}
+
+    this.initializingProperty = []
+
+    // TODO: deletingProperty
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} propertyName
+    /// @param {Object} propertyValue
+    /// 
+    /// @returns {Object}
+    ///   A value set on property or undefined if not set.
+    ///   
+    ///   The first non-undefined return value will be sent with the "satProperty" event (which 
+    ///   may differ from the incoming propertyValue).
+
+    this.settingProperty = []
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {}
+    /// 
+    /// @returns {}
+
+    this.gettingProperty = []
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} methodName
+    /// @param {String[]} methodParameters
+    /// @param {String} methodBody
+    /// 
+    /// @returns {Handler} methodHandler
+
+    this.creatingMethod = [ /* nodeID, methodName, methodParameters, methodBody */ ]
+
+    // TODO: deletingMethod
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} methodName
+    /// @param {Handler} methodHandler
+    /// 
+    /// @returns {Handler} methodHandler
+
+    this.settingMethod = [ /* nodeID, methodName, methodHandler */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} methodName
+    /// 
+    /// @returns {Handler} methodHandler
+
+    this.gettingMethod = [ /* nodeID, methodName */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} methodName
+    /// @param {String[]} methodParameters
+    /// 
+    /// @returns {}
+
+    this.callingMethod = [ /* nodeID, methodName, methodParameters */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} eventName
+    /// @param {ListenerID} eventListenerID
+    /// @param {Handler} eventHandler
+    /// @param {ID} eventContextID
+    /// @param {String[]} eventPhases
+    /// 
+    /// @returns {Boolean}
+
+    this.addingEventListener = [ /* nodeID, eventName, eventListenerID, eventHandler, eventContextID, eventPhases */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} eventName
+    /// @param {ListenerID} eventListenerID
+    /// 
+    /// @returns {Boolean}
+
+    this.removingEventListener = [ /* nodeID, eventName, eventListenerID */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} eventName
+    /// @param {ListenerID} eventListenerID
+    /// @param {Listener} eventListener
+    /// 
+    /// @returns {Listener}
+
+    this.settingEventListener = [ /* nodeID, eventName, eventListenerID, eventListener */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} eventName
+    /// @param {ListenerID} eventListenerID
+    /// 
+    /// @returns {Listener}
+
+    this.gettingEventListener = [ /* nodeID, eventName, eventListenerID */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} eventName
+    /// @param {ID} eventContextID
+    /// 
+    /// @returns {}
+
+    this.flushingEventListeners = [ /* nodeID, eventName, eventContextID */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} eventName
+    /// @param {String[]} eventParameters
+    /// 
+    /// @returns {}
+
+    this.creatingEvent = [ /* nodeID, eventName, eventParameters */ ]
+
+    // TODO: deletingEvent
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} eventName
+    /// @param {String[]} eventParameters
+    /// 
+    /// @returns {}
+
+    this.firingEvent = [ /* nodeID, eventName, eventParameters */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {}
+    /// @returns {}
+
+    this.executing = []
+
+    /// Time has changed, probably by about the same amount as last time.
+    /// 
+    /// Don't rely on `ticking` notifications; but if you do, don't rely on them to arrive at
+    /// any particular rate. `ticking` may be removed in the future to allow the reflector to
+    /// vary the idle message interval.
+    /// 
+    /// To schedule actions for certain times, use the `when` parameter in the
+    /// {@link module:vwf/kernel/model Kernel API}.
+    /// 
+    /// @function
+    /// 
+    /// @param {Number} time
+    /// 
+    /// @returns {}
+    /// 
+    /// @deprecated in version 0.6.23. Use the {@link module:vwf/kernel/model Kernel API} `when`
+    ///   parameter to schedule future actions.
+
+    this.ticking = []
+
+    /// Description.
+    /// 
+    /// @callback module:vwf/api/model~readyCallback
+    /// 
+    /// @param {Boolean} ready
+
+
+    }
+}
+
+export {
+    ModelApi
+  }
+

+ 358 - 0
public/core/vwf/api/view.js

@@ -0,0 +1,358 @@
+/*
+The MIT License (MIT)
+Copyright (c) 2014-2020 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+
+Virtual World Framework Apache 2.0 license  (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
+*/
+
+/// View API.
+/// 
+/// @module vwf/api/view
+
+class ViewApi {
+
+    constructor() {
+        //console.log("View constructor");
+
+ /// Description.
+        /// 
+        /// @function
+        /// 
+        /// @param {ID} nodeID
+        /// @param {ID} childID
+        /// @param {String} childExtendsID
+        /// @param {String[]} childImplementsIDs
+        /// @param {String} childSource
+        /// @param {String} childType
+        /// @param {String} childIndex
+        ///   When `nodeID` is falsy, the URI of the component, or `undefined` if the component
+        ///   wasn't loaded from a URI. When `nodeID` is truthy, the numerical index of the child's
+        ///   position in the parent's array, starting at `0`. When child order is significant to
+        ///   the driver, the child should be placed at the given position in the parent's array.
+        ///   Nodes won't necessarily arrive in numerical order since varying dependencies cause
+        ///   nodes to become ready at indeterminate times.
+        /// @param {String} childName
+        /// @param {module:vwf/api/view~readyCallback} callback
+        /// 
+        /// @returns {}
+
+        this.createdNode = [ /* nodeID, childID, childExtendsID, childImplementsIDs, childSource, childType,
+        childIndex, childName, callback( ready ) */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {ID} childID
+    /// @param {String} childExtendsID
+    /// @param {String[]} childImplementsIDs
+    /// @param {String} childSource
+    /// @param {String} childType
+    /// @param {String} childIndex
+    ///   When `nodeID` is falsy, the URI of the component, or `undefined` if the component
+    ///   wasn't loaded from a URI. When `nodeID` is truthy, the numerical index of the child's
+    ///   position in the parent's array, starting at `0`. When child order is significant to
+    ///   the driver, the child should be placed at the given position in the parent's array.
+    ///   Nodes won't necessarily arrive in numerical order since varying dependencies cause
+    ///   nodes to become ready at indeterminate times.
+    /// @param {String} childName
+    /// 
+    /// @returns {}
+
+    this.initializedNode = [ /* nodeID, childID, childExtendsID, childImplementsIDs, childSource, childType,
+        childIndex, childName */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// 
+    /// @returns {}
+
+    this.deletedNode = [ /* nodeID */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {ID} childID
+    /// @param {String} childName
+    /// 
+    /// @returns {}
+
+    this.addedChild = [ /* nodeID, childID, childName */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {ID} childID
+    /// 
+    /// @returns {}
+
+    this.removedChild = [ /* nodeID, childID */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} propertyName
+    /// @param {Value} propertyValue
+    /// 
+    /// @returns {}
+
+    this.createdProperty = [ /* nodeID, propertyName, propertyValue */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} propertyName
+    /// @param {Value} propertyValue
+    /// 
+    /// @returns {}
+
+    this.initializedProperty = [ /* nodeID, propertyName, propertyValue */ ]
+
+    // TODO: deletedProperty
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} propertyName
+    /// @param {Value} propertyValue
+    ///   The internal value of the property set in the model driver (which 
+    ///   may differ from the value originally passed in to the model driver).
+    /// 
+    /// @returns {}
+
+    this.satProperty = [ /* nodeID, propertyName, propertyValue */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} propertyName
+    /// @param {Value} propertyValue
+    /// 
+    /// @returns {}
+
+    this.gotProperty = [ /* nodeID, propertyName, propertyValue */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} methodName
+    /// @param {String[]} methodParameters
+    /// @param {String} methodBody
+    /// 
+    /// @returns {}
+
+    this.createdMethod = [ /* nodeID, methodName, methodParameters, methodBody */ ]
+
+    // TODO: deletedMethod
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} methodName
+    /// @param {Handler} methodHandler
+    /// 
+    /// @returns {}
+
+    this.satMethod = [ /* nodeID, methodName, methodHandler */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} methodName
+    /// @param {Handler} methodHandler
+    /// 
+    /// @returns {}
+
+    this.gotMethod = [ /* nodeID, methodName, methodHandler */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} methodName
+    /// @param {String[]} methodParameters
+    /// @param {Value} methodValue
+    /// 
+    /// @returns {}
+
+    this.calledMethod = [ /* nodeID, methodName, methodParameters, methodValue */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} eventName
+    /// @param {String[]} eventParameters
+    /// 
+    /// @returns {}
+
+    this.createdEvent = [ /* nodeID, eventName, eventParameters */ ]
+
+    // TODO: deletedEvent
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} eventName
+    /// @param {ListenerID} eventListenerID
+    /// @param {Handler} eventHandler
+    /// @param {ID} eventContextID
+    /// @param {String[]} eventPhases
+    /// 
+    /// @returns {}
+
+    this.addedEventListener = [ /* nodeID, eventName, eventListenerID, eventHandler, eventContextID, eventPhases */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} eventName
+    /// @param {ListenerID} eventListenerID
+    /// 
+    /// @returns {}
+
+    this.removedEventListener = [ /* nodeID, eventName, eventListenerID */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} eventName
+    /// @param {ListenerID} eventListenerID
+    /// @param {Listener} eventListener
+    /// 
+    /// @returns {}
+
+    this.satEventListener = [ /* nodeID, eventName, eventListenerID, eventListener */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} eventName
+    /// @param {ListenerID} eventListenerID
+    /// @param {Listener} eventListener
+    /// 
+    /// @returns {}
+
+    this.gotEventListener = [ /* nodeID, eventName, eventListenerID, eventListener */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} eventName
+    /// @param {ID} eventContextID
+    /// 
+    /// @returns {}
+
+    this.flushedEventListeners = [ /* nodeID, eventName, eventContextID */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} eventName
+    /// @param {String[]} eventParameters
+    /// 
+    /// @returns {}
+
+    this.firedEvent = [ /* nodeID, eventName, eventParameters */ ]
+
+    /// Description.
+    /// 
+    /// @function
+    /// 
+    /// @param {ID} nodeID
+    /// @param {String} scriptText
+    /// @param {String} scriptType
+    /// 
+    /// @returns {}
+
+    this.executed = [ /* nodeID, scriptText, scriptType */ ]
+
+    /// Time has changed, probably by about the same amount as last time.
+    /// 
+    /// `ticked` notifications are sent periodically as time moves forward. They are sent at
+    /// roughly consistent intervals in real time while the application is running. However,
+    /// application processing delays and network jitter will affect the specific interval.
+    /// 
+    /// Don't rely on `ticked` notifications to arrive at any particular rate. `ticked` is
+    /// currently derived from reflector idle messages. Future versions of the reflector may
+    /// vary the idle message interval based on network conditions and the application state.
+    /// 
+    /// Use {@link external:Window#requestAnimationFrame window.requestAnimationFrame} or
+    /// {@link external:WindowTimers#setInterval window.setInterval} for real-time
+    /// notifications. To receive notifications following application state changes, but not
+    /// necessarily periodically, listen for {@link module:vwf/api/view.tocked view.tocked}.
+    /// 
+    /// @function
+    /// 
+    /// @param {Number} time
+    /// 
+    /// @returns {}
+
+    this.ticked = [ /* time */ ]
+
+    /// Time has changed.
+    /// 
+    /// Unlike {@link module:vwf/api/view.ticked view.ticked}, `tocked` notifications are sent
+    /// each time that time moves forward. Time changes may occur when previously scheduled
+    /// actions are executed or as regular idle progress. Since the application state only
+    /// changes when simulation time changes, `tocked` notifications may be used as an
+    /// application-wide change notification.
+    /// 
+    /// @function
+    /// 
+    /// @param {Number} time
+    /// 
+    /// @returns {}
+
+    this.tocked = [ /* time */ ]
+
+    /// Description.
+    /// 
+    /// @callback module:vwf/api/view~readyCallback
+    /// 
+    /// @param {Boolean} ready
+
+
+    }
+}
+
+export {
+    ViewApi
+  }
+

+ 186 - 0
public/core/vwf/fabric.js

@@ -0,0 +1,186 @@
+/*
+The MIT License (MIT)
+Copyright (c) 2014-2020 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+
+Virtual World Framework Apache 2.0 license  (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
+*/
+/// 
+/// @module vwf/view
+/// @requires logger
+/// @requires vwf/api/kernel
+/// @requires vwf/api/view
+
+import { Utility } from '/core/vwf/utility/utility.js';
+import { Helpers } from '/core/helpers.js';
+import { Logger } from '/core/vwf/utility/logger.js';
+import { Kernel } from '/core/vwf/api/kernel.js';
+import { ViewApi } from '/core/vwf/api/view.js';
+import { ModelApi } from '/core/vwf/api/model.js';
+
+class Fabric {
+
+    constructor(module, api) {
+
+        //console.log("Fabric constructor");
+
+        // this.view_api = new ViewApi;
+        this.api = (api == 'View') ? new ViewApi : new ModelApi
+        this.kernel_api = new Kernel;
+        
+        this.loggerFactory = new Logger;
+        this.utility = new Utility;
+        this.helpers = new Helpers;
+        
+
+        this.module = module;
+        this.label = module.id.replace( /\//g, "." );
+        this.loggerFactory.for( this.label ).debug( "loading" );
+
+        this.logger = this.loggerFactory.for( this.label );
+
+    }
+
+    load ( module, initializer, fabricGenerator, kernelGenerator ) {
+
+        var instance = Object.create( this );
+        instance.driver = this;
+
+        instance.module = module;
+        instance.logger = this.logger.for( instance.module.id.replace( /\//g, "." ), instance );
+        
+        instance.logger.debug( "loading" );
+
+        if ( typeof initializer == "function" || initializer instanceof Function ) {
+            initializer = initializer();
+        }
+
+        for ( var key in initializer ) {
+            instance[key] = initializer[key]; 
+        }
+
+        fabricGenerator && Object.keys( this.api ).forEach( function( fabricFunctionName ) {
+            if ( ! instance.hasOwnProperty( fabricFunctionName ) ) {
+                instance[fabricFunctionName] = fabricGenerator.call( instance, fabricFunctionName );
+                instance[fabricFunctionName] || delete instance[fabricFunctionName];
+            }
+        } );
+
+        kernelGenerator && Object.keys( this.kernel_api ).forEach( function( kernelFunctionName ) {
+            if ( ! instance.hasOwnProperty( kernelFunctionName ) ) {
+                instance[kernelFunctionName] = kernelGenerator.call( instance, kernelFunctionName );
+                instance[kernelFunctionName] || delete instance[kernelFunctionName];
+            }
+        } );
+
+        return instance;
+    }
+
+    create ( kernel, fabric, stages, state, parameters ) {
+
+        let self = this;
+        // let view_api = this.view_api;
+        // let kernel_api = this.kernel_api;
+
+        this.logger.debug( "creating" );
+
+        // Interpret create( kernel, stages, ... ) as create( kernel, undefined, stages, ... )
+
+        if ( fabric && fabric.length !== undefined ) { // is an array?
+            parameters = state;
+            state = stages;
+            stages = fabric;
+            fabric = undefined;
+        }
+
+        // Append this driver's stages to the pipeline to be placed in front of this driver.
+
+        if ( ! fabric ) {
+            stages = Array.prototype.concat.apply( [], ( this.pipeline || [] ).map( function( stage ) {
+                return ( stages || [] ).concat( stage );
+            } ) ).concat( stages || [] );
+        } else {
+            stages = ( stages || [] ).concat( this.pipeline || [] );
+        }
+
+        // Create the driver stage using its module as its prototype.
+
+        var instance = Object.create( this );
+
+
+
+        // Attach the reference to the stage to the right through the view API.
+
+        fabricize.call( instance, fabric, self.api );
+
+        // Create the pipeline to the left and attach the reference to the stage to the left
+        // through the kernel API.
+
+        kernelize.call( instance,
+            stages.length ?
+                stages.pop().create( kernel, instance, stages ) :
+                kernel,
+            self.kernel_api );
+
+        // Attach the shared state object.
+
+        instance.state = state || {};
+
+        // Call the driver's initialize().
+
+        initialize.apply( instance, parameters );
+
+        // Call viewize() on the driver.
+
+        function fabricize( fabric_type, fabric_api ) {
+            Object.getPrototypeOf( this ) && fabricize.call( Object.getPrototypeOf( this ), fabric_type, fabric_api ); // depth-first recursion through the prototypes
+            this.hasOwnProperty( "fabricize" ) && this.fabricize.call( instance, fabric_type, fabric_api ); // viewize() from the bottom up
+        }
+
+        // Call kernelize() on the driver.
+
+        function kernelize( kernel, kernel_api ) {
+            Object.getPrototypeOf( this ) && kernelize.call( Object.getPrototypeOf( this ), kernel, kernel_api ); // depth-first recursion through the prototypes
+            this.hasOwnProperty( "kernelize" ) && this.kernelize.call( instance, kernel, kernel_api ); // kernelize() from the bottom up
+        }
+
+        // Call initialize() on the driver.
+
+        function initialize( /* parameters */ ) {
+            Object.getPrototypeOf( this ) && initialize.apply( Object.getPrototypeOf( this ), arguments ); // depth-first recursion through the prototypes
+            this.hasOwnProperty( "initialize" ) && this.initialize.apply( instance, arguments ); // initialize() from the bottom up
+        }
+
+        // Return the driver stage. For the actual driver, return the leftmost stage in the
+        // pipeline.
+
+        if ( ! fabric ) {
+            while ( instance.kernel !== kernel ) {
+                instance = instance.kernel;
+            }
+        }
+
+        this.driver.instance = instance;
+        return instance;
+    }
+
+    kernelize ( kernel, kernel_api ) {
+        this.kernel = kernel;
+    }
+
+    static getPrototypes(kernel, extendsID) {
+        let prototypes = [];
+        let id = extendsID;
+
+        while (id !== undefined) {
+            prototypes.push(id);
+            id = kernel.prototype(id);
+        }
+        return prototypes;
+    }
+
+}
+
+export {
+    Fabric
+  }
+

+ 736 - 0
public/core/vwf/model.js

@@ -0,0 +1,736 @@
+/*
+The MIT License (MIT)
+Copyright (c) 2014-2020 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+
+Virtual World Framework Apache 2.0 license  (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
+*/
+
+/// FROM FABRIC.JS
+/// vwf/model.js is the common implementation of all Virtual World Framework models. Each model
+/// is part of a federation with other models attached to the simulation that implements part of
+/// the greater model. Taken together, the models create the entire model system for the
+/// simulation.
+///
+/// Models are inside of, and directly part of the simulation. They may control the simulation
+/// and cause immediate change, but they cannot accept external input. The model configuration is
+/// identical for all participants in a shared world.
+/// 
+/// A given model might be responsible for a certain subset of nodes in the the simulation, such
+/// as those representing Flash objects. Or it might implement part of the functionality of any
+/// node, such as translating 3-D transforms and material properties back and forth to a scene
+/// manager. Or it might implement functionality that is only active for a short period, such as
+/// importing a document.
+
+/// @module vwf/kernel/model
+/// @requires vwf/model
+
+
+import { Fabric } from '/core/vwf/fabric.js';
+
+class ModelKernel extends Fabric{
+
+    constructor(module) {
+
+        //console.log("ModelKernel constructor");
+        super( module, "Model")
+        
+    }
+
+    factory(){
+
+        return this.load( this.module, {
+
+            // == Module Definition ====================================================================
+    
+            initialize: function() {
+                this.state.enabled = true; // kernel reentry allowed?
+                this.state.blocked = false; // kernel reentry attempted?
+            },
+    
+            /// Allow kernel reentry from the drivers.
+    
+            enable: function() {
+                this.state.enabled = true;
+                this.state.blocked = false;
+            },
+            
+            /// Prevent kernel reentry from the drivers.
+    
+            disable: function() {
+                this.state.enabled = false;
+                this.state.blocked = false;
+            },
+    
+            /// Indicate if kernel reentry is currently enabled.
+    
+            enabled: function() {
+                return this.state.enabled;
+            },
+    
+            /// Indicate if kernel reentry is currently disabled.
+    
+            disabled: function() {
+                return ! this.state.enabled;
+            },
+    
+            /// Indicate if a driver attempted to call back into the kernel while reentry was disabled,
+            /// and clear the *blocked* flag.
+            
+            blocked: function() {
+                var blocked = this.state.blocked;
+                this.state.blocked = false;
+                return blocked;
+            },
+    
+            /// Invoke a task and record any async actions that it initiates. After the actions have
+            /// completed, execute their callbacks, then call a completion callback.
+            /// 
+            /// @param {Function} task
+            ///   The task to execute and monitor for async actions. `task` is invoked with no
+            ///   arguments.
+            /// @param {Function} callback
+            ///   Invoked after the async actions have completed.
+            /// @param {Object} [that]
+            ///   The `this` value for the `task` and `callback` functions.
+    
+            capturingAsyncs: function( task, callback, that ) {
+    
+                // Create an array to capture the callbacks and results from async actions. When
+                // `this.state.asyncs` exists, async actions hand off their callbacks to `asyncs.defer`.
+    
+                var asyncs = this.state.asyncs = [];
+    
+                asyncs.defer = defer;
+                asyncs.completed = 0;
+    
+                asyncs.callback = callback;
+                asyncs.that = that;
+    
+                // Invoke the task.
+    
+                task.call( that );
+    
+                // Detach the array from `this.state.asyncs` to stop capturing async actions.
+    
+                this.state.asyncs = undefined;
+    
+                // If there were no async actions, call the completion callback immediately.
+    
+                if ( asyncs.completed == asyncs.length ) {
+                    asyncs.callback.call( asyncs.that );
+                }
+    
+                /// The `this.state.asyncs` array `defer` method.
+                /// 
+                /// Wrap a callback function with a new function that will defer the original callback
+                /// until a collection of actions have completed, then call the deferred callbacks
+                /// followed by a completion callback.
+    
+                function defer( callback /* result */ ) {
+    
+                    var self = this;
+    
+                    // Save the original callback. The wrapping callback will save the result here when
+                    // received.
+    
+                    var deferred = {
+                        callback: callback /* result */,
+                        result: undefined
+                    };
+    
+                    this.push( deferred );
+    
+                    // Return a new callback in place of the original. Record the result, then if all
+                    // actions have completed, call the original callbacks, then call the completion
+                    // function.
+    
+                    return function( result ) {
+    
+                        deferred.result = result;
+    
+                        if ( ++self.completed == self.length ) {
+    
+                            // Call the original callbacks.
+    
+                            self.forEach( function( deferred ) {
+                                deferred.callback && deferred.callback( deferred.result );
+                            } );
+    
+                            // Call the completion callback.
+    
+                            if ( self.callback ) {
+                                self.callback.call( self.that );
+                            }
+    
+                        }
+    
+                    }
+    
+                };
+    
+            },
+    
+        }, function( modelFunctionName ) {
+    
+            // == Model API ============================================================================
+    
+            // The kernel bypasses vwf/kernel/model and calls directly into the first driver stage.
+    
+            return undefined;
+    
+        }, function( kernelFunctionName ) {
+    
+            // == Kernel API ===========================================================================
+    
+            switch ( kernelFunctionName ) {
+    
+                // -- Read-write functions -------------------------------------------------------------
+    
+                // TODO: setState
+                // TODO: getState
+                // TODO: hashState
+    
+                case "createNode":
+    
+                    return function( nodeComponent, nodeAnnotation, baseURI, when, callback /* nodeID */ ) {
+    
+                        // Interpret `createNode( nodeComponent, when, callback )` as
+                        // `createNode( nodeComponent, undefined, undefined, when, callback )` and
+                        // `createNode( nodeComponent, nodeAnnotation, when, callback )` as
+                        // `createNode( nodeComponent, nodeAnnotation, undefined, when, callback )`.
+                        // `nodeAnnotation` was added in 0.6.12, and `baseURI` was added in 0.6.25.
+    
+                        if ( typeof baseURI == "function" || baseURI instanceof Function ) {
+                            callback = baseURI;
+                            when = nodeAnnotation;
+                            baseURI = undefined;
+                            nodeAnnotation = undefined;
+                        } else if ( typeof when == "function" || when instanceof Function ) {
+                            callback = when;
+                            when = baseURI;
+                            baseURI = undefined;
+                        }
+    
+                        // Make the call.
+    
+                        if ( this.state.enabled ) {
+    
+                            if ( when === undefined ) {
+    
+                                if ( this.state.asyncs ) {
+                                    callback = this.state.asyncs.defer( callback /* nodeID */ );
+                                }
+    
+                                return this.kernel[kernelFunctionName]( nodeComponent, nodeAnnotation, function( nodeID ) {
+                                    callback && callback( nodeID );
+                                } );
+    
+                            } else {
+                                this.kernel.virtualTime.plan( undefined, kernelFunctionName, undefined,
+                                    [ nodeComponent, nodeAnnotation ], when, callback /* result */ );
+                            }
+    
+                        } else {
+                            this.state.blocked = true;
+                        }
+    
+                    };
+    
+                case "deleteNode":
+    
+                    return function( nodeID, when, callback ) {
+    
+                        if ( this.state.enabled ) {
+    
+                            if ( when === undefined ) {
+                                return this.kernel[kernelFunctionName]( nodeID );
+                            } else {
+                                this.kernel.virtualTime.plan( nodeID, kernelFunctionName, undefined,
+                                    undefined, when, callback /* result */ );
+                            }
+    
+                        } else {
+                            this.state.blocked = true;
+                        }
+    
+                    };
+    
+                // TODO: setNode
+                // TODO: getNode
+    
+                case "createChild":
+    
+                    return function( nodeID, childName, childComponent, childURI, when, callback /* childID */ ) {
+    
+                        if ( this.state.enabled ) {
+    
+                            if ( when === undefined ) {
+    
+                                if ( this.state.asyncs ) {
+                                    callback = this.state.asyncs.defer( callback /* childID */ );
+                                }
+    
+                                return this.kernel[kernelFunctionName]( nodeID, childName, childComponent, childURI, function( childID ) {
+                                    callback && callback( childID );
+                                } );
+    
+                            } else {
+                                this.kernel.virtualTime.plan( nodeID, kernelFunctionName, childName,
+                                    [ childComponent, childURI ], when, callback /* result */ );
+                            }
+    
+                        } else {
+                            this.state.blocked = true;
+                        }
+    
+                    };
+    
+                case "deleteChild":
+    
+                    return function( nodeID, childName, when, callback ) {
+    
+                        if ( this.state.enabled ) {
+    
+                            if ( when === undefined ) {
+                                return this.kernel[kernelFunctionName]( nodeID, childName );
+                            } else {
+                                this.kernel.virtualTime.plan( nodeID, kernelFunctionName, childName,
+                                    undefined, when, callback /* result */ );
+                            }
+    
+                        } else {
+                            this.state.blocked = true;
+                        }
+    
+                    };
+    
+                case "addChild":
+    
+                    return function( nodeID, childID, childName, when, callback ) {
+    
+                        if ( this.state.enabled ) {
+    
+                            if ( when === undefined ) {
+                                return this.kernel[kernelFunctionName]( nodeID, childID, childName );
+                            } else {
+                                this.kernel.virtualTime.plan( nodeID, kernelFunctionName, undefined,
+                                    [ childID, childName ], when, callback /* result */ );  // TODO: swap childID & childName?
+                            }
+    
+                        } else {
+                            this.state.blocked = true;
+                        }
+    
+                    };
+    
+                case "removeChild":
+    
+                    return function( nodeID, childID, when, callback ) {
+    
+                        if ( this.state.enabled ) {
+    
+                            if ( when === undefined ) {
+                                return this.kernel[kernelFunctionName]( nodeID, childID );
+                            } else {
+                                this.kernel.virtualTime.plan( nodeID, kernelFunctionName, undefined,
+                                    [ childID ], when, callback /* result */ );  // TODO: swap childID & childName?
+                            }
+    
+                        } else {
+                            this.state.blocked = true;
+                        }
+    
+                    };
+    
+                case "setProperties":
+    
+                    return function( nodeID, properties, when, callback ) {
+    
+                        if ( this.state.enabled ) {
+    
+                            if ( when === undefined ) {
+                                return this.kernel[kernelFunctionName]( nodeID, properties );
+                            } else {
+                                this.kernel.virtualTime.plan( nodeID, kernelFunctionName, undefined,
+                                    [ properties ], when, callback /* result */ );
+                            }
+    
+                        } else {
+                            this.state.blocked = true;
+                        }
+        
+                    };
+    
+                case "getProperties":
+    
+                    return function( nodeID, when, callback ) {
+    
+                        if ( this.state.enabled ) {
+    
+                            if ( when === undefined ) {
+                                return this.kernel[kernelFunctionName]( nodeID );
+                            } else {
+                                this.kernel.virtualTime.plan( nodeID, kernelFunctionName, undefined,
+                                    undefined, when, callback /* result */ );
+                            }
+    
+                        } else {
+                            this.state.blocked = true;
+                        }
+    
+                    };
+        
+                case "createProperty":
+    
+                    return function( nodeID, propertyName, propertyValue, propertyGet, propertySet, when, callback ) {
+    
+                        if ( this.state.enabled ) {
+    
+                            if ( when === undefined ) {
+                                return this.kernel[kernelFunctionName]( nodeID, propertyName, propertyValue, propertyGet, propertySet );
+                            } else {
+                                this.kernel.virtualTime.plan( nodeID, kernelFunctionName, propertyName,
+                                    [ propertyValue, propertyGet, propertySet ], when, callback /* result */ );  // TODO: { value: propertyValue, get: propertyGet, set: propertySet } ? -- vwf.receive() needs to parse
+                            }
+    
+                        } else {
+                            this.state.blocked = true;
+                        }
+    
+                    };
+    
+                // TODO: deleteProperty
+    
+                case "setProperty":
+    
+                    return function( nodeID, propertyName, propertyValue, when, callback ) {
+    
+                        if ( this.state.enabled ) {
+    
+                            if ( when === undefined ) {
+                                return this.kernel[kernelFunctionName]( nodeID, propertyName, propertyValue );
+                            } else {
+                                this.kernel.virtualTime.plan( nodeID, kernelFunctionName, propertyName,
+                                    [ propertyValue ], when, callback /* result */ );
+                            }
+    
+                        } else {
+                            this.state.blocked = true;
+                        }
+    
+                    };
+    
+                case "getProperty":
+    
+                    return function( nodeID, propertyName, when, callback ) {
+    
+                        if ( this.state.enabled ) {
+    
+                            if ( when === undefined ) {
+                                return this.kernel[kernelFunctionName]( nodeID, propertyName );
+                            } else {
+                                this.kernel.virtualTime.plan( nodeID, kernelFunctionName, propertyName,
+                                    undefined, when, callback /* result */ );
+                            }
+    
+                        } else {
+                            this.state.blocked = true;
+                        }
+    
+                    };
+        
+                case "createMethod":
+    
+                    return function( nodeID, methodName, methodParameters, methodBody, when, callback ) {
+    
+                        if ( this.state.enabled ) {
+    
+                            if ( when === undefined ) {
+                                return this.kernel[kernelFunctionName]( nodeID, methodName, methodParameters, methodBody );
+                            } else {
+                                this.kernel.virtualTime.plan( nodeID, kernelFunctionName, methodName,
+                                    [ methodParameters, methodBody ], when, callback /* result */ );  // TODO: { parameters: methodParameters, body: methodBody } ? -- vwf.receive() needs to parse
+                            }
+    
+                        } else {
+                            this.state.blocked = true;
+                        }
+    
+                    };
+    
+                // TODO: deleteMethod
+    
+                case "setMethod":
+    
+                    return function( nodeID, methodName, methodHandler, when, callback ) {
+    
+                        if ( this.state.enabled ) {
+    
+                            if ( when === undefined ) {
+                                return this.kernel[kernelFunctionName]( nodeID, methodName, methodHandler );
+                            } else {
+                                this.kernel.virtualTime.plan( nodeID, kernelFunctionName, methodName,
+                                    [ methodHandler ], when, callback /* result */ );
+                            }
+    
+                        } else {
+                            this.state.blocked = true;
+                        }
+    
+                    };
+    
+                case "getMethod":
+    
+                    return function( nodeID, methodName, when, callback ) {
+    
+                        if ( this.state.enabled ) {
+    
+                            if ( when === undefined ) {
+                                return this.kernel[kernelFunctionName]( nodeID, methodName );
+                            } else {
+                                this.kernel.virtualTime.plan( nodeID, kernelFunctionName, methodName,
+                                    undefined, when, callback /* result */ );
+                            }
+    
+                        } else {
+                            this.state.blocked = true;
+                        }
+    
+                    };
+    
+                case "callMethod":
+    
+                    return function( nodeID, methodName, methodParameters, when, callback ) {
+    
+                        if ( this.state.enabled ) {
+    
+                            if ( when === undefined ) {
+                                return this.kernel[kernelFunctionName]( nodeID, methodName, methodParameters );
+                            } else {
+                                this.kernel.virtualTime.plan( nodeID, kernelFunctionName, methodName,
+                                    [ methodParameters ], when, callback /* result */ );
+                            }
+    
+                        } else {
+                            this.state.blocked = true;
+                        }
+    
+                    };
+        
+                case "createEvent":
+    
+                    return function( nodeID, eventName, eventParameters, when, callback ) {
+    
+                        if ( this.state.enabled ) {
+    
+                            if ( when === undefined ) {
+                                return this.kernel[kernelFunctionName]( nodeID, eventName, eventParameters );
+                            } else {
+                                this.kernel.virtualTime.plan( nodeID, kernelFunctionName, eventName,
+                                    [ eventParameters ], when, callback /* result */ );
+                            }
+    
+                        } else {
+                            this.state.blocked = true;
+                        }
+    
+                    };
+    
+                // TODO: deleteEvent
+    
+                case "addEventListener":
+    
+                    return function( nodeID, eventName, eventHandler, eventContextID, eventPhases, when, callback ) {
+    
+                        if ( this.state.enabled ) {
+    
+                            if ( when === undefined ) {
+                                return this.kernel[kernelFunctionName]( nodeID, eventName, eventHandler, eventContextID, eventPhases );
+                            } else {
+                                this.kernel.virtualTime.plan( nodeID, kernelFunctionName, eventName,
+                                    [ eventHandler, eventContextID, eventPhases ], when, callback /* result */ );
+                            }
+    
+                        } else {
+                            this.state.blocked = true;
+                        }
+    
+                    };
+    
+                case "removeEventListener":
+    
+                    return function( nodeID, eventName, eventListenerID, when, callback ) {
+    
+                        if ( this.state.enabled ) {
+    
+                            if ( when === undefined ) {
+                                return this.kernel[kernelFunctionName]( nodeID, eventName, eventListenerID );
+                            } else {
+                                this.kernel.virtualTime.plan( nodeID, kernelFunctionName, eventName,
+                                    [ eventListenerID ], when, callback /* result */ );
+                            }
+    
+                        } else {
+                            this.state.blocked = true;
+                        }
+    
+                    };
+    
+                case "flushEventListeners":
+    
+                    return function( nodeID, eventName, eventContextID, when, callback ) {
+    
+                        if ( this.state.enabled ) {
+    
+                            if ( when === undefined ) {
+                                return this.kernel[kernelFunctionName]( nodeID, eventName, eventContextID );
+                            } else {
+                                this.kernel.virtualTime.plan( nodeID, kernelFunctionName, eventName,
+                                    [ eventContextID ], when, callback /* result */ );
+                            }
+    
+                        } else {
+                            this.state.blocked = true;
+                        }
+    
+                    };
+    
+                case "fireEvent":
+    
+                    return function( nodeID, eventName, eventParameters, when, callback ) {
+    
+                        if ( this.state.enabled ) {
+    
+                            if ( when === undefined ) {
+                                return this.kernel[kernelFunctionName]( nodeID, eventName, eventParameters );
+                            } else {
+                                this.kernel.virtualTime.plan( nodeID, kernelFunctionName, eventName,
+                                    [ eventParameters ], when, callback /* result */ );
+                            }
+    
+                        } else {
+                            this.state.blocked = true;
+                        }
+    
+                    };
+        
+                case "dispatchEvent":
+    
+                    return function( nodeID, eventName, eventParameters, eventNodeParameters, when, callback ) {
+    
+                        if ( this.state.enabled ) {
+    
+                            if ( when === undefined ) {
+                                return this.kernel[kernelFunctionName]( nodeID, eventName, eventParameters, eventNodeParameters );
+                            } else {
+                                this.kernel.virtualTime.plan( nodeID, kernelFunctionName, eventName,
+                                    [ eventParameters, eventNodeParameters ], when, callback /* result */ );
+                            }
+    
+                        } else {
+                            this.state.blocked = true;
+                        }
+    
+                    };
+        
+                case "execute":
+    
+                    return function( nodeID, scriptText, scriptType, when, callback /* result */ ) {
+    
+                        if ( this.state.enabled ) {
+    
+                            if ( when === undefined ) {
+                                if ( this.state.asyncs ) {
+                                    callback = this.state.asyncs.defer( callback /* result */ );
+                                }
+                                return this.kernel[kernelFunctionName]( nodeID, scriptText, scriptType, function( result ) {
+                                    callback && callback( result );
+                                } );
+                            } else {
+                                this.kernel.virtualTime.plan( nodeID, kernelFunctionName, undefined,
+                                    [ scriptText, scriptType ], when, callback /* result */ );  // TODO: { text: scriptText, type: scriptType } ? -- vwf.receive() needs to parse
+                            }
+    
+                        } else {
+                            this.state.blocked = true;
+                        }
+                    };
+    
+                case "random":
+    
+                    return function( nodeID, when, callback ) {
+    
+                        if ( this.state.enabled ) {
+    
+                            if ( when === undefined ) {
+                                return this.kernel[kernelFunctionName]( nodeID );
+                            } else {
+                                this.kernel.virtualTime.plan( nodeID, kernelFunctionName, undefined,
+                                    undefined, when, callback /* result */ );
+                            }
+    
+                        } else {
+                            this.state.blocked = true;
+                        }
+    
+                    };
+    
+                case "seed":
+    
+                    return function( nodeID, seed, when, callback ) {
+    
+                        if ( this.state.enabled ) {
+    
+                            if ( when === undefined ) {
+                                return this.kernel[kernelFunctionName]( nodeID, seed );
+                            } else {
+                                this.kernel.virtualTime.plan( nodeID, kernelFunctionName, undefined,
+                                    [ seed ], when, callback /* result */ );
+                            }
+    
+                        } else {
+                            this.state.blocked = true;
+                        }
+    
+                    };
+    
+                // -- Read-only functions --------------------------------------------------------------
+    
+                case "time":
+                case "client":
+                case "moniker":
+    
+                case "application":
+    
+                case "intrinsics":
+                case "uri":
+                case "name":
+    
+                case "prototype":
+                case "prototypes":
+                case "behaviors":
+    
+                case "ancestors":
+                case "parent":
+                case "children":
+                case "descendants":
+    
+                case "find":
+                case "test":
+                case "findClients":
+    
+                    return function() {
+                        return this.kernel[kernelFunctionName].apply( this.kernel, arguments );
+                    };
+    
+            }
+    
+        } );
+
+    }
+}
+
+
+export {
+    ModelKernel
+  }

+ 168 - 142
public/vwf/model/javascript.js → public/core/vwf/model/javascript.js

@@ -1,17 +1,9 @@
-"use strict";
-
-// Copyright 2012 United States Government, as represented by the Secretary of Defense, Under
-// Secretary of Defense (Personnel & Readiness).
-// 
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
-// in compliance with the License. You may obtain a copy of the License at
-// 
-//   http://www.apache.org/licenses/LICENSE-2.0
-// 
-// Unless required by applicable law or agreed to in writing, software distributed under the License
-// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
-// or implied. See the License for the specific language governing permissions and limitations under
-// the License.
+/*
+The MIT License (MIT)
+Copyright (c) 2014-2020 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+
+Virtual World Framework Apache 2.0 license  (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
+*/
 
 /// vwf/model/javascript.js is a placeholder for the JavaScript object interface to the
 /// simulation.
@@ -22,10 +14,28 @@
 /// @requires vwf/utility
 /// @requires vwf/configuration
 
-define( [ "module", "vwf/model", "vwf/kernel/utility", "vwf/utility", "vwf/configuration" ],
-        function( module, model, kutility, utility, configuration ) {
 
-    var exports = model.load( module, {
+import { Utility } from "/core/vwf/utility/utility.js";
+import { KUtility } from "/core/vwf/utility/kutility.js";
+import { Fabric } from "/core/vwf/fabric.js";
+
+class VWFJavaScript extends Fabric {
+
+  constructor(module) {
+
+    console.log("JavaScript constructor");
+    super(module, "Model")
+
+  }
+
+  factory() {
+
+    let _self_ = this;
+
+    return this.load(
+      this.module,
+
+      {
 
         // This is a placeholder for providing a natural integration between simulation and the
         // browser's JavaScript environment.
@@ -78,7 +88,7 @@ define( [ "module", "vwf/model", "vwf/kernel/utility", "vwf/utility", "vwf/confi
             // the prototype, or above the most recently-attached behavior.
 
             behaviors.forEach( function( behavior ) {
-                prototype = proxiedBehavior.call( self, prototype, behavior );
+                prototype = VWFJavaScript.proxiedBehavior.call( self, prototype, behavior );
             } );
 
             // Create the node. Its prototype is the most recently-attached behavior, or the
@@ -86,7 +96,7 @@ define( [ "module", "vwf/model", "vwf/kernel/utility", "vwf/utility", "vwf/confi
 
             var node = this.nodes[childID] = Object.create( prototype );
 
-            if ( childID === kutility.protoNodeURI ) {
+            if ( childID === VWFJavaScript.kutility.protoNodeURI ) {
                 this.protoNode = node;
             }
 
@@ -153,9 +163,9 @@ define( [ "module", "vwf/model", "vwf/kernel/utility", "vwf/utility", "vwf/confi
 
             // Attach the property meta events to `node.properties.{created,initialized,deleted}`.
 
-            createEventAccessor.call( this, node.properties, "created", "properties" );
-            createEventAccessor.call( this, node.properties, "initialized", "properties" );
-            createEventAccessor.call( this, node.properties, "deleted", "properties" );
+            VWFJavaScript.createEventAccessor.call( this, node.properties, "created", "properties" );
+            VWFJavaScript.createEventAccessor.call( this, node.properties, "initialized", "properties" );
+            VWFJavaScript.createEventAccessor.call( this, node.properties, "deleted", "properties" );
 
             node.private.getters = Object.create( prototype.private ?
                 prototype.private.getters : Object.prototype
@@ -179,8 +189,8 @@ define( [ "module", "vwf/model", "vwf/kernel/utility", "vwf/utility", "vwf/confi
 
             // Attach the method meta events to `node.methods.{created,deleted}`.
 
-            createEventAccessor.call( this, node.methods, "created", "methods" );
-            createEventAccessor.call( this, node.methods, "deleted", "methods" );
+            VWFJavaScript.createEventAccessor.call( this, node.methods, "created", "methods" );
+            VWFJavaScript.createEventAccessor.call( this, node.methods, "deleted", "methods" );
 
             node.private.bodies = Object.create( prototype.private ?
                 prototype.private.bodies : Object.prototype
@@ -202,8 +212,8 @@ define( [ "module", "vwf/model", "vwf/kernel/utility", "vwf/utility", "vwf/confi
 
             // Attach the event meta events to `node.events.{created,deleted}`.
 
-            createEventAccessor.call( this, node.events, "created", "events" );
-            createEventAccessor.call( this, node.events, "deleted", "events" );
+            VWFJavaScript.createEventAccessor.call( this, node.events, "created", "events" );
+            VWFJavaScript.createEventAccessor.call( this, node.events, "deleted", "events" );
 
             // Provide helper functions to create the directives for adding, removing and flushing
             // event handlers.
@@ -228,7 +238,7 @@ define( [ "module", "vwf/model", "vwf/kernel/utility", "vwf/utility", "vwf/confi
 
                     // Interpret `add( handler, context, ... )` as `add( handler, undefined, context, ... )`.
 
-                    if ( valueIsNode.call( self, phases ) ) {
+                    if ( VWFJavaScript.valueIsNode.call( self, phases ) ) {
                         context = phases;
                         phases = undefined;
                     }
@@ -298,11 +308,11 @@ define( [ "module", "vwf/model", "vwf/kernel/utility", "vwf/utility", "vwf/confi
                     // node reference.
 
                     if ( callback ) {
-                        self.kernel.createChild( this.node.id, name, componentKernelFromJS.call( self, component ), undefined, undefined, function( childID ) {
+                        self.kernel.createChild( this.node.id, name, VWFJavaScript.componentKernelFromJS.call( self, component ), undefined, undefined, function( childID ) {
                             callback.call( node, self.nodes[childID] );
                         } );
                     } else { 
-                        return self.kernel.createChild( this.node.id, name, componentKernelFromJS.call( self, component ) );
+                        return self.kernel.createChild( this.node.id, name, VWFJavaScript.componentKernelFromJS.call( self, component ) );
                     }
 
                 }
@@ -320,8 +330,8 @@ define( [ "module", "vwf/model", "vwf/kernel/utility", "vwf/utility", "vwf/confi
 
             // Attach the child meta events to `node.children.{added,removed}`.
 
-            createEventAccessor.call( this, node.children, "added", "children" );
-            createEventAccessor.call( this, node.children, "removed", "children" );
+            VWFJavaScript.createEventAccessor.call( this, node.children, "added", "children" );
+            VWFJavaScript.createEventAccessor.call( this, node.children, "removed", "children" );
 
             // Define the "random" and "seed" functions.
 
@@ -388,14 +398,14 @@ define( [ "module", "vwf/model", "vwf/kernel/utility", "vwf/utility", "vwf/confi
 
             Object.defineProperty( node, "in", {  // TODO: only define on shared "node" prototype?
                 value: function( when, callback ) { // "this" is node
-                    return refreshedFuture.call( self, this, -when, callback ); // relative time
+                    return VWFJavaScript.refreshedFuture.call( self, this, -when, callback ); // relative time
                 },
                 enumerable: true,
             } );
 
             Object.defineProperty( node, "at", {  // TODO: only define on shared "node" prototype?
                 value: function( when, callback ) { // "this" is node
-                    return refreshedFuture.call( self, this, when, callback ); // absolute time
+                    return VWFJavaScript.refreshedFuture.call( self, this, when, callback ); // absolute time
                 },
                 enumerable: true,
             } );
@@ -444,7 +454,7 @@ define( [ "module", "vwf/model", "vwf/kernel/utility", "vwf/utility", "vwf/confi
                 ( function( scriptText ) { return eval( scriptText ) } ).call( child, scriptText );
             } catch ( e ) {
                 this.logger.warnx( "initializingNode", childID,
-                    "exception in initialize:", utility.exceptionMessage( e ) );
+                    "exception in initialize:", VWFJavaScript.utility.exceptionMessage( e ) );
             }
 
             // The node is fully initialized at this point
@@ -495,7 +505,7 @@ node.hasOwnProperty( childName ) ||  // TODO: recalculate as properties, methods
                 }
             } catch ( e ) {
                 this.logger.warnx( "initializingNodeFromPrototype", childID,
-                    "exception in initialize:", utility.exceptionMessage( e ) );
+                    "exception in initialize:", VWFJavaScript.utility.exceptionMessage( e ) );
             }
 
             return undefined;
@@ -561,7 +571,7 @@ node.hasOwnProperty( childName ) ||  // TODO: recalculate as properties, methods
             var self = this;
 
             var getter = propertyGet &&  // TODO: assuming javascript here; how to specify script type?
-                functionFromHandler( { body: propertyGet, type: scriptMediaType },
+            VWFJavaScript.functionFromHandler( { body: propertyGet, type: VWFJavaScript.scriptMediaType },
                     logGetterException );
 
             if ( getter ) {
@@ -571,7 +581,7 @@ node.hasOwnProperty( childName ) ||  // TODO: recalculate as properties, methods
             }
         
             var setter = propertySet &&  // TODO: assuming javascript here; how to specify script type?
-                functionFromHandler( { parameters: [ "value" ], body: propertySet, type: scriptMediaType },
+            VWFJavaScript.functionFromHandler( { parameters: [ "value" ], body: propertySet, type: VWFJavaScript.scriptMediaType },
                     logSetterException );
 
             if ( setter ) {
@@ -582,12 +592,12 @@ node.hasOwnProperty( childName ) ||  // TODO: recalculate as properties, methods
 
             function logGetterException( exception ) {
                 self.logger.warnx( "creatingProperty", nodeID, propertyName, propertyValue,
-                    "exception evaluating getter:", utility.exceptionMessage( exception ) );
+                    "exception evaluating getter:", VWFJavaScript.utility.exceptionMessage( exception ) );
             }
 
             function logSetterException( exception ) {
                 self.logger.warnx( "creatingProperty", nodeID, propertyName, propertyValue,
-                    "exception evaluating setter:", utility.exceptionMessage( exception ) );
+                    "exception evaluating setter:", VWFJavaScript.utility.exceptionMessage( exception ) );
             }
 
             return this.initializingProperty( nodeID, propertyName, propertyValue );
@@ -599,10 +609,10 @@ node.hasOwnProperty( childName ) ||  // TODO: recalculate as properties, methods
 
             var node = this.nodes[nodeID];
 
-            createPropertyAccessor.call( this, node.properties, propertyName );
+            VWFJavaScript.createPropertyAccessor.call( this, node.properties, propertyName );
 
 node.hasOwnProperty( propertyName ) ||  // TODO: recalculate as properties, methods, events and children are created and deleted; properties take precedence over methods over events over children, for example
-            createPropertyAccessor.call( this, node, propertyName );
+            VWFJavaScript.createPropertyAccessor.call( this, node, propertyName );
 
             // Invalidate the "future" cache.
 
@@ -626,12 +636,12 @@ if ( ! node ) return;  // TODO: patch until full-graph sync is working; drivers
 
             if ( setter && setter !== true ) { // is there is a setter (and not just a guard value)
                 try {
-                    var valueJS = valueJSFromKernel.call( this, propertyValue );
+                    var valueJS = VWFJavaScript.valueJSFromKernel.call( this, propertyValue );
                     var resultJS = setter.call( node, valueJS );
-                    return valueKernelFromJS.call( this, resultJS );
+                    return VWFJavaScript.valueKernelFromJS.call( this, resultJS );
                 } catch ( e ) {
                     this.logger.warnx( "settingProperty", nodeID, propertyName, propertyValue,
-                        "exception in setter:", utility.exceptionMessage( e ) );
+                        "exception in setter:", VWFJavaScript.utility.exceptionMessage( e ) );
                 }
             }
 
@@ -643,17 +653,21 @@ if ( ! node ) return;  // TODO: patch until full-graph sync is working; drivers
         gettingProperty: function( nodeID, propertyName, propertyValue ) {
 
             var node = this.nodes[nodeID];
+
+           //if ( ! node ) return; 
+
             var getter = node.private.getters && node.private.getters[propertyName];
 
             if ( getter && getter !== true ) { // is there is a getter (and not just a guard value)
                 try {
                     var resultJS = getter.call( node );
-                    return valueKernelFromJS.call( this, resultJS );
+                    return VWFJavaScript.valueKernelFromJS.call( this, resultJS );
                 } catch ( e ) {
                     this.logger.warnx( "gettingProperty", nodeID, propertyName, propertyValue,
-                        "exception in getter:", utility.exceptionMessage( e ) );
+                        "exception in getter:", VWFJavaScript.utility.exceptionMessage( e ) );
                 }
             }
+        
 
             return undefined;
         },
@@ -664,10 +678,10 @@ if ( ! node ) return;  // TODO: patch until full-graph sync is working; drivers
 
             var node = this.nodes[nodeID];
 
-            createMethodAccessor.call( this, node.methods, methodName );
+            VWFJavaScript.createMethodAccessor.call( this, node.methods, methodName );
 
 node.hasOwnProperty( methodName ) ||  // TODO: recalculate as properties, methods, events and children are created and deleted; properties take precedence over methods over events over children, for example
-            createMethodAccessor.call( this, node, methodName );
+            VWFJavaScript.createMethodAccessor.call( this, node, methodName );
 
             // Invalidate the "future" cache.
 
@@ -681,7 +695,7 @@ node.hasOwnProperty( methodName ) ||  // TODO: recalculate as properties, method
                 body: methodBody,
 
                 type: typeof methodBody === "string" || methodBody instanceof String ?
-                    scriptMediaType : undefined,  // TODO: heuristic duplicated in vwf.js `normalizedHandler`.
+                    VWFJavaScript.scriptMediaType : undefined,  // TODO: heuristic duplicated in vwf.js `normalizedHandler`.
             } );
 
         },
@@ -696,19 +710,19 @@ node.hasOwnProperty( methodName ) ||  // TODO: recalculate as properties, method
 
             var self = this;
 
-            var body = functionFromHandler( methodHandler, logException,
-                configuration.active[ "preserve-script-closures" ] );
+            var body = VWFJavaScript.functionFromHandler( methodHandler, logException,
+                vwf.configuration[ "preserve-script-closures" ] );
 
             if ( body ) {
                 node.private.bodies[ methodName ] = body;
-                return handlerFromFunction( body );  // TODO: shortcut to avoid retrieving this?
+                return VWFJavaScript.handlerFromFunction( body );  // TODO: shortcut to avoid retrieving this?
             } else  {
                 delete node.private.bodies[ methodName ];
             }
 
             function logException( exception ) {
                 self.logger.warnx( "settingMethod", nodeID, methodName, methodHandler.parameters,
-                    "exception evaluating body:", utility.exceptionMessage( exception ) );
+                    "exception evaluating body:", VWFJavaScript.utility.exceptionMessage( exception ) );
             }
 
             return undefined;
@@ -722,7 +736,7 @@ node.hasOwnProperty( methodName ) ||  // TODO: recalculate as properties, method
             var body = node.private.bodies && node.private.bodies[methodName];
 
             if ( body ) {
-                return handlerFromFunction( body );
+                return VWFJavaScript.handlerFromFunction( body );
             }
 
             return undefined;
@@ -737,12 +751,12 @@ node.hasOwnProperty( methodName ) ||  // TODO: recalculate as properties, method
 
             if ( body ) {
                 try {
-                    var parametersJS = parametersJSFromKernel.call( this, methodParameters );
+                    var parametersJS = VWFJavaScript.parametersJSFromKernel.call( this, methodParameters );
                     var resultJS = body.apply( node, parametersJS );
-                    return valueKernelFromJS.call( this, resultJS );
+                    return VWFJavaScript.valueKernelFromJS.call( this, resultJS );
                 } catch ( e ) {
                     this.logger.warnx( "callingMethod", nodeID, methodName, methodParameters, // TODO: limit methodParameters for log
-                        "exception:", utility.exceptionMessage( e ) );
+                        "exception:", VWFJavaScript.utility.exceptionMessage( e ) );
                 }
             }
 
@@ -755,10 +769,10 @@ node.hasOwnProperty( methodName ) ||  // TODO: recalculate as properties, method
 
             var node = this.nodes[nodeID];
 
-            createEventAccessor.call( this, node.events, eventName );
+            VWFJavaScript.createEventAccessor.call( this, node.events, eventName );
 
 node.hasOwnProperty( eventName ) ||  // TODO: recalculate as properties, methods, events and children are created and deleted; properties take precedence over methods over events over children, for example
-            createEventAccessor.call( this, node, eventName );
+            VWFJavaScript.createEventAccessor.call( this, node, eventName );
 
             // Invalidate the "future" cache.
 
@@ -781,7 +795,7 @@ node.hasOwnProperty( eventName ) ||  // TODO: recalculate as properties, methods
 
             // Build a `Listener` from the `Handler` and the context and phases.
 
-            var eventListener = utility.merge( eventHandler, {
+            var eventListener = VWFJavaScript.utility.merge( eventHandler, {
                 context: eventContextID,
                 phases: eventPhases,
             } );
@@ -818,8 +832,8 @@ node.hasOwnProperty( eventName ) ||  // TODO: recalculate as properties, methods
 
             var listeners = node.private.listeners[eventName];
 
-            var handler = functionFromHandler( eventListener, logException,
-                configuration.active[ "preserve-script-closures" ] );
+            var handler = VWFJavaScript.functionFromHandler( eventListener, logException,
+                vwf.configuration[ "preserve-script-closures" ] );
 
             if ( handler ) {
 
@@ -853,7 +867,7 @@ node.hasOwnProperty( eventName ) ||  // TODO: recalculate as properties, methods
 
             function logException( exception ) {
                 self.logger.warnx( "settingEventListener", nodeID, eventName, eventListenerID,
-                    "exception evaluating listener:", utility.exceptionMessage( exception ) );
+                    "exception evaluating listener:", VWFJavaScript.utility.exceptionMessage( exception ) );
             }
 
             return undefined;
@@ -871,7 +885,7 @@ node.hasOwnProperty( eventName ) ||  // TODO: recalculate as properties, methods
 
                 var listener = listeners[ eventListenerID ];
 
-                return utility.merge( handlerFromFunction( listener.handler ), {
+                return VWFJavaScript.utility.merge( VWFJavaScript.handlerFromFunction( listener.handler ), {
                     context: listener.context,
                     phases: listener.phases,
                 } );
@@ -912,9 +926,9 @@ node.hasOwnProperty( eventName ) ||  // TODO: recalculate as properties, methods
             var phase = eventParameters && eventParameters.phase; // the phase is smuggled across on the parameters array  // TODO: add "phase" as a fireEvent() parameter? it isn't currently needed in the kernel public API (not queueable, not called by the drivers), so avoid if possible
 
             var node = this.nodes[nodeID];
-            var listeners = findListeners( node, eventName );
+            var listeners = VWFJavaScript.findListeners( node, eventName );
 
-            var parametersJS = parametersJSFromKernel.call( this, eventParameters );
+            var parametersJS = VWFJavaScript.parametersJSFromKernel.call( this, eventParameters );
 
             var self = this;
 
@@ -932,12 +946,12 @@ node.hasOwnProperty( eventName ) ||  // TODO: recalculate as properties, methods
                     if ( ! phase || listener.phases && listener.phases.indexOf( phase ) >= 0 ) {
                         var contextNode = self.nodes[ listener.context ] || self.nodes[ 0 ];  // default context is the global root  // TODO: this presumes this.creatingNode( undefined, 0 ) is retained above
                         var resultJS = listener.handler.apply( contextNode, parametersJS );
-                        var result = valueKernelFromJS.call( self, resultJS );
+                        var result = VWFJavaScript.valueKernelFromJS.call( self, resultJS );
                         return handled || result || result === undefined;  // interpret no return as "return true"
                     }
                 } catch ( e ) {
                     self.logger.warnx( "firingEvent", nodeID, eventName, eventParameters,  // TODO: limit eventParameters for log
-                        "exception:", utility.exceptionMessage( e ) );
+                        "exception:", VWFJavaScript.utility.exceptionMessage( e ) );
                 }
 
                 return handled;
@@ -953,13 +967,13 @@ node.hasOwnProperty( eventName ) ||  // TODO: recalculate as properties, methods
 
             var node = this.nodes[nodeID];
 
-            if ( scriptType == scriptMediaType ) {
+            if ( scriptType == VWFJavaScript.scriptMediaType ) {
                 try {
                     var resultJS = ( function( scriptText ) { return eval( scriptText ) } ).call( node, scriptText || "" );
-                    return valueKernelFromJS.call( this, resultJS );
+                    return VWFJavaScript.valueKernelFromJS.call( this, resultJS );
                 } catch ( e ) {
                     this.logger.warnx( "executing", nodeID,
-                        ( scriptText || "" ).replace( /\s+/g, " " ).substring( 0, 100 ), scriptType, "exception:", utility.exceptionMessage( e ) );
+                        ( scriptText || "" ).replace( /\s+/g, " " ).substring( 0, 100 ), scriptType, "exception:", VWFJavaScript.utility.exceptionMessage( e ) );
                 }
             }
 
@@ -968,11 +982,14 @@ node.hasOwnProperty( eventName ) ||  // TODO: recalculate as properties, methods
 
     } );
 
-    // == Private functions ========================================================================
+  }
+
+
+// == Private functions ========================================================================
 
     // -- proxiedBehavior --------------------------------------------------------------------------
 
-    function proxiedBehavior( prototype, behavior ) { // invoke with the model as "this"  // TODO: this is a lot like createProperty()/createMethod()/createEvent(), and refreshedFuture(). Find a way to merge.  // TODO: nodes need to keep a list of proxies on them and callback here to refresh after changes
+    static proxiedBehavior( prototype, behavior ) { // invoke with the model as "this"  // TODO: this is a lot like createProperty()/createMethod()/createEvent(), and refreshedFuture(). Find a way to merge.  // TODO: nodes need to keep a list of proxies on them and callback here to refresh after changes
 
         var self = this;
 
@@ -1015,9 +1032,9 @@ node.hasOwnProperty( eventName ) ||  // TODO: recalculate as properties, methods
             if ( behavior.properties.hasOwnProperty( propertyName ) ) {
 
                 ( function( propertyName ) {
-                    createPropertyAccessor.call( self, proxy.properties, propertyName );
+                    VWFJavaScript.createPropertyAccessor.call( self, proxy.properties, propertyName );
 proxy.hasOwnProperty( propertyName ) ||  // TODO: recalculate as properties, methods, events and children are created and deleted; properties take precedence over methods over events over children, for example
-                    createPropertyAccessor.call( self, proxy, propertyName );
+                    VWFJavaScript.createPropertyAccessor.call( self, proxy, propertyName );
                 } )( propertyName );
             
                 if ( behavior.private.getters.hasOwnProperty( propertyName ) ) {
@@ -1045,9 +1062,9 @@ proxy.hasOwnProperty( propertyName ) ||  // TODO: recalculate as properties, met
             if ( behavior.methods.hasOwnProperty( methodName ) ) {
 
                 ( function( methodName ) {
-                    createMethodAccessor.call( self, proxy.methods, methodName );
+                    VWFJavaScript.createMethodAccessor.call( self, proxy.methods, methodName );
 proxy.hasOwnProperty( methodName ) ||  // TODO: recalculate as properties, methods, events and children are created and deleted; properties take precedence over methods over events over children, for example
-                    createMethodAccessor.call( self, proxy, methodName );
+                    VWFJavaScript.createMethodAccessor.call( self, proxy, methodName );
                 } )( methodName );
             
                 if ( behavior.private.bodies.hasOwnProperty( methodName ) ) {
@@ -1069,9 +1086,9 @@ proxy.hasOwnProperty( methodName ) ||  // TODO: recalculate as properties, metho
             if ( behavior.events.hasOwnProperty( eventName ) ) {
 
                 ( function( eventName ) {
-                    createEventAccessor.call( self, proxy.events, eventName );
+                    VWFJavaScript.createEventAccessor.call( self, proxy.events, eventName );
 proxy.hasOwnProperty( eventName ) ||  // TODO: recalculate as properties, methods, events and children are created and deleted; properties take precedence over methods over events over children, for example
-                    createEventAccessor.call( self, proxy, eventName );
+                    VWFJavaScript.createEventAccessor.call( self, proxy, eventName );
                 } )( eventName );
 
             }
@@ -1103,12 +1120,12 @@ proxy.hasOwnProperty( eventName ) ||  // TODO: recalculate as properties, method
 
     // -- refreshedFuture --------------------------------------------------------------------------
 
-    function refreshedFuture( node, when, callback ) { // invoke with the model as "this"
+    static refreshedFuture( node, when, callback ) { // invoke with the model as "this"
 
         var self = this;
 
         if ( Object.getPrototypeOf( node ).private ) {
-            refreshedFuture.call( this, Object.getPrototypeOf( node ) );
+            VWFJavaScript.refreshedFuture.call( this, Object.getPrototypeOf( node ) );
         }
 
         var future = node.private.future;
@@ -1132,9 +1149,9 @@ proxy.hasOwnProperty( eventName ) ||  // TODO: recalculate as properties, method
                 if ( node.properties.hasOwnProperty( propertyName ) ) {
 
                     ( function( propertyName ) {
-                        createPropertyAccessor.call( self, future.properties, propertyName );
+                        VWFJavaScript.createPropertyAccessor.call( self, future.properties, propertyName );
 future.hasOwnProperty( propertyName ) ||  // TODO: calculate so that properties take precedence over methods over events, for example
-                        createPropertyAccessor.call( self, future, propertyName );
+                        VWFJavaScript.createPropertyAccessor.call( self, future, propertyName );
                     } )( propertyName );
                 
                 }
@@ -1150,9 +1167,9 @@ future.hasOwnProperty( propertyName ) ||  // TODO: calculate so that properties
                 if ( node.methods.hasOwnProperty( methodName ) ) {
 
                     ( function( methodName ) {
-                        createMethodAccessor.call( self, future.methods, methodName, true );
+                        VWFJavaScript.createMethodAccessor.call( self, future.methods, methodName, true );
 future.hasOwnProperty( methodName ) ||  // TODO: calculate so that properties take precedence over methods over events, for example
-                        createMethodAccessor.call( self, future, methodName, true );
+                        VWFJavaScript.createMethodAccessor.call( self, future, methodName, true );
                     } )( methodName );
 
                 }
@@ -1168,9 +1185,9 @@ future.hasOwnProperty( methodName ) ||  // TODO: calculate so that properties ta
                 if ( node.events.hasOwnProperty( eventName ) ) {
 
                     ( function( eventName ) {
-                        createEventAccessor.call( self, future.events, eventName, undefined, true );
+                        VWFJavaScript.createEventAccessor.call( self, future.events, eventName, undefined, true );
 future.hasOwnProperty( eventName ) ||  // TODO: calculate so that properties take precedence over methods over events, for example
-                        createEventAccessor.call( self, future, eventName, undefined, true );
+                        VWFJavaScript.createEventAccessor.call( self, future, eventName, undefined, true );
                     } )( eventName );
 
                 }
@@ -1200,7 +1217,7 @@ future.hasOwnProperty( eventName ) ||  // TODO: calculate so that properties tak
     /// @param {String} propertyName
     ///   The name of the property to create on `container`.
 
-    function createPropertyAccessor( container, propertyName ) {
+    static createPropertyAccessor( container, propertyName ) {
 
         var self = this;
 
@@ -1212,14 +1229,14 @@ future.hasOwnProperty( eventName ) ||  // TODO: calculate so that properties tak
                 var node = this.node || this;  // the node via node.properties.node, or just node
                 var resultKernel = self.kernel.getProperty( node.id, propertyName,
                     node.private.when, node.private.callback );
-                return valueJSFromKernel.call( self, resultKernel );
+                return VWFJavaScript.valueJSFromKernel.call( self, resultKernel );
             },
 
             // On write, pass the assigned value to `kernel.setProperty`.
 
             set: function( value ) {  // `this` is the container
                 var node = this.node || this;  // the node via node.properties.node, or just node
-                var valueKernel = valueKernelFromJS.call( self, value );
+                var valueKernel = VWFJavaScript.valueKernelFromJS.call( self, value );
                 self.kernel.setProperty( node.id, propertyName, valueKernel,
                     node.private.when, node.private.callback );
             },
@@ -1248,7 +1265,7 @@ future.hasOwnProperty( eventName ) ||  // TODO: calculate so that properties tak
     ///   When truthy, don't create the `set` accessor. An unsettable method property doesn't allow
     ///   the method body to be changed.
 
-    function createMethodAccessor( container, methodName, unsettable ) {
+    static createMethodAccessor( container, methodName, unsettable ) {
 
         var self = this;
 
@@ -1259,10 +1276,10 @@ future.hasOwnProperty( eventName ) ||  // TODO: calculate so that properties tak
             get: function() {  // `this` is the container
                 var node = this.node || this;  // the node via node.methods.node, or just node
                 return function( /* parameter1, parameter2, ... */ ) {  // `this` is the container
-                    var argumentsKernel = parametersKernelFromJS.call( self, arguments );
+                    var argumentsKernel =  VWFJavaScript.parametersKernelFromJS.call( self, arguments );
                     var resultKernel = self.kernel.callMethod( node.id, methodName, argumentsKernel,
                         node.private.when, node.private.callback );
-                    return valueJSFromKernel.call( self, resultKernel );
+                    return VWFJavaScript.valueJSFromKernel.call( self, resultKernel );
                 };
             },
 
@@ -1271,7 +1288,7 @@ future.hasOwnProperty( eventName ) ||  // TODO: calculate so that properties tak
             set: unsettable ? undefined : function( value ) {  // `this` is the container
                 var node = this.node || this;  // the node via node.methods.node, or just node
                 self.kernel.setMethod( node.id, methodName,
-                    handlerFromFunction( value, configuration.active[ "preserve-script-closures" ] ) );
+                    VWFJavaScript.handlerFromFunction( value, vwf.configuration[ "preserve-script-closures" ] ) );
             },
 
             enumerable: true,
@@ -1305,7 +1322,7 @@ future.hasOwnProperty( eventName ) ||  // TODO: calculate so that properties tak
     ///   When truthy, don't create the `set` accessor. An unsettable event property can't add or
     ///   remove listeners.
 
-    function createEventAccessor( container, eventName, eventNamespace, unsettable ) {
+    static createEventAccessor( container, eventName, eventNamespace, unsettable ) {
 
         var self = this;
 
@@ -1321,12 +1338,12 @@ future.hasOwnProperty( eventName ) ||  // TODO: calculate so that properties tak
 
                 return function( /* parameter1, parameter2, ... */ ) {  // `this` is the container
 
-                    var argumentsKernel = parametersKernelFromJS.call( self, arguments );
+                    var argumentsKernel = VWFJavaScript.parametersKernelFromJS.call( self, arguments );
 
                     var resultKernel = self.kernel.fireEvent( node.id, eventName, argumentsKernel,
                         node.private.when, node.private.callback );
 
-                    return valueJSFromKernel.call( self, resultKernel );
+                    return VWFJavaScript.valueJSFromKernel.call( self, resultKernel );
                 };
 
             },
@@ -1376,7 +1393,7 @@ future.hasOwnProperty( eventName ) ||  // TODO: calculate so that properties tak
                 function addListener( handler, context, phases, callback ) {
 
                     var listenerID = self.kernel.addEventListener( node.id, namespacedName,
-                        handlerFromFunction( handler, configuration.active[ "preserve-script-closures" ] ),
+                        VWFJavaScript.handlerFromFunction( handler, vwf.configuration[ "preserve-script-closures" ] ),
                         context && context.id, phases );
 
                     // For 0.6.23 and earlier, listeners were removed using a direct reference to
@@ -1418,13 +1435,13 @@ future.hasOwnProperty( eventName ) ||  // TODO: calculate so that properties tak
     ///   The function generated from `handler`, or `undefined` if `handler` does not describe a
     ///   JavaScript function or if the function could not be converted.
 
-    function functionFromHandler( handler, errback /* exception */, bypass ) {
+    static functionFromHandler( handler, errback /* exception */, bypass ) {
 
         if ( bypass && ( typeof handler.body === "function" || handler.body instanceof Function ) ) {
 
             return handler.body;
 
-        } else if ( handler.type === scriptMediaType ) {
+        } else if ( handler.type === VWFJavaScript.scriptMediaType ) {
 
             var name = handler.name, parameters = handler.parameters, body = handler.body;
 
@@ -1473,9 +1490,9 @@ future.hasOwnProperty( eventName ) ||  // TODO: calculate so that properties tak
     ///   The `Handler` generated from `funcshun`, or `undefined` if the function's `toString` could
     ///   not be parsed.
 
-    function handlerFromFunction( funcshun, bypass ) {
+    static handlerFromFunction( funcshun, bypass ) {
 
-        var name, parameters, body, type = scriptMediaType;
+        var name, parameters, body, type = VWFJavaScript.scriptMediaType;
 
         var match, leadingMatch, trailingMatch, indention = "";
 
@@ -1485,7 +1502,7 @@ future.hasOwnProperty( eventName ) ||  // TODO: calculate so that properties tak
                 body: funcshun,
             };
 
-        } else if ( match = /* assignment! */ functionRegex.exec( funcshun.toString() ) ) {
+        } else if ( match = /* assignment! */ VWFJavaScript.functionRegex.exec( funcshun.toString() ) ) {
 
             name = match[1];
 
@@ -1553,34 +1570,15 @@ future.hasOwnProperty( eventName ) ||  // TODO: calculate so that properties tak
         return undefined;
     }
 
-    /// The `application/javascript` media type for scripts that this driver recognizes.
-    /// 
-    /// @field
-
-    var scriptMediaType = "application/javascript";
-
-    /// Regex to crack a `Function.toString()` result.
-    /// 
-    /// @field
-
-    var functionRegex = new RegExp(
-        "function" +                    // `function`
-        "\\s*" +
-        "([a-zA-Z_$][0-9a-zA-Z_$]*)?" + // optional name; capture #1
-        "\\s*" +
-        "\\(([^)]*)\\)" +               // `(...)`; capture #2 inside `()`
-        "\\s*" +
-        "\\{([^]*)\\}"                  // `{...}`; capture #3 inside `{}`
-    );
 
     // -- findListeners ----------------------------------------------------------------------------
 
 // TODO: this walks the full prototype chain and is probably horribly inefficient.
 
-    function findListeners( node, eventName, targetOnly ) {
+    static findListeners( node, eventName, targetOnly ) {
 
         var prototypeListeners = Object.getPrototypeOf( node ).private ?  // get any self-targeted listeners from the prototypes
-            findListeners( Object.getPrototypeOf( node ), eventName, true ) : [];
+        VWFJavaScript.findListeners( Object.getPrototypeOf( node ), eventName, true ) : [];
 
         var nodeListeners = node.private.listeners && node.private.listeners[eventName] || [];
 
@@ -1610,8 +1608,8 @@ future.hasOwnProperty( eventName ) ||  // TODO: calculate so that properties tak
     /// @returns {String|Object}
     ///   `component` with node references replaced with kernel-style node references.
 
-    function componentKernelFromJS( component ) {
-        return valueKernelFromJS.call( this, component );
+    static componentKernelFromJS( component ) {
+        return VWFJavaScript.valueKernelFromJS.call( this, component );
     }
 
     /// Convert a parameter array of values using `valueKernelFromJS`.
@@ -1623,8 +1621,8 @@ future.hasOwnProperty( eventName ) ||  // TODO: calculate so that properties tak
     /// 
     /// @returns {Object}
 
-    function parametersKernelFromJS( parameters ) {
-        return valueKernelFromJS.call( this, parameters );
+    static parametersKernelFromJS( parameters ) {
+        return VWFJavaScript.valueKernelFromJS.call( this, parameters );
     }
 
     /// Convert a parameter array of values using `valueJSFromKernel`.
@@ -1636,8 +1634,8 @@ future.hasOwnProperty( eventName ) ||  // TODO: calculate so that properties tak
     /// 
     /// @returns {Object}
 
-    function parametersJSFromKernel( parameters ) {
-        return valueJSFromKernel.call( this, parameters );
+    static parametersJSFromKernel( parameters ) {
+        return VWFJavaScript.valueJSFromKernel.call( this, parameters );
     }
 
     /// Convert node references into special values that can pass through the kernel. These values
@@ -1653,19 +1651,19 @@ future.hasOwnProperty( eventName ) ||  // TODO: calculate so that properties tak
     /// 
     /// @returns {Object}
 
-    function valueKernelFromJS( value ) {
+    static valueKernelFromJS( value ) {
 
         var self = this;
 
-        return utility.transform( value, function( object, names, depth, finished ) {
+        return VWFJavaScript.utility.transform( value, function( object, names, depth, finished ) {
 
-            if ( valueIsNode.call( self, object ) ) {
+            if ( VWFJavaScript.valueIsNode.call( self, object ) ) {
                 finished();
-                return kutility.nodeReference( object.id );
+                return VWFJavaScript.kutility.nodeReference( object.id );
             } else if ( object && object.buffer && object.buffer.toString() === "[object ArrayBuffer]" ) {
                 finished();
                 return object;
-            } else if ( kutility.valueIsNodeReference( object ) ) {
+            } else if ( VWFJavaScript.kutility.valueIsNodeReference( object ) ) {
                 finished();
                 self.logger.warnx( "valueKernelFromJS", "javascript-format value contains a kernel-format node reference" );
                 return object;
@@ -1687,19 +1685,19 @@ future.hasOwnProperty( eventName ) ||  // TODO: calculate so that properties tak
     /// 
     /// @returns {Object}
 
-    function valueJSFromKernel( value ) {
+    static valueJSFromKernel( value ) {
 
         var self = this;
 
-        return utility.transform( value, function( object, names, depth, finished ) {
+        return VWFJavaScript.utility.transform( value, function( object, names, depth, finished ) {
 
-            if ( kutility.valueIsNodeReference( object ) ) {
+            if ( VWFJavaScript.kutility.valueIsNodeReference( object ) ) {
                 finished();
                 return self.nodes[ object.id ];
             } else if ( object && object.buffer && object.buffer.toString() === "[object ArrayBuffer]" ) {
                 finished();
                 return object;
-            } else if ( valueIsNode.call( self, object ) ) {
+            } else if ( VWFJavaScript.valueIsNode.call( self, object ) ) {
                 finished();
                 self.logger.warnx( "valueJSFromKernel", "kernel-format value contains a javascript-format node reference" );
                 return object;
@@ -1720,12 +1718,40 @@ future.hasOwnProperty( eventName ) ||  // TODO: calculate so that properties tak
     /// 
     /// @returns {Boolean}
 
-    function valueIsNode( value ) {
+    static valueIsNode( value ) {
 
         return this.protoNode &&  // our proxy for the node.vwf prototype
             ( this.protoNode.isPrototypeOf( value ) || value === this.protoNode );
     }
 
-    return exports;
 
-} );
+}
+
+
+
+  /// The `application/javascript` media type for scripts that this driver recognizes.
+    ///
+    /// @field
+
+    VWFJavaScript.scriptMediaType = "application/javascript";
+
+    /// Regex to crack a `Function.toString()` result.
+    ///
+    /// @field
+
+    VWFJavaScript.functionRegex = new RegExp(
+      "function" + // `function`
+      "\\s*" +
+      "([a-zA-Z_$][0-9a-zA-Z_$]*)?" + // optional name; capture #1
+      "\\s*" +
+      "\\(([^)]*)\\)" + // `(...)`; capture #2 inside `()`
+        "\\s*" +
+        "\\{([^]*)\\}" // `{...}`; capture #3 inside `{}`
+    );
+
+    VWFJavaScript.kutility = new KUtility();
+    VWFJavaScript.utility = new Utility();
+
+
+export { VWFJavaScript as default }
+

+ 46 - 32
public/vwf/model/object.js → public/core/vwf/model/object.js

@@ -1,17 +1,9 @@
-"use strict";
-
-// Copyright 2012 United States Government, as represented by the Secretary of Defense, Under
-// Secretary of Defense (Personnel & Readiness).
-// 
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
-// in compliance with the License. You may obtain a copy of the License at
-// 
-//   http://www.apache.org/licenses/LICENSE-2.0
-// 
-// Unless required by applicable law or agreed to in writing, software distributed under the License
-// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
-// or implied. See the License for the specific language governing permissions and limitations under
-// the License.
+/*
+The MIT License (MIT)
+Copyright (c) 2014-2020 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+
+Virtual World Framework Apache 2.0 license  (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
+*/
 
 /// vwf/model/object.js is a backstop property store.
 /// 
@@ -19,12 +11,23 @@
 /// @requires vwf/model
 /// @requires vwf/configuration
 
-define( [ "module", "vwf/model", "vwf/utility", "vwf/configuration" ],
-        function( module, model, utility, configuration ) {
+import { Fabric } from '/core/vwf/fabric.js';
+
+class VWFObject extends Fabric{
 
-    return model.load( module, {
+    constructor(module) {
 
-        // == Module Definition ====================================================================
+        console.log("Object constructor");
+        super(module, "Model");
+
+    }
+
+    factory(){
+        let _self_ = this;
+
+        return this.load( this.module, 
+            {
+                        // == Module Definition ====================================================================
 
         // -- initialize ---------------------------------------------------------------------------
 
@@ -45,6 +48,7 @@ define( [ "module", "vwf/model", "vwf/utility", "vwf/configuration" ],
             // doing any async operations, a second time after loading the prototype and behaviors,
             // then a third time in the normal order as the last driver.
 
+ 
             var parent = nodeID != 0 && this.objects[nodeID];
 
             var child = this.objects[childID];
@@ -78,7 +82,7 @@ define( [ "module", "vwf/model", "vwf/utility", "vwf/configuration" ],
 
                     prng: parent ?                    // pseudorandom number generator, seeded by ...
                         new Alea( JSON.stringify( parent.prng.state ), childID ) :  // ... the parent's prng and the child ID, or
-                        new Alea( configuration.active["random-seed"], childID ),   // ... the global seed and the child ID
+                        new Alea( vwf.configuration["random-seed"], childID ),   // ... the global seed and the child ID
 
                     // TODO: The 'patches' object is in the process of moving to the kernel
                     //       Those objects that are double-commented out have already moved
@@ -196,7 +200,8 @@ define( [ "module", "vwf/model", "vwf/utility", "vwf/configuration" ],
 
             var object = this.objects[nodeID];
 
-if ( ! object ) return;  // TODO: patch until full-graph sync is working; drivers should be able to assume that nodeIDs refer to valid objects
+            if ( ! object ) return;  
+            // TODO: patch until full-graph sync is working; drivers should be able to assume that nodeIDs refer to valid objects
 
             var node_properties = object.properties;
 
@@ -274,7 +279,7 @@ if ( ! object ) return;  // TODO: patch until full-graph sync is working; driver
             }
 
             return this.settingEventListener( nodeID, eventName, eventListenerID,
-                utility.merge( eventHandler, { context: eventContextID, phases: eventPhases } ) ) ?
+                _self_.utility.merge( eventHandler, { context: eventContextID, phases: eventPhases } ) ) ?
                     true : undefined;
         },
 
@@ -457,7 +462,7 @@ if ( ! object ) return;  // TODO: patch until full-graph sync is working; driver
                     object.initialized && object.patches && ( object.patches.internals = true );
                 }
                 if ( internals.random !== undefined ) {
-                    merge( object.prng.state, internals.random );
+                    _self_.merge( object.prng.state, internals.random );
                     object.initialized && object.patches && ( object.patches.internals = true );
                 }
             } else { // get
@@ -497,19 +502,28 @@ if ( ! object ) return;  // TODO: patch until full-graph sync is working; driver
 
     } );
 
-    /// Merge fields from the `source` objects into `target`.
 
-    function merge( target /* [, source1 [, source2 ... ] ] */ ) {
+    }
+
+        /// Merge fields from the `source` objects into `target`.
 
-        for ( var index = 1; index < arguments.length; index++ ) {
-            var source = arguments[index];
+        merge( target /* [, source1 [, source2 ... ] ] */ ) {
 
-            Object.keys( source ).forEach( function( key ) {
-                target[key] = source[key];
-            } );
+            for ( var index = 1; index < arguments.length; index++ ) {
+                var source = arguments[index];
+    
+                Object.keys( source ).forEach( function( key ) {
+                    target[key] = source[key];
+                } );
+            }
+    
+            return target;
         }
 
-        return target;
-    }
 
-} );
+}
+
+export {
+    VWFObject as default
+  }
+

+ 235 - 0
public/core/vwf/model/ohm.js

@@ -0,0 +1,235 @@
+/*
+The MIT License (MIT)
+Copyright (c) 2014-2020 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+
+Virtual World Framework Apache 2.0 license  (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
+*/
+
+// VWF & Ohm model driver
+
+import { Fabric } from '/core/vwf/fabric.js';
+
+class OhmModel extends Fabric {
+
+    constructor(module) {
+
+        console.log("OhmModel constructor");
+        super(module, "Model");
+    }
+
+    factory() {
+        let _self_ = this;
+
+        return this.load( this.module, 
+            {
+
+                // == Module Definition ====================================================================
+        
+                // -- pipeline -----------------------------------------------------------------------------
+        
+                // pipeline: [ log ], // vwf <=> log <=> scene
+        
+                // -- initialize ---------------------------------------------------------------------------
+        
+                initialize: function() {
+                    
+                    var self = this;
+        
+                   this.state = {
+                        nodes: {},
+                        scenes: {},
+                        prototypes: {},
+                        createLocalNode: function (nodeID, childID, childExtendsID, childImplementsIDs,
+                            childSource, childType, childIndex, childName, callback) {
+                            return {
+                                "parentID": nodeID,
+                                "ID": childID,
+                                "extendsID": childExtendsID,
+                                "implementsIDs": childImplementsIDs,
+                                "source": childSource,
+                                "type": childType,
+                                "name": childName,
+                                "prototypes": undefined,
+                                "lang": {   
+                                    "grammar": undefined,
+                                    "semantics": undefined,
+                                    "source": undefined
+                                }
+                            };
+                        },
+                        isOhmNodeComponent: function (prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] === "proxy/ohm/node.vwf");
+                                }
+                            }
+                            return found;
+                        }
+                    };
+        
+                    this.state.kernel = this.kernel.kernel.kernel;
+        
+                    this.ohm = ohm;
+                    window._ohm = this.ohm;
+                    //this.state.kernel = this.kernel.kernel.kernel;
+                    
+                },
+                // == Model API ============================================================================
+        
+                // -- creatingNode -------------------------------------------------------------------------
+        
+                creatingNode: function( nodeID, childID, childExtendsID, childImplementsIDs,
+                    childSource, childType, childIndex, childName, callback /* ( ready ) */ ) {
+        
+                    // If the parent nodeID is 0, this node is attached directly to the root and is therefore either 
+                    // the scene or a prototype.  In either of those cases, save the uri of the new node
+                    var childURI = (nodeID === 0 ? childIndex : undefined);
+                    var appID = this.kernel.application();
+        
+                    // If the node being created is a prototype, construct it and add it to the array of prototypes,
+                    // and then return
+                    var prototypeID = _self_.utility.ifPrototypeGetId(appID, this.state.prototypes, nodeID, childID);
+                    if (prototypeID !== undefined) {
+        
+                        this.state.prototypes[prototypeID] = {
+                            parentID: nodeID,
+                            ID: childID,
+                            extendsID: childExtendsID,
+                            implementsID: childImplementsIDs,
+                            source: childSource,
+                            type: childType,
+                            name: childName
+                        };
+                        return;
+                    }
+        
+                    var protos = _self_.getPrototypes(this.kernel, childExtendsID);
+                    //var kernel = this.kernel.kernel.kernel;
+                    var node;
+        
+                    if (this.state.isOhmNodeComponent(protos)) {
+        
+                        // Create the local copy of the node properties
+                        if (this.state.nodes[childID] === undefined) {
+                            this.state.nodes[childID] = this.state.createLocalNode(nodeID, childID, childExtendsID, childImplementsIDs,
+                                childSource, childType, childIndex, childName, callback);
+                        }
+        
+                        node = this.state.nodes[childID];
+                        node.prototypes = protos;
+        
+                        //node.aframeObj = createAFrameObject(node);
+                        //addNodeToHierarchy(node);
+                        //notifyDriverOfPrototypeAndBehaviorProps();
+                    }
+                },
+        
+                 // -- initializingNode -------------------------------------------------------------------------
+        
+                //   initializingNode: function( nodeID, childID, childExtendsID, childImplementsIDs,
+                //     childSource, childType, childIndex, childName ) {
+        
+                // },
+        
+                // -- deletingNode -------------------------------------------------------------------------
+        
+                //deletingNode: function( nodeID ) {
+                //},
+        
+                 // -- initializingProperty -----------------------------------------------------------------
+        
+                initializingProperty: function( nodeID, propertyName, propertyValue ) {
+        
+                     var value = undefined;
+                    var node = this.state.nodes[nodeID];
+                    if (node !== undefined) {
+                        value = this.settingProperty(nodeID, propertyName, propertyValue);
+                    }
+                    return value;
+                
+            },
+        
+                // -- creatingProperty ---------------------------------------------------------------------
+        
+                creatingProperty: function (nodeID, propertyName, propertyValue) {
+                    return this.initializingProperty(nodeID, propertyName, propertyValue);
+                },
+        
+        
+                // -- settingProperty ----------------------------------------------------------------------
+        
+                settingProperty: function( nodeID, propertyName, propertyValue ) {
+        
+                    var node = this.state.nodes[nodeID];
+                    var value = undefined;
+                    if (node && _self_.utility.validObject(propertyValue)) {
+                           switch ( propertyName ) {
+                         
+                                case "ohmLang":
+                                                node.lang.source = propertyValue;
+                                                node.lang.grammar = ohm.grammar(propertyValue);
+                                                node.lang.semantics = node.lang.grammar.createSemantics();
+                                                break;
+        
+                                default:
+                                    value = undefined;
+                                    break;
+                            }
+                         }
+        
+                     return value;
+        
+                },
+        
+                // -- gettingProperty ----------------------------------------------------------------------
+        
+                gettingProperty: function( nodeID, propertyName, propertyValue ) {
+        
+                     var node = this.state.nodes[nodeID];
+                    var value = undefined;
+                    if (node) {
+                           switch ( propertyName ) {
+                         
+                                   case "grammar":
+                                               value = node.lang.grammar;
+                                                break;
+        
+                                 case "semantics":
+                                               value = node.lang.semantics;
+                                                break;
+                            }
+                         }
+        
+                     if ( value !== undefined ) {
+                        propertyValue = value;
+                    }
+        
+                    return value;
+        
+                     
+                }
+        
+            } );
+
+        }
+
+
+    getPrototypes(kernel, extendsID) {
+        var prototypes = [];
+        var id = extendsID;
+    
+        while (id !== undefined) {
+            prototypes.push(id);
+            id = kernel.prototype(id);
+        }
+        return prototypes;
+    }
+    
+
+    }
+
+    export {
+        OhmModel as default
+      }
+    

+ 65 - 0
public/core/vwf/model/stage.js

@@ -0,0 +1,65 @@
+/*
+The MIT License (MIT)
+Copyright (c) 2014-2020 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+
+Virtual World Framework Apache 2.0 license  (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
+*/
+
+/// @module vwf/model/stage
+/// @requires vwf/model
+
+
+import { Fabric } from '/core/vwf/fabric.js';
+
+class Stage extends Fabric {
+
+    constructor(module) {
+
+        //console.log("Stage constructor");
+        super(module, "Model")
+
+    }
+
+    factory(){
+
+        return this.load( this.module, 
+            {
+
+                fabricize: function ( model, model_api ) {
+
+                    this.model = model;
+        
+                    // Suppress functions that aren't implemented in the stage to the right.
+        
+                    Object.keys( model_api ).forEach( function( modelFunctionName ) {
+                        if ( ! model[modelFunctionName] ) {
+                            this[modelFunctionName] = undefined;
+                        }
+                    }, this );
+        
+                }
+
+            },
+             function( modelFunctionName ) {
+        
+            // == Model API ============================================================================
+    
+            return function() {
+                return this.model[modelFunctionName] && this.model[modelFunctionName].apply( this.model, arguments );
+            };
+    
+        }, function( kernelFunctionName ) {
+    
+            // == Kernel API ===========================================================================
+    
+            return function() {
+                return this.kernel[kernelFunctionName].apply( this.kernel, arguments );
+            };
+            
+        } );
+    }
+}
+
+export {
+    Stage
+  }

+ 249 - 0
public/core/vwf/model/stage/log.js

@@ -0,0 +1,249 @@
+/*
+The MIT License (MIT)
+Copyright (c) 2014-2020 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+
+Virtual World Framework Apache 2.0 license  (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
+*/
+
+/// @module vwf/model/stage/log
+/// @requires vwf/model/stage
+
+import { Stage } from '/core/vwf/model/stage.js';
+
+class Log extends Stage {
+
+    constructor(module) {
+        //console.log("Stage constructor");
+        super(module);
+
+    }
+
+
+    factory(){
+        let _self_ = this;
+
+        return super.factory().load( this.module, 
+            {
+                 // == Module Definition ====================================================================
+
+     initialize: function () {
+        this.logger = this.model.logger; // steal the model's logger since we're logging for it
+    }
+            },
+            function( modelFunctionName ) {
+
+                // == Model API ============================================================================
+        
+                return function() {
+        
+                    if ( this.model[modelFunctionName] ) {
+        
+                        var logees = Array.prototype.slice.call( arguments );
+        
+                        switch ( modelFunctionName ) {
+        
+                            case "creatingNode": // nodeID, childID, childExtendsID, childImplementsIDs, childSource, childType, childIndex, childName, callback /* ( ready ) */
+                                logees[8] = undefined; // callback /* ( ready ) */
+                                break;
+        
+                            case "creatingProperty":
+                                logees[3] && ( logees[3] = _self_.loggableScript( logees[3] ) ); // propertyGet
+                                logees[4] && ( logees[4] = _self_.loggableScript( logees[4] ) ); // propertySet
+                                break;
+        
+                            case "executing":
+                                logees[1] && ( logees[1] = _self_.loggableScript( logees[1] ) ); // scriptText
+                                break;
+        
+                            case "ticking":
+                                logees = undefined; // no logging for model.ticking()
+                                break;
+        
+                        }
+        
+                        if ( logees ) {
+                            this.logger.tracex.apply( this.logger, [ modelFunctionName ].concat( logees ) );
+                        }
+        
+                        return this.model[modelFunctionName].apply( this.model, arguments );
+                    }
+        
+                };
+        
+            }, function( kernelFunctionName ) {
+        
+                // == Kernel API ===========================================================================
+        
+                return function() {
+        
+                    var logees = Array.prototype.slice.call( arguments );
+        
+                    switch ( kernelFunctionName ) {
+        
+                        case "createNode": // nodeComponent, [ nodeAnnotation, ] [ baseURI, ] callback /* ( nodeID ) */
+                            _self_.objectIsComponent( logees[0] ) && ( logees[0] = JSON.stringify( _self_.loggableComponent( logees[0] ) ) ); // nodeComponent
+                            break;
+        
+                        case "createChild": // nodeID, childName, childComponent, childURI, callback /* ( childID ) */
+                            _self_.objectIsComponent( logees[2] ) && ( logees[2] = JSON.stringify( _self_.loggableComponent( logees[2] ) ) ); // childComponent
+                            break;
+        
+                        case "createProperty":
+                            logees[3] && ( logees[3] = _self_.loggableScript( logees[3] ) ); // propertyGet
+                            logees[4] && ( logees[4] = _self_.loggableScript( logees[4] ) ); // propertySet
+                            break;
+        
+                        case "execute":
+                            logees[1] && ( logees[1] = _self_.loggableScript( logees[1] ) ); // scriptText
+                            break;
+        
+                        case "time":
+                            logees = undefined; // no logging for kernel.time()
+                            break;
+        
+                    }
+        
+                    if ( logees ) {
+                        this.logger.tracex.apply( this.logger, [ kernelFunctionName ].concat( logees ) );
+                    } 
+        
+                    return this.kernel[kernelFunctionName].apply( this.kernel, arguments );
+                };
+                
+            } );
+
+        }
+
+         // == Private functions ========================================================================
+
+    objectIsComponent( candidate ) {  // TODO: this was copied from vwf.js; find a way to share (use the log stage for incoming logging too?)
+
+        var componentAttributes = [
+            "extends",
+            "implements",
+            "source",
+            "type",
+            "properties",
+            "methods",
+            "events",
+            "children",
+            "scripts",
+        ];
+
+        var isComponent = false;
+
+        if ( typeof candidate == "object" && candidate != null ) {
+
+            componentAttributes.forEach( function( attributeName ) {
+                isComponent = isComponent || Boolean( candidate[attributeName] );
+            } );
+
+        }
+            
+        return isComponent; 
+    }
+
+    loggableComponent( component ) {  // TODO: this was copied from vwf.js; find a way to share (use the log stage for incoming logging too?)
+
+        var loggable = {};
+
+        for ( var elementName in component ) {
+
+            switch ( elementName ) {
+
+                case "properties":
+
+                    loggable.properties = {};
+
+                    for ( var propertyName in component.properties ) {
+
+                        var componentPropertyValue = component.properties[propertyName];
+                        var loggablePropertyValue = loggable.properties[propertyName] = {};
+
+                        if ( this.valueHasAccessors( componentPropertyValue ) ) {
+                            for ( var propertyElementName in componentPropertyValue ) {
+                                if ( propertyElementName == "set" || propertyElementName == "get" ) {
+                                    loggablePropertyValue[propertyElementName] = "...";
+                                } else {
+                                    loggablePropertyValue[propertyElementName] = componentPropertyValue[propertyElementName];
+                                }
+                            }
+                        } else {
+                            loggable.properties[propertyName] = componentPropertyValue;
+                        }
+
+                    }
+
+                    break;
+
+                case "children":
+
+                    loggable.children = {};
+
+                    for ( var childName in component.children ) {
+                        loggable.children[childName] = {};
+                    }
+
+                    break;
+
+                case "scripts":
+
+                    loggable.scripts = [];
+
+                    component.scripts.forEach( function( script ) {
+
+                        var loggableScript = {};
+
+                        for ( var scriptElementName in script ) {
+                            loggableScript[scriptElementName] = scriptElementName == "text" ? "..." : script[scriptElementName];
+                        }
+
+                        loggable.scripts.push( loggableScript );
+
+                    } );
+
+                    break;
+
+                default:
+
+                    loggable[elementName] = component[elementName];
+
+                    break;
+            }
+
+        }
+
+        return loggable;
+    }
+
+    valueHasAccessors( candidate ) {  // TODO: this was copied from vwf.js; find a way to share (use the log stage for incoming logging too?)
+
+        var accessorAttributes = [
+            "get",
+            "set",
+            "value",
+        ];
+
+        var hasAccessors = false;
+
+        if ( typeof candidate == "object" && candidate != null ) {
+
+            accessorAttributes.forEach( function( attributeName ) {
+                hasAccessors = hasAccessors || Boolean( candidate[attributeName] );
+            } );
+
+        }
+            
+        return hasAccessors; 
+    }
+
+    loggableScript( scriptText ) {
+        return ( scriptText || "" ).replace( /\s+/g, " " ).substring( 0, 100 );
+    }
+
+
+}
+
+export {
+    Log
+  }

+ 0 - 0
public/vwf/model/stage/map.js → public/core/vwf/model/stage/map.js


+ 88 - 0
public/core/vwf/utility/kutility.js

@@ -0,0 +1,88 @@
+/*
+The MIT License (MIT)
+Copyright (c) 2014-2020 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+
+Virtual World Framework Apache 2.0 license  (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
+*/
+
+/// Kernel utility functions and objects.
+/// 
+/// @module vwf/kernel/utility
+
+
+class KUtility {
+
+    constructor() {
+        //console.log("Kernel Utility constructor");
+
+        /// ID of the pseudo node that appears as the parent of the simulation's global nodes.
+        /// 
+        /// @field
+
+        this.globalRootID = 0
+
+        /// The URI of the VWF proto-prototype node `node.vwf`. `protoNodeDescriptor` contains the
+        /// descriptor associated with this URI.
+        /// 
+        /// @field
+
+        this.protoNodeURI = "proxy/node.vwf"
+
+        /// The component descriptor of the VWF proto-prototype node `node.vwf`.
+        /// 
+        /// @field
+
+        this.protoNodeDescriptor = {
+            extends: null
+        } // TODO: detect protoNodeDescriptor in createChild() a different way and remove this explicit null prototype
+
+        /// The key prototype for application values that reference nodes.
+        /// 
+        /// Application values that reference VWF nodes are objects of the form `{ id: id }` that also
+        /// extend this object. Application values include property values, method parameters and
+        /// results, and event listener parameters.
+        /// 
+        /// `nodeReferencePrototype` serves as a key to distinguish real node references from other
+        /// arbitrary values.
+        /// 
+        /// @field
+
+        this.nodeReferencePrototype = {}
+
+    }
+
+    /// Wrap `nodeID` in an object in such a way that it can stand in for a node reference
+    /// without being confused with any other application value. The returned object will
+    /// contain `nodeID` in the `id` field. `valueIsNodeReference` may be used to determine if
+    /// an arbitrary value is such a node reference.
+    /// 
+    /// @param {ID} nodeID
+    /// 
+    /// @returns {Object}
+
+    nodeReference(nodeID) {
+        return Object.create(this.nodeReferencePrototype, {
+            id: {
+                value: nodeID
+            } // TODO: same wrapper for same id so that === works
+        });
+    }
+
+
+    /// Determine if a value is a node reference. If it is, it will contain the `nodeID` in the
+    /// `id` field.
+    /// 
+    /// @param {Object} value
+    /// 
+    /// @returns {Boolean}
+
+    valueIsNodeReference(value) {
+        return this.nodeReferencePrototype.isPrototypeOf(value);
+    }
+
+
+}
+
+export {
+    KUtility
+}

+ 146 - 153
public/logger.js → public/core/vwf/utility/logger.js

@@ -1,226 +1,229 @@
-// Copyright 2012 United States Government, as represented by the Secretary of Defense, Under
-// Secretary of Defense (Personnel & Readiness).
-// 
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
-// in compliance with the License. You may obtain a copy of the License at
-// 
-//   http://www.apache.org/licenses/LICENSE-2.0
-// 
-// Unless required by applicable law or agreed to in writing, software distributed under the License
-// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
-// or implied. See the License for the specific language governing permissions and limitations under
-// the License.
+/*
+The MIT License (MIT)
+Copyright (c) 2014-2020 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+
+Virtual World Framework Apache 2.0 license  (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
+*/
 
 /// @module logger
 /// @requires vwf/configuration
+//define( [ "vwf/configuration" ], function( configuration ) {
+
 
-define( [ "vwf/configuration" ], function( configuration ) {
+    class Logger {
 
-    var TRACE = 1,
-        DEBUG = 2,
-        INFO = 3,
-        WARN = 4,
-        ERROR = 5,
-        FATAL = 6;
+        constructor() {
+            //console.log("Logger constructor");
 
-    var exports = {
+            // exports.configure( undefined, undefined, vwf.configuration["log-level"] || "warn" );
 
-        levels: {
-            trace: TRACE,
-            debug: DEBUG,
-            info: INFO,
-            warn: WARN,
-            error: ERROR,
-            // fatal: FATAL,
-        },
+            this.TRACE = 1
+            this.DEBUG = 2
+            this.INFO = 3
+            this.WARN = 4
+            this.ERROR = 5
+            this.FATAL = 6
 
-        label: undefined,
-        context: undefined,
+            this.levels = {
+                trace: this.TRACE,
+                debug: this.DEBUG,
+                info: this.INFO,
+                warn: this.WARN,
+                error: this.ERROR,
+                // fatal: this.FATAL,
+            }
 
-        level: WARN,
+            this.label = undefined
+            this.context = undefined
+    
+            this.level = this.WARN
+    
+
+        }
 
-        for: function( label, context, level ) {
+        for ( label, context, level ) {
             return Object.create( this ).configure( label, context, level );
-        },
+        }
 
-        configure: function( label, context, level ) {
+        configure ( label, context, level ) {
 
             var proto = Object.getPrototypeOf( this ) !== Object.prototype ?
                 Object.getPrototypeOf( this ) : undefined;
 
-            this.label = combined_label( proto && proto.label, label );
+            this.label = this.combined_label( proto && proto.label, label );
 
             this.context = context || proto && proto.context;
 
-            this.level = { name: "warn", number: WARN }; // default
+            this.level = { name: "warn", number: this.WARN }; // default
 
             switch( level ) {
-                case "trace": case "TRACE": this.level = { name: "trace", number: TRACE }; break;
-                case "debug": case "DEBUG": this.level = { name: "debug", number: DEBUG }; break;
-                case "info":  case "INFO":  this.level = { name: "info",  number: INFO };  break;
-                case "warn":  case "WARN":  this.level = { name: "warn",  number: WARN };  break;
-                case "error": case "ERROR": this.level = { name: "error", number: ERROR }; break;
+                case "trace": case "TRACE": this.level = { name: "trace", number: this.TRACE }; break;
+                case "debug": case "DEBUG": this.level = { name: "debug", number: this.DEBUG }; break;
+                case "info":  case "INFO":  this.level = { name: "info",  number: this.INFO };  break;
+                case "warn":  case "WARN":  this.level = { name: "warn",  number: this.WARN };  break;
+                case "error": case "ERROR": this.level = { name: "error", number: this.ERROR }; break;
                 // case "fatal": case "FATAL": this.level = { name: "fatal", number: FATAL }; break;
                 default: proto && delete this.level; break; // inherit from the prototype
             }
 
             return this;
-        },
+        }
 
         /// Log a message and open a group if the log threshold is "trace" or below.
 
-        traceg: function( /* ... */ ) {
-            TRACE >= this.level.number &&
-                log.call( this, arguments, console && console.group, console );
-        },
+        traceg ( /* ... */ ) {
+            this.TRACE >= this.level.number &&
+                this.log.call( this, arguments, console && console.group, console );
+        }
 
         /// Log a message and open a group if the log threshold is "debug" or below.
 
-        debugg: function( /* ... */ ) {
-            DEBUG >= this.level.number &&
-                log.call( this, arguments, console && console.group, console );
-        },
+        debugg ( /* ... */ ) {
+            this.DEBUG >= this.level.number &&
+                this.log.call( this, arguments, console && console.group, console );
+        }
 
         /// Log a message and open a group if the log threshold is "info" or below.
 
-        infog: function( /* ... */ ) {
-            INFO >= this.level.number &&
-                log.call( this, arguments, console && console.group, console );
-        },
+        infog ( /* ... */ ) {
+            this.INFO >= this.level.number &&
+                this.log.call( this, arguments, console && console.group, console );
+        }
 
         /// Close a group if the log threshold is "trace" or below.
 
-        traceu: function() {
-            TRACE >= this.level.number &&
-                log.call( this, arguments, console && console.groupEnd, console );
-        },
+        traceu () {
+            this.TRACE >= this.level.number &&
+                this.log.call( this, arguments, console && console.groupEnd, console );
+        }
 
         /// Close a group if the log threshold is "debug" or below.
 
-        debugu: function() {
-            DEBUG >= this.level.number &&
-                log.call( this, arguments, console && console.groupEnd, console );
-        },
+        debugu () {
+            this.DEBUG >= this.level.number &&
+                this.log.call( this, arguments, console && console.groupEnd, console );
+        }
 
         /// Close a group if the log threshold is "info" or below.
 
-        infou: function() {
-            INFO >= this.level.number &&
-                log.call( this, arguments, console && console.groupEnd, console );
-        },
+        infou () {
+            this.INFO >= this.level.number &&
+                this.log.call( this, arguments, console && console.groupEnd, console );
+        }
 
         /// Log a message if the log threshold is "trace" or below.
 
-        trace: function( /* ... */ ) {
-            TRACE >= this.level.number &&
-                log.call( this, arguments, console && console.debug, console ); // not console.trace(), which would log the stack
-        },
+        trace ( /* ... */ ) {
+            this.TRACE >= this.level.number &&
+                this.log.call( this, arguments, console && console.debug, console ); // not console.trace(), which would log the stack
+        }
 
-        /// Log a message if the log threshold is "debug" or below.
+         /// Log a message if the log threshold is "debug" or below.
 
-        debug: function( /* ... */ ) {
-            DEBUG >= this.level.number &&
-                log.call( this, arguments, console && console.debug, console );
-        },
+         debug ( /* ... */ ) {
+            this.DEBUG >= this.level.number &&
+                this.log.call( this, arguments, console && console.debug, console );
+        }
 
         /// Log a message if the log threshold is "info" or below.
 
-        info: function( /* ... */ ) {
-            INFO >= this.level.number &&
-                log.call( this, arguments, console && console.info, console );
-        },
+        info ( /* ... */ ) {
+            this.INFO >= this.level.number &&
+                this.log.call( this, arguments, console && console.info, console );
+        }
 
         /// Log a message if the log threshold is "warn" or below.
 
-        warn: function( /* ... */ ) {
-            WARN >= this.level.number &&
-                log.call( this, arguments, console && console.warn, console );
-        },
+        warn ( /* ... */ ) {
+            this.WARN >= this.level.number &&
+                this.log.call( this, arguments, console && console.warn, console );
+        }
 
         /// Log a message if the log threshold is "error" or below.
 
-        error: function( /* ... */ ) {
-            ERROR >= this.level.number &&
-                log.call( this, arguments, console && console.error, console );
-        },
+        error ( /* ... */ ) {
+            this.ERROR >= this.level.number &&
+                this.log.call( this, arguments, console && console.error, console );
+        }
 
         /// Log a message.
 
-        log: function( /* ... */ ) {
-            log.call( this, arguments, console && console.log, console );
-        },
+        log ( /* ... */ ) {
+            this.log.call( this, arguments, console && console.log, console );
+        }
 
-        // Log with an extra one-time label. Equivalent to this.logger.for( label ).log( ... ),
+         // Log with an extra one-time label. Equivalent to this.logger.for( label ).log( ... ),
         // etc., but without the overhead of creating a new logger.
 
         /// Log a message with an extra one-time label and open a group if the log threshold is
         /// "trace" or below.
 
-        tracegx: function( /* label, ... */ ) {
-            TRACE >= this.level.number &&
-                log.call( this, arguments, console && console.group, console, true );
-        },
+        tracegx ( /* label, ... */ ) {
+            this.TRACE >= this.level.number &&
+                this.log.call( this, arguments, console && console.group, console, true );
+        }
 
         /// Log a message with an extra one-time label and open a group if the log threshold is
         /// "debug" or below.
 
-        debuggx: function( /* label, ... */ ) {
-            DEBUG >= this.level.number &&
-                log.call( this, arguments, console && console.group, console, true );
-        },
+        debuggx ( /* label, ... */ ) {
+            this.DEBUG >= this.level.number &&
+                this.log.call( this, arguments, console && console.group, console, true );
+        }
+
 
         /// Log a message with an extra one-time label and open a group if the log threshold is
         /// "info" or below.
 
-        infogx: function( /* label, ... */ ) {
-            INFO >= this.level.number &&
-                log.call( this, arguments, console && console.group, console, true );
-        },
+        infogx ( /* label, ... */ ) {
+            this.INFO >= this.level.number &&
+                this.log.call( this, arguments, console && console.group, console, true );
+        }
 
         /// Log a message with an extra one-time label if the log threshold is "trace" or below.
 
-        tracex: function( /* ... */ ) {
-            TRACE >= this.level.number &&
-                log.call( this, arguments, console && console.debug, console, true ); // not console.trace(), which would log the stack
-        },
+        tracex ( /* ... */ ) {
+            this.TRACE >= this.level.number &&
+                this.log.call( this, arguments, console && console.debug, console, true ); // not console.trace(), which would log the stack
+        }
 
         /// Log a message with an extra one-time label if the log threshold is "debug" or below.
 
-        debugx: function( /* label, ... */ ) {
-            DEBUG >= this.level.number &&
-                log.call( this, arguments, console && console.debug, console, true );
-        },
+        debugx ( /* label, ... */ ) {
+            this.DEBUG >= this.level.number &&
+                this.log.call( this, arguments, console && console.debug, console, true );
+        }
 
         /// Log a message with an extra one-time label if the log threshold is "info" or below.
 
-        infox: function( /* label, ... */ ) {
-            INFO >= this.level.number &&
-                log.call( this, arguments, console && console.info, console, true );
-        },
+        infox ( /* label, ... */ ) {
+            this.INFO >= this.level.number &&
+                this.log.call( this, arguments, console && console.info, console, true );
+        }
 
         /// Log a message with an extra one-time label if the log threshold is "warn" or below.
 
-        warnx: function( /* label, ... */ ) {
-            WARN >= this.level.number &&
-                log.call( this, arguments, console && console.warn, console, true );
-        },
+        warnx ( /* label, ... */ ) {
+            this.WARN >= this.level.number &&
+                this.log.call( this, arguments, console && console.warn, console, true );
+        }
 
         /// Log a message with an extra one-time label if the log threshold is "error" or below.
 
-        errorx: function( /* label, ... */ ) {
-            ERROR >= this.level.number &&
-                log.call( this, arguments, console && console.warn, console, true );
-        },
+        errorx ( /* label, ... */ ) {
+            this.ERROR >= this.level.number &&
+                this.log.call( this, arguments, console && console.warn, console, true );
+        }
 
         /// Log a message with an extra one-time label.
 
-        logx: function( /* label, ... */ ) {
-            log.call( this, arguments, console && console.log, console, true );
-        },
+        logx ( /* label, ... */ ) {
+            this.log.call( this, arguments, console && console.log, console, true );
+        }
 
-    };
+        /////
 
-    /// Log a message to the console. Normalize the arguments list and invoke the appender function.
+        /// Log a message to the console. Normalize the arguments list and invoke the appender function.
     /// 
     /// @param {Array} args
     ///   An Array-like list of arguments passed to a log function. normalize describes the formats
@@ -232,12 +235,12 @@ define( [ "vwf/configuration" ], function( configuration ) {
     /// @param {Boolean} [extra]
     ///   If true, interpret args[0] as a one-time label that extends the logger's output prefix.
 
-    function log( args, appender, context, extra ) {  // invoke with *this* as the logger module
+    log ( args, appender, context, extra ) {  // invoke with *this* as the logger module
 
         // Normalize the arguments and log the message. Don't log a message if normalize() returned
         // undefined (because a generator function didn't return a result).
 
-        if ( args = /* assignment! */ normalize.call( this, args, extra ) ) {
+        if ( args = /* assignment! */ this.normalize.call( this, args, extra ) ) {
             appender && appender.apply( context, args );
         }
 
@@ -279,7 +282,7 @@ define( [ "vwf/configuration" ], function( configuration ) {
     /// @param {Boolean} [extra]
     ///   If true, interpret args[0] as a one-time label that extends the logger's output prefix.
 
-    function normalize( args, extra ) {  // invoke with *this* as the logger module
+    normalize( args, extra ) {  // invoke with *this* as the logger module
 
         // Record the extra one-time label if one is provided. We leave it in the arguments list so
         // that we don't convert Arguments to an Array if it isn't necessary.
@@ -322,21 +325,21 @@ define( [ "vwf/configuration" ], function( configuration ) {
         // Add the prefix label to the arguments list and return. But return undefined if a
         // generator didn't return a result.
 
-        return args ? prefixed_arguments( this.label, label, args ) : undefined;
+        return args ? this.prefixed_arguments( this.label, label, args ) : undefined;
     }
 
-    /// Update an arguments list to prepend "<label>: " to the output.
+       /// Update an arguments list to prepend "<label>: " to the output.
 
-    function prefixed_arguments( label, extra, args ) {
+       prefixed_arguments( label, extra, args ) {
 
         if ( label || extra ) {
 
             if ( args.length == 0 ) {
-                return [ combined_label( label, extra ) ]; // just show the module and function name when there are no additional arguments
+                return [ this.combined_label( label, extra ) ]; // just show the module and function name when there are no additional arguments
             } else if ( typeof args[0] == "string" || args[0] instanceof String ) {
-                return [ combined_label( label, extra ) + ": " + args[0] ].concat( Array.prototype.slice.call( args, 1 ) ); // concatenate when the first field is a string so that it may remain a format string
+                return [ this.combined_label( label, extra ) + ": " + args[0] ].concat( Array.prototype.slice.call( args, 1 ) ); // concatenate when the first field is a string so that it may remain a format string
             } else {
-                return [ combined_label( label, extra ) + ":" ].concat( args ); // otherwise insert a new first field
+                return [ this.combined_label( label, extra ) + ":" ].concat( args ); // otherwise insert a new first field
             }
 
         } else {
@@ -347,9 +350,9 @@ define( [ "vwf/configuration" ], function( configuration ) {
 
     }
 
-    /// Generate a new label from a parent label and an extra part.
+     /// Generate a new label from a parent label and an extra part.
 
-    function combined_label( label, extra ) {
+    combined_label( label, extra ) {
 
         // Combine with "." unless the extension provides its own separator.
 
@@ -369,19 +372,9 @@ define( [ "vwf/configuration" ], function( configuration ) {
 
     }
 
-    // Get the initial level setting from the configuration module, and update the level when the
-    // configuration changes.
-
-    // TODO: should be done somewhere else; the logger isn't bound to VWF and shouldn't have a dependency on the configuration module.
 
-    exports.configure( undefined, undefined, configuration.active["log-level"] || "warn" );
-
-    configuration.changed( function( active ) {
-        exports.configure( undefined, undefined, active["log-level"] || "warn" );
-    }, this );
-
-    // Return the module.
-
-    return exports;
+    }
 
-} );
+    export {
+        Logger
+    }    

+ 134 - 163
public/vwf/utility.js → public/core/vwf/utility/utility.js

@@ -1,35 +1,118 @@
-"use strict";
-
-// Copyright 2012 United States Government, as represented by the Secretary of Defense, Under
-// Secretary of Defense (Personnel & Readiness).
-// 
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
-// in compliance with the License. You may obtain a copy of the License at
-// 
-//   http://www.apache.org/licenses/LICENSE-2.0
-// 
-// Unless required by applicable law or agreed to in writing, software distributed under the License
-// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
-// or implied. See the License for the specific language governing permissions and limitations under
-// the License.
+/*
+The MIT License (MIT)
+Copyright (c) 2014-2020 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+
+Virtual World Framework Apache 2.0 license  (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
+*/
 
 /// Kernel utility functions.
 /// 
 /// @module vwf/utility
 
-define( [ "module",
-    "vwf/utility/xpath",
-    "vwf/utility/color",
-    "vwf/utility/coordinates"
-], function( module,
-    xpath,
-    color,
-    coordinates
-) {
+class Utility {
+
+    constructor() {
+        //console.log("Utility constructor");
+  
+        let self = this;
+
+         // -- transforms ---------------------------------------------------------------------------
+
+        /// Transformation functions for vwf.utility.transform. Invoke these as:
+        /// 
+        ///   utility.transform( object, utility.transforms.*transform* )
+        ///
+        /// to apply the transform utility.transforms.*transform* to object.
+
+        this.transforms = {
+
+            // -- transit --------------------------------------------------------------------------
+
+            /// A vwf.utility.transform transformation function to convert an object for proper JSON
+            /// serialization. Array-like objects are converted to actual Arrays. All other objects
+            /// are unchanged. Invoke as: utility.transform( object, utility.transforms.transit ).
+            /// 
+            /// @param {Object} object
+            ///   The object being transformed or one of its descendants.
+            /// 
+            /// @returns {Object}
+            ///   An Array-like converted to an Array, or *object*.
+
+            transit: function( object ) {
+
+                // Determine if an object is Array-like (but not an Array) to identify objects that
+                // need to be converted to Arrays for normalization.
+
+                function isArraylike( candidate ) {
+
+                    var arraylike = false;
+
+                    // Filter with typeof and instanceof since they're much faster than toString().
+                    // Then check for typed arrays (Int8Array, Uint8Array, ...) or the Arguments
+                    // object using the type string.
+
+                    if ( typeof candidate == "object" && candidate != null && ! ( candidate instanceof Array ) ) {
+                        var typeString = Object.prototype.toString.call( candidate ) // eg, "[object *Type*]"
+                        arraylike = ( typeString.slice( -6 ) == "Array]" || typeString == "[object Arguments]" );
+                    }
+
+                    return arraylike;
+                };
+
+                // Convert typed arrays to regular arrays.
+
+                return isArraylike( object ) ?
+                    Array.prototype.slice.call( object ) : object;
+
+            },
 
-    var exports = {
+            // -- hash -----------------------------------------------------------------------------
 
-        // -- transform ----------------------------------------------------------------------------
+            /// A vwf.utility.transform transformation function to normalize an object so that it
+            /// can be serialized and hashed with consistent results. Numeric precision is reduced
+            /// to match the precision retained by the reflector. Non-Array objects are reordered so
+            /// that their keys are in alphabetic order. Other objects are unchanged. Invoke as:
+            /// utility.transform( object, utility.transforms.hash ).
+            /// 
+            /// @param {Object} object
+            ///   The object being transformed or one of its descendants.
+            /// 
+            /// @returns {Object}
+            ///   A reduced-precision number, an Object with alphabetic keys, or *object*.
+
+            hash: function( object ) {
+
+                // Apply the `transit` transform first to convert Array-likes into Arrays.
+
+                object = exports.transforms.transit( object );
+
+                // Reduce numeric precision slightly to match what passes through the reflector.
+
+                if ( typeof object == "number" ) {
+
+                    return Number( object.toPrecision(15) );
+                }
+
+                // Order objects alphabetically.
+
+                else if ( typeof object == "object" && object != null && ! ( object instanceof Array ) ) {
+
+                    var ordered = {};
+
+                    Object.keys( object ).sort().forEach( function( key ) {
+                        ordered[key] = object[key];
+                    } );
+
+                    return ordered;
+                }
+
+                return object;
+            },
+
+        }
+    }
+
+       // -- transform ----------------------------------------------------------------------------
 
         /// Recursively transform an arbitrary object using the provided transformation function.
         /// Containers are duplicated where necessary so that the original object and any contained
@@ -51,7 +134,7 @@ define( [ "module",
         /// @returns {Object}
         ///   The transformed object.
 
-        transform: function( object, transformation /* ( object, names, depth, finished ) */, names, depth ) {
+        transform ( object, transformation /* ( object, names, depth, finished ) */, names, depth ) {
 
             names = names || [];
             depth = depth || 0;
@@ -127,104 +210,9 @@ define( [ "module",
             }
 
             return result;
-        },
+        }
 
-        // -- transforms ---------------------------------------------------------------------------
-
-        /// Transformation functions for vwf.utility.transform. Invoke these as:
-        /// 
-        ///   utility.transform( object, utility.transforms.*transform* )
-        ///
-        /// to apply the transform utility.transforms.*transform* to object.
-
-        transforms: {
-
-            // -- transit --------------------------------------------------------------------------
-
-            /// A vwf.utility.transform transformation function to convert an object for proper JSON
-            /// serialization. Array-like objects are converted to actual Arrays. All other objects
-            /// are unchanged. Invoke as: utility.transform( object, utility.transforms.transit ).
-            /// 
-            /// @param {Object} object
-            ///   The object being transformed or one of its descendants.
-            /// 
-            /// @returns {Object}
-            ///   An Array-like converted to an Array, or *object*.
-
-            transit: function( object ) {
-
-                // Determine if an object is Array-like (but not an Array) to identify objects that
-                // need to be converted to Arrays for normalization.
-
-                function isArraylike( candidate ) {
-
-                    var arraylike = false;
-
-                    // Filter with typeof and instanceof since they're much faster than toString().
-                    // Then check for typed arrays (Int8Array, Uint8Array, ...) or the Arguments
-                    // object using the type string.
-
-                    if ( typeof candidate == "object" && candidate != null && ! ( candidate instanceof Array ) ) {
-                        var typeString = Object.prototype.toString.call( candidate ) // eg, "[object *Type*]"
-                        arraylike = ( typeString.slice( -6 ) == "Array]" || typeString == "[object Arguments]" );
-                    }
-
-                    return arraylike;
-                };
-
-                // Convert typed arrays to regular arrays.
-
-                return isArraylike( object ) ?
-                    Array.prototype.slice.call( object ) : object;
-
-            },
-
-            // -- hash -----------------------------------------------------------------------------
-
-            /// A vwf.utility.transform transformation function to normalize an object so that it
-            /// can be serialized and hashed with consistent results. Numeric precision is reduced
-            /// to match the precision retained by the reflector. Non-Array objects are reordered so
-            /// that their keys are in alphabetic order. Other objects are unchanged. Invoke as:
-            /// utility.transform( object, utility.transforms.hash ).
-            /// 
-            /// @param {Object} object
-            ///   The object being transformed or one of its descendants.
-            /// 
-            /// @returns {Object}
-            ///   A reduced-precision number, an Object with alphabetic keys, or *object*.
-
-            hash: function( object ) {
-
-                // Apply the `transit` transform first to convert Array-likes into Arrays.
-
-                object = exports.transforms.transit( object );
-
-                // Reduce numeric precision slightly to match what passes through the reflector.
-
-                if ( typeof object == "number" ) {
-
-                    return Number( object.toPrecision(15) );
-                }
-
-                // Order objects alphabetically.
-
-                else if ( typeof object == "object" && object != null && ! ( object instanceof Array ) ) {
-
-                    var ordered = {};
-
-                    Object.keys( object ).sort().forEach( function( key ) {
-                        ordered[key] = object[key];
-                    } );
-
-                    return ordered;
-                }
-
-                return object;
-            },
-
-        },
-
-        // -- exceptionMessage ---------------------------------------------------------------------
+                // -- exceptionMessage ---------------------------------------------------------------------
 
         /// Format the stack trace for readability.
         /// 
@@ -234,7 +222,7 @@ define( [ "module",
         /// @returns {String}
         ///   An error message that may be written to the console or a log.
 
-        exceptionMessage: function( error ) {
+        exceptionMessage ( error ) {
 
             // https://github.com/eriwen/javascript-stacktrace sniffs the browser type from the
             // exception this way.
@@ -258,7 +246,7 @@ define( [ "module",
 
             }
 
-        },
+        }
 
         // -- resolveURI ---------------------------------------------------------------------------
 
@@ -275,7 +263,7 @@ define( [ "module",
         /// @returns {String}
         ///   uri as an absolute URI.
 
-        resolveURI: function( uri, baseURI ) {
+        resolveURI ( uri, baseURI ) {
 
             var doc = document;
 
@@ -302,13 +290,13 @@ define( [ "module",
 
             // return a.href;
             return uri
-        },
+        }
 
         // -- merge --------------------------------------------------------------------------------
 
         /// Merge fields from the `source` objects into `target`.
 
-        merge: function( target /* [, source1 [, source2 ... ] ] */ ) {
+        merge ( target /* [, source1 [, source2 ... ] ] */ ) {
 
             for ( var index = 1; index < arguments.length; index++ ) {
                 var source = arguments[index];
@@ -321,18 +309,18 @@ define( [ "module",
             }
 
             return target;
-        },
+        }
 
-        validObject: function( obj ) {
+        validObject ( obj ) {
             var objType = ( {} ).toString.call( obj ).match( /\s([a-zA-Z]+)/ )[ 1 ].toLowerCase();
             return ( objType != 'null' && objType != 'undefined' );
-        },
+        }
 
-        hasFileType: function( value ) {
+        hasFileType ( value ) {
             return ( this.fileType( value ) !== undefined )
-        },
+        }
 
-        fileType: function( filename ) {
+        fileType ( filename ) {
             var fileFormat = undefined;
 
             var temp = filename.split( '.' );
@@ -343,9 +331,9 @@ define( [ "module",
                 }
             }
             return fileFormat;
-        },  
+        }
 
-        ifPrototypeGetId: function( appID, prototypes, nodeID, childID ) {
+        ifPrototypeGetId ( appID, prototypes, nodeID, childID ) {
             var prototypeID = undefined;
             if ( ( nodeID == 0 && childID != appID ) || prototypes[ nodeID ] !== undefined ) {
                 if ( nodeID != 0 || childID != appID ) {
@@ -357,37 +345,20 @@ define( [ "module",
                 } 
             }
             return undefined;
-        },
+        }
 
-        isString: function( s ) {
+        isString ( s ) {
             return ( typeof( s ) === 'string' || s instanceof String );
-        },
+        }
 
-        isFunction: function( obj ) {
+        isFunction ( obj ) {
             return ( typeof obj === 'function' || obj instanceof Function );
-        },
-
-
-        // -- xpath --------------------------------------------------------------------------------
-
-        /// XPath resolution functions.
-
-        xpath: xpath,
-
-        // -- color --------------------------------------------------------------------------------
-
-        /// HTML/CSS color conversion functions.
-
-        color: color,
-
-        // -- coordinates --------------------------------------------------------------------------
-
-        /// DOM element coordinate conversion functions.
-
-        coordinates: coordinates,
+        }
 
-    };
 
-    return exports;
+}
 
-} );
+export {
+    Utility
+  }
+  

+ 491 - 0
public/core/vwf/utility/xpath.js

@@ -0,0 +1,491 @@
+/*
+The MIT License (MIT)
+Copyright (c) 2014-2020 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+
+Virtual World Framework Apache 2.0 license  (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
+*/
+
+/// XPath resolution functions.
+/// 
+/// @module vwf/utility/xpath
+
+class XPath {
+
+  constructor() {
+      console.log("XPath constructor");
+
+      let self = this;
+
+     // -- regex ----------------------------------------------------------------------------
+
+        /// Regexes to crack the XPath string.
+
+        this.regex = ( function() {
+
+          var name = "[A-Za-z_][A-Za-z_0-9.-]*",              // XPath QName: http://www.w3.org/TR/xpath20/#prod-xpath-QName
+              singleQuotedName = "'(?:[^'\\\\]|\\'|\\\\)+'",  // Single-quoted QName (VWF extension)
+              doubleQuotedName = '"(?:[^"\\\\]|\\"|\\\\)+"',  // Double-quoted QName (VWF extension)
+              wildcard = "\\*";                               // XPath Wildcard: http://www.w3.org/TR/xpath20/#prod-xpath-Wildcard
+
+          /// @field
+
+          var step =                                          // XPath StepExpr: http://www.w3.org/TR/xpath20/#prod-xpath-StepExpr
+
+              "(?:" +
+
+                "(?:" +
+
+                  "(?:" +
+                    "(?:(" + name + ")::)" +                  // "axis", as in "axis::"" (axis_name)
+                  "|" +
+                    "(@|)" +                                  // "@", "" (abbreviated_axis_specifier)
+                  ")" +
+
+                  "(?:" +
+
+                    "(?:" +
+                      "(" + name + ")" +                      // "kind" (node_kind)
+                      "\\(" +                                 // "("
+                        "(?:" +
+                          "(?:" +
+                            "(" + name + ")" +                // "node" (node_name)
+                          "|" +
+                            "(" +
+                              "(?:" + singleQuotedName + ")" + // "'node'" (node_name_quoted, quoted and with internal escapes)
+                            "|" +
+                              "(?:" + doubleQuotedName + ")" + // "\"node\"" (node_name_quoted, quoted and with internal escapes)
+                            ")" +
+                          "|" +
+                            "(" + wildcard + ")" +            // "*" (node_name_wildcard)
+                          ")" +
+                          "(?:" +
+                            "," +                             // ","
+                            "(?:" +
+                              "(" + name + ")" +              // "type" (type_name)
+                            "|" +
+                              "(" +
+                                "(?:" + singleQuotedName + ")" + // "'type'" (type_name_quoted, quoted and with internal escapes)
+                              "|" +
+                                "(?:" + doubleQuotedName + ")" + // '"type"' (type_name_quoted, quoted and with internal escapes)
+                              ")" +
+                            ")" +
+                          ")?" +
+                        ")?" +
+                      "\\)" +                                 // ")"
+                    ")" +
+
+                  "|" +
+
+                    "(?:" +
+                      "(" + name + ")" +                      // "name" (name_test)
+                    "|" +
+                      "(" +
+                        "(?:" + singleQuotedName + ")" +      // "'name'" (name_test_quoted, quoted and with internal escapes)
+                      "|" +
+                        "(?:" + doubleQuotedName + ")" +      // '"name"' (name_test_quoted, quoted and with internal escapes)
+                      ")" +
+                    "|" +
+                      "(" + wildcard + ")" +                  // "*" (name_test_wildcard)
+                    ")" +
+
+                  ")" +
+
+                ")" +
+
+              "|" +
+
+                "(\\.\\.|\\.)" +                             // "..", "." (abbreviated_step)
+
+              ")";
+
+          /// @field
+
+          var predicate =                                     // XPath Predicate: http://www.w3.org/TR/xpath20/#prod-xpath-Predicate
+
+              "\\[" +
+                  "(" +
+                      step + // "[^\\]]*" +
+                  ")" +
+              "\\]";
+
+          /// @field
+
+          var separator = "/";
+
+          var regexes = {
+
+              /// @field
+
+              step: new RegExp( "^" + step ),
+
+              /// @field
+
+              predicate: new RegExp( "^" + predicate ),
+
+              /// @field
+
+              separator: new RegExp( "^" + separator ),
+
+          };
+
+          return regexes;
+
+      } )()
+
+
+  }
+
+  // -- resolve --------------------------------------------------------------------------
+
+        /// Resolve an XPath expression, using a callback function to interpret each step.
+        /// 
+        /// @param {String|String[]|Object[]} xpath
+        /// @param {ID} rootID
+        /// @param {ID|ID[]} contextIDs
+        /// @param {Function} callback
+        /// @param {Object} [thisArg]
+        /// 
+        /// @returns {ID[]|undefined}
+
+        resolve ( xpath, rootID, contextIDs, callback /* ( step, id, resolveAttributes ) */, thisArg ) {
+
+          // Accept contextIDs as either a single id or an array of ids.
+
+          if ( ! ( contextIDs instanceof Array ) ) {
+              contextIDs = [ contextIDs ];
+          }
+
+          // Parse the expression.
+
+          var steps = this.parse( xpath );
+
+          if ( steps ) {
+
+              // Reset the context if it's an absolute path.
+
+              if ( steps.absolute ) {
+                  contextIDs = rootID ? [ rootID ] : [];
+              }
+
+              // Resolve each step.
+
+              steps.forEach( function( step ) {
+
+                  contextIDs = Array.prototype.concat.apply( [], contextIDs.map( function( id ) {
+
+                      var stepIDs = callback.call( thisArg, step, id );
+
+                      step.predicates && step.predicates.forEach( function( predicate ) {
+
+                          stepIDs = stepIDs.filter( function( stepID ) {
+                              return this.resolve( predicate, rootID, stepID, function( step, id ) {
+                                  return callback.call( this, step, id, true );
+                              }, thisArg ).length;
+                          }, this );
+
+                      }, this  );
+
+                      return stepIDs;
+
+                  }, this ) );
+
+              }, this );
+
+              return contextIDs;
+          }
+
+      }
+
+              // -- parse ----------------------------------------------------------------------------
+
+        /// Parse an XPath expression into a series of steps.
+        /// 
+        /// @param {String|String[]|Object[]} xpath
+        /// 
+        /// @returns {Object[]|undefined}
+
+        parse ( xpath ) {
+
+          var steps = [], step;
+
+          if ( typeof xpath == "string" || xpath instanceof String ) {
+
+              if ( xpath[0] == "/" ) {
+                  steps.absolute = true;
+                  xpath = { string: xpath, index: 1 };
+              } else {
+                  xpath = { string: xpath, index: 0 };
+              }
+
+              while ( xpath.index < xpath.string.length &&
+                      ( step = /* assignment! */ this.parseStep( xpath ) ) ) {
+                  steps.push( step );
+              }
+
+              if ( xpath.index < xpath.string.length ) {
+                  return undefined;
+              }
+
+          } else if ( typeof xpath[0] == "string" || xpath[0] instanceof String ) {
+
+              var valid = true;
+
+              steps = xpath.map( function( step ) {
+
+                  step = this.parseStep( step );
+                  valid = valid && step;
+
+                  return step;
+
+              }, this );
+
+              if ( ! valid ) {
+                  return undefined;
+              }
+
+          } else {
+
+              steps = xpath;
+          }
+
+          return steps;
+      }
+
+      // -- parseStep ------------------------------------------------------------------------
+
+      /// Parse an XPath step expression.
+      /// 
+      /// @param {String|Object} xpath
+      /// 
+      /// @returns {Object|undefined}
+
+      parseStep ( xpath ) {
+
+          if ( typeof xpath == "string" || xpath instanceof String ) {
+              xpath = { string: xpath, index: 0 };
+          }
+
+          if ( xpath.index < xpath.string.length && ! this.regex.separator.test( xpath.string.slice( xpath.index ) ) ) {
+              var step_match = this.regex.step.exec( xpath.string.slice( xpath.index ) );
+          } else {
+              var step_match = [].concat( "", new Array( 11 ), "" /* abbreviated_step */ ); // special case for "//"
+          }
+
+          if ( step_match ) {
+
+              xpath.index += step_match[0].length;
+
+              var axis_name = step_match[1],
+                  abbreviated_axis_specifier = step_match[2],
+                  node_kind = step_match[3],
+                  node_name = step_match[4],
+                  node_name_quoted = step_match[5],
+                  node_name_wildcard = step_match[6],
+                  type_name = step_match[7],
+                  type_name_quoted = step_match[8],
+                  name_test = step_match[9],
+                  name_test_quoted = step_match[10],
+                  name_test_wildcard = step_match[11],
+                  abbreviated_step = step_match[12];
+
+              if ( name_test || name_test_quoted || name_test_wildcard ) {
+                node_name = name_test;
+                node_name_quoted = name_test_quoted;
+                node_name_wildcard = name_test_wildcard;
+              }
+
+              if ( node_name_quoted ) {
+                node_name = this.unquoteName( node_name_quoted );
+              }
+
+              if ( type_name_quoted ) {
+                type_name = this.unquoteName( type_name_quoted );
+              }
+
+              switch ( abbreviated_step ) {
+
+                  case "": // "" == "descendant-or-self:node()"
+                      axis_name = "descendant-or-self";
+                      node_kind = "node";
+                      break;
+
+                  case ".": // "." == "self::node()"
+                      axis_name = "self";
+                      node_kind = "node";
+                      break;
+
+                  case "..": // ".." == "parent::node()"
+                      axis_name = "parent";
+                      node_kind = "node";
+                      break;
+
+              }
+
+              switch ( abbreviated_axis_specifier ) {
+
+                  case "": // "name" == "child::name"
+                      axis_name = "child";
+                      break;
+
+                  case "@": // "@name" == "attribute::name"
+                      axis_name = "attribute";
+                      break;
+
+              }
+
+              // // * == element()
+
+              // "preceding::"
+              // "preceding-sibling::"
+
+              // "ancestor-or-self::"
+              // "ancestor::"
+              // "parent::"
+              // "self::"
+              // "child::"
+              // "descendant::"
+              // "descendant-or-self::"
+
+              // "following-sibling::"
+              // "following::"
+
+              // // * == attribute()
+
+              // "attribute::"
+
+              // // * == namespace()
+
+              // "namespace::"
+
+              if ( node_name_wildcard && ! node_kind ) {
+
+                  switch ( axis_name ) {
+
+                      default:
+                          node_kind = "element";
+                          break;
+
+                      case "attribute":
+                          node_kind = "attribute";
+                          break;
+
+                      case "namespace":
+                          node_kind = "namespace";
+                          break;
+
+                  }
+
+              }
+
+              // Parse the predicates.
+
+              var predicates = [], predicate;
+
+              while ( predicate = /* assignment! */ this.parsePredicate( xpath ) ) {
+                  predicates.push( predicate );
+              }
+
+              // Absorb the separator.
+
+              this.parseSeparator( xpath );
+
+              // Now have: axis_name and name_test | node_kind(node_name,type_name)
+
+              var step = {
+                  axis: axis_name,
+                  kind: node_kind,
+                  name: node_name,
+                  type: type_name,
+              };
+
+              if ( predicates.length ) {
+                  step.predicates = predicates;
+              }
+
+              return step;
+          }
+
+      }
+
+      // -- parsePredicate -------------------------------------------------------------------
+
+      /// Parse an XPath step predicate.
+      /// 
+      /// @param {String|Object} xpath
+      /// 
+      /// @returns {Object[]|undefined}
+
+      parsePredicate ( xpath ) {
+
+          if ( typeof xpath == "string" || xpath instanceof String ) {
+              xpath = { string: xpath, index: 0 };
+          }
+
+          var predicate_match = this.regex.predicate.exec( xpath.string.slice( xpath.index ) );
+
+          if ( predicate_match ) {
+              xpath.index += predicate_match[0].length;
+              return this.parse( predicate_match[1] );
+          }
+
+      }
+
+      // -- parseSeparator -------------------------------------------------------------------
+
+      /// Parse an XPath step separator.
+      /// 
+      /// @param {String|Object} xpath
+      /// 
+      /// @returns {Boolean|undefined}
+
+      parseSeparator ( xpath ) {
+
+          if ( typeof xpath == "string" || xpath instanceof String ) {
+              xpath = { string: xpath, index: 0 };
+          }
+
+          var separator_match = this.regex.separator.exec( xpath.string.slice( xpath.index ) );
+
+          if ( separator_match ) {
+              xpath.index += separator_match[0].length;
+              return true;
+          }
+
+      }
+
+      // -- quoteName --------------------------------------------------------------------------
+
+      /// Apply quotation marks around a name and escape internal quotation marks and escape
+      /// characters.
+      /// 
+      /// @param {String} unquoted_name
+      /// 
+      /// @returns {String}
+
+      quoteName ( unquoted_name ) {
+          return '"' + unquoted_name.replace( /(["\\])/g, "\\$1" ) + '"';
+      }
+
+      // -- unquoteName ------------------------------------------------------------------------
+
+      /// Remove the enclosing quotation marks and unescape internal quotation marks and escape
+      /// characters of a quoted name.
+      /// 
+      /// @param {String} quoted_name
+      /// 
+      /// @returns {String}
+
+      unquoteName ( quoted_name ) {
+          if ( quoted_name[0] == "'" ) {
+            return quoted_name.slice( 1, -1 ).replace( /\\(['\\])/g, "$1" );
+          } else if ( quoted_name[0] == '"' ) {
+            return quoted_name.slice( 1, -1 ).replace( /\\(["\\])/g, "$1" );
+          }
+      }
+
+
+}
+
+export {
+  XPath
+}

+ 303 - 0
public/core/vwf/view.js

@@ -0,0 +1,303 @@
+/*
+The MIT License (MIT)
+Copyright (c) 2014-2020 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+
+Virtual World Framework Apache 2.0 license  (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
+*/
+
+/// FROM FABRIC.JS
+/// vwf/view.js is the common implementation of all Virtual World Framework views. Views
+/// interpret information from the simulation, present it to the user, and accept user input
+/// influencing the simulation.
+///
+/// Views are outside of the simulation. Unlike models, they may accept external input--such as
+/// pointer and key events from a user--but may only affect the simulation indirectly through the
+/// synchronization server.
+/// 
+/// vwf/view and all deriving views are loaded as RequireJS (http://requirejs.org) modules.
+
+/// @module vwf/kernel/view
+/// @requires vwf/view
+
+import { Fabric } from '/core/vwf/fabric.js';
+
+class ViewKernel extends Fabric {
+
+    constructor(module) {
+
+        //console.log("ViewKernel constructor");
+        super(module, "View")
+        this.module = module;
+        //this.view = this;
+
+        // new Fabric({
+        //     id:"vwf/view"
+        // }, 'View');
+
+    }
+
+    factory(){
+
+        return this.load( this.module, {
+
+            // == Module Definition ====================================================================
+            
+        }, function( viewFunctionName ) {
+    
+            // == View API =============================================================================
+    
+            // The kernel bypasses vwf/kernel/view and calls directly into the first driver stage.
+    
+            return undefined;
+    
+        }, function( kernelFunctionName ) {
+    
+            // == Kernel API ===========================================================================
+    
+            switch ( kernelFunctionName ) {
+    
+                // -- Read-write functions -------------------------------------------------------------
+    
+                // TODO: setState
+                // TODO: getState
+                // TODO: hashState
+    
+                case "createNode":
+    
+                    return function( nodeComponent, nodeAnnotation, baseURI, when, callback /* nodeID */ ) {
+    
+                        // Interpret `createNode( nodeComponent, when, callback )` as
+                        // `createNode( nodeComponent, undefined, undefined, when, callback )` and
+                        // `createNode( nodeComponent, nodeAnnotation, when, callback )` as
+                        // `createNode( nodeComponent, nodeAnnotation, undefined, when, callback )`.
+                        // `nodeAnnotation` was added in 0.6.12, and `baseURI` was added in 0.6.25.
+    
+                        if ( typeof baseURI == "function" || baseURI instanceof Function ) {
+                            callback = baseURI;
+                            when = nodeAnnotation;
+                            baseURI = undefined;
+                            nodeAnnotation = undefined;
+                        } else if ( typeof when == "function" || when instanceof Function ) {
+                            callback = when;
+                            when = baseURI;
+                            baseURI = undefined;
+                        }
+    
+                        // Make the call.
+    
+                        this.kernel.virtualTime.send( undefined, kernelFunctionName, undefined,
+                            [ nodeComponent, nodeAnnotation ], when || 0, callback /* result */ );
+    
+                    };
+    
+                case "deleteNode":
+    
+                    return function( nodeID, when, callback ) {
+                        this.kernel.virtualTime.send( nodeID, kernelFunctionName, undefined,
+                            undefined, when || 0, callback /* result */ );
+                    };
+    
+                // TODO: setNode
+                // TODO: getNode
+    
+                case "createChild":
+    
+                    return function( nodeID, childName, childComponent, childURI, when, callback /* childID */ ) {
+                        this.kernel.virtualTime.send( nodeID, kernelFunctionName, childName,
+                            [ childComponent, childURI ], when || 0, callback /* result */ );
+                    };
+    
+                case "deleteChild":
+    
+                    return function( nodeID, childName, when, callback ) {
+                        this.kernel.virtualTime.send( nodeID, kernelFunctionName, childName,
+                            undefined, when || 0, callback /* result */ );
+                    };
+    
+                case "addChild":
+                
+                    return function( nodeID, childID, childName, when, callback ) {
+                        this.kernel.virtualTime.send( nodeID, kernelFunctionName, undefined,
+                            [ childID, childName ], when || 0, callback /* result */ );  // TODO: swap childID & childName?
+                    };
+    
+                case "removeChild":
+    
+                    return function( nodeID, childID, when, callback ) {
+                        this.kernel.virtualTime.send( nodeID, kernelFunctionName, undefined,
+                            [ childID ], when || 0, callback /* result */ );  // TODO: swap childID & childName?
+                    };
+    
+                case "setProperties":
+    
+                    return function( nodeID, properties, when, callback ) {
+                        this.kernel.virtualTime.send( nodeID, kernelFunctionName, undefined,
+                            [ properties ], when || 0, callback /* result */ );
+                    };
+    
+                case "getProperties":
+    
+                    return function( nodeID, when, callback ) {
+                        this.kernel.virtualTime.send( nodeID, kernelFunctionName, undefined,
+                            undefined, when || 0, callback /* result */ );
+                    };
+    
+                case "createProperty":
+    
+                    return function( nodeID, propertyName, propertyValue, propertyGet, propertySet, when, callback ) {
+                        this.kernel.virtualTime.send( nodeID, kernelFunctionName, propertyName,
+                            [ propertyValue, propertyGet, propertySet ], when || 0, callback /* result */ );  // TODO: { value: propertyValue, get: propertyGet, set: propertySet } ? -- vwf.receive() needs to parse
+                    };
+    
+                // TODO: deleteProperty
+    
+                case "setProperty":
+    
+                    return function( nodeID, propertyName, propertyValue, when, callback ) {
+                        this.kernel.virtualTime.send( nodeID, kernelFunctionName, propertyName,
+                            [ propertyValue ], when || 0, callback /* result */ );
+                    };
+    
+                case "getProperty":
+    
+                    return function( nodeID, propertyName, when, callback ) {
+                        this.kernel.virtualTime.send( nodeID, kernelFunctionName, propertyName,
+                            undefined, when || 0, callback /* result */ );
+                    };
+        
+                case "createMethod":
+    
+                    return function( nodeID, methodName, methodParameters, methodBody, when, callback ) {
+                        this.kernel.virtualTime.send( nodeID, kernelFunctionName, methodName,
+                            [ methodParameters, methodBody ], when || 0, callback /* result */ );  // TODO: { parameters: methodParameters, body: methodBody } ? -- vwf.receive() needs to parse
+                    };
+    
+                // TODO: deleteMethod
+    
+                case "setMethod":
+    
+                    return function( nodeID, methodName, methodHandler, when, callback ) {
+                        this.kernel.virtualTime.send( nodeID, kernelFunctionName, methodName,
+                            [ methodHandler ], when || 0, callback /* result */ );
+                    };
+    
+                case "getMethod":
+    
+                    return function( nodeID, methodName, when, callback ) {
+                        this.kernel.virtualTime.send( nodeID, kernelFunctionName, methodName,
+                            undefined, when || 0, callback /* result */ );
+                    };
+    
+                case "callMethod":
+    
+                    return function( nodeID, methodName, methodParameters, when, callback ) {
+                        this.kernel.virtualTime.send( nodeID, kernelFunctionName, methodName,
+                            [ methodParameters ], when || 0, callback /* result */ );
+                    };
+        
+                case "createEvent":
+    
+                    return function( nodeID, eventName, eventParameters, when, callback ) {
+                        this.kernel.virtualTime.send( nodeID, kernelFunctionName, eventName,
+                            [ eventParameters ], when || 0, callback /* result */ );
+                    };
+    
+                // TODO: deleteEvent
+    
+                case "addEventListener":
+    
+                    return function( nodeID, eventName, eventHandler, eventContextID, eventPhases, when, callback ) {
+                        this.kernel.virtualTime.send( nodeID, kernelFunctionName, eventName,
+                            [ eventHandler, eventContextID, eventPhases ], when || 0, callback /* result */ );
+                    };
+    
+                case "removeEventListener":
+    
+                    return function( nodeID, eventName, eventListenerID, when, callback ) {
+                        this.kernel.virtualTime.send( nodeID, kernelFunctionName, eventName,
+                            [ eventListenerID ], when || 0, callback /* result */ );
+                    };
+    
+                case "flushEventListeners":
+    
+                    return function( nodeID, eventName, eventContextID, when, callback ) {
+                        this.kernel.virtualTime.send( nodeID, kernelFunctionName, eventName,
+                            [ eventContextID ], when || 0, callback /* result */ );
+                    };
+    
+                case "fireEvent":
+    
+                    return function( nodeID, eventName, eventParameters, when, callback ) {
+                        this.kernel.virtualTime.send( nodeID, kernelFunctionName, eventName,
+                            [ eventParameters ], when || 0, callback /* result */ );
+                    };
+        
+                case "dispatchEvent":
+    
+                    return function( nodeID, eventName, eventParameters, eventNodeParameters, when, callback ) {
+                        this.kernel.virtualTime.send( nodeID, kernelFunctionName, eventName,
+                            [ eventParameters, eventNodeParameters ], when || 0, callback /* result */ );
+                    };
+        
+                case "execute":
+    
+                    return function( nodeID, scriptText, scriptType, when, callback ) {
+                        this.kernel.virtualTime.send( nodeID, kernelFunctionName, undefined,
+                            [ scriptText, scriptType ], when || 0, callback /* result */ );  // TODO: { text: scriptText, type: scriptType } ? -- vwf.receive() needs to parse
+                    };
+    
+                case "random":
+    
+                    return function( nodeID, when, callback ) {
+                        this.kernel.virtualTime.send( nodeID, kernelFunctionName, undefined,
+                            undefined, when || 0, callback /* result */ );
+                    };
+    
+                case "seed":
+    
+                    return function( nodeID, seed, when, callback ) {
+                        this.kernel.virtualTime.send( nodeID, kernelFunctionName, undefined,
+                            [ seed ], when || 0, callback /* result */ );
+                    };
+    
+                // -- Read-only functions --------------------------------------------------------------
+    
+                case "time":
+                case "client":
+                case "moniker":
+    
+                case "application":
+    
+                case "intrinsics":
+                case "uri":
+                case "name":
+    
+                case "prototype":
+                case "prototypes":
+                case "behaviors":
+    
+                case "ancestors":
+                case "parent":
+                case "children":
+                case "descendants":
+    
+                case "find":
+                case "test":
+                case "findClients":
+    
+                    return function() {
+                        return this.kernel[kernelFunctionName].apply( this.kernel, arguments );
+                    };
+    
+            }
+    
+        } );
+
+    }
+
+}
+
+export {
+    ViewKernel
+  }
+

+ 88 - 0
public/core/vwf/view/document.js

@@ -0,0 +1,88 @@
+//"use strict";
+/*
+The MIT License (MIT)
+Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+
+Virtual World Framework Apache 2.0 license  (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
+*/
+import {Fabric} from '/core/vwf/fabric.js';
+
+class VWFDocument extends Fabric {
+
+  constructor(module) {
+    console.log("Document constructor");
+    super(module, 'View');
+  }
+
+  factory() {
+
+    let _self_ = this;
+
+    return this.load(this.module, {
+
+      // == Module Definition ====================================================================
+
+      initialize: function () {
+        window.vwf_view = this;
+        this.fabric = _self_;
+      },
+
+      // == Model API ============================================================================
+
+      createdNode: function (nodeID, childID, childExtendsID, childImplementsIDs,
+        childSource, childType, childURI, childName, callback /* ( ready ) */ ) {
+
+        let self = this;
+        // At the root node of the application, load the UI chrome if available.
+
+        if (childID == this.kernel.application() &&
+          (window.location.protocol == "http:" || window.location.protocol == "https:")) {
+
+          // Suspend the queue.
+
+          callback(false);
+
+          let moduleData = vwf.doc;
+          if (moduleData) {
+          var source;
+          if(moduleData.startsWith('{')){  //EXAMPLE: {"url": "/defaults/worlds/pure/driver.js"}
+            source = JSON.parse(moduleData).url;
+          } else { //LOAD MODULE from string
+            let data = _self_.helpers.replaceSubStringALL(moduleData, '$host', window.location.origin);
+            source = "data:text/javascript;base64," + btoa(data);
+          }
+
+          import(source).then(res => {
+
+            if (self.createdNode !== Object.getPrototypeOf(self).createdNode) {
+              self.createdNode(nodeID, childID, childExtendsID, childImplementsIDs,
+                childSource, childType, childURI, childName);
+            }
+            
+            this.userView = new res.default;
+            callback(true);
+          })
+        } else {
+          setTimeout(() => {
+            callback(true);
+          }, 0);
+
+        }
+
+        }
+
+      },
+
+    }, function (viewFunctionName) {
+
+      // == View API =============================================================================
+
+    });
+
+
+  }
+
+}
+
+
+export { VWFDocument as default}

+ 114 - 0
public/core/vwf/view/ohm.js

@@ -0,0 +1,114 @@
+//"use strict";
+/*
+The MIT License (MIT)
+Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+
+Virtual World Framework Apache 2.0 license  (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
+*/
+// VWF & Ohm view driver
+
+import {Fabric} from '/core/vwf/fabric.js';
+
+class OhmViewDriver extends Fabric {
+
+  constructor(module) {
+    console.log("OhmViewDriver constructor");
+    super(module, 'View');
+  }
+
+  factory() {
+
+    let _self_ = this;
+
+	return this.load(this.module, 
+        {
+
+            // == Module Definition ====================================================================
+    
+            initialize: function (options) {
+                let self = this;
+
+                this.fabric = _self_;
+                this.nodes = {};
+    
+            },
+    
+            // initializedNode: function( nodeID, childID ) {
+            // },
+    
+            createdProperty: function (nodeId, propertyName, propertyValue) {
+                return this.satProperty(nodeId, propertyName, propertyValue);
+            },
+    
+            initializedProperty: function (nodeId, propertyName, propertyValue) {
+                return this.satProperty(nodeId, propertyName, propertyValue);
+            },
+    
+            satMethod: function (nodeId, methodName) {
+    
+                let self = this;
+    
+                 var node = this.state.nodes[ nodeId ];
+    
+                if ( !( node && node.lang) ) {
+                    return;
+                }
+                     if (methodName.indexOf("Operation") > -1) {
+    
+    
+                      //self.kernel.callMethod (nodeId, methodName);
+                       self.kernel.setProperty(nodeId, 'ohmLang', node.lang.source);
+                        console.log("set new lang properties");
+                     }
+    
+            },
+    
+            // createdMethod: function( nodeId, methodName, methodParameters, methodBody) {
+    
+            //     var self = this;
+            //      var node = this.state.nodes[ nodeId ];
+            //     if ( !( node && node.lang) ) {
+            //         return;
+            //     }
+    
+            //     if (methodName.indexOf("Operation") > -1) {
+    
+            //         console.log("add semantic operations");
+            //           self.kernel.callMethod (nodeId, methodName);
+    
+            //          }
+    
+            // },
+    
+            satProperty: function (nodeId, propertyName, propertyValue) {
+               
+                let self = this;
+                 var node = this.state.nodes[ nodeId ];
+                if ( !( node && node.lang) ) {
+                    return;
+                }
+                var lang = node.lang;
+                switch (propertyName) {
+                    case "ohmLang":
+                        if (propertyValue) {
+                            self.kernel.callMethod (nodeId, 'initLang');
+                        }
+                        break;
+                }
+    
+            },
+    
+            // firedEvent: function (nodeID, eventName, eventParameters) {
+            // },
+    
+    
+            // ticked: function (vwfTime) {
+            // }
+    
+        });
+    
+
+    }
+}
+
+export { OhmViewDriver as default }

BIN
public/defaults/assets/webimg.jpg


+ 29 - 3
public/defaults/proxy/aframe/ascene.js

@@ -468,6 +468,32 @@ this.createModelObj = function (mtlSrc, objSrc, name, avatar) {
 
 }
 
+
+this.createLegoBoost = function (boostID, parentName) {
+    var self = this;
+    let nodeName = 'legoboost-' + this.smallRandomId();
+
+    let legoBoostNode = {
+        "extends": "proxy/objects/legoboost.vwf",
+        "properties": {
+            "boostID": boostID,
+            "position": "0 1 0",
+            "displayName": boostID,
+            "tracking": false
+        }
+    }
+
+    let rootNode = parentName ? this.getChildByName(parentName) : this;
+    rootNode.children.create(nodeName, legoBoostNode, function( child ) {
+
+        child.createVisual();
+        child.trackLego();
+
+    })
+
+
+}
+
 this.createModel = function (modelType, modelSrc, avatar) {
 
     var self = this;
@@ -650,7 +676,7 @@ this.createPrimitive = function (type, params, name, node, avatar) {
             if (avatar) child.lookAt(self.children[avatar].worldPosition());
             
             if (type == "text"){
-                child.properties.font = "/vwf/model/aframe/fonts/custom-msdf.json"
+                child.properties.font = "/drivers/model/aframe/fonts/custom-msdf.json"
             }
           });
     }
@@ -830,7 +856,7 @@ this.createImage = function (imgSrc, name, node, avatar) {
     
         } else {
 
-            let allNodes = vwf.models["vwf/model/aframe"].model.state.nodes;
+            let allNodes = vwf.models["/drivers/model/aframe"].model.state.nodes;
             let imgAssetNode = allNodes[child.id];
     
             imgAssetNode.aframeObj.onload = function(){
@@ -924,7 +950,7 @@ this.createVideo = function (vidSrc, name, node, avatar) {
 
         } else {
 
-            let allNodes = vwf.models["vwf/model/aframe"].model.state.nodes;
+            let allNodes = vwf.models["/drivers/model/aframe"].model.state.nodes;
             let imgAssetNode = allNodes[child.id];
     
             imgAssetNode.aframeObj.onloadeddata = function(){

+ 6 - 0
public/defaults/proxy/aframe/ascene.vwf.json

@@ -138,6 +138,12 @@
         "width",
         "pos"
       ]
+    },
+    "createLegoBoost": {
+      "parameters": [
+        "boostID",
+        "parentName"
+      ]
     }
   },
   "scripts": {

+ 38 - 5
public/defaults/proxy/animation/animationNode.js

@@ -54,7 +54,7 @@ this.rotateBy = function(rotation, duration, frame) {
     let deltaQuaternion = (new THREE.Quaternion()).setFromEuler(new THREE.Euler(
        (THREE.Math.degToRad(rotationValue[0])),
        (THREE.Math.degToRad(rotationValue[1])),
-       (THREE.Math.degToRad(rotationValue[2])), 'YXZ'
+       (THREE.Math.degToRad(rotationValue[2])), 'XYZ'
      ));
      this.quaterniateBy( deltaQuaternion, duration, frame ); //@ sourceURL=node3.animation.rotateBy.vwf
 }
@@ -64,7 +64,7 @@ this.rotateTo = function(rotation, duration){
     let stopQuaternion = (new THREE.Quaternion()).setFromEuler(new THREE.Euler(
         (THREE.Math.degToRad(rotationValue[0])),
         (THREE.Math.degToRad(rotationValue[1])),
-        (THREE.Math.degToRad(rotationValue[2])), 'YXZ'
+        (THREE.Math.degToRad(rotationValue[2])), 'XYZ'
       ));
     this.quaterniateTo( stopQuaternion, duration ); //@ sourceURL=node3.animation.rotateTo.vwf
 }
@@ -74,7 +74,7 @@ this.quaterniateBy = function(quaternion, duration, frame) {
       this.startQuaternionSIM = (new THREE.Quaternion()).setFromEuler(new THREE.Euler(
           (THREE.Math.degToRad(this.rotation[0])),
           (THREE.Math.degToRad(this.rotation[1])),
-          (THREE.Math.degToRad(this.rotation[2])), 'YXZ'
+          (THREE.Math.degToRad(this.rotation[2])), 'XYZ'
         ));
       var deltaQuaternion = (new THREE.Quaternion).copy(quaternion);
       if ( ! frame || frame == "rotated" ) {
@@ -92,7 +92,7 @@ this.quaterniateBy = function(quaternion, duration, frame) {
           let step = (time >= duration) ? 1 : time / duration;
 
           THREE.Quaternion.slerp(this.startQuaternionSIM, this.stopQuaternionSIM, q, step || 0);
-          let interp = e.setFromQuaternion(q, 'YXZ');
+          let interp = e.setFromQuaternion(q, 'XYZ');
           this.rotation = [THREE.Math.radToDeg(interp.x), THREE.Math.radToDeg(interp.y), THREE.Math.radToDeg(interp.z)];
         }
         this.animationPlay(0, duration);
@@ -100,13 +100,46 @@ this.quaterniateBy = function(quaternion, duration, frame) {
       else {
         let eE = new THREE.Euler();
         let eQ = (new THREE.Quaternion).copy(this.stopQuaternionSIM);
-        let interpE = eE.setFromQuaternion(eQ, 'YXZ');
+        let interpE = eE.setFromQuaternion(eQ, 'XYZ');
         this.rotation = [THREE.Math.radToDeg(interpE.x),THREE.Math.radToDeg(interpE.y), THREE.Math.radToDeg(interpE.z)];
         //this.quaternion = this.stopQuaternionSIM;
       } //@ sourceURL=node3.animation.quaterniateBy.vwf
 
     }
   
+    this.quaterniateTo = function(quaternion, duration) {
+
+      this.startQuaternionSIM = (new THREE.Quaternion()).setFromEuler(new THREE.Euler(
+        (THREE.Math.degToRad(this.rotation[0])),
+        (THREE.Math.degToRad(this.rotation[1])),
+        (THREE.Math.degToRad(this.rotation[2])), 'XYZ'
+      ));
+    this.stopQuaternionSIM = (new THREE.Quaternion).copy(quaternion);
+    if(duration > 0) {
+      this.animationDuration = duration;
+      this.animationUpdate = function(time, duration) {
+
+        let q = new THREE.Quaternion();
+        let e = new THREE.Euler();
+        let step = (time >= duration) ? 1 : time / duration;
+
+        THREE.Quaternion.slerp(this.startQuaternionSIM, this.stopQuaternionSIM, q, step || 0);
+        let interp = e.setFromQuaternion(q, 'XYZ');
+        this.rotation = [THREE.Math.radToDeg(interp.x), THREE.Math.radToDeg(interp.y), THREE.Math.radToDeg(interp.z)];
+      }
+      this.animationPlay(0, duration);
+    }
+    else {
+      let eE = new THREE.Euler();
+      let eQ = (new THREE.Quaternion).copy(this.stopQuaternionSIM);
+      let interpE = eE.setFromQuaternion(eQ, 'XYZ');
+      this.rotation = [THREE.Math.radToDeg(interpE.x),THREE.Math.radToDeg(interpE.y), THREE.Math.radToDeg(interpE.z)];
+      //this.quaternion = this.stopQuaternionSIM;
+    } //@ sourceURL=node3.animation.quaterniateTo.vwf
+
+    }
+
+
     this.scaleBy = function(scale, duration){
 
       this.startScaleSIM = this.scale || goog.vec.Vec3.createFromValues( 1, 1, 1 );

+ 3 - 0
public/defaults/proxy/animation/animationNode.vwf.json

@@ -69,5 +69,8 @@
 },
   "events":{
     "changingTransformFromView": {}
+  },
+  "scripts": {
+    "source": "animationNode.js"
   }
 }

+ 167 - 0
public/defaults/proxy/objects/legoboost.js

@@ -0,0 +1,167 @@
+this.initialize = function () {
+}
+
+this.createVisual = function () {
+
+    let motorNode = function (position, rotation) {
+
+        let rot = rotation ? rotation : [0, 0, 0];
+
+        return {
+            "extends": "proxy/aframe/abox.vwf",
+            "properties": {
+                "position": position,
+                "rotation": rot,
+                "height": 0.05,
+                "width": 0.05,
+                "depth": 0.05
+            },
+            "children": {
+                "material": {
+                    "extends": "proxy/aframe/aMaterialComponent.vwf",
+                    "type": "component",
+                    "properties": {
+                        "color": "white",
+                        "transparent": true,
+                        "opacity": 0.5
+                    }
+                },
+
+                "vis": {
+                    "extends": "proxy/aframe/abox.vwf",
+                    "properties": {
+                        "height": 0.4,
+                        "width": 0.05,
+                        "depth": 0.4
+                    },
+                    "children": {
+                        "material": {
+                            "extends": "proxy/aframe/aMaterialComponent.vwf",
+                            "type": "component",
+                            "properties": {
+                                "color": "orange"
+                            }
+                        }
+                    }
+                }
+
+            }
+        }
+    }
+
+    let visNode = {
+        "extends": "proxy/aframe/abox.vwf",
+        "properties": {
+            "position": [0, 0, 0],
+            "rotation": [0, 0, 0],
+            "height": 0.5,
+            "width": 0.5,
+            "depth": 0.5
+        },
+        "children": {
+            "material": {
+                "extends": "proxy/aframe/aMaterialComponent.vwf",
+                "type": "component",
+                "properties": {
+                    "color": "green"
+
+                }
+            },
+            "motorA": motorNode([0.3, 0, 0]),
+            "motorB": motorNode([-0.3, 0, 0]),
+            "motorC": motorNode([0, 0, 0.3], [0, 90, 0])
+        }
+    }
+
+    this.children.create("visualNode", visNode);
+
+}
+
+this.setPitch = function (value) {
+    this.pitch = value;
+    let rot = this.visualNode.rotation;
+    this.visualNode.rotation = [this.pitch, rot[1], rot[2]];
+}
+
+this.getPitch = function () {
+    return this.pitch;
+}
+
+this.gotDeviceInfo = function (info, key) {
+    //got device info
+    console.log(info);
+
+    if (key == 'pitch' || key == 'roll') {
+
+        this.pitch = info.tilt.pitch;
+        this.roll = info.tilt.roll;
+
+    }
+
+    if (key == 'led') {
+        this.rawLed = this.led = info.led;
+        this.setVisualLed(this.led);
+    }
+
+    if (key == 'A' || key == 'B' || key == 'C') {
+
+        this.setVisualMotorRotation('motor' + key, info.ports[key].angle - this['motor' + key]);
+        this['rawMotor' + key] = this['motor' + key] = info.ports[key].angle;
+
+
+    }
+
+    return info
+
+}
+
+this.trackLego = function () {
+    if (this.tracking) {
+        this.getDeviceInfo('pitch');
+    }
+    this.future(0.2).trackLego();
+}
+
+
+this.setRoll = function (value) {
+    this.roll = value;
+    let rot = this.visualNode.rotation;
+    this.visualNode.rotation = [rot[0], rot[1], this.roll];
+}
+
+this.getRoll = function () {
+    return this.roll;
+}
+
+this.setLed = function (value, sync) {
+    //set led after lego boost action - in sat_led
+}
+
+this.setDelay = function (value, sync) {
+    //set delay after lego boost action - in sat_led
+}
+
+this.sat_setLed = function (value) {
+    this.getDeviceInfo('led');
+}
+
+this.setVisualLed = function (value) {
+    this.visualNode.material.color = value;
+}
+
+this.setVisualMotorRotation = function (port, angle, dutyCycle) {
+    //let rot = this.visualNode[motor].vis.rotation;
+    //TODO: remap dutyCycle (10,100, 1, 0.1)
+    this.visualNode[port].vis.rotateBy([angle, 0, 0], 0.1);
+}
+
+
+this.sat_setMotorAngle = function (port, angle, dutyCycle) {
+    this.getDeviceInfo(port);
+}
+
+this.setMotorAngle = function (port, angle, dutyCycle, sync) {
+}
+
+this.sat_setDelay = function (value) {
+}

+ 73 - 0
public/defaults/proxy/objects/legoboost.vwf.json

@@ -0,0 +1,73 @@
+{
+    "extends": "proxy/aframe/aentity.vwf",
+    "type": "legoboost",
+    "properties": {
+        "boostID": {
+            "set": "this.value = value; this.boostID = value; this.displayName = value",
+            "get": "return this.value"
+          },
+        "led": null,
+        "rawLed": null,
+          "roll": {
+            "set": "this.setRoll(value)",
+            "get": "return this.getRoll()"
+          },
+          "pitch": {
+            "set": "this.setPitch(value)",
+            "get": "return this.getPitch()"
+          },
+          "motorA": null,
+          "motorB": null,
+          "motorC": null,
+          "rawMotorA": null,
+          "rawMotorB": null,
+          "rawMotorC": null,
+          "tracking": null
+    },
+    "methods": {
+        "createVisual": {},
+        "gotDeviceInfo": {
+            "parameters": ["info", "key"],
+            "type": "application/javascript"
+        },
+        "getDeviceInfo": {
+            "parameters": ["value"],
+            "body": "\/\/get device info \n",
+            "type": "application/javascript"
+        },
+        "setLed": {
+            "parameters": ["value", "sync"],
+            "type": "application/javascript"
+        },
+        "setDelay": {
+            "parameters": ["value", "sync"],
+            "type": "application/javascript"
+        },
+        "sat_setLed": {
+            "parameters": ["value"]
+        },
+        "sat_setDelay": {
+            "parameters": ["value"]
+        },
+        "setMotorAngle":{
+            "parameters": ["port", "angle", "dutyCycle", "sync"]
+        },
+        "sat_setMotorAngle":{
+            "parameters": ["port", "angle", "dutyCycle"]
+        },
+        "setVisualMotorRotation":{
+            "parameters": ["port", "angle", "dutyCycle"]
+        },
+        "setVisualLed":{
+            "parameters": ["value"]
+        },
+        "getRoll":{},
+        "setRoll":{},
+        "getPitch":{},
+        "setPitch":{},
+        "trackLego":{}
+    },
+    "scripts": {
+        "source": "legoboost.js"
+    }
+}

+ 7 - 4
public/defaults/worlds/aframe-ar/index.vwf.config.json

@@ -3,11 +3,14 @@
     "title": "VWF & AFrame Example App"
   },
   "model": {
-    "vwf/model/aframe": {}
+    "/drivers/model/aframe": {},
+    "/drivers/model/aframeComponent": {}
   },
   "view": {
-    "vwf/view/aframe": null,
-    "vwf/view/aframe-ar-driver": null
+    "/drivers/view/aframe": null,
+    "/drivers/view/aframeComponent": null,
+    "/drivers/view/aframe-ar-driver": null
   }
 }
-  
+  
+

+ 0 - 21
public/defaults/worlds/aframe-ar/index.vwf.html

@@ -1,21 +0,0 @@
-<!DOCTYPE html>
-<html>
-
-<head>
-    <script type="text/javascript">
-
-        vwf_view.satProperty = function (nodeID, propertyName, propertyValue) {
-            if (propertyValue === undefined || propertyValue == null) {
-                return;
-            }
-
-        }
-
-
-    </script>
-</head>
-
-<body>
-</body>
-
-</html>

+ 1 - 1
public/defaults/worlds/aframe-ar/index.vwf.json

@@ -124,7 +124,7 @@
           },
           "methods": {
             "run": {
-              "body": "    var time = vwf.now;\n    let rot = this.rotation; //AFRAME.utils.coordinates.parse(this.rotation);\n    this.rotation = [rot[0], rot[1]+2, rot[2]];\n    this.future( 0.05 ).run();\n",
+              "body": "    var time = this.time;\n    let rot = this.rotation; //AFRAME.utils.coordinates.parse(this.rotation);\n    this.rotation = [rot[0], rot[1]+2, rot[2]];\n    this.future( 0.05 ).run();\n",
               "type": "application/javascript"
             }
           },

+ 4 - 2
public/defaults/worlds/aframe/index.vwf.config.json

@@ -3,9 +3,11 @@
     "title": "VWF & AFrame Example App"
   },
   "model": {
-    "vwf/model/aframe": {}
+    "/drivers/model/aframe": {},
+    "/drivers/model/aframeComponent": null
   },
   "view": {
-    "vwf/view/aframe": null
+    "/drivers/view/aframe": null,
+    "/drivers/view/aframeComponent": null
   }
 }

+ 0 - 19
public/defaults/worlds/aframe/index.vwf.html

@@ -1,19 +0,0 @@
-<!DOCTYPE html>
-<html>
-
-<head>
-    <script type="text/javascript">
-
-        vwf_view.satProperty = function (nodeID, propertyName, propertyValue) {
-            if (propertyValue === undefined || propertyValue == null) {
-                return;
-            }
-
-        }
-    </script>
-</head>
-
-<body>
-</body>
-
-</html>

+ 1 - 1
public/defaults/worlds/aframe/index.vwf.json

@@ -171,7 +171,7 @@
           },
           "methods": {
             "run": {
-              "body": "    var time = vwf.now;\n    let rot = this.rotation; //AFRAME.utils.coordinates.parse(this.rotation);\n    let pos = this.position; //AFRAME.utils.coordinates.parse(this.position);\n    this.position = [pos[0], pos[1], Math.sin(time)];\n    this.rotation = [rot[0], rot[1], Math.sin(time)*100];\n    this.future( 0.01 ).run();\n",
+              "body": "    var time = this.time;\n    let rot = this.rotation; //AFRAME.utils.coordinates.parse(this.rotation);\n    let pos = this.position; //AFRAME.utils.coordinates.parse(this.position);\n    this.position = [pos[0], pos[1], Math.sin(time)];\n    this.rotation = [rot[0], rot[1], Math.sin(time)*100];\n    this.future( 0.01 ).run();\n",
               "type": "application/javascript"
             }
           },

+ 5 - 3
public/defaults/worlds/aframe2/index.vwf.config.json

@@ -3,10 +3,12 @@
     "title": "VWF & AFrame Example App"
   },
   "model": {
-    "vwf/model/aframe": {}
+    "/drivers/model/aframe": {},
+    "/drivers/model/aframeComponent": {}
   },
   "view": {
-    "vwf/view/aframe": null,
-    "vwf/view/editor-new": null
+    "/drivers/view/aframe": null,
+    "/drivers/view/aframeComponent": null,
+    "/drivers/view/editor": null
   }
 }

+ 1 - 1
public/defaults/worlds/aframe2/index.vwf.json

@@ -254,7 +254,7 @@
           },
           "methods": {
             "run": {
-              "body": "    var time = vwf.now;\n    let pos = this.position; //AFRAME.utils.coordinates.parse(this.position);\n    this.position = [pos[0], pos[1], Math.sin(time)]\n    this.future( 0.01 ).run();\n",
+              "body": "    var time = this.time;\n    let pos = this.position; //AFRAME.utils.coordinates.parse(this.position);\n    this.position = [pos[0], pos[1], Math.sin(time)]\n    this.future( 0.01 ).run();\n",
               "type": "application/javascript"
             }
           },

+ 6 - 3
public/defaults/worlds/gearvr/index.vwf.config.json

@@ -3,10 +3,13 @@
     "title": "VWF & AFrame Example App"
   },
   "model": {
-    "vwf/model/aframe": {}
+    "/drivers/model/aframe": {},
+    "/drivers/model/aframeComponent": {}
   },
   "view": {
-    "vwf/view/aframe": null,
-    "vwf/view/editor-new": null
+    "/drivers/view/aframe": null,
+    "/drivers/view/aframeComponent": null,
+    "/drivers/view/editor": null,
+    "/drivers/view/webrtc": null
   }
 }

+ 105 - 0
public/defaults/worlds/lego-boost/appui.js

@@ -0,0 +1,105 @@
+//-----App ui-----
+
+function createApp() {
+
+    let self = this
+
+
+    return {
+        $cell: true,
+        $type: "div",
+        class: "propGrid max-width mdc-layout-grid mdc-layout-grid--align-left",
+        $components: [
+            {
+                $cell: true,
+                $type: "div",
+                class: "mdc-layout-grid__inner",
+                $components: [
+                    {
+                        $type: "div",
+                        class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-12",
+                        $components: [
+                            self.widgets.inputTextFieldOutlined({
+                                "id": 'deviceIDInput',
+                                "label": "Device ID",
+                                "value": window._LegoView.device.id,
+                                "change": function (e) {
+                                    window._LegoView.changeDeviceID(this.value);
+                                }
+                            })
+
+                        ]
+                    },
+
+                    {
+                        $cell: true,
+                        $type: "div",
+                        class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-12",
+                        $components: [
+                            self.widgets.buttonRaised(
+                                {
+                                    "label": _LegoView.isConnected() ? "Disconnect" : "Connect",
+                                    "onclick": function (e) {
+                                        if (!_LegoView.isConnected()) {
+                                            this.$text = 'Disconnect';
+                                            _LegoView.connect();
+                                        } else {
+                                            this.$text = 'Connect';
+                                            _LegoView.disconnect();
+                                        }
+                                    }
+                                }
+                            ),
+
+                            // _app.widgets.divider,
+                            // {
+                            //     $cell: true,
+                            //     $type: "button",
+                            //     class: "mdc-button mdc-button--raised",
+                            //     $text: "Start tracking",
+                            //     onclick: function (e) {
+
+                            //        // window._LegoView.testLED();
+
+                            //     }
+
+                            // },
+                            {
+                                $cell: true,
+                                $type: "button",
+                                class: "mdc-button mdc-button--raised",
+                                $text: "Create Robot device",
+                                onclick: function (e) {
+
+                                    let sceneID = vwf.find("", "/")[0];
+                                    let boostID = _LegoView.isConnected() ? _LegoView.device.id : 'none';
+                                    vwf_view.kernel.callMethod(sceneID, "createLegoBoost", [boostID]);
+
+
+                                }
+
+                            },
+                            _app.widgets.divider,
+                            {
+                                $cell: true,
+                                $type: "button",
+                                class: "mdc-button mdc-button--raised",
+                                $text: "Действие!",
+                                onclick: function (e) {
+
+                                    let sceneID = vwf.find("", "/")[0];
+                                    vwf_view.kernel.callMethod(sceneID, "doLegoBoostAction");
+
+
+                                }
+
+                            }
+
+                        ]
+                    }
+
+                ]
+            }
+        ]
+    }
+}

+ 16 - 0
public/defaults/worlds/lego-boost/index.vwf.config.json

@@ -0,0 +1,16 @@
+{
+    "info":{
+      "title": "VWF & AFrame Example App"
+    },
+    "model": {
+      "/drivers/model/aframe": null,
+      "/drivers/model/aframeComponent": null,
+      "/drivers/model/lego-boost": null
+    },
+    "view": {
+      "/drivers/view/aframe": null,
+      "/drivers/view/aframeComponent": null,
+      "/drivers/view/editor": null,
+      "/drivers/view/lego-boost": null
+    }
+  }

+ 121 - 0
public/defaults/worlds/lego-boost/index.vwf.json

@@ -0,0 +1,121 @@
+{
+  "extends": "proxy/aframe/ascene.vwf",
+  "properties":{},
+  "children": {
+    "floorTexture": {
+    "extends": "proxy/aframe/a-asset-image-item.vwf",
+    "properties": {
+        "itemID": "bg2",
+        "itemSrc": "/defaults/assets/checker.jpg"
+    }
+  },
+  "skyTexture": {
+    "extends": "proxy/aframe/a-asset-image-item.vwf",
+    "properties": {
+        "itemID": "sky",
+        "itemSrc": "/defaults/assets/skyes/sky3.jpg"
+    }
+  },
+    "fog": {
+      "extends": "proxy/aframe/aSceneFogComponent.vwf",
+      "type": "component",
+      "properties": {
+        "fogType": "linear",
+        "fogColor": "#ECECEC",
+        "far": 20,
+        "near": 0
+      }
+    },
+    "groundPlane": {
+      "extends": "proxy/aframe/aplane.vwf",
+      "properties": {
+        "height": "50",
+        "width": "50",
+        "rotation": [
+          -90,
+          0,
+          0
+        ]
+      },
+      "children": {
+        "material": {
+          "extends": "proxy/aframe/aMaterialComponent.vwf",
+          "properties": {
+            "wireframe": false,
+            "src": "#bg2",
+            "repeat": "10 10"
+          }
+        }
+      }
+    },
+    "myLight": {
+      "extends": "proxy/aframe/alight.vwf",
+      "properties": {
+        "type": "directional",
+        "color": "white",
+        "position": [
+          0,
+          3,
+          2
+        ],
+        "rotation": [
+          0,
+          0,
+          0
+        ]
+      }
+    },
+    "sky": {
+      "extends": "proxy/aframe/asky.vwf",
+      "children": {
+        "material": {
+          "extends": "proxy/aframe/aMaterialComponent.vwf",
+          "properties": {
+            "color": "#ECECEC",
+            "src": "#sky",
+            "fog": false,
+            "side": "back"
+          }
+        }
+      }
+    },
+    "spaceText": {
+      "extends": "proxy/aframe/atext.vwf",
+      "properties": {
+        "value": "WebRTC app",
+        "color": "white",
+        "position": [
+          -2,
+          2.5,
+          -2
+        ]
+      }
+    },
+    "box": {
+      "extends": "proxy/aframe/abox.vwf",
+      "properties": {
+        "position": [
+          0,
+          0,
+          -3
+        ],
+        "rotation": [
+          0,
+          0,
+          0
+        ],
+        "depth": "3",
+        "height": "0.2",
+        "width": "3"
+      },
+      "children": {
+        "material": {
+          "extends": "proxy/aframe/aMaterialComponent.vwf",
+          "properties": {
+            "color": "#3c7249"
+          }
+        }
+      }
+    }
+  }
+}

+ 14 - 0
public/defaults/worlds/lego-boost/info.json

@@ -0,0 +1,14 @@
+{
+    "info": {
+        "en": {
+            "title": "LEGO Boost app",
+            "imgUrl": "/defaults/worlds/webrtc/webimg.jpg",
+            "text": "Demo connecting to LEGO Boost"
+        },
+        "ru": {
+            "title": "LEGO Boost приложение",
+            "imgUrl": "/defaults/worlds/webrtc/webimg.jpg",
+            "text": "Пример приложения с использованием LEGO Boost"
+        }
+    }
+}

+ 5 - 3
public/defaults/worlds/multipixel/index.vwf.config.json

@@ -3,10 +3,12 @@
     "title": "VWF & AFrame Example App"
   },
   "model": {
-    "vwf/model/aframe": {}
+    "/drivers/model/aframe": {},
+    "/drivers/model/aframeComponent": null
   },
   "view": {
-    "vwf/view/aframe": null,
-    "vwf/view/editor-new": null
+    "/drivers/view/aframe": null,
+    "/drivers/view/aframeComponent": null,
+    "/drivers/view/editor": null
   }
 }

+ 1 - 43
public/defaults/worlds/multipixel/index.vwf.json

@@ -125,7 +125,7 @@
       },
       "methods": {
         "run": {
-          "body": "    var time = vwf.now;\n    let rot = this.rotation; //AFRAME.utils.coordinates.parse(this.rotation);\n    this.rotation = [rot[0], Math.sin(time)*50, rot[2]];\n    this.future( 0.01 ).run();\n",
+          "body": "    var time = this.time;\n    let rot = this.rotation; //AFRAME.utils.coordinates.parse(this.rotation);\n    this.rotation = [rot[0], Math.sin(time)*50, rot[2]];\n    this.future( 0.01 ).run();\n",
           "type": "application/javascript"
         }
       },
@@ -307,53 +307,11 @@
         "camera1": {
           "extends": "proxy/aframe/offsetcamera.vwf",
           "children": {
-            "cam": {
-              "extends": "proxy/aframe/acamera.vwf",
-              "properties": {
-                "look-controls-enabled": "false",
-                "wasd-controls-enabled": "false",
-                "user-height": "0"
-              },
-              "children": {
-                "viewoffset": {
-                  "extends": "proxy/aframe/viewOffsetCamera-component.vwf",
-                  "properties": {
-                    "fullWidth": 3000,
-                    "fullHeight": 2000,
-                    "xoffset": 0,
-                    "yoffset": 0,
-                    "subcamWidth": 1500,
-                    "subcamHeight": 2000
-                  }
-                }
-              }
-            }
           }
         },
         "camera2": {
           "extends": "proxy/aframe/offsetcamera.vwf",
           "children": {
-            "cam": {
-              "extends": "proxy/aframe/acamera.vwf",
-              "properties": {
-                "look-controls-enabled": "false",
-                "wasd-controls-enabled": "false",
-                "user-height": "0"
-              },
-              "children": {
-                "viewoffset": {
-                  "extends": "proxy/aframe/viewOffsetCamera-component.vwf",
-                  "properties": {
-                    "fullWidth": 3000,
-                    "fullHeight": 2000,
-                    "xoffset": 1500,
-                    "yoffset": 0,
-                    "subcamWidth": 1500,
-                    "subcamHeight": 2000
-                  }
-                }
-              }
-            }
           }
         }
       }

+ 5 - 3
public/defaults/worlds/ohmlang-calc/index.vwf.config.json

@@ -3,10 +3,12 @@
     "title": "VWF & AFrame Example App"
   },
   "model": {
-    "vwf/model/aframe": {}
+    "/drivers/model/aframe": {},
+    "/drivers/model/aframeComponent": null
   },
   "view": {
-    "vwf/view/aframe": null,
-    "vwf/view/editor-new": null
+    "/drivers/view/aframe": null,
+    "/drivers/view/aframeComponent": null,
+    "/drivers/view/editor": null
   }
 }

+ 5 - 3
public/defaults/worlds/ohmlang-lsys/index.vwf.config.json

@@ -3,10 +3,12 @@
     "title": "VWF & AFrame Example App"
   },
   "model": {
-    "vwf/model/aframe": {}
+    "/drivers/model/aframe": {},
+    "/drivers/model/aframeComponent": {}
   },
   "view": {
-    "vwf/view/aframe": null,
-    "vwf/view/editor-new": null
+    "/drivers/view/aframe": null,
+    "/drivers/view/aframeComponent": null,
+    "/drivers/view/editor": null
   }
 }

+ 12 - 9
public/defaults/worlds/ohmlang-lsys/index.vwf.html → public/defaults/worlds/ohmlang-lsys/index.vwf.js

@@ -1,8 +1,12 @@
-<!DOCTYPE html>
-<html>
+class UserView {
 
-<head>
-    <script type="text/javascript">
+    constructor(view) {
+        this.view = view;
+        this.init();
+    }
+    
+
+    init() {
 
         vwf_view.satProperty = function (nodeID, propertyName, propertyValue) {
             if (propertyValue === undefined || propertyValue == null) {
@@ -33,10 +37,9 @@
         }
 
 
-    </script>
-</head>
+    }
 
-<body>
-</body>
+       
+}
 
-</html>
+export {UserView as default}

+ 5 - 3
public/defaults/worlds/orchestra/index.vwf.config.json

@@ -3,10 +3,12 @@
     "title": "VWF & AFrame Example App"
   },
   "model": {
-    "vwf/model/aframe": {}
+    "/drivers/model/aframe": {},
+    "/drivers/model/aframeComponent": null
   },
   "view": {
-    "vwf/view/aframe": null,
-    "vwf/view/editor-new": null
+    "/drivers/view/aframe": null,
+    "/drivers/view/aframeComponent": null,
+    "/drivers/view/editor": null
   }
 }

+ 7 - 3
public/defaults/worlds/osc-example/index.vwf.config.json

@@ -3,10 +3,14 @@
     "title": "VWF & AFrame Example App"
   },
   "model": {
-    "vwf/model/aframe": {}
+    "/drivers/model/aframe": {},
+    "/drivers/model/aframeComponent": {},
+    "/drivers/model/osc": {}
   },
   "view": {
-    "vwf/view/aframe": null,
-    "vwf/view/editor-new": null
+    "/drivers/view/aframe": null,
+    "/drivers/view/aframeComponent": null,
+    "/drivers/view/editor": null,
+    "/drivers/view/osc": null
   }
 }

+ 5 - 3
public/defaults/worlds/paint/index.vwf.config.json

@@ -3,10 +3,12 @@
     "title": "Paint App"
   },
   "model": {
-    "vwf/model/aframe": {}
+    "/drivers/model/aframe": {},
+    "/drivers/model/aframeComponent": null
   },
   "view": {
-    "vwf/view/aframe": null,
-    "vwf/view/editor-new": null
+    "/drivers/view/aframe": null,
+    "/drivers/view/aframeComponent": null,
+    "/drivers/view/editor": null
   }
 }

+ 15 - 0
public/defaults/worlds/pure/appui.js

@@ -0,0 +1,15 @@
+//-----App ui-----
+
+function createApp() {
+    
+        let self = this
+    
+        return {
+            $cell: true,
+            $type: "div",
+            class: "propGrid max-width mdc-layout-grid mdc-layout-grid--align-left",
+            $components: []
+        }
+
+    }
+    

+ 10 - 0
public/defaults/worlds/pure/index.vwf.config.json

@@ -0,0 +1,10 @@
+{
+  "info":{
+    "title": "Pure App"
+  },
+  "model": {
+  },
+  "view": {
+    "/drivers/view/editor": null
+  }
+}

+ 48 - 0
public/defaults/worlds/pure/index.vwf.js

@@ -0,0 +1,48 @@
+import { h, text,patch } from "$host/lib/ui/superfine.js"
+
+class UserView {
+
+    constructor(view) {
+        this.view = view;
+        this.init();
+    }
+    
+
+    init() {
+        let self = this;
+
+        vwf_view.initializedNode = function (nodeID, childID, childExtendsID, childImplementsIDs,
+            childSource, childType, childIndex, childName) {
+            
+            if (childID == vwf_view.kernel.application()) {
+                let el = document.createElement("pure");
+                el.setAttribute("id", childID);
+                document.querySelector("body").appendChild(el);
+            }
+        }
+
+        vwf_view.satProperty = function (nodeID, propertyName, propertyValue) {
+
+            if (propertyValue === undefined || propertyValue == null) {
+                return;
+            }
+            let el = document.querySelector("[id='" + nodeID + "']");
+
+            if (!el)
+                return
+
+            if (propertyName == 'pure') {
+                self.updatePure(propertyValue, el)
+            }
+        }
+    }
+
+    updatePure(state, el) {
+        patch(
+            el, h("pure", {style: "position: absolute; top: 100px;" }, [
+                    h("h1", {}, text(state))
+            ]));
+    }
+}
+
+export {UserView as default}

+ 16 - 0
public/defaults/worlds/pure/index.vwf.json

@@ -0,0 +1,16 @@
+{
+  "extends": "proxy/node.vwf",
+  "properties": {
+    "pure": 1
+  },
+  "methods":{
+    "initialize": {
+			"body": "this.run();\n",
+			"type": "application/javascript"
+		},
+    "run": {
+      "body": "this.pure = this.time; \n console.log(this.pure);\n this.future(1).run(); \n ",
+			"type": "application/javascript"
+    }
+  }
+}

+ 14 - 0
public/defaults/worlds/pure/info.json

@@ -0,0 +1,14 @@
+{
+    "info": {
+        "en": {
+            "title": "Pure app",
+            "imgUrl": "/defaults/assets/webimg.jpg",
+            "text": "Pure app example"
+        },
+        "ru": {
+            "title": "Pure app",
+            "imgUrl": "/defaults/assets/webimg.jpg",
+            "text": "Pure app example"
+        }
+    }
+}

+ 6 - 4
public/defaults/worlds/webrtc/index.vwf.config.json

@@ -3,11 +3,13 @@
       "title": "VWF & AFrame Example App"
     },
     "model": {
-      "vwf/model/aframe": {}
+      "/drivers/model/aframe": {},
+      "/drivers/model/aframeComponent": null
     },
     "view": {
-      "vwf/view/aframe": null,
-      "vwf/view/editor-new": null,
-      "vwf/view/webrtc": null
+      "/drivers/view/aframe": null,
+      "/drivers/view/aframeComponent": null,
+      "/drivers/view/editor": null,
+      "/drivers/view/webrtc": null
     }
   }

+ 0 - 129
public/domReady.js

@@ -1,129 +0,0 @@
-/**
- * @license RequireJS domReady 2.0.1 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
- * Available via the MIT or new BSD license.
- * see: http://github.com/requirejs/domReady for details
- */
-/*jslint */
-/*global require: false, define: false, requirejs: false,
-  window: false, clearInterval: false, document: false,
-  self: false, setInterval: false */
-
-
-define(function () {
-    'use strict';
-
-    var isTop, testDiv, scrollIntervalId,
-        isBrowser = typeof window !== "undefined" && window.document,
-        isPageLoaded = !isBrowser,
-        doc = isBrowser ? document : null,
-        readyCalls = [];
-
-    function runCallbacks(callbacks) {
-        var i;
-        for (i = 0; i < callbacks.length; i += 1) {
-            callbacks[i](doc);
-        }
-    }
-
-    function callReady() {
-        var callbacks = readyCalls;
-
-        if (isPageLoaded) {
-            //Call the DOM ready callbacks
-            if (callbacks.length) {
-                readyCalls = [];
-                runCallbacks(callbacks);
-            }
-        }
-    }
-
-    /**
-     * Sets the page as loaded.
-     */
-    function pageLoaded() {
-        if (!isPageLoaded) {
-            isPageLoaded = true;
-            if (scrollIntervalId) {
-                clearInterval(scrollIntervalId);
-            }
-
-            callReady();
-        }
-    }
-
-    if (isBrowser) {
-        if (document.addEventListener) {
-            //Standards. Hooray! Assumption here that if standards based,
-            //it knows about DOMContentLoaded.
-            document.addEventListener("DOMContentLoaded", pageLoaded, false);
-            window.addEventListener("load", pageLoaded, false);
-        } else if (window.attachEvent) {
-            window.attachEvent("onload", pageLoaded);
-
-            testDiv = document.createElement('div');
-            try {
-                isTop = window.frameElement === null;
-            } catch (e) {}
-
-            //DOMContentLoaded approximation that uses a doScroll, as found by
-            //Diego Perini: http://javascript.nwbox.com/IEContentLoaded/,
-            //but modified by other contributors, including jdalton
-            if (testDiv.doScroll && isTop && window.external) {
-                scrollIntervalId = setInterval(function () {
-                    try {
-                        testDiv.doScroll();
-                        pageLoaded();
-                    } catch (e) {}
-                }, 30);
-            }
-        }
-
-        //Check if document already complete, and if so, just trigger page load
-        //listeners. Latest webkit browsers also use "interactive", and
-        //will fire the onDOMContentLoaded before "interactive" but not after
-        //entering "interactive" or "complete". More details:
-        //http://dev.w3.org/html5/spec/the-end.html#the-end
-        //http://stackoverflow.com/questions/3665561/document-readystate-of-interactive-vs-ondomcontentloaded
-        //Hmm, this is more complicated on further use, see "firing too early"
-        //bug: https://github.com/requirejs/domReady/issues/1
-        //so removing the || document.readyState === "interactive" test.
-        //There is still a window.onload binding that should get fired if
-        //DOMContentLoaded is missed.
-        if (document.readyState === "complete") {
-            pageLoaded();
-        }
-    }
-
-    /** START OF PUBLIC API **/
-
-    /**
-     * Registers a callback for DOM ready. If DOM is already ready, the
-     * callback is called immediately.
-     * @param {Function} callback
-     */
-    function domReady(callback) {
-        if (isPageLoaded) {
-            callback(doc);
-        } else {
-            readyCalls.push(callback);
-        }
-        return domReady;
-    }
-
-    domReady.version = '2.0.1';
-
-    /**
-     * Loader Plugin API method
-     */
-    domReady.load = function (name, req, onLoad, config) {
-        if (config.isBuild) {
-            onLoad(null);
-        } else {
-            domReady(onLoad);
-        }
-    };
-
-    /** END OF PUBLIC API **/
-
-    return domReady;
-});

+ 1604 - 0
public/drivers/model/aframe.js

@@ -0,0 +1,1604 @@
+//"use strict";
+/*
+The MIT License (MIT)
+Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+
+Virtual World Framework Apache 2.0 license  (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
+*/
+
+// VWF & A-Frame model driver
+
+import {Fabric} from '/core/vwf/fabric.js';
+
+class AFrameModel extends Fabric {
+
+    constructor(module) {
+        console.log("AFrame constructor");
+        super(module, "Model");
+    }
+
+    factory() {
+        let _self_ = this;
+
+        return this.load(this.module, {
+
+            // == Module Definition ====================================================================
+
+            // -- initialize ---------------------------------------------------------------------------
+
+            initialize: function () {
+
+                let self = this;
+
+                this.state = {
+                    nodes: {},
+                    scenes: {},
+                    prototypes: {},
+                    createLocalNode: function (nodeID, childID, childExtendsID, childImplementsIDs,
+                        childSource, childType, childIndex, childName, callback) {
+                        return {
+                            "parentID": nodeID,
+                            "ID": childID,
+                            "extendsID": childExtendsID,
+                            "implementsIDs": childImplementsIDs,
+                            "source": childSource,
+                            "type": childType,
+                            "name": childName,
+                            "prototypes": undefined,
+                            "aframeObj": undefined,
+                            "scene": undefined,
+                            "componentName": undefined,
+                            "events": {
+                                "clickable": false
+                            }
+                        };
+                    },
+                    isAFrameClass: function (prototypes, classID) {
+                        if (prototypes) {
+                            for (var i = 0; i < prototypes.length; i++) {
+                                if (prototypes[i] === classID) {
+                                    //console.info( "prototypes[ i ]: " + prototypes[ i ] );
+                                    return true;
+                                }
+                            }
+                        }
+                        return false;
+                    },
+                    isAFrameComponent: function (prototypes) {
+                        var found = false;
+                        if (prototypes) {
+                            for (var i = 0; i < prototypes.length && !found; i++) {
+                                found = (prototypes[i] === "proxy/aframe/node.vwf");
+                            }
+                        }
+                        return found;
+                    },
+                    isNodeDefinition: function (prototypes) {
+                        var found = false;
+                        if (prototypes) {
+                            for (var i = 0; i < prototypes.length && !found; i++) {
+                                found = (prototypes[i] == "proxy/aframe/node.vwf");
+                            }
+                        }
+                        return found;
+                    },
+
+                    isAEntityDefinition: function (prototypes) {
+                        var found = false;
+                        if (prototypes) {
+                            for (var i = 0; i < prototypes.length && !found; i++) {
+                                found = (prototypes[i] == "proxy/aframe/aentity.vwf");
+                            }
+                        }
+                        return found;
+                    },
+
+                    setAFrameProperty: function (propertyName, propertyValue, aframeObject) {
+                        //console.log(propertyValue);
+                        if (propertyValue.hasOwnProperty('x')) {
+                            aframeObject.setAttribute(propertyName, propertyValue)
+                        } else
+                        if (Array.isArray(propertyValue)) {
+                            aframeObject.setAttribute(propertyName, {
+                                x: propertyValue[0],
+                                y: propertyValue[1],
+                                z: propertyValue[2]
+                            })
+                        } else if (typeof propertyValue === 'string') {
+
+                            propertyValue.includes(',') ? aframeObject.setAttribute(propertyName, AFRAME.utils.coordinates.parse(propertyValue.split(',').join(' '))) : aframeObject.setAttribute(propertyName, AFRAME.utils.coordinates.parse(propertyValue))
+
+                            //aframeObject.setAttribute(propertyName, AFRAME.utils.coordinates.parse(propertyValue))
+                        } else if (propertyValue.hasOwnProperty('0')) {
+                            aframeObject.setAttribute(propertyName, {
+                                x: propertyValue[0],
+                                y: propertyValue[1],
+                                z: propertyValue[2]
+                            })
+                        }
+
+                    },
+                    setFromValue: function (propertyValue) {
+
+                        var value = goog.vec.Vec3.create();
+                        if (propertyValue.hasOwnProperty('x')) {
+                            value = goog.vec.Vec3.createFromValues(propertyValue.x, propertyValue.y, propertyValue.z)
+                        } else if (Array.isArray(propertyValue) || propertyValue instanceof Float32Array) {
+                            value = goog.vec.Vec3.createFromArray(propertyValue);
+                        } else if (typeof propertyValue === 'string') {
+
+                            let val = propertyValue.includes(',') ? AFRAME.utils.coordinates.parse(propertyValue.split(',').join(' ')) : AFRAME.utils.coordinates.parse(propertyValue);
+                            value = goog.vec.Vec3.createFromValues(val.x, val.y, val.z)
+
+                            // let val = AFRAME.utils.coordinates.parse(propertyValue);
+                            // value = goog.vec.Vec3.createFromValues(val.x, val.y, val.z)
+
+
+                        } else if (propertyValue.hasOwnProperty('0')) {
+                            value = goog.vec.Vec3.createFromValues(propertyValue[0], propertyValue[1], propertyValue[2])
+                        }
+
+                        return value
+                    },
+
+                    updateStoredTransformFor: function (node, propertyName) {
+
+                        if (node && node.aframeObj && node.aframeObj.object3D) {
+                            // Add a local model-side transform that can stay pure even if the view changes the
+                            // transform on the threeObject - this already happened in creatingNode for those nodes that
+                            // didn't need to load a model
+                            if (!node.transform)
+                                node.transform = {};
+
+                            if (propertyName == 'position') {
+                                let pos = (new THREE.Vector3()).copy(node.aframeObj.object3D.position);
+                                node.transform.position = goog.vec.Vec3.createFromValues(pos.x, pos.y, pos.z);
+                                node.transform.storedPositionDirty = false;
+                            }
+
+                            if (propertyName == 'rotation') {
+                                // let rot = (new THREE.Vector3()).copy(node.aframeObj.object3D.rotation);
+                                let rot = node.aframeObj.getAttribute('rotation');
+                                node.transform.rotation = goog.vec.Vec3.createFromValues(rot.x, rot.y, rot.z);
+                                node.transform.storedRotationDirty = false;
+                            }
+
+                            if (propertyName == 'scale') {
+                                let scale = (new THREE.Vector3()).copy(node.aframeObj.object3D.scale);
+                                node.transform.scale = goog.vec.Vec3.createFromValues(scale.x, scale.y, scale.z);
+                                node.transform.storedScaleDirty = false;
+                            }
+
+                            //node.transform.position = AFRAME.utils.coordinates.stringify(node.aframeObj.object3D.position);
+
+                            // node.transform.rotation = AFRAME.utils.coordinates.stringify(node.aframeObj.object3D.rotation);
+                            // node.storedTransformDirty = false;             
+                        }
+
+                    },
+
+
+                    addNodeToHierarchy: function (node) {
+
+                        if (node.aframeObj) {
+                            if (this.nodes[node.parentID] !== undefined) {
+                                var parent = this.nodes[node.parentID];
+                                if (parent.aframeObj) {
+
+                                    if (parent.children === undefined) {
+                                        parent.children = [];
+                                    }
+                                    parent.children.push(node.ID);
+                                    //console.info( "Adding child: " + childID + " to " + nodeID );
+                                    if (node.aframeObj.nodeName !== "A-ASSET-ITEM" &&
+                                        node.aframeObj.nodeName !== "IMG" &&
+                                        node.aframeObj.nodeName !== "AUDIO" &&
+                                        node.aframeObj.nodeName !== "VIDEO"
+                                    ) {
+                                        parent.aframeObj.appendChild(node.aframeObj);
+                                    }
+                                }
+                            }
+                            if (node.aframeObj.nodeName !== "A-SCENE") {
+                                node.scene = this.scenes[self.kernel.application()];
+                            }
+
+                        }
+
+                    },
+
+                    createAFrameObject: function (node, config) {
+
+                        var protos = node.prototypes;
+                        var aframeObj = undefined;
+
+                        if (this.isAFrameClass(protos, "proxy/aframe/ascene.vwf")) {
+                            aframeObj = document.createElement('a-scene');
+                            let assetsElement = document.createElement('a-assets');
+                            aframeObj.appendChild(assetsElement);
+                            aframeObj.setAttribute('scene-utils', "");
+                            aframeObj.setAttribute('light', 'defaultLightsEnabled', false);
+                            //aframeObj.setAttribute('embedded', {});
+                            //aframeObj.setAttribute('loading-screen', "backgroundColor: black");
+                            this.scenes[node.ID] = aframeObj;
+                            //TODO: move from veiw here
+                            //document.body.appendChild(aframeObj);
+
+                        } else if (this.isAFrameClass(protos, "proxy/aframe/a-asset-item.vwf")) {
+
+                            let assets = document.querySelector('a-assets');
+                            if (assets) {
+
+                                aframeObj = document.createElement('a-asset-item');
+                                aframeObj.setAttribute('id', "item-" + _self_.helpers.GUID());
+                                aframeObj.setAttribute('src', "");
+                                aframeObj.setAttribute('crossorigin', "anonymous");
+                                assets.appendChild(aframeObj);
+                            }
+
+                        } else if (this.isAFrameClass(protos, "proxy/aframe/a-asset-image-item.vwf")) {
+
+                            let assets = document.querySelector('a-assets');
+                            if (assets) {
+                                let elID = "item-" + _self_.helpers.GUID();
+                                aframeObj = document.createElement('img');
+                                aframeObj.setAttribute('id', elID);
+                                aframeObj.setAttribute('src', "");
+                                aframeObj.setAttribute('crossorigin', "anonymous");
+                                assets.appendChild(aframeObj);
+                            }
+
+                        } else if (this.isAFrameClass(protos, "proxy/aframe/a-asset-audio-item.vwf")) {
+
+                            let assets = document.querySelector('a-assets');
+                            if (assets) {
+
+                                aframeObj = document.createElement('audio');
+                                aframeObj.setAttribute('id', "item-" + _self_.helpers.GUID());
+                                aframeObj.setAttribute('src', "");
+                                aframeObj.setAttribute('crossorigin', "anonymous");
+                                assets.appendChild(aframeObj);
+                            }
+
+
+                        } else if (this.isAFrameClass(protos, "proxy/aframe/a-asset-video-item.vwf")) {
+
+                            let assets = document.querySelector('a-assets');
+                            if (assets) {
+
+                                aframeObj = document.createElement('video');
+                                aframeObj.setAttribute('id', "item-" + _self_.helpers.GUID());
+                                aframeObj.setAttribute('src', "");
+                                aframeObj.setAttribute('crossorigin', "anonymous");
+                                aframeObj.setAttribute('autoplay', "");
+                                aframeObj.setAttribute('loop', true);
+
+                                assets.appendChild(aframeObj);
+                            }
+
+                        } else if (this.isAFrameClass(protos, "proxy/aframe/asky.vwf")) {
+                            aframeObj = document.createElement('a-sky');
+                        } else if (this.isAFrameClass(protos, "proxy/aframe/a-arjs-anchor.vwf")) {
+                            aframeObj = document.createElement('a-anchor');
+
+                        } else if (this.isAFrameClass(protos, "proxy/aframe/alight.vwf")) {
+                            aframeObj = document.createElement('a-light');
+                        } else if (this.isAFrameClass(protos, "proxy/aframe/acursor.vwf")) {
+                            aframeObj = document.createElement('a-cursor');
+                        } else if (this.isAFrameClass(protos, "proxy/aframe/a-sun-sky.vwf")) {
+                            aframeObj = document.createElement('a-sun-sky');
+                        } else if (this.isAFrameClass(protos, "proxy/aframe/abox.vwf")) {
+                            aframeObj = document.createElement('a-box');
+                        } else if (this.isAFrameClass(protos, "proxy/aframe/aplane.vwf")) {
+                            aframeObj = document.createElement('a-plane');
+                        } else if (this.isAFrameClass(protos, "proxy/aframe/acylinder.vwf")) {
+                            aframeObj = document.createElement('a-cylinder');
+                        } else if (this.isAFrameClass(protos, "proxy/aframe/acone.vwf")) {
+                            aframeObj = document.createElement('a-cone');
+                        } else if (this.isAFrameClass(protos, "proxy/aframe/atext.vwf")) {
+                            aframeObj = document.createElement('a-text');
+                        } else if (this.isAFrameClass(protos, "proxy/aframe/acolladamodel.vwf")) {
+                            aframeObj = document.createElement('a-collada-model');
+                        } else if (this.isAFrameClass(protos, "proxy/aframe/aobjmodel.vwf")) {
+                            aframeObj = document.createElement('a-obj-model');
+                        } else if (this.isAFrameClass(protos, "proxy/aframe/agltfmodel.vwf")) {
+                            aframeObj = document.createElement('a-gltf-model');
+                        } else if (this.isAFrameClass(protos, "proxy/aframe/asphere.vwf")) {
+                            aframeObj = document.createElement('a-sphere');
+                        } else if (this.isAFrameClass(protos, "proxy/aframe/aanimation.vwf")) {
+                            aframeObj = document.createElement('a-animation');
+                        } else if (this.isAFrameClass(protos, "proxy/aframe/acamera.vwf")) {
+                            aframeObj = document.createElement('a-camera');
+                            aframeObj.setAttribute('camera', 'active', false);
+                        } else if (this.isAFrameClass(protos, "proxy/aframe/aentity.vwf")) {
+                            aframeObj = document.createElement('a-entity');
+                        }
+
+                        if (aframeObj.nodeName !== "A-ASSET-ITEM" &&
+                            aframeObj.nodeName !== "IMG" &&
+                            aframeObj.nodeName !== "AUDIO" &&
+                            aframeObj.nodeName !== "VIDEO"
+                        ) {
+                            aframeObj.setAttribute('id', node.ID);
+                        }
+
+
+                        return aframeObj;
+                    }
+                };
+
+                this.state.kernel = this.kernel.kernel.kernel;
+
+                this.aframeDef = {
+                    'A-BOX': [
+                        'depth', 'height', 'segments-depth',
+                        'segments-height', 'segments-width',
+                        'width'
+                    ],
+
+                    'A-SPHERE': [
+                        'phi-length', 'phi-start', 'radius',
+                        'segments-depth',
+                        'segments-height', 'segments-width',
+                        'theta-length', 'theta-start'
+                    ],
+
+                    'A-CYLINDER': [
+                        'height', 'radius',
+                        'open-ended', 'radius-bottom', 'radius-top',
+                        'segments-height', 'segments-radial',
+                        'theta-length', 'theta-start'
+                    ],
+
+                    'A-CONE': [
+                        'height',
+                        'open-ended', 'radius-bottom', 'radius-top',
+                        'segments-height', 'segments-radial',
+                        'theta-length', 'theta-start'
+                    ],
+
+                    'A-PLANE': [
+                        'height', 'segments-height', 'segments-width', 'width'
+                    ],
+
+                    'A-TEXT': [
+                        'align', 'alpha-test', 'anchor',
+                        'baseline', 'color', 'font',
+                        'font-image', 'height',
+                        'letter-spacing', 'line-height',
+                        'opacity', 'shader',
+                        'side', 'tab-size',
+                        'transparent', 'value',
+                        'white-space', 'width',
+                        'wrap-count', 'wrap-pixels',
+                        'z-offset', 'negate'
+                    ],
+
+                    'A-SKY': [
+                        'phi-length', 'phi-start', 'radius', 'segments-height',
+                        'segments-width',
+                        'theta-length', 'theta-start',
+                    ],
+
+                    'A-LIGHT': [
+                        'angle', 'color', 'decay', 'distance',
+                        'ground-color', 'intensity', 'penumbra',
+                        'type', 'target'
+                    ]
+                }
+
+
+
+            },
+
+            // == Model API ============================================================================
+
+            // -- creatingNode -------------------------------------------------------------------------
+
+            creatingNode: function (nodeID, childID, childExtendsID, childImplementsIDs,
+                childSource, childType, childIndex, childName, callback /* ( ready ) */ ) {
+
+                // If the parent nodeID is 0, this node is attached directly to the root and is therefore either 
+                // the scene or a prototype.  In either of those cases, save the uri of the new node
+                var childURI = (nodeID === 0 ? childIndex : undefined);
+                var appID = this.kernel.application();
+
+                // If the node being created is a prototype, construct it and add it to the array of prototypes,
+                // and then return
+                var prototypeID = _self_.utility.ifPrototypeGetId(appID, this.state.prototypes, nodeID, childID);
+                if (prototypeID !== undefined) {
+
+                    this.state.prototypes[prototypeID] = {
+                        parentID: nodeID,
+                        ID: childID,
+                        extendsID: childExtendsID,
+                        implementsID: childImplementsIDs,
+                        source: childSource,
+                        type: childType,
+                        name: childName
+                    };
+                    return;
+                }
+
+                var protos = _self_.constructor.getPrototypes(this.kernel, childExtendsID);
+                //var kernel = this.kernel.kernel.kernel;
+                var node;
+
+                if (this.state.isAFrameComponent(protos)) {
+
+                    // Create the local copy of the node properties
+                    if (this.state.nodes[childID] === undefined) {
+                        this.state.nodes[childID] = this.state.createLocalNode(nodeID, childID, childExtendsID, childImplementsIDs,
+                            childSource, childType, childIndex, childName, callback);
+                    }
+
+                    node = this.state.nodes[childID];
+                    node.prototypes = protos;
+
+                    // if (childType == "component") {
+                    //     if (nodeID !== undefined) {
+                    //         node.aframeObj =  setAFrameObjectComponents(node);
+                    //         addNodeToHierarchy(node);
+                    //     }
+                    // } else {
+
+                    node.aframeObj = this.state.createAFrameObject(node);
+                    this.state.addNodeToHierarchy(node);
+
+                    if (this.state.isAEntityDefinition(node.prototypes)) {
+                        //updateStoredTransform( node );
+                        this.state.updateStoredTransformFor(node, 'position');
+                        this.state.updateStoredTransformFor(node, 'rotation');
+                        this.state.updateStoredTransformFor(node, 'scale');
+                    }
+
+                    //notifyDriverOfPrototypeAndBehaviorProps();
+                    //  }
+
+                    //addNodeToHierarchy(node);
+                }
+
+
+
+            },
+
+            // initializingNode: function( nodeID, childID, childExtendsID, childImplementsIDs,
+            //     childSource, childType, childIndex, childName ) {
+            //     var node = this.state.nodes[childID];
+
+
+            //     if ( node && childType == "component" ) {
+
+            //     }
+            // },
+
+            // -- initializingProperty -----------------------------------------------------------------
+
+            initializingProperty: function (nodeID, propertyName, propertyValue) {
+
+                var value = undefined;
+                var node = this.state.nodes[nodeID];
+                if (node !== undefined) {
+                    value = this.settingProperty(nodeID, propertyName, propertyValue);
+                }
+                return value;
+            },
+
+            // -- creatingProperty ---------------------------------------------------------------------
+
+            creatingProperty: function (nodeID, propertyName, propertyValue) {
+
+                return this.initializingProperty(nodeID, propertyName, propertyValue);
+            },
+
+
+            // -- callingMethod ------------------------------------------------------------------------
+
+            callingMethod: function (nodeID, methodName, methodParameters) {
+
+                let self = this;
+                var node = this.state.nodes[nodeID];
+
+                if (!node) return;
+
+                if (node && node.aframeObj) {
+
+                    if (methodName == 'lookAt') {
+                        console.log('lookAt: ' + methodParameters[0]);
+                        let target = methodParameters[0];
+                        node.aframeObj.object3D.lookAt(new THREE.Vector3(target.x, target.y, target.z));
+                        let newRotation = node.aframeObj.getAttribute('rotation');
+                        self.kernel.setProperty(nodeID, "rotation", {
+                            x: 0,
+                            y: newRotation.y,
+                            z: 0
+                        });
+                    }
+
+                    if (methodName == 'worldRotation') {
+
+                        var worldQuat = new THREE.Quaternion();
+                        node.aframeObj.object3D.getWorldQuaternion(worldQuat);
+
+                        let angle = (new THREE.Euler()).setFromQuaternion(worldQuat, 'YXZ');
+                        let rotation = (new THREE.Vector3(THREE.Math.radToDeg(angle.x),
+                            THREE.Math.radToDeg(angle.y), THREE.Math.radToDeg(angle.z)));
+                        return rotation
+
+                    }
+
+                    if (methodName == 'worldPosition') {
+
+                        var position = new THREE.Vector3();
+
+                        node.aframeObj.object3D.getWorldPosition(position);
+                        return position
+
+                    }
+
+                }
+
+
+            },
+
+
+            // -- deletingNode -------------------------------------------------------------------------
+
+            //deletingNode: function( nodeID ) {
+            //},
+
+            // -- deletingNode -------------------------------------------------------------------------
+
+            deletingNode: function (nodeID) {
+
+                if (nodeID) {
+                    var childNode = this.state.nodes[nodeID];
+                    if (!childNode) return;
+
+
+                    if (childNode !== undefined) {
+
+                        if (childNode.children) {
+
+                            for (var i = 0; i < childNode.children.length; i++) {
+                                this.deletingNode(childNode.children[i]);
+                            }
+                        }
+
+                        if (childNode.aframeObj !== undefined) {
+                            // removes and destroys object
+                            childNode.aframeObj.parentNode.removeChild(childNode.aframeObj);
+                            childNode.aframeObj.destroy();
+                            childNode.aframeObj = undefined;
+                        }
+
+                        delete this.state.nodes[nodeID];
+                    }
+
+                }
+            },
+
+
+
+            // -- settingProperty ----------------------------------------------------------------------
+
+            settingProperty: function (nodeID, propertyName, propertyValue) {
+
+                let self = this;
+                var node = this.state.nodes[nodeID];
+                var value = undefined;
+
+                //    if (node.componentName == 'line') {
+                //        console.log(node.aframeObj);
+                //    }
+
+                if (node && node.aframeObj && _self_.utility.validObject(propertyValue)) {
+
+                    var aframeObject = node.aframeObj;
+
+                    if (this.state.isNodeDefinition(node.prototypes)) {
+
+                        // 'id' will be set to the nodeID
+                        value = propertyValue;
+                        switch (propertyName) {
+
+                            default:
+                                value = undefined;
+                                break;
+                        }
+
+                    }
+
+
+                    if (value === undefined && this.state.isAEntityDefinition(node.prototypes)) {
+
+                        value = propertyValue;
+
+                        switch (propertyName) {
+
+                            case "position":
+
+                                if (!node.transform)
+                                node.transform = {};
+
+                                var position = this.state.setFromValue(propertyValue || []); //goog.vec.Vec3.createFromArray( propertyValue || [] );
+                                node.transform.position = goog.vec.Vec3.clone(position);
+                                //value = propertyValue;
+                                node.transform.storedPositionDirty = true;
+                                //setTransformsDirty( threeObject );
+                                //this.state.setAFrameProperty('position', propertyValue, aframeObject);
+                                break;
+
+                            case "rotation":
+
+                                if (!node.transform)
+                                node.transform = {};
+
+                                var rotation = this.state.setFromValue(propertyValue || []); //goog.vec.Vec3.createFromArray( propertyValue || [] );
+                                node.transform.rotation = goog.vec.Vec3.clone(rotation);
+                                //value = propertyValue;
+                                node.transform.storedRotationDirty = true;
+
+                                //this.state.setAFrameProperty('rotation', propertyValue, aframeObject);
+                                break;
+
+                            case "scale":
+
+                                if (!node.transform)
+                                node.transform = {};
+
+                                var scale = this.state.setFromValue(propertyValue || []); //goog.vec.Vec3.createFromArray( propertyValue || [] );
+                                node.transform.scale = goog.vec.Vec3.clone(scale);
+                                //value = propertyValue;
+                                node.transform.storedScaleDirty = true;
+                                //setTransformsDirty( threeObject );
+                                //this.state.setAFrameProperty('position', propertyValue, aframeObject);
+
+                                //this.state.setAFrameProperty('scale', propertyValue, aframeObject);
+                                break;
+
+
+                            case "animationTimeUpdated":
+                                if (node.transform) {
+                                    node.transform.storedPositionDirty = true;
+                                    node.transform.storedRotationDirty = true;
+                                    node.transform.storedScaleDirty = true;
+                                }
+
+                                break;
+
+                            case "clickable":
+                                if (propertyValue) {
+                                    aframeObject.setAttribute('class', 'intersectable');
+                                } else {
+                                    aframeObject.setAttribute('class', 'nonintersectable');
+                                }
+                                node.events.clickable = propertyValue;
+                                break;
+
+                            case "class":
+                                var newClasses = [];
+                                if (propertyValue.includes(',')) {
+                                    newClasses = propertyValue.split(',');
+                                } else {
+                                    newClasses = propertyValue.split(' ')
+                                }
+
+                                // let newClasses = propertyValue.split(','); //trim()
+                                aframeObject.setAttribute('class', "");
+                                newClasses.forEach(el => {
+                                    aframeObject.classList.add(el.trim());
+                                })
+                                break;
+
+                            case "ownedBy":
+                                aframeObject.setAttribute('ownedby', propertyValue);
+                                break;
+
+                            case "visible":
+                                aframeObject.setAttribute('visible', propertyValue);
+                                break;
+
+                                //  case "clickable":   
+                                //          console.log("set clickable!");
+                                //          value = propertyValue;
+                                //      break;
+
+                                // case "clickable":
+                                //     if (propertyValue) {
+                                //         aframeObject.addEventListener('click', function (evt) {
+                                //              console.log("click!");
+                                //             vwf_view.kernel.fireEvent(node.ID, "clickEvent",evt.detail.cursorEl.id);
+                                //         });
+                                //     }
+                                //     break;
+
+
+                                // case "look-controls-enabled":
+                                //     aframeObject.setAttribute('look-controls', 'enabled', propertyValue);
+                                //     break;
+                            case "wasd-controls":
+                                aframeObject.setAttribute('wasd-controls', 'enabled', propertyValue);
+                                break;
+
+                            default:
+                                value = undefined;
+                                break;
+                        }
+
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "A-ASSET-ITEM") {
+                        value = propertyValue;
+
+                        switch (propertyName) {
+
+                            case "itemID":
+                                aframeObject.setAttribute('id', propertyValue);
+                                break;
+
+                            case "itemSrc":
+                                aframeObject.setAttribute('src', propertyValue);
+                                break;
+
+                            default:
+                                value = undefined;
+                                break;
+                        }
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "IMG") {
+                        value = propertyValue;
+
+                        switch (propertyName) {
+
+                            case "itemID":
+                                aframeObject.setAttribute('id', propertyValue);
+                                break;
+
+                            case "itemSrc":
+                                aframeObject.setAttribute('src', propertyValue);
+                                break;
+
+                                // case "width":
+                                //     aframeObject.width = propertyValue;
+                                // break;
+
+                                // case "height":
+                                //     aframeObject.height = propertyValue;
+                                // break;
+
+                            default:
+                                value = undefined;
+                                break;
+                        }
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "AUDIO") {
+                        value = propertyValue;
+
+                        switch (propertyName) {
+
+                            case "itemID":
+                                aframeObject.setAttribute('id', propertyValue);
+                                break;
+
+                            case "itemSrc":
+                                aframeObject.setAttribute('src', propertyValue);
+                                break;
+
+                            default:
+                                value = undefined;
+                                break;
+                        }
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "VIDEO") {
+                        value = propertyValue;
+
+                        switch (propertyName) {
+
+                            case "itemID":
+                                aframeObject.setAttribute('id', propertyValue);
+                                break;
+
+                            case "itemSrc":
+                                aframeObject.setAttribute('src', propertyValue);
+                                break;
+
+                            default:
+                                value = undefined;
+                                break;
+                        }
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "A-SKY") {
+                        value = propertyValue;
+
+                        self.aframeDef['A-SKY'].forEach(element => {
+                            element == propertyName ? aframeObject.setAttribute(element, propertyValue) :
+                                value = undefined;
+                        })
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "A-TEXT") {
+                        value = propertyValue;
+
+                        //.filter(el=>el !== 'font')
+                        self.aframeDef['A-TEXT'].forEach(element => {
+
+
+                            element == propertyName ? aframeObject.setAttribute(element, propertyValue) :
+                                value = undefined;
+
+                        })
+
+                        // if(propertyName == 'font'){
+                        //     console.log('Loading font...', element);
+                        // }
+
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "A-SCENE") {
+                        value = propertyValue;
+
+                        switch (propertyName) {
+
+                            case "color":
+                                aframeObject.setAttribute('background', {
+                                    'color': propertyValue
+                                });
+                                break;
+
+                            case "transparent":
+                                aframeObject.setAttribute('background', {
+                                    'transparent': propertyValue
+                                });
+                                break;
+
+                                // case "fog":
+                                //     aframeObject.setAttribute('fog', propertyValue);
+                                //     break;
+                            case "assets":
+                                _self_.initAssets(propertyValue)
+                                break;
+
+
+                            default:
+                                value = undefined;
+                                break;
+                        }
+                    }
+
+
+                    if (value === undefined && aframeObject.nodeName == "A-BOX") {
+                        value = propertyValue;
+
+                        self.aframeDef['A-BOX'].forEach(element => {
+                            element == propertyName ? aframeObject.setAttribute(element, propertyValue) :
+                                value = undefined;
+                        })
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "A-LIGHT") {
+                        value = propertyValue;
+
+                        self.aframeDef['A-LIGHT'].forEach(element => {
+                            element == propertyName ? aframeObject.setAttribute(element, propertyValue) :
+                                value = undefined;
+                        })
+
+                        switch (propertyName) {
+                            case "castShadow":
+                                aframeObject.setAttribute('light', 'castShadow', propertyValue);
+                                break;
+
+                            case "shadowCameraVisible":
+                                aframeObject.setAttribute('light', 'shadowCameraVisible', propertyValue);
+                                break;
+
+                            default:
+                                value = undefined;
+                                break;
+
+                        }
+
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "A-GLTF-MODEL") {
+                        value = propertyValue;
+
+                        switch (propertyName) {
+
+                            case "src":
+                                aframeObject.setAttribute('src', propertyValue);
+                                break;
+
+
+                            default:
+                                value = undefined;
+                                break;
+                        }
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "A-COLLADA-MODEL") {
+                        value = propertyValue;
+
+                        switch (propertyName) {
+
+                            case "src":
+                                aframeObject.setAttribute('src', propertyValue);
+                                break;
+
+
+                            default:
+                                value = undefined;
+                                break;
+                        }
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "A-OBJ-MODEL") {
+                        value = propertyValue;
+
+                        switch (propertyName) {
+
+                            case "src":
+                                aframeObject.setAttribute('src', propertyValue);
+                                break;
+
+                            case "mtl":
+                                aframeObject.setAttribute('mtl', propertyValue);
+                                break;
+
+                            default:
+                                value = undefined;
+                                break;
+                        }
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "A-PLANE") {
+                        value = propertyValue;
+
+                        self.aframeDef['A-PLANE'].forEach(element => {
+                            element == propertyName ? aframeObject.setAttribute(element, propertyValue) :
+                                value = undefined;
+                        })
+
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "A-SPHERE") {
+                        value = propertyValue;
+
+                        self.aframeDef['A-SPHERE'].forEach(element => {
+                            element == propertyName ? aframeObject.setAttribute(element, propertyValue) :
+                                value = undefined;
+                        })
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "A-CYLINDER") {
+                        value = propertyValue;
+
+                        self.aframeDef['A-CYLINDER'].forEach(element => {
+                            element == propertyName ? aframeObject.setAttribute(element, propertyValue) :
+                                value = undefined;
+                        })
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "A-CONE") {
+                        value = propertyValue;
+
+                        self.aframeDef['A-CONE'].forEach(element => {
+                            element == propertyName ? aframeObject.setAttribute(element, propertyValue) :
+                                value = undefined;
+                        })
+                    }
+
+
+                    if (value === undefined && aframeObject.nodeName == "A-ANIMATION") {
+                        value = propertyValue;
+                        switch (propertyName) {
+
+                            // attribute:
+                            // dur:
+                            // from:
+                            // to:
+                            // repeat:
+
+                            case "dur":
+                                aframeObject.setAttribute('dur', propertyValue);
+                                break;
+
+                            case "from":
+                                aframeObject.setAttribute('from', propertyValue);
+                                break;
+
+                            case "to":
+                                aframeObject.setAttribute('to', propertyValue);
+                                break;
+
+                            case "repeat":
+                                aframeObject.setAttribute('repeat', propertyValue);
+                                break;
+
+                            case "attribute":
+                                aframeObject.setAttribute('attribute', propertyValue);
+                                break;
+
+                            case "begin":
+                                aframeObject.setAttribute('begin', propertyValue);
+                                break;
+
+                            default:
+                                value = undefined;
+                                break;
+                        }
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "A-CAMERA") {
+                        value = propertyValue;
+                        switch (propertyName) {
+
+                            case "user-height":
+                                aframeObject.setAttribute('user-height', propertyValue);
+                                break;
+
+                            case "look-controls-enabled":
+                                aframeObject.setAttribute('look-controls-enabled', propertyValue);
+                                break;
+
+                            case "wasd-controls-enabled":
+                                aframeObject.setAttribute('wasd-controls-enabled', propertyValue);
+                                break;
+
+                                // case "active":
+                                //     aframeObject.setAttribute('camera', 'active', propertyValue);
+                                //        break;
+
+                            default:
+                                value = undefined;
+                                break;
+                        }
+                    }
+                    if (value === undefined && aframeObject.nodeName == "A-SUN-SKY") {
+                        value = propertyValue;
+                        switch (propertyName) {
+
+                            case "sunPosition":
+                                aframeObject.setAttribute('material', 'sunPosition', propertyValue);
+                                break;
+
+                                // case "active":
+                                //     aframeObject.setAttribute('camera', 'active', propertyValue);
+                                //        break;
+
+                            default:
+                                value = undefined;
+                                break;
+                        }
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "A-ANCHOR") {
+                        value = propertyValue;
+
+                        switch (propertyName) {
+                            case "changeMatrixMode":
+                                aframeObject.setAttribute('arjs-anchor', 'changeMatrixMode', propertyValue);
+                                break;
+
+                            case 'hit-testing-enabled':
+                                aframeObject.setAttribute('hit-testing-enabled', propertyValue);
+                                break;
+
+                            case 'preset':
+                                aframeObject.setAttribute('preset', propertyValue);
+                                break;
+
+                            case 'markerType':
+                                aframeObject.setAttribute('type', propertyValue);
+                                break;
+
+                            case 'markerValue':
+                                aframeObject.setAttribute('velue', propertyValue);
+                                break;
+
+                            default:
+                                value = undefined;
+                                break;
+
+                        }
+
+                    }
+
+
+                }
+                return value;
+            },
+
+            // -- gettingProperty ----------------------------------------------------------------------
+
+            gettingProperty: function (nodeID, propertyName, propertyValue) {
+
+                let self = this;
+                var node = this.state.nodes[nodeID];
+                var value = undefined;
+
+                if (node && node.aframeObj) {
+
+                    var aframeObject = node.aframeObj;
+
+                    if (this.state.isNodeDefinition(node.prototypes)) {
+                        switch (propertyName) {}
+                    }
+
+
+                    if (value === undefined && this.state.isAEntityDefinition(node.prototypes)) {
+
+                        switch (propertyName) {
+
+                            case "position":
+                                // var pos = aframeObject.getAttribute('position');
+                                // if (pos !== undefined) {
+                                //     value = pos//[pos.x, pos.y, pos.z]//AFRAME.utils.coordinates.stringify(pos);
+                                // }
+
+                                if (node.transform.position) {
+
+                                    if (node.transform.storedPositionDirty) {
+                                        this.state.updateStoredTransformFor(node, 'position');
+                                    }
+
+                                    value = goog.vec.Vec3.clone(node.transform.position);
+                                    //value =  node.transform.position;
+                                }
+                                break;
+
+                            case "rotation":
+
+                                if (node.transform.rotation) {
+
+                                    if (node.transform.storedRotationDirty) {
+                                        this.state.updateStoredTransformFor(node, 'rotation');
+                                    }
+
+                                    value = goog.vec.Vec3.clone(node.transform.rotation);
+
+                                    // var rot = aframeObject.getAttribute('rotation');
+                                    // if (rot !== undefined) {
+                                    //     value = rot//AFRAME.utils.coordinates.stringify(rot);
+                                    // }
+                                }
+
+                                break;
+
+
+                            case "scale":
+
+                                if (node.transform.scale) {
+
+                                    if (node.transform.storedScaleDirty) {
+                                        this.state.updateStoredTransformFor(node, 'scale');
+                                    }
+
+                                    value = goog.vec.Vec3.clone(node.transform.scale);
+                                    // var scale = aframeObject.getAttribute('scale');
+                                    // if (scale !== undefined) {
+                                    //     value = scale//AFRAME.utils.coordinates.stringify(scale);
+                                    // }
+                                }
+                                break;
+
+
+
+                            case "clickable":
+                                value = node.events.clickable;
+                                break;
+
+                            case "class":
+                                value = aframeObject.classList.toString();
+                                //aframeObject.getAttribute('class');
+                                break;
+
+                                // case "look-controls-enabled":
+                                //     var look = aframeObject.getAttribute('look-controls-enabled');
+                                //     if (look !== null && look !== undefined) {
+                                //         value = aframeObject.getAttribute('look-controls').enabled;
+                                //     }
+                                //     break;
+                                // case "wasd-controls":
+                                //     var wasd = aframeObject.getAttribute('wasd-controls');
+                                //     if (wasd !== null && wasd !== undefined) {
+                                //         value = aframeObject.getAttribute('wasd-controls').enabled;
+                                //     }
+                                //     break;
+
+                            case "ownedBy":
+                                value = aframeObject.getAttribute('ownedby');
+                                break;
+
+
+                            case "visible":
+                                value = aframeObject.getAttribute('visible');
+                                break;
+
+                        }
+                    }
+
+
+
+                    if (value === undefined && aframeObject.nodeName == "A-ASSET-ITEM") {
+
+                        switch (propertyName) {
+
+                            case "itemID":
+                                value = aframeObject.getAttribute('id');
+                                break;
+
+                            case "itemSrc":
+                                value = aframeObject.getAttribute('src');
+                                break;
+
+
+                        }
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "IMG") {
+
+                        switch (propertyName) {
+
+                            case "itemID":
+                                value = aframeObject.getAttribute('id');
+                                break;
+
+                            case "itemSrc":
+                                value = aframeObject.getAttribute('src');
+                                break;
+
+                            case "width":
+                                value = aframeObject.width;
+                                break;
+
+                            case "height":
+                                value = aframeObject.height;
+                                break;
+
+                        }
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "AUDIO") {
+
+                        switch (propertyName) {
+
+                            case "itemID":
+                                value = aframeObject.getAttribute('id');
+                                break;
+
+                            case "itemSrc":
+                                value = aframeObject.getAttribute('src');
+                                break;
+                        }
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "VIDEO") {
+
+                        switch (propertyName) {
+
+                            case "itemID":
+                                value = aframeObject.getAttribute('id');
+                                break;
+
+                            case "itemSrc":
+                                value = aframeObject.getAttribute('src');
+                                break;
+
+                            case "videoWidth":
+                                value = aframeObject.videoWidth;
+                                break;
+
+                            case "videoHeight":
+                                value = aframeObject.videoHeight;
+                                break;
+
+                        }
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "A-SCENE") {
+
+                        switch (propertyName) {
+                            // case "fog":
+                            //     value = aframeObject.getAttribute('fog');
+                            //     break;
+
+                            case "assets":
+                                break;
+                            case "color":
+                                if (aframeObject.getAttribute('background')) {
+                                    value = aframeObject.getAttribute('background').color;
+                                }
+                                break;
+
+                            case "transparent":
+                                if (aframeObject.getAttribute('background')) {
+                                    value = aframeObject.getAttribute('background').transparent;
+                                }
+                                break;
+                        }
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "A-SKY") {
+
+                        self.aframeDef['A-SKY'].forEach(element => {
+                            if (element == propertyName) {
+                                value = aframeObject.getAttribute(element);
+                            }
+                        })
+
+                    }
+
+
+
+                    if (value === undefined && aframeObject.nodeName == "A-LIGHT") {
+
+                        self.aframeDef['A-LIGHT'].forEach(element => {
+                            if (element == propertyName) {
+                                value = aframeObject.getAttribute(element);
+                            }
+                        })
+
+                        switch (propertyName) {
+                            case "castShadow":
+                                value = aframeObject.getAttribute('light').castShadow;
+                                break;
+
+                            case "shadowCameraVisible":
+                                value = aframeObject.getAttribute('light').shadowCameraVisible;
+                                break;
+
+                        }
+
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "A-BOX") {
+
+                        self.aframeDef['A-BOX'].forEach(element => {
+                            if (element == propertyName) {
+                                value = aframeObject.getAttribute(element);
+                            }
+                        })
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "A-PLANE") {
+
+                        self.aframeDef['A-PLANE'].forEach(element => {
+                            if (element == propertyName) {
+                                value = aframeObject.getAttribute(element);
+                            }
+                        })
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "A-SPHERE") {
+
+                        self.aframeDef['A-SPHERE'].forEach(element => {
+                            if (element == propertyName) {
+                                value = aframeObject.getAttribute(element);
+                            }
+                        })
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "A-CYLINDER") {
+
+                        self.aframeDef['A-CYLINDER'].forEach(element => {
+                            if (element == propertyName) {
+                                value = aframeObject.getAttribute(element);
+                            }
+                        })
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "A-CONE") {
+
+                        self.aframeDef['A-CONE'].forEach(element => {
+                            if (element == propertyName) {
+                                value = aframeObject.getAttribute(element);
+                            }
+                        })
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "A-TEXT") {
+
+                        self.aframeDef['A-TEXT'].forEach(element => {
+                            if (element == propertyName) {
+                                value = aframeObject.getAttribute(element);
+                            }
+                        })
+                    }
+
+
+                    if (value === undefined && aframeObject.nodeName == "A-ANIMATION") {
+
+
+                        switch (propertyName) {
+
+                            case "attribute":
+                                value = aframeObject.getAttribute('attribute');
+                                break;
+
+
+                            case "dur":
+                                value = aframeObject.getAttribute('dur');
+                                break;
+
+
+
+                            case "from":
+                                value = aframeObject.getAttribute('from');
+                                break;
+
+
+
+                            case "to":
+                                value = aframeObject.getAttribute('to');
+                                break;
+
+
+
+                            case "repeat":
+                                value = aframeObject.getAttribute('repeat');
+                                break;
+
+
+
+                            case "begin":
+                                value = aframeObject.getAttribute('begin');
+                                break;
+                        }
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "A-CAMERA") {
+
+
+                        switch (propertyName) {
+                            case "user-height":
+                                value = aframeObject.getAttribute('user-height');
+                                break;
+                            case "look-controls-enabled":
+                                value = aframeObject.getAttribute('look-controls-enabled');
+                                break;
+
+                            case "wasd-controls-enabled":
+                                value = aframeObject.getAttribute('wasd-controls-enabled');
+                                break;
+
+                        }
+
+                        //    switch (propertyName) {
+                        //         case "active":
+                        //             value = aframeObject.getAttribute('camera').active;
+                        //             break;
+                        //         }
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "A-SUN-SKY") {
+
+
+                        switch (propertyName) {
+                            case "sunPosition":
+                                value = aframeObject.getAttribute('material').sunPosition;
+                                break;
+                        }
+
+                        //    switch (propertyName) {
+                        //         case "active":
+                        //             value = aframeObject.getAttribute('camera').active;
+                        //             break;
+                        //         }
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "A-COLLADA-MODEL") {
+
+                        switch (propertyName) {
+                            case "src":
+                                value = aframeObject.getAttribute('src');
+                                break;
+                        }
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "A-OBJ-MODEL") {
+
+                        switch (propertyName) {
+                            case "src":
+                                value = aframeObject.getAttribute('src');
+                                break;
+                            case "mtl":
+                                value = aframeObject.getAttribute('mtl');
+                                break;
+                        }
+                    }
+
+
+                    if (value === undefined && aframeObject.nodeName == "A-GLTF-MODEL") {
+
+                        switch (propertyName) {
+                            case "src":
+                                value = aframeObject.getAttribute('src');
+                                break;
+                        }
+                    }
+
+                    if (value === undefined && aframeObject.nodeName == "A-ANCHOR") {
+
+                        switch (propertyName) {
+                            case "changeMatrixMode":
+                                value = aframeObject.getAttribute('arjs-anchor').changeMatrixMode;
+                                break;
+                            case "hit-testing-enabled":
+                                value = aframeObject.getAttribute('hit-testing-enabled');
+                                break;
+                            case "preset":
+                                value = aframeObject.getAttribute('preset');
+                                break;
+
+                            case 'markerType':
+                                value = aframeObject.getAttribute('type');
+                                break;
+
+                            case 'markerValue':
+                                value = aframeObject.getAttribute('value');
+                                break;
+
+
+                        }
+
+                    }
+
+
+
+                }
+
+                if (value !== undefined) {
+                    propertyValue = value;
+                }
+
+                return value;
+            }
+        });
+    }
+
+
+    async initAssets(propertyValue) {
+
+        let assetsEl = document.querySelector('a-assets');
+        if (!assetsEl) {
+            let newAssetsEl = document.createElement('a-assets');
+            aframeObject.appendChild(newAssetsEl);
+        }
+        var assetsElement = document.querySelector('a-assets');
+        if (propertyValue) {
+
+            let path = JSON.parse(localStorage.getItem('lcs_app')).path.public_path;
+            let worldName = path.slice(1);
+            let dbPath = propertyValue.split(".").join("_");
+
+            let userDB = _LCSDB.user(_LCS_WORLD_USER.pub);
+            userDB.get('worlds').get(worldName).get(dbPath).load(function (response) {
+                if (response) {
+
+                    if (Object.keys(response).length > 0) {
+                        //console.log(JSON.parse(response));
+                        let assets = (typeof (response) == 'object') ? response : JSON.parse(response);
+                        for (var prop in assets) {
+                            var elm = document.createElement(assets[prop].tag);
+                            elm.setAttribute('id', prop);
+                            elm.setAttribute('src', assets[prop].src);
+                            assetsElement.appendChild(elm);
+                        }
+                    }
+                }
+            });
+        }
+    }
+
+}
+
+export {
+    AFrameModel as default
+}
+

+ 0 - 0
public/vwf/model/aframe/addon/BVHLoader.js → public/drivers/model/aframe/addon/BVHLoader.js


+ 0 - 0
public/vwf/model/aframe/addon/SkyShader.js → public/drivers/model/aframe/addon/SkyShader.js


+ 0 - 0
public/vwf/model/aframe/addon/THREE.MeshLine.js → public/drivers/model/aframe/addon/THREE.MeshLine.js


+ 2 - 2
public/vwf/model/aframe/addon/TransformControls.js → public/drivers/model/aframe/addon/TransformControls.js

@@ -255,7 +255,7 @@ THREE.TransformControls = function ( camera, domElement ) {
 
 			if ( planeIntersect ) {
 
-				let viewDriver = vwf.views["vwf/view/aframeComponent"];
+				let viewDriver = vwf.views["/drivers/view/aframeComponent"];
 					viewDriver.interpolateView = false;
 
 					event.preventDefault();
@@ -510,7 +510,7 @@ THREE.TransformControls = function ( camera, domElement ) {
 
 		if ( pointer.button !== undefined && pointer.button !== 0 ) return;
 
-		let viewDriver = vwf.views["vwf/view/aframeComponent"];
+		let viewDriver = vwf.views["/drivers/view/aframeComponent"];
 			viewDriver.interpolateView = true;
 
 		if ( this.dragging && ( this.axis !== null ) ) {

+ 4 - 4
public/vwf/model/aframe/addon/aframe-components.js → public/drivers/model/aframe/addon/aframe-components.js

@@ -26,7 +26,7 @@ AFRAME.registerComponent('scene-utils', {
         //this.setCameraControl();
         const sceneEnterVR = (e) => {
 
-            let driver = vwf.views["vwf/view/aframe"];
+            let driver = vwf.views["/drivers/view/aframe"];
 
             // let avatarEl = document.querySelector('#avatarControlParent');
              let avatarID = 'avatar-' + vwf_view.kernel.moniker();
@@ -44,7 +44,7 @@ AFRAME.registerComponent('scene-utils', {
 
         const sceneExitVR = (e) => {
 
-            let driver = vwf.views["vwf/view/aframe"];
+            let driver = vwf.views["/drivers/view/aframe"];
             let avatarID = 'avatar-' + vwf_view.kernel.moniker();
 
             driver.hmd = false;
@@ -291,7 +291,7 @@ AFRAME.registerComponent('raycaster-listener', {
         this.intersected = false;
         this.casters = {}
         this.me = vwf_view.kernel.moniker();
-        this.driver = vwf.views["vwf/view/aframe"];
+        this.driver = vwf.views["/drivers/view/aframe"];
 
         this.el.addEventListener('raycaster-intersected', function (evt) {
 
@@ -646,7 +646,7 @@ AFRAME.registerComponent('streamsound', {
     init: function () {
         var self = this;
 
-        let driver = vwf.views["vwf/view/webrtc"];
+        let driver = vwf.views["/drivers/view/webrtc"];
 
         this.listener = null;
         this.stream = null;

+ 1 - 1
public/vwf/model/aframe/addon/aframe-interpolation.js → public/drivers/model/aframe/addon/aframe-interpolation.js

@@ -18,7 +18,7 @@ AFRAME.registerComponent('interpolation', {
 
   init: function () {
 
-    this.driver = vwf.views["vwf/view/aframeComponent"];
+    this.driver = vwf.views["/drivers/view/aframeComponent"];
     //this.el.sceneEl.components['scene-utils'].interpolationComponents[this.el.id] = this;
 
   },

+ 0 - 0
public/vwf/model/aframe/addon/aframe-sun-sky.js → public/drivers/model/aframe/addon/aframe-sun-sky.js


+ 0 - 0
public/vwf/model/aframe/addon/aframe-sun-sky.min.js → public/drivers/model/aframe/addon/aframe-sun-sky.min.js


+ 0 - 0
public/vwf/model/aframe/addon/aframe-teleport-controls.js → public/drivers/model/aframe/addon/aframe-teleport-controls.js


+ 0 - 0
public/vwf/model/aframe/addon/aframe-teleport-controls.min.js → public/drivers/model/aframe/addon/aframe-teleport-controls.min.js


+ 0 - 0
public/vwf/model/aframe/addon/three/BufferGeometryUtils.js → public/drivers/model/aframe/addon/three/BufferGeometryUtils.js


+ 0 - 0
public/vwf/model/aframe/addon/virtualgc/nipplejs.js → public/drivers/model/aframe/addon/virtualgc/nipplejs.js


+ 0 - 0
public/vwf/model/aframe/addon/virtualgc/virtual-gamepad-controls.css → public/drivers/model/aframe/addon/virtualgc/virtual-gamepad-controls.css


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 213 - 255
public/drivers/model/aframe/aframe-master.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
public/drivers/model/aframe/aframe-master.js.map


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 12 - 0
public/drivers/model/aframe/aframe-master.min.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
public/drivers/model/aframe/aframe-master.min.js.map


+ 0 - 0
public/vwf/model/aframe/extras/aframe-extras.controls.js → public/drivers/model/aframe/extras/aframe-extras.controls.js


+ 0 - 0
public/vwf/model/aframe/extras/aframe-extras.controls.min.js → public/drivers/model/aframe/extras/aframe-extras.controls.min.js


+ 0 - 0
public/vwf/model/aframe/extras/aframe-extras.js → public/drivers/model/aframe/extras/aframe-extras.js


+ 0 - 0
public/vwf/model/aframe/extras/aframe-extras.loaders.js → public/drivers/model/aframe/extras/aframe-extras.loaders.js


+ 0 - 0
public/vwf/model/aframe/extras/aframe-extras.loaders.min.js → public/drivers/model/aframe/extras/aframe-extras.loaders.min.js


+ 0 - 0
public/vwf/model/aframe/extras/aframe-extras.min.js → public/drivers/model/aframe/extras/aframe-extras.min.js


+ 0 - 0
public/vwf/model/aframe/extras/aframe-extras.misc.js → public/drivers/model/aframe/extras/aframe-extras.misc.js


+ 0 - 0
public/vwf/model/aframe/extras/aframe-extras.misc.min.js → public/drivers/model/aframe/extras/aframe-extras.misc.min.js


+ 0 - 0
public/vwf/model/aframe/extras/aframe-extras.pathfinding.js → public/drivers/model/aframe/extras/aframe-extras.pathfinding.js


+ 0 - 0
public/vwf/model/aframe/extras/aframe-extras.pathfinding.min.js → public/drivers/model/aframe/extras/aframe-extras.pathfinding.min.js


+ 0 - 0
public/vwf/model/aframe/extras/aframe-extras.primitives.js → public/drivers/model/aframe/extras/aframe-extras.primitives.js


+ 0 - 0
public/vwf/model/aframe/extras/aframe-extras.primitives.min.js → public/drivers/model/aframe/extras/aframe-extras.primitives.min.js


+ 0 - 0
public/vwf/model/aframe/extras/components/grab.js → public/drivers/model/aframe/extras/components/grab.js


+ 0 - 0
public/vwf/model/aframe/extras/components/grab.min.js → public/drivers/model/aframe/extras/components/grab.min.js


+ 0 - 0
public/vwf/model/aframe/extras/components/sphere-collider.js → public/drivers/model/aframe/extras/components/sphere-collider.js


+ 0 - 0
public/vwf/model/aframe/extras/components/sphere-collider.min.js → public/drivers/model/aframe/extras/components/sphere-collider.min.js


+ 0 - 0
public/vwf/model/aframe/fonts/custom-msdf.json → public/drivers/model/aframe/fonts/custom-msdf.json


+ 0 - 0
public/vwf/model/aframe/fonts/custom.png → public/drivers/model/aframe/fonts/custom.png


+ 0 - 0
public/vwf/model/aframe/kframe/aframe-aabb-collider-component.js → public/drivers/model/aframe/kframe/aframe-aabb-collider-component.js


+ 0 - 0
public/vwf/model/aframe/kframe/aframe-aabb-collider-component.min.js → public/drivers/model/aframe/kframe/aframe-aabb-collider-component.min.js


+ 1453 - 0
public/drivers/model/aframeComponent.js

@@ -0,0 +1,1453 @@
+//"use strict";
+/*
+The MIT License (MIT)
+Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+
+Virtual World Framework Apache 2.0 license  (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
+*/
+
+// VWF & A-Frame components model driver
+
+import {Fabric} from '/core/vwf/fabric.js';
+
+class AFrameComponentModel extends Fabric {
+
+    constructor(module) {
+        console.log("AFrameComponentModel constructor");
+        super(module, "Model");
+    }
+
+    factory() {
+        let _self_ = this;
+
+        return this.load(this.module, 
+            
+            {
+
+                // == Module Definition ====================================================================
+        
+                // -- pipeline -----------------------------------------------------------------------------
+        
+                // pipeline: [ log ], // vwf <=> log <=> scene
+        
+                // -- initialize ---------------------------------------------------------------------------
+        
+                initialize: function () {
+        
+                    let self = this;
+        
+                    this.state = {
+                        nodes: {},
+                        scenes: {},
+                        prototypes: {},
+                        createLocalNode: function (nodeID, childID, childExtendsID, childImplementsIDs,
+                            childSource, childType, childIndex, childName, callback) {
+                            return {
+                                "parentID": nodeID,
+                                "ID": childID,
+                                "extendsID": childExtendsID,
+                                "implementsIDs": childImplementsIDs,
+                                "source": childSource,
+                                "type": childType,
+                                "name": childName,
+                                "prototypes": undefined,
+                                "aframeObj": undefined,
+                                "componentName": undefined
+        
+                            };
+                        },
+        
+                        isComponentClass: function (prototypes, classID) {
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length; i++) {
+                                    if (prototypes[i] === classID) {
+                                        //console.info( "prototypes[ i ]: " + prototypes[ i ] );
+                                        return true;
+                                    }
+                                }
+                            }
+                            return false;
+                        },
+                        isComponentNode: function (prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] === "proxy/aframe/componentNode.vwf");
+                                }
+                            }
+                            return found;
+                        },
+
+                        isAAnimMixerDefinition: function(prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] == "proxy/aframe/anim-mixer-component.vwf");
+                                }
+                            }
+                            return found;
+                        },
+                    
+                        isAInterpolationDefinition: function(prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] == "proxy/aframe/interpolation-component.vwf");
+                                }
+                            }
+                            return found;
+                        },
+                    
+                        isALinePathDefinition: function(prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] == "proxy/aframe/linepath.vwf");
+                                }
+                            }
+                            return found;
+                        },
+                    
+                        isAMaterialDefinition: function(prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] == "proxy/aframe/aMaterialComponent.vwf");
+                                }
+                            }
+                            return found;
+                        },
+                    
+                        isASoundDefinition: function(prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] == "proxy/aframe/a-sound-component.vwf");
+                                }
+                            }
+                            return found;
+                        },
+                    
+                    
+                        isAViewOffsetCameraDefinition: function(prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] == "proxy/aframe/viewOffsetCamera-component.vwf");
+                                }
+                            }
+                            return found;
+                        },
+                    
+                    
+                        isAGizmoDefinition: function(prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] == "proxy/aframe/gizmoComponent.vwf");
+                                }
+                            }
+                            return found;
+                        },
+                    
+                        isAFogDefinition: function(prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] == "proxy/aframe/aSceneFogComponent.vwf");
+                                }
+                            }
+                            return found;
+                        },
+                    
+                        isAShadowDefinition: function(prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] == "proxy/aframe/shadowComponent.vwf");
+                                }
+                            }
+                            return found;
+                        },
+                    
+                        isAAabbColliderDefinition: function(prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] == "proxy/aframe/aabb-collider-component.vwf");
+                                }
+                            }
+                            return found;
+                        },
+                    
+                        isAMirrorDefinition: function(prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] == "proxy/aframe/a-mirror-component.vwf");
+                                }
+                            }
+                            return found;
+                        },
+                    
+                    
+                        isARayCasterDefinition: function(prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] == "proxy/aframe/raycasterComponent.vwf");
+                                }
+                            }
+                            return found;
+                        },
+                    
+                        isARayCasterListenerDefinition: function(prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] ==  "proxy/aframe/app-raycaster-listener-component.vwf");
+                                }
+                            }
+                            return found;
+                        },
+                    
+                       isAAabbColliderListenerDefinition: function(prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] ==  "proxy/aframe/app-aabb-collider-listener-component.vwf");
+                                }
+                            }
+                            return found;
+                        },
+                    
+                       isALineDefinition: function(prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] == "proxy/aframe/lineComponent.vwf");
+                                }
+                            }
+                            return found;
+                        },
+                    
+                        isGearVRControlsDefinition: function(prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] == "proxy/aframe/gearvr-controlsComponent.vwf");
+                                }
+                            }
+                            return found;
+                        },
+                    
+                       isComponentNodeDefinition: function(prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] == "proxy/aframe/componentNode.vwf");
+                                }
+                            }
+                            return found;
+                        },
+                    
+                        isComponentDefinition: function(prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] == "proxy/aframe/aentityComponent.vwf");
+                                }
+                            }
+                            return found;
+                        },
+
+                        setAFrameObject: function(node, config) {
+                            var protos = node.prototypes;
+                            var aframeObj = {};
+                            var sceneEl = document.querySelector('a-scene');
+                    
+                            aframeObj.id = node.parentID;
+                            // aframeObj.el = sceneEl.children[node.parentID];
+                            aframeObj.el = Array.from(sceneEl.querySelectorAll('*')).filter(item => { return item.id == aframeObj.id })[0];
+                    
+                    
+                            if (this.isComponentClass(protos, "proxy/aframe/lineComponent.vwf")) {
+                                // aframeObj.id = node.parentID;
+                                // aframeObj.el = sceneEl.children[node.parentID];
+                    
+                                if (node.name !== 'line') {
+                                    aframeObj.compName = node.name;
+                                } else {
+                                    aframeObj.compName = "line";
+                                }
+                                //aframeObj.el.setAttribute(node.type, {});
+                                aframeObj.el.setAttribute(aframeObj.compName, {});
+                    
+                            }
+                    
+                            if (this.isComponentClass(protos, "proxy/aframe/gizmoComponent.vwf")) {
+                    
+                    
+                                // aframeObj.el.setAttribute(node.type, {});
+                                aframeObj.compName = "gizmo";
+                                aframeObj.el.setAttribute(aframeObj.compName, {});
+                    
+                            }
+                    
+                            if (this.isComponentClass(protos, "proxy/aframe/aSceneFogComponent.vwf")) {
+                    
+                                // aframeObj.el.setAttribute(node.type, {});
+                    
+                                aframeObj = {};
+                                //var sceneEl = document.querySelector('a-scene');
+                    
+                                aframeObj.id = node.parentID;
+                                aframeObj.el = document.querySelector('a-scene');
+                    
+                                aframeObj.compName = "fog";
+                                aframeObj.el.setAttribute('fog', {});
+                    
+                            }
+                    
+                            if (this.isComponentClass(protos, "proxy/aframe/raycasterComponent.vwf")) {
+                    
+                    
+                                // aframeObj.el.setAttribute(node.type, {});
+                                aframeObj.compName = "raycaster";
+                                aframeObj.el.setAttribute(aframeObj.compName, {});
+                    
+                            }
+                    
+                    
+                    
+                            if (this.isComponentClass(protos, "proxy/aframe/shadowComponent.vwf")) {
+                    
+                                // aframeObj.el.setAttribute(node.type, {});
+                                aframeObj.compName = "shadow";
+                                aframeObj.el.setAttribute(aframeObj.compName, {});
+                    
+                            }
+                    
+                            if (this.isComponentClass(protos, "proxy/aframe/aabb-collider-component.vwf")) {
+                    
+                                // aframeObj.el.setAttribute(node.type, {});
+                                aframeObj.compName = "aabb-collider";
+                                aframeObj.el.setAttribute(aframeObj.compName, {});
+                    
+                            }
+                    
+                            if (this.isComponentClass(protos, "proxy/aframe/a-mirror-component.vwf")) {
+                    
+                                // aframeObj.el.setAttribute(node.type, {});
+                                aframeObj.compName = "mirror";
+                                aframeObj.el.setAttribute(aframeObj.compName, {});
+                    
+                            }
+                    
+                            if (this.isComponentClass(protos, "proxy/aframe/viewOffsetCamera-component.vwf")) {
+                    
+                                // aframeObj.el.setAttribute(node.type, {});
+                                aframeObj.compName = "viewoffset";
+                                aframeObj.el.setAttribute(aframeObj.compName, {});
+                    
+                            }
+                    
+                            if (this.isComponentClass(protos, "proxy/aframe/streamSoundComponent.vwf")) {
+                    
+                    
+                                // aframeObj.el.setAttribute(node.type, {});
+                                aframeObj.compName = "streamsound";
+                                aframeObj.el.setAttribute(aframeObj.compName, {});
+                    
+                            }
+                    
+                            if (this.isComponentClass(protos, "proxy/aframe/linepath.vwf")) {
+                    
+                    
+                                // aframeObj.el.setAttribute(node.type, {});
+                                aframeObj.compName = "linepath";
+                                aframeObj.el.setAttribute(aframeObj.compName, {});
+                    
+                            }
+                    
+                            if (this.isComponentClass(protos, "proxy/aframe/aMaterialComponent.vwf")) {
+                    
+                                // aframeObj.el.setAttribute(node.type, {});
+                                aframeObj.compName = "material";
+                                aframeObj.el.setAttribute(aframeObj.compName, {});
+                    
+                            }
+                    
+                            if (this.isComponentClass(protos, "proxy/aframe/a-sound-component.vwf")) {
+                    
+                                // aframeObj.el.setAttribute(node.type, {});
+                                aframeObj.compName = "sound";
+                                aframeObj.el.setAttribute(aframeObj.compName, {});
+                    
+                            }
+                    
+                            if (this.isComponentClass(protos, "proxy/aframe/interpolation-component.vwf")) {
+                    
+                    
+                                // aframeObj.el.setAttribute(node.type, {});
+                                aframeObj.compName = "interpolation";
+                                aframeObj.el.setAttribute(aframeObj.compName, {});
+                    
+                            }
+                    
+                            if (this.isComponentClass(protos, "proxy/aframe/anim-mixer-component.vwf")) {
+                    
+                    
+                                // aframeObj.el.setAttribute(node.type, {});
+                                aframeObj.compName = "animation-mixer";
+                                aframeObj.el.setAttribute(aframeObj.compName, {});
+                    
+                            }
+                    
+                            if (this.isComponentClass(protos, "proxy/aframe/app-envmap-component.vwf")) {
+                    
+                    
+                                // aframeObj.el.setAttribute(node.type, {});
+                                aframeObj.compName = "envmap";
+                                aframeObj.el.setAttribute(aframeObj.compName, {});
+                    
+                            }
+                    
+                    
+                            if (this.isComponentClass(protos, "proxy/aframe/app-avatarbvh-component.vwf")) {
+                    
+                    
+                                // aframeObj.el.setAttribute(node.type, {});
+                                aframeObj.compName = "avatarbvh";
+                                aframeObj.el.setAttribute(aframeObj.compName, {});
+                    
+                            }
+                    
+                            if (this.isComponentClass(protos, "proxy/aframe/app-sun-component.vwf")) {
+                    
+                    
+                                // aframeObj.el.setAttribute(node.type, {});
+                                aframeObj.compName = "sun";
+                                aframeObj.el.setAttribute('id', "sun");
+                                aframeObj.el.setAttribute(aframeObj.compName, {});
+                    
+                            }
+                    
+                            if (this.isComponentClass(protos, "proxy/aframe/app-skyshader-component.vwf")) {
+                    
+                    
+                                // aframeObj.el.setAttribute(node.type, {});
+                                aframeObj.compName = "skyshader";
+                                aframeObj.el.setAttribute(aframeObj.compName, {});
+                    
+                            }
+                    
+                            if (this.isComponentClass(protos, "proxy/aframe/app-raycaster-listener-component.vwf")) {
+                    
+                    
+                                // aframeObj.el.setAttribute(node.type, {});
+                                aframeObj.compName = "raycaster-listener";
+                                aframeObj.el.setAttribute(aframeObj.compName, {});
+                    
+                            }
+                    
+                            if (this.isComponentClass(protos, "proxy/aframe/app-aabb-collider-listener-component.vwf")) {
+                    
+                    
+                                // aframeObj.el.setAttribute(node.type, {});
+                                aframeObj.compName = "aabb-collider-listener";
+                                aframeObj.el.setAttribute(aframeObj.compName, {});
+                    
+                            }
+                    
+                            if (this.isComponentClass(protos, "proxy/aframe/app-cursor-listener-component.vwf")) {
+                    
+                    
+                                // aframeObj.el.setAttribute(node.type, {});
+                                aframeObj.compName = "cursor-listener";
+                                aframeObj.el.setAttribute(aframeObj.compName, {});
+                    
+                            }
+                    
+                    
+                            return aframeObj;
+                        },
+
+                        addNodeToHierarchy: function(node) {
+
+                            if (node.aframeObj) {
+                                if (this.nodes[node.parentID] !== undefined) {
+                                    var parent = this.nodes[node.parentID];
+                                    if (parent.aframeObj) {
+                    
+                                        if (parent.children === undefined) {
+                                            parent.children = [];
+                                        }
+                                        parent.children.push(node.ID);
+                    
+                                    }
+                                }
+                    
+                    
+                            }
+                    
+                        }
+                    
+                    };
+        
+                    this.state.kernel = this.kernel.kernel.kernel;
+        
+                    this.aframeComponentDef = {
+                        'A-MATERIAL': [
+                            "alphaTest",
+                            "depthTest",
+                            "flatShading",
+                            "npot",
+                            "offset",
+                            "opacity",
+                            "remain",
+                            "repeat",
+                            "shader",
+                            "side",
+                            "transparent",
+                            "vertexColors",
+                            "visible",
+                            "ambient-occlusion-map",
+                            "ambient-occlusion-map-intensity",
+                            "ambient-occlusion-texture-offset",
+                            "ambient-occlusion-texture-repeat",
+                            "color",
+                            "displacement-bias",
+                            "displacement-map",
+                            "displacement-scale",
+                            "displacement-texture-offset",
+                            "displacement-texture-repeat",
+                            "emissive",
+                            "emissiveIntensity",
+                            "height",
+                            "envMap",
+                            "fog",
+                            "metalness",
+                            "normal-map",
+                            "normal-scale",
+                            "normal-texture-offset",
+                            "normal-texture-repeat",
+                            "roughness",
+                            "sphericalEnvMap",
+                            "width",
+                            "wireframe",
+                            "wireframe-linewidth",
+                            "src"],
+                    
+                            'A-SOUND': [
+                                "autoplay", "distanceModel", "loop", "maxDistance", "on", "poolSize", "refDistance",
+                                "rolloffFactor", "src", "volume"
+                            ],
+                            'aabb-collider':[
+                                "collideNonVisible", "debug",  "enabled", "objects", "interval"
+                            ],
+                            'mirror': [
+                                "camera", "renderothermirror"
+                            ]
+        
+                    }
+                    
+        
+                    //this.state.kernel = this.kernel.kernel.kernel;
+        
+                },
+                // == Model API ============================================================================
+        
+                // -- creatingNode -------------------------------------------------------------------------
+        
+                creatingNode: function (nodeID, childID, childExtendsID, childImplementsIDs,
+                    childSource, childType, childIndex, childName, callback /* ( ready ) */) {
+
+                    let self = this;
+                    // If the parent nodeID is 0, this node is attached directly to the root and is therefore either 
+                    // the scene or a prototype.  In either of those cases, save the uri of the new node
+                    var childURI = (nodeID === 0 ? childIndex : undefined);
+                    var appID = this.kernel.application();
+        
+                    // If the node being created is a prototype, construct it and add it to the array of prototypes,
+                    // and then return
+                    var prototypeID = _self_.utility.ifPrototypeGetId(appID, this.state.prototypes, nodeID, childID);
+                    if (prototypeID !== undefined) {
+        
+                        this.state.prototypes[prototypeID] = {
+                            parentID: nodeID,
+                            ID: childID,
+                            extendsID: childExtendsID,
+                            implementsID: childImplementsIDs,
+                            source: childSource,
+                            type: childType,
+                            name: childName
+                        };
+                        return;
+                    }
+        
+                    var protos = _self_.constructor.getPrototypes(this.kernel, childExtendsID);
+                    //var kernel = this.kernel.kernel.kernel;
+                    var node;
+        
+                    if (this.state.isComponentNode(protos)) {
+        
+                        // Create the local copy of the node properties
+                        if (this.state.nodes[childID] === undefined) {
+                            this.state.nodes[childID] = this.state.createLocalNode(nodeID, childID, childExtendsID, childImplementsIDs,
+                                childSource, childType, childIndex, childName, callback);
+                        }
+        
+                        node = this.state.nodes[childID];
+                        node.prototypes = protos;
+        
+                        //node.aframeObj = createAFrameObject(node);
+                        node.aframeObj = this.state.setAFrameObject(node);
+                        // addNodeToHierarchy(node);
+        
+                        //notifyDriverOfPrototypeAndBehaviorProps();
+                    }
+                },
+        
+                // -- initializingNode -------------------------------------------------------------------------
+        
+                //   initializingNode: function( nodeID, childID, childExtendsID, childImplementsIDs,
+                //     childSource, childType, childIndex, childName ) {
+        
+                // },
+        
+                // -- deletingNode -------------------------------------------------------------------------
+        
+                deletingNode: function (nodeID) {
+        
+                    if (this.state.nodes[nodeID] !== undefined) {
+        
+                        var node = this.state.nodes[nodeID];
+        
+                        if (node.aframeObj.compName !== undefined) {
+                            // removes and destroys object
+                            node.aframeObj.el.removeAttribute(node.aframeObj.compName);
+                            node.aframeObj = undefined;
+                        }
+        
+                        delete this.state.nodes[nodeID];
+                    }
+        
+                },
+        
+                // -- initializingProperty -----------------------------------------------------------------
+        
+                initializingProperty: function (nodeID, propertyName, propertyValue) {
+        
+                    var value = undefined;
+                    var node = this.state.nodes[nodeID];
+                    if (node !== undefined) {
+                        value = this.settingProperty(nodeID, propertyName, propertyValue);
+                    }
+                    return value;
+        
+                },
+        
+                // -- creatingProperty ---------------------------------------------------------------------
+        
+                creatingProperty: function (nodeID, propertyName, propertyValue) {
+                    return this.initializingProperty(nodeID, propertyName, propertyValue);
+                },
+        
+        
+                // -- settingProperty ----------------------------------------------------------------------
+        
+                settingProperty: function (nodeID, propertyName, propertyValue) {
+        
+                    let self = this;
+                    var node = this.state.nodes[nodeID];
+                    var value = undefined;
+        
+                    if (node && node.aframeObj && _self_.utility.validObject(propertyValue)) {
+        
+                        var aframeObject = node.aframeObj;
+        
+                        if (self.state.isComponentNodeDefinition(node.prototypes)) {
+        
+        
+                            value = propertyValue;
+                            switch (propertyName) {
+        
+                                default:
+                                    value = undefined;
+                                    break;
+                            }
+        
+                        }
+        
+        
+                        if (value === undefined && self.state.isComponentDefinition(node.prototypes)) {
+        
+                            value = propertyValue;
+        
+                            switch (propertyName) {
+        
+        
+                                case "compName":
+        
+                                    break;
+        
+                                default:
+                                    value = undefined;
+                                    break;
+                            }
+                        }
+        
+                        if (value === undefined && self.state.isAViewOffsetCameraDefinition(node.prototypes)) {
+                            if (aframeObject.el.getAttribute(aframeObject.compName)) {
+        
+                                value = propertyValue;
+                                let parentNodeAF = aframeObject.el;
+                                // let defs = ['fullWidth', 'fullHeight', 'xoffset', 'yoffset'];
+        
+                                // defs.forEach(element => {
+                                //     element == propertyName ? parentNodeAF.setAttribute('viewoffset', element, propertyValue) :
+                                //         value = undefined;
+                                // })
+        
+                                switch (propertyName) {
+        
+                                    case "fullWidth":
+                                        parentNodeAF.setAttribute('viewoffset', 'fullWidth', propertyValue);
+                                        break;
+        
+                                    case "fullHeight":
+                                        parentNodeAF.setAttribute('viewoffset', 'fullHeight', propertyValue);
+                                        break;
+        
+                                    case "yoffset":
+                                        parentNodeAF.setAttribute('viewoffset', 'yoffset', propertyValue);
+                                        break;
+        
+                                    case "xoffset":
+                                        parentNodeAF.setAttribute('viewoffset', 'xoffset', propertyValue);
+                                        break;
+        
+        
+                                    case "subcamWidth":
+                                        parentNodeAF.setAttribute('viewoffset', 'width', propertyValue);
+                                        break;
+        
+                                    case "subcamHeight":
+                                        parentNodeAF.setAttribute('viewoffset', 'height', propertyValue);
+                                        break;
+        
+                                    default:
+                                        value = undefined;
+                                        break;
+                                }
+        
+        
+                            }
+                        }
+        
+        
+                        // if (value === undefined && isARayCasterListenerDefinition(node.prototypes)) {
+                        //     if (aframeObject.el.getAttribute(aframeObject.compName)) {
+        
+                        //         value = propertyValue;
+                        //         let parentNodeAF = aframeObject.el;
+        
+        
+                        //         switch (propertyName) {
+        
+                        //             default:
+                        //                 value = undefined;
+                        //                 break;
+                        //         }
+        
+        
+                        //     }
+                        // }
+        
+                        if (value === undefined && self.state.isAShadowDefinition(node.prototypes)) {
+                            if (aframeObject.el.getAttribute(aframeObject.compName)) {
+        
+                                value = propertyValue;
+                                let parentNodeAF = aframeObject.el;
+        
+        
+                                switch (propertyName) {
+        
+                                    case "cast":
+                                        parentNodeAF.setAttribute('shadow', 'cast', propertyValue);
+                                        break;
+        
+                                    case "receive":
+                                        parentNodeAF.setAttribute('shadow', 'receive', propertyValue);
+                                        break;
+        
+                                    default:
+                                        value = undefined;
+                                        break;
+                                }
+        
+        
+                            }
+                        }
+        
+        
+                        if (value === undefined && self.state.isAMaterialDefinition(node.prototypes)) {
+                            if (aframeObject.el.getAttribute(aframeObject.compName)) {
+        
+                                value = propertyValue;
+                                let parentNodeAF = aframeObject.el;
+                                //let defs = ['color', 'transparent', 'opacity', 'side'];
+        
+                                self.aframeComponentDef['A-MATERIAL'].forEach(element => {
+                                    element == propertyName ? parentNodeAF.setAttribute('material', element, propertyValue) :
+                                        value = undefined;
+                                })
+                            }
+                        }
+        
+                        if (value === undefined && self.state.isASoundDefinition(node.prototypes)) {
+                            if (aframeObject.el.getAttribute(aframeObject.compName)) {
+        
+                                value = propertyValue;
+                                let parentNodeAF = aframeObject.el;
+                                //let defs = ['color', 'transparent', 'opacity', 'side'];
+        
+                                self.aframeComponentDef['A-SOUND'].forEach(element => {
+                                    element == propertyName ? parentNodeAF.setAttribute('sound', element, propertyValue) :
+                                        value = undefined;
+                                })
+                            }
+                        }
+        
+                       
+        
+                        if (value === undefined && self.state.isAMirrorDefinition(node.prototypes)) {
+                            if (aframeObject.el.getAttribute(aframeObject.compName)) {
+        
+                                value = propertyValue;
+                                let parentNodeAF = aframeObject.el;
+        
+                                self.aframeComponentDef['mirror'].forEach(element => {
+                                    element == propertyName ? parentNodeAF.setAttribute('mirror', element, propertyValue) :
+                                        value = undefined;
+                                })
+                            }
+                        }
+        
+                        if (value === undefined && self.state.isAAabbColliderDefinition(node.prototypes)) {
+                            if (aframeObject.el.getAttribute(aframeObject.compName)) {
+        
+                                value = propertyValue;
+                                let parentNodeAF = aframeObject.el;
+                                //let defs = ['color', 'transparent', 'opacity', 'side'];
+        
+                                self.aframeComponentDef['aabb-collider'].forEach(element => {
+                                    element == propertyName ? parentNodeAF.setAttribute('aabb-collider', element, propertyValue) :
+                                        value = undefined;
+                                })
+                            }
+                        }
+        
+                        if (value === undefined && self.state.isAFogDefinition(node.prototypes)) {
+                            if (aframeObject.el.getAttribute(aframeObject.compName)) {
+        
+                                value = propertyValue;
+                                let parentNodeAF = aframeObject.el;
+                                let defs = ['fogType', 'fogColor', 'density', 'near', 'far'];
+        
+                                defs.forEach(element => {
+                                    if (element == propertyName) {
+        
+                                        switch (element) {
+        
+                                            case 'fogType':
+                                                parentNodeAF.setAttribute('fog', 'type', propertyValue);
+                                                break;
+        
+                                            case 'fogColor':
+                                                parentNodeAF.setAttribute('fog', 'color', propertyValue);
+                                                break;
+        
+                                            default:
+                                                value = parentNodeAF.setAttribute('fog', element, propertyValue);
+                                                break;
+                                        }
+        
+        
+        
+                                    } else {
+                                        value = undefined
+                                    }
+                                    // element == propertyName ? parentNodeAF.setAttribute('fog', element, propertyValue) :
+                                    //     value = undefined;
+                                })
+                            }
+                        }
+        
+                        if (value === undefined && self.state.isARayCasterDefinition(node.prototypes)) {
+                            if (aframeObject.el.getAttribute(aframeObject.compName)) {
+        
+                                value = propertyValue;
+                                let parentNodeAF = aframeObject.el;
+                                let defs = ['direction', 'far', 'interval', 'near', 'objects', 'origin', 'recursive', 'showLine', 'useWorldCoordinates'];
+        
+                                defs.forEach(element => {
+                                    element == propertyName ? parentNodeAF.setAttribute('raycaster', element, propertyValue) :
+                                        value = undefined;
+                                })
+                            }
+                        }
+        
+                        if (value === undefined && self.state.isALinePathDefinition(node.prototypes)) {
+                            if (aframeObject.el.getAttribute(aframeObject.compName)) {
+        
+                                value = propertyValue;
+        
+        
+                                let parentNodeAF = aframeObject.el;
+        
+                                switch (propertyName) {
+        
+                                    case "color":
+                                        parentNodeAF.setAttribute(aframeObject.compName, 'color', propertyValue);
+                                        break;
+        
+                                    case "path":
+                                        parentNodeAF.setAttribute(aframeObject.compName, 'path', propertyValue);
+                                        break;
+        
+                                    case "width":
+                                        parentNodeAF.setAttribute(aframeObject.compName, 'width', propertyValue);
+                                        break;
+        
+                                    default:
+                                        value = undefined;
+                                        break;
+        
+        
+                                }
+        
+                            }
+                        }
+        
+                        //isALineDefinition(node.prototypes)
+                        //if (value === undefined && node.componentName == 'line') { //isALineDefinition( node.prototypes )
+                        if (node.extendsID == "proxy/aframe/lineComponent.vwf") {
+                            if (value === undefined && aframeObject.el.getAttribute(aframeObject.compName)) {
+        
+                                value = propertyValue;
+        
+                                //let parentNodeAF = self.state.kernel.find(node.parentID);
+        
+                                // aframeObject.el.setAttribute('line', 'color')
+                                let parentNodeAF = aframeObject.el;
+        
+                                switch (propertyName) {
+        
+                                    case "start":
+        
+                                        parentNodeAF.setAttribute(aframeObject.compName, { start: propertyValue });
+                                        break;
+        
+                                    case "end":
+        
+                                        parentNodeAF.setAttribute(aframeObject.compName, { end: propertyValue });
+                                        break;
+        
+                                    case "color":
+        
+                                        parentNodeAF.setAttribute(aframeObject.compName, 'color', propertyValue);
+                                        break;
+        
+                                    case "opacity":
+                                        parentNodeAF.setAttribute(aframeObject.compName, 'opacity', propertyValue);
+                                        break;
+        
+                                    case "visible":
+        
+                                        parentNodeAF.setAttribute(aframeObject.compName, 'visible', propertyValue);
+                                        break;
+        
+                                    default:
+                                        value = undefined;
+                                        break;
+                                }
+        
+                            }
+                        }
+        
+                        if (value === undefined && self.state.isAAnimMixerDefinition(node.prototypes)) {
+                            if (aframeObject.el.getAttribute(aframeObject.compName)) {
+                                value = propertyValue;
+        
+        
+                                let parentNodeAF = aframeObject.el;
+        
+                                switch (propertyName) {
+        
+                                    case "clip":
+                                        parentNodeAF.setAttribute(aframeObject.compName, 'clip', propertyValue);
+                                        break;
+        
+                                    case "duration":
+                                        parentNodeAF.setAttribute(aframeObject.compName, 'duration', propertyValue);
+                                        break;
+        
+                                    case "crossFadeDuration":
+                                        parentNodeAF.setAttribute(aframeObject.compName, 'crossFadeDuration', propertyValue);
+                                        break;
+        
+                                    case "loop":
+                                        parentNodeAF.setAttribute(aframeObject.compName, 'loop', propertyValue);
+                                        break;
+        
+                                    case "repetitions":
+                                        parentNodeAF.setAttribute(aframeObject.compName, 'repetitions', propertyValue);
+                                        break;
+        
+                                    default:
+                                        value = undefined;
+                                        break;
+        
+        
+                                }
+        
+                            }
+                        }
+        
+                        if (value === undefined && self.state.isAInterpolationDefinition(node.prototypes)) {
+                            if (aframeObject.el.getAttribute(aframeObject.compName)) {
+        
+                                value = propertyValue;
+        
+        
+                                let parentNodeAF = aframeObject.el;
+        
+                                switch (propertyName) {
+        
+                                    case "enabled":
+                                        parentNodeAF.setAttribute(aframeObject.compName, 'enabled', propertyValue);
+                                        break;
+        
+                                    case "deltaPos":
+                                        parentNodeAF.setAttribute(aframeObject.compName, 'deltaPos', propertyValue);
+                                        break;
+        
+                                    case "deltaRot":
+                                        parentNodeAF.setAttribute(aframeObject.compName, 'deltaRot', propertyValue);
+                                        break;
+                                    
+                                    case "deltaScale":
+                                        parentNodeAF.setAttribute(aframeObject.compName, 'deltaScale', propertyValue);
+                                        break;
+        
+                                    default:
+                                        value = undefined;
+                                        break;
+        
+        
+                                }
+        
+                            }
+                        }
+        
+                        if (value === undefined && self.state.isAGizmoDefinition(node.prototypes)) {
+                            if (aframeObject.el.getAttribute(aframeObject.compName)) {
+        
+                                value = propertyValue;
+        
+        
+                                let parentNodeAF = aframeObject.el;
+        
+                                switch (propertyName) {
+        
+                                    case "mode":
+                                        parentNodeAF.setAttribute(aframeObject.compName, 'mode', propertyValue);
+                                        break;
+        
+                                    default:
+                                        value = undefined;
+                                        break;
+        
+        
+                                }
+        
+                            }
+                        }
+        
+        
+        
+        
+        
+                    }
+        
+                    return value;
+        
+                },
+        
+                // -- gettingProperty ----------------------------------------------------------------------
+        
+                gettingProperty: function (nodeID, propertyName, propertyValue) {
+        
+                    let self = this;
+                    var node = this.state.nodes[nodeID];
+                    var value = undefined;
+                    if (node && node.aframeObj) {
+        
+                        var aframeObject = node.aframeObj;
+        
+                        if (self.state.isComponentNodeDefinition(node.prototypes)) {
+                            switch (propertyName) {
+                            }
+                        }
+        
+                        if (value === undefined && self.state.isComponentDefinition(node.prototypes)) {
+        
+                            switch (propertyName) {
+        
+                                case "compName":
+        
+                                    break;
+                            }
+                        }
+        
+                        // isALineDefinition( node.prototypes ) aframeObject.compName == compName
+        
+        
+        
+                        if (value === undefined && self.state.isARayCasterDefinition(node.prototypes)) {
+                            value = propertyValue;
+        
+                            let parentNodeAF = aframeObject.el;
+                            let defs = ['direction', 'far', 'interval', 'near', 'objects', 'origin', 'recursive', 'showLine', 'useWorldCoordinates'];
+        
+                            defs.forEach(element => {
+                                if (element == propertyName) {
+                                    let val = parentNodeAF.getAttribute('raycaster').element;
+                                    value = AFRAME.utils.coordinates.isCoordinates(val) ? AFRAME.utils.coordinates.stringify(val) : val
+                                }
+                            })
+                        }
+        
+        
+                        // if (value === undefined && isARayCasterListenerDefinition(node.prototypes)) {
+                            
+                        //         value = propertyValue;
+                        //         let parentNodeAF = aframeObject.el;
+        
+                        //         switch (propertyName) {
+        
+                        //             default:
+                        //                 value = undefined;
+                        //                 break;
+                        //         }
+                        // }
+        
+        
+                        if (value === undefined && self.state.isAFogDefinition(node.prototypes)) {
+                            value = propertyValue;
+        
+                            let parentNodeAF = aframeObject.el;
+                            let defs = ['fogType', 'color', 'density', 'near', 'far'];
+        
+                            defs.forEach(element => {
+                                if (element == propertyName) {
+        
+        
+                                    switch (element) {
+        
+                                        case 'fogType':
+        
+                                            value = parentNodeAF.getAttribute('fog').type;
+                                            break;
+        
+                                        case 'fogColor':
+        
+                                            value = parentNodeAF.getAttribute('fog').color;
+                                            break;
+        
+                                        default:
+                                            value = parentNodeAF.getAttribute('fog').element;
+                                            break;
+        
+                                    }
+        
+                                }
+                            })
+                        }
+        
+                        if (value === undefined && self.state.isAShadowDefinition(node.prototypes)) {
+                            value = propertyValue;
+        
+                            // let parentNodeAF = self.state.nodes[node.parentID].aframeObj;
+                            let parentNodeAF = aframeObject.el;
+        
+                            switch (propertyName) {
+        
+                                case "cast":
+                                    value = parentNodeAF.getAttribute(aframeObject.compName).cast;
+                                    break;
+        
+                                case "receive":
+                                    value = parentNodeAF.getAttribute(aframeObject.compName).receive;
+                                    break;
+        
+        
+                            }
+                        }
+        
+        
+                        if (value === undefined && self.state.isALineDefinition(node.prototypes)) {
+                            value = propertyValue;
+        
+                            // let parentNodeAF = self.state.nodes[node.parentID].aframeObj;
+                            let parentNodeAF = aframeObject.el;
+        
+                            switch (propertyName) {
+        
+                                case "start":
+        
+                                    value = AFRAME.utils.coordinates.stringify(parentNodeAF.getAttribute(aframeObject.compName).start);
+                                    break;
+        
+                                case "end":
+                                    value = AFRAME.utils.coordinates.stringify(parentNodeAF.getAttribute(aframeObject.compName).end);
+                                    break;
+        
+                                case "color":
+                                    value = parentNodeAF.getAttribute(aframeObject.compName).color;
+                                    break;
+        
+                                case "opacity":
+                                    value = parentNodeAF.getAttribute(aframeObject.compName).opacity;
+                                    break;
+        
+                                case "visible":
+                                    value = parentNodeAF.getAttribute(aframeObject.compName).visible;
+                                    break;
+        
+                            }
+                        }
+        
+        
+                        if (value === undefined && self.state.isAAnimMixerDefinition(node.prototypes)) {
+                            value = propertyValue;
+        
+                            // let parentNodeAF = self.state.nodes[node.parentID].aframeObj;
+                            let parentNodeAF = aframeObject.el;
+        
+                            switch (propertyName) {
+        
+                                case "clip":
+                                    value = parentNodeAF.getAttribute(aframeObject.compName).clip;
+                                    break;
+        
+                                case "duration":
+                                    value = parentNodeAF.getAttribute(aframeObject.compName).duration;
+                                    break;
+        
+                                case "crossFadeDuration":
+                                    value = parentNodeAF.getAttribute(aframeObject.compName).crossFadeDuration;
+                                    break;
+        
+                                case "loop":
+                                    value = parentNodeAF.getAttribute(aframeObject.compName).loop;
+                                    break;
+        
+                                case "repetitions":
+                                    value = parentNodeAF.getAttribute(aframeObject.compName).repetitions;
+                                    break;
+        
+                            }
+        
+                        }
+        
+                        if (value === undefined && self.state.isAMaterialDefinition(node.prototypes)) {
+                            value = propertyValue;
+        
+                            // let parentNodeAF = self.state.nodes[node.parentID].aframeObj;
+                            let parentNodeAF = aframeObject.el;
+        
+                            self.aframeComponentDef['A-MATERIAL'].forEach(element => {
+                                if (element == propertyName) {
+                                    value = parentNodeAF.getAttribute('material').element;
+                                }
+        
+                            })
+                        }
+        
+                        if (value === undefined && self.state.isASoundDefinition(node.prototypes)) {
+                            value = propertyValue;
+        
+                            // let parentNodeAF = self.state.nodes[node.parentID].aframeObj;
+                            let parentNodeAF = aframeObject.el;
+        
+                            self.aframeComponentDef['A-SOUND'].forEach(element => {
+                                if (element == propertyName) {
+                                    value = parentNodeAF.getAttribute('sound').element;
+                                }
+        
+                            })
+                        }
+        
+                        if (value === undefined && self.state.isAMirrorDefinition(node.prototypes)) {
+                            value = propertyValue;
+        
+                            // let parentNodeAF = self.state.nodes[node.parentID].aframeObj;
+                            let parentNodeAF = aframeObject.el;
+        
+                            self.aframeComponentDef['mirror'].forEach(element => {
+                                if (element == propertyName) {
+                                    value = parentNodeAF.getAttribute('mirror').element;
+                                }
+        
+                            })
+                        }
+        
+                        if (value === undefined && self.state.isAAabbColliderDefinition(node.prototypes)) {
+                            value = propertyValue;
+        
+                            // let parentNodeAF = self.state.nodes[node.parentID].aframeObj;
+                            let parentNodeAF = aframeObject.el;
+        
+                            self.aframeComponentDef['aabb-collider'].forEach(element => {
+                                if (element == propertyName) {
+                                    value = parentNodeAF.getAttribute('aabb-collider').element;
+                                }
+        
+                            })
+                        }
+        
+                        if (value === undefined && self.state.isALinePathDefinition(node.prototypes)) {
+                            value = propertyValue;
+        
+                            // let parentNodeAF = self.state.nodes[node.parentID].aframeObj;
+                            let parentNodeAF = aframeObject.el;
+        
+                            switch (propertyName) {
+        
+                                case "color":
+                                    value = parentNodeAF.getAttribute(aframeObject.compName).color;
+                                    break;
+        
+                                case "path":
+                                    value = parentNodeAF.getAttribute(aframeObject.compName).path;
+                                    break;
+        
+                                case "width":
+                                    value = parentNodeAF.getAttribute(aframeObject.compName).width;
+                                    break;
+        
+        
+                            }
+        
+                        }
+        
+                        if (value === undefined && self.state.isAViewOffsetCameraDefinition(node.prototypes)) {
+                            value = propertyValue;
+        
+                            // let parentNodeAF = self.state.nodes[node.parentID].aframeObj;
+                            let parentNodeAF = aframeObject.el;
+        
+                            switch (propertyName) {
+        
+                                case "fullWidth":
+                                    value = parentNodeAF.getAttribute(aframeObject.compName).fullWidth;
+                                    break;
+        
+                                case "fullHeight":
+                                    value = parentNodeAF.getAttribute(aframeObject.compName).fullHeight;
+                                    break;
+        
+                                case "subcamWidth":
+                                    value = parentNodeAF.getAttribute(aframeObject.compName).width;
+                                    break;
+        
+                                case "subcamHeight":
+                                    value = parentNodeAF.getAttribute(aframeObject.compName).height;
+                                    break;
+        
+                                case "xoffset":
+                                    value = parentNodeAF.getAttribute(aframeObject.compName).xoffset;
+                                    break;
+        
+                                case "yoffset":
+                                    value = parentNodeAF.getAttribute(aframeObject.compName).yoffset;
+                                    break;
+                            }
+        
+                        }
+        
+                        if (value === undefined && self.state.isAInterpolationDefinition(node.prototypes)) {
+                            value = propertyValue;
+        
+                            // let parentNodeAF = self.state.nodes[node.parentID].aframeObj;
+                            let parentNodeAF = aframeObject.el;
+        
+                            switch (propertyName) {
+        
+                                case "enabled":
+                                    value = parentNodeAF.getAttribute(aframeObject.compName).enabled;
+                                    break;
+        
+                                case "deltaPos":
+                                    value = parentNodeAF.getAttribute(aframeObject.compName).deltaPos;
+                                    break;
+        
+                                case "deltaRot":
+                                    value = parentNodeAF.getAttribute(aframeObject.compName).deltaRot;
+                                    break;
+                                
+                                case "deltaScale":
+                                    value = parentNodeAF.getAttribute(aframeObject.compName).deltaScale;
+                                    break;
+        
+                            }
+        
+                        }
+        
+                        if (value === undefined && self.state.isAGizmoDefinition(node.prototypes)) {
+                            value = propertyValue;
+        
+                            // let parentNodeAF = self.state.nodes[node.parentID].aframeObj;
+                            let parentNodeAF = aframeObject.el;
+        
+                            switch (propertyName) {
+        
+                                case "mode":
+                                    value = parentNodeAF.getAttribute(aframeObject.compName).mode;
+                                    break;
+        
+        
+        
+                            }
+        
+                        }
+        
+        
+                    }
+        
+                    if (value !== undefined) {
+                        propertyValue = value;
+                    }
+        
+                    return value;
+        
+        
+                }
+        
+            });
+
+        }
+
+
+
+
+    }
+
+    export {
+        AFrameComponentModel as default
+    }
+    
+

+ 146 - 0
public/drivers/model/lego-boost.js

@@ -0,0 +1,146 @@
+/*
+The MIT License (MIT)
+Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+
+Virtual World Framework Apache 2.0 license  (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
+*/
+
+// VWF & OSC model driver
+
+import { Fabric } from '/core/vwf/fabric.js';
+
+class LegoBoostModel extends Fabric {
+
+    constructor(module) {
+
+        console.log("LegoBoostModel constructor");
+        super(module, "Model");
+    }
+
+    factory() {
+        let _self_ = this;
+
+        return this.load( this.module,
+            {
+
+                // == Module Definition ====================================================================
+        
+                // -- pipeline -----------------------------------------------------------------------------
+        
+                // pipeline: [ log ], // vwf <=> log <=> scene
+        
+                // -- initialize ---------------------------------------------------------------------------
+        
+                initialize: function() {
+                 
+                    this.objects = {}; // maps id => { property: value, ... }
+
+                },
+        
+                // == Model API ============================================================================
+        
+                // -- creatingNode -------------------------------------------------------------------------
+        
+                creatingNode: function( nodeID, childID, childExtendsID, childImplementsIDs,
+                    childSource, childType, childURI, childName, callback /* ( ready ) */ ) {
+                },
+        
+                // -- deletingNode -------------------------------------------------------------------------
+        
+                deletingNode: function( nodeID ) {
+                },
+        
+                // -- addingChild --------------------------------------------------------------------------
+        
+                addingChild: function( nodeID, childID, childName ) {
+                },
+        
+                // -- removingChild ------------------------------------------------------------------------
+        
+                removingChild: function( nodeID, childID ) {
+                },
+        
+                // -- parenting ----------------------------------------------------------------------------
+        
+                parenting: function( nodeID ) {
+                },
+        
+                // -- childrening --------------------------------------------------------------------------
+        
+                childrening: function( nodeID ) {
+                },
+        
+                // -- naming -------------------------------------------------------------------------------
+        
+                naming: function( nodeID ) {
+                },
+        
+                // -- creatingProperty ---------------------------------------------------------------------
+        
+                creatingProperty: function( nodeID, propertyName, propertyValue ) {
+                    // var object = this.objects[nodeID] || ( this.objects[nodeID] = {} );
+                    // return object[propertyName] = propertyValue;
+                },
+        
+                // -- initializingProperty -----------------------------------------------------------------
+        
+                initializingProperty: function( nodeID, propertyName, propertyValue ) {
+                    // var object = this.objects[nodeID] || ( this.objects[nodeID] = {} );
+                    // return object[propertyName] = propertyValue;
+                },
+        
+                // TODO: deletingProperty
+        
+                // -- settingProperty ----------------------------------------------------------------------
+        
+                settingProperty: function( nodeID, propertyName, propertyValue ) {
+                    // var object = this.objects[nodeID] || ( this.objects[nodeID] = {} );
+                    // return object[propertyName] = propertyValue;
+                },
+        
+                // -- gettingProperty ----------------------------------------------------------------------
+        
+                gettingProperty: function( nodeID, propertyName, propertyValue ) {
+                    // var object = this.objects[nodeID];
+                    // return object && object[propertyName];
+                },
+        
+                // -- creatingMethod -----------------------------------------------------------------------
+        
+                creatingMethod: function( nodeID, methodName, methodParameters, methodBody ) {
+                },
+        
+                // TODO: deletingMethod
+        
+                // -- callingMethod ------------------------------------------------------------------------
+        
+                callingMethod: function( nodeID, methodName, methodParameters ) {
+        
+                },
+        
+                // -- creatingEvent ------------------------------------------------------------------------
+        
+                creatingEvent: function( nodeID, eventName, eventParameters ) {
+                },
+        
+                // TODO: deletingEvent
+        
+                // -- firingEvent --------------------------------------------------------------------------
+        
+                firingEvent: function( nodeID, eventName, eventParameters ) {
+                },
+        
+                // -- executing ----------------------------------------------------------------------------
+        
+                executing: function( nodeID, scriptText, scriptType ) {
+                }
+        
+            } );
+        }
+        
+    }
+
+    export {
+        LegoBoostModel as default
+      }
+    

+ 0 - 0
public/lib/closure/base.js → public/drivers/model/math/closure/base.js


Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels