diff --git a/src/htmldrawer.js b/src/htmldrawer.js
index fa613dbf..34f9fce4 100644
--- a/src/htmldrawer.js
+++ b/src/htmldrawer.js
@@ -68,12 +68,55 @@ class HTMLDrawer extends OpenSeadragon.DrawerBase{
         this.viewer.rejectEventHandler("tile-drawing", "The HTMLDrawer does not raise the tile-drawing event");
         // Since the tile-drawn event is fired by this drawer, make sure handlers can be added for it
         this.viewer.allowEventHandler("tile-drawn");
+
+        // works with canvas & image objects
+        function _prepareTile(tile, data) {
+            const element = $.makeNeutralElement( "div" );
+            const imgElement = data.cloneNode();
+            imgElement.style.msInterpolationMode = "nearest-neighbor";
+            imgElement.style.width = "100%";
+            imgElement.style.height = "100%";
+
+            const style = element.style;
+            style.position = "absolute";
+
+            return {
+                element, imgElement, style, data
+            };
+        }
+
+        // The actual placing logics will not happen at draw event, but when the cache is created:
+        $.convertor.learn("context2d", HTMLDrawer.canvasCacheType, (t, d) => _prepareTile(t, d.canvas), 1, 1);
+        $.convertor.learn("image", HTMLDrawer.imageCacheType, _prepareTile, 1, 1);
+        // Also learn how to move back, since these elements can be just used as-is
+        $.convertor.learn(HTMLDrawer.canvasCacheType, "context2d", (t, d) => d.data.getContext('2d'), 1, 3);
+        $.convertor.learn(HTMLDrawer.imageCacheType, "image", (t, d) => d.data, 1, 3);
+
+        function _freeTile(data) {
+            if ( data.imgElement && data.imgElement.parentNode ) {
+                data.imgElement.parentNode.removeChild( data.imgElement );
+            }
+            if ( data.element && data.element.parentNode ) {
+                data.element.parentNode.removeChild( data.element );
+            }
+        }
+
+        $.convertor.learnDestroy(HTMLDrawer.canvasCacheType, _freeTile);
+        $.convertor.learnDestroy(HTMLDrawer.imageCacheType, _freeTile);
+    }
+
+    static get imageCacheType() {
+        return 'htmlDrawer[image]';
+    }
+
+    static get canvasCacheType() {
+        return 'htmlDrawer[canvas]';
     }
 
     /**
      * @returns {Boolean} always true
      */
-    static isSupported(){
+    static isSupported() {
         return true;
     }
 
@@ -86,7 +129,7 @@ class HTMLDrawer extends OpenSeadragon.DrawerBase{
     }
 
     getSupportedDataFormats() {
-        return ["image"];
+        return [HTMLDrawer.imageCacheType];
     }
 
     /**
@@ -215,41 +258,25 @@ class HTMLDrawer extends OpenSeadragon.DrawerBase{
         //EXPERIMENTAL - trying to figure out how to scale the container
         //               content during animation of the container size.
 
-        if ( !tile.element ) {
-            const image = this.getDataToDraw(tile);
-            if (!image) {
-                return;
-            }
-
-            tile.element                              = $.makeNeutralElement( "div" );
-            tile.imgElement                           = image.cloneNode();
-            tile.imgElement.style.msInterpolationMode = "nearest-neighbor";
-            tile.imgElement.style.width               = "100%";
-            tile.imgElement.style.height              = "100%";
-
-            tile.style                     = tile.element.style;
-            tile.style.position            = "absolute";
+        const dataObject = this.getDataToDraw(tile);
+        if ( dataObject.element.parentNode !== container ) {
+            container.appendChild( dataObject.element );
+        }
+        if ( dataObject.imgElement.parentNode !== dataObject.element ) {
+            dataObject.element.appendChild( dataObject.imgElement );
         }
 
-        if ( tile.element.parentNode !== container ) {
-            container.appendChild( tile.element );
-        }
-        if ( tile.imgElement.parentNode !== tile.element ) {
-            tile.element.appendChild( tile.imgElement );
-        }
-
-        tile.style.top     = tile.position.y + "px";
-        tile.style.left    = tile.position.x + "px";
-        tile.style.height  = tile.size.y + "px";
-        tile.style.width   = tile.size.x + "px";
+        dataObject.style.top     = tile.position.y + "px";
+        dataObject.style.left    = tile.position.x + "px";
+        dataObject.style.height  = tile.size.y + "px";
+        dataObject.style.width   = tile.size.x + "px";
 
         if (tile.flipped) {
-            tile.style.transform = "scaleX(-1)";
+            dataObject.style.transform = "scaleX(-1)";
         }
 
-        $.setElementOpacity( tile.element, tile.opacity );
+        $.setElementOpacity( dataObject.element, tile.opacity );
     }
-
 }
 
 $.HTMLDrawer = HTMLDrawer;
diff --git a/src/openseadragon.js b/src/openseadragon.js
index d52c9ae0..c5f2ce5c 100644
--- a/src/openseadragon.js
+++ b/src/openseadragon.js
@@ -2379,6 +2379,14 @@ function OpenSeadragon( options ){
          * @param {Boolean} [options.withCredentials=false] - whether to set the XHR's withCredentials
          * @throws {Error}
          * @returns {XMLHttpRequest}
+         *//**
+         * Makes an AJAX request.
+         * @param {String} url - the url to request
+         * @param {Function} onSuccess
+         * @param {Function} onError
+         * @throws {Error}
+         * @returns {XMLHttpRequest}
+         * @deprecated deprecated way of calling this function
          */
         makeAjaxRequest: function( url, onSuccess, onError ) {
             var withCredentials;
@@ -2388,7 +2396,6 @@ function OpenSeadragon( options ){
 
             // Note that our preferred API is that you pass in a single object; the named
             // arguments are for legacy support.
-            // FIXME ^ are we ready to drop legacy support? since we abandoned old ES...
             if( $.isPlainObject( url ) ){
                 onSuccess = url.success;
                 onError = url.error;
@@ -2397,6 +2404,8 @@ function OpenSeadragon( options ){
                 responseType = url.responseType || null;
                 postData = url.postData || null;
                 url = url.url;
+            } else {
+                $.console.warn("OpenSeadragon.makeAjaxRequest() deprecated usage!");
             }
 
             var protocol = $.getUrlProtocol( url );
diff --git a/src/tile.js b/src/tile.js
index 263791ea..95e41c23 100644
--- a/src/tile.js
+++ b/src/tile.js
@@ -160,26 +160,6 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, aja
      * @memberof OpenSeadragon.Tile#
      */
     this.loading = false;
-
-    /**
-     * The HTML div element for this tile
-     * @member {Element} element
-     * @memberof OpenSeadragon.Tile#
-     */
-    this.element    = null;
-    /**
-     * The HTML img element for this tile.
-     * @member {Element} imgElement
-     * @memberof OpenSeadragon.Tile#
-     */
-    this.imgElement = null;
-
-    /**
-     * The alias of this.element.style.
-     * @member {String} style
-     * @memberof OpenSeadragon.Tile#
-     */
-    this.style      = null;
     /**
      * This tile's position on screen, in pixels.
      * @member {OpenSeadragon.Point} position
@@ -365,6 +345,63 @@ $.Tile.prototype = {
         return this.getUrl();
     },
 
+    /**
+     * The HTML div element for this tile
+     * @member {Element} element
+     * @memberof OpenSeadragon.Tile#
+     * @deprecated
+     */
+    get element() {
+        $.console.error("Tile::element property is deprecated. Use cache API instead. Moreover, this property might be unstable.");
+        const cache = this.getCache();
+        if (!cache || !cache.loaded) {
+            return null;
+        }
+        if (cache.type !== OpenSeadragon.HTMLDrawer.canvasCacheType || cache.type !== OpenSeadragon.HTMLDrawer.imageCacheType) {
+            $.console.error("Access to HtmlDrawer property via Tile instance: HTMLDrawer must be used!");
+            return null;
+        }
+        return cache.data.element;
+    },
+
+    /**
+     * The HTML img element for this tile.
+     * @member {Element} imgElement
+     * @memberof OpenSeadragon.Tile#
+     * @deprecated
+     */
+    get imgElement() {
+        $.console.error("Tile::imgElement property is deprecated. Use cache API instead. Moreover, this property might be unstable.");
+        const cache = this.getCache();
+        if (!cache || !cache.loaded) {
+            return null;
+        }
+        if (cache.type !== OpenSeadragon.HTMLDrawer.canvasCacheType || cache.type !== OpenSeadragon.HTMLDrawer.imageCacheType) {
+            $.console.error("Access to HtmlDrawer property via Tile instance: HTMLDrawer must be used!");
+            return null;
+        }
+        return cache.data.imgElement;
+    },
+
+    /**
+     * The alias of this.element.style.
+     * @member {String} style
+     * @memberof OpenSeadragon.Tile#
+     * @deprecated
+     */
+    get style() {
+        $.console.error("Tile::style property is deprecated. Use cache API instead. Moreover, this property might be unstable.");
+        const cache = this.getCache();
+        if (!cache || !cache.loaded) {
+            return null;
+        }
+        if (cache.type !== OpenSeadragon.HTMLDrawer.canvasCacheType || cache.type !== OpenSeadragon.HTMLDrawer.imageCacheType) {
+            $.console.error("Access to HtmlDrawer property via Tile instance: HTMLDrawer must be used!");
+            return null;
+        }
+        return cache.data.style;
+    },
+
     /**
      * Get the Image object for this tile.
      * @returns {?Image}
@@ -486,10 +523,12 @@ $.Tile.prototype = {
             return; //async context can access the tile outside its lifetime
         }
 
+        this.__restoreRequestedFree = freeIfUnused;
         if (this.originalCacheKey !== this.cacheKey) {
-            this.__restoreRequestedFree = freeIfUnused;
             this.__restore = true;
         }
+        // Somebody has called restore on this tile, make sure we delete working cache in case there was some
+        this.removeCache(this._wcKey, true);
     },
 
     /**
@@ -528,7 +567,7 @@ $.Tile.prototype = {
             const cache = this.getCache(this.originalCacheKey);
 
             this.tiledImage._tileCache.restoreTilesThatShareOriginalCache(
-                this, cache
+                this, cache, this.__restoreRequestedFree
             );
             this.__restore = false;
             return cache.prepareForRendering(drawerId, supportedFormats, usePrivateCache, this.processing);
@@ -555,7 +594,7 @@ $.Tile.prototype = {
         //TODO IMPLEMENT LOCKING AND IGNORE PIPELINE OUT OF THESE CALLS
 
         // Now, if working cache exists, we set main cache to the working cache, since it has been updated
-        const cache = this.getCache(this._wcKey);
+        const cache = !requestedRestore && this.getCache(this._wcKey);
         if (cache) {
             let newCacheKey = this.cacheKey === this.originalCacheKey ? "mod://" + this.originalCacheKey : this.cacheKey;
             this.tiledImage._tileCache.consumeCache({
@@ -569,7 +608,7 @@ $.Tile.prototype = {
         // If we requested restore, perform now
         if (requestedRestore) {
             this.tiledImage._tileCache.restoreTilesThatShareOriginalCache(
-                this, this.getCache(this.originalCacheKey)
+                this, this.getCache(this.originalCacheKey), this.__restoreRequestedFree
             );
         }
         // Else no work to be done
@@ -805,17 +844,22 @@ $.Tile.prototype = {
     },
 
     /**
-     * Removes tile from its container.
-     * @function
+     * Removes tile from the system: it will still be present in the
+     * OSD memory, but marked as loaded=false, and its data will be erased.
+     * @param {boolean} [erase=false]
      */
-    unload: function() {
-        //TODO AIOSA remove this.element and move it to a data constructor
-        if ( this.imgElement && this.imgElement.parentNode ) {
-            this.imgElement.parentNode.removeChild( this.imgElement );
-        }
-        if ( this.element && this.element.parentNode ) {
-            this.element.parentNode.removeChild( this.element );
+    unload: function(erase = false) {
+        if (!this.loaded) {
+            return;
         }
+        this.tiledImage._tileCache.unloadTile(this, erase);
+    },
+
+    /**
+     * this method shall be called only by cache system when the tile is already empty of data
+     * @private
+     */
+    _unload: function () {
         this.tiledImage = null;
         this._caches    = {};
         this._cacheSize = 0;
diff --git a/src/tilecache.js b/src/tilecache.js
index 3a941170..47d343e3 100644
--- a/src/tilecache.js
+++ b/src/tilecache.js
@@ -412,16 +412,17 @@
                 if (data instanceof $.Promise) {
                     this._promise = data.then(d => {
                         this._data = d;
+                        this.loaded = true;
                         return d;
                     });
                     this._data = null;
                 } else {
                     this._promise = $.Promise.resolve(data);
                     this._data = data;
+                    this.loaded = true;
                 }
 
                 this._type = type;
-                this.loaded = true;
                 this._tiles.push(tile);
             } else if (!this._tiles.includes(tile)) {
                 this._tiles.push(tile);
@@ -936,11 +937,11 @@
          * was requested restore().
          * @param tile
          * @param originalCache
+         * @param freeIfUnused if true, zombie is not created
          */
-        restoreTilesThatShareOriginalCache(tile, originalCache) {
+        restoreTilesThatShareOriginalCache(tile, originalCache, freeIfUnused) {
             for (let t of originalCache._tiles) {
-                // todo a bit dirty, touching tile privates
-                this.unloadCacheForTile(t, t.cacheKey, t.__restoreRequestedFree);
+                this.unloadCacheForTile(t, t.cacheKey, freeIfUnused);
                 delete t._caches[t.cacheKey];
                 t.cacheKey = t.originalCacheKey;
             }
@@ -993,7 +994,7 @@
                     }
 
                     if ( worstTile && worstTileIndex >= 0 ) {
-                        this.unloadTile(worstTile, true);
+                        this._unloadTile(worstTile, true);
                         insertionIndex = worstTileIndex;
                     }
                 }
@@ -1033,7 +1034,7 @@
                         //iterates from the array end, safe to remove
                         this._tilesLoaded.splice( i, 1 );
                     } else if ( tile.tiledImage === tiledImage ) {
-                        this.unloadTile(tile, !tiledImage._zombieCache || cacheOverflows, i);
+                        this._unloadTile(tile, !tiledImage._zombieCache || cacheOverflows, i);
                     }
                 }
             }
@@ -1096,14 +1097,28 @@
             return false;
         }
 
+        /**
+         * Unload tile: this will free the tile data and mark the tile as unloaded.
+         * @param {OpenSeadragon.Tile} tile
+         * @param {boolean} destroy if set to true, tile data is not preserved as zombies but deleted immediatelly
+         */
+        unloadTile(tile, destroy = false) {
+            if (!tile.loaded) {
+                $.console.warn("Attempt to unload already unloaded tile.");
+                return;
+            }
+            const index = this._tilesLoaded.findIndex(x => x === tile);
+            this._unloadTile(tile, destroy, index);
+        }
+
         /**
          * @param tile tile to unload
          * @param destroy destroy tile cache if the cache tile counts falls to zero
-         * @param deleteAtIndex index to remove the tile record at, will not remove from _tiledLoaded if not set
+         * @param deleteAtIndex index to remove the tile record at, will not remove from _tilesLoaded if not set
          * @private
          */
-        unloadTile(tile, destroy, deleteAtIndex) {
-            $.console.assert(tile, '[TileCache.unloadTile] tile is required');
+        _unloadTile(tile, destroy, deleteAtIndex) {
+            $.console.assert(tile, '[TileCache._unloadTile] tile is required');
 
             for (let key in tile._caches) {
                 //we are 'ok' to remove tile caches here since we later call destroy on tile, otherwise
@@ -1121,7 +1136,7 @@
             }
 
             const tiledImage = tile.tiledImage;
-            tile.unload();
+            tile._unload();
 
             /**
              * Triggered when a tile has just been unloaded from memory.
diff --git a/src/tiledimage.js b/src/tiledimage.js
index 98c976b1..0c989e5d 100644
--- a/src/tiledimage.js
+++ b/src/tiledimage.js
@@ -1885,8 +1885,8 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
         // if we find existing record, check the original data of existing tile of this record
         let baseTile = record._tiles[0];
         if (!baseTile) {
-            // we are unable to setup the tile, this might be a bug somewhere else
-            return false;
+            // zombie cache -> revive, it's okay to use current tile as state inherit point since there is no state
+            baseTile = tile;
         }
 
         // Setup tile manually, data can be null -> we already have existing cache to share, share also caches
diff --git a/test/demo/basic-html-drawer.html b/test/demo/basic-html-drawer.html
new file mode 100644
index 00000000..b5757f8d
--- /dev/null
+++ b/test/demo/basic-html-drawer.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>OpenSeadragon Basic Demo</title>
+    <script type="text/javascript" src='../../build/openseadragon/openseadragon.js'></script>
+    <script type="text/javascript" src='../lib/jquery-1.9.1.min.js'></script>
+    <style type="text/css">
+
+        .openseadragon1 {
+            width: 800px;
+            height: 600px;
+        }
+
+    </style>
+</head>
+<body>
+    <div>
+        Simple demo page to show a default OpenSeadragon viewer.
+    </div>
+    <div id="contentDiv" class="openseadragon1"></div>
+    <script type="text/javascript">
+
+        var viewer = OpenSeadragon({
+            // debugMode: true,
+            id: "contentDiv",
+            prefixUrl: "../../build/openseadragon/images/",
+            tileSources: "../data/testpattern.dzi",
+            drawer: 'html',
+            showNavigator: true,
+        });
+    </script>
+</body>
+</html>
diff --git a/test/modules/tilecache.js b/test/modules/tilecache.js
index d12ee130..d6c0a3da 100644
--- a/test/modules/tilecache.js
+++ b/test/modules/tilecache.js
@@ -218,7 +218,8 @@
         const fakeTiledImage0 = MockSeadragon.getTiledImage(fakeViewer);
         const fakeTiledImage1 = MockSeadragon.getTiledImage(fakeViewer);
 
-        //load data
+        //load data: note that tests SETUP MORE CACHES than they might use: it tests that some other caches / tiles
+        // are not touched during the manipulation of unrelated caches / tiles
         const tile00 = MockSeadragon.getTile('foo.jpg', fakeTiledImage0);
         tile00.addCache(tile00.cacheKey, 0, T_A, false, false);
         const tile01 = MockSeadragon.getTile('foo2.jpg', fakeTiledImage0);
@@ -324,72 +325,192 @@
             test.equal(c12.data, 6, "In total 6 conversions on the cache object, above set changes working cache.");
             test.equal(c12.data, 6, "Changing type of working cache fires no conversion, we overwrite cache state.");
 
+            // Get set collide tries to modify the cache: all first request the data, and set the data in random order,
+            // but writing is done after reading --> we start from TA
+            collideGetSet(tile12, T_A); // no conversion, already in TA
+            collideGetSet(tile12, T_B); // conversion to TB
+            collideGetSet(tile12, T_B); // no conversion, already in TA
+            collideGetSet(tile12, T_A); // conversion to TB
+            collideGetSet(tile12, T_B); // conversion to TB
+            //should finish with next await with 6 steps at this point, add two more and await end
+            value = await collideGetSet(tile12, T_C); // A -> B -> C (forced await)
+            test.equal(typeAtoB, 8, "Conversion A->B increased by three + one for the last await.");
+            test.equal(typeBtoC, 6, "Conversion B->C + one for the last await.");
+            test.equal(typeCtoA, 5, "Conversion C->A did not happen.");
+            test.equal(typeDtoA, 0, "Conversion D->A did not happen.");
+            test.equal(typeCtoE, 0, "Conversion C->E did not happen.");
+            test.equal(value, 8, "6+2 steps (writes are colliding, just single write will happen).");
+            const workingc12 = tile12.getCache(tile12._wcKey);
+            test.equal(workingc12.type, T_C, "Working cache is really type C.");
+
+            //working cache not shared, even if these two caches share key they have different data now
+            value = await tile00.getData(T_C);  // B -> C
+            test.equal(typeAtoB, 8, "Conversion A->B nor triggered.");
+            test.equal(typeBtoC, 7, "Conversion B->C triggered.");
+            const workingc00 = tile00.getCache(tile00._wcKey);
+            test.notEqual(workingc00, workingc12, "Underlying working cache is not shared despite tiles share hash key.");
+
             //TODO fix test from here
             test.ok("TODO: FIX TEST SUITE FOR NEW CACHE SYSTEM");
 
-            // // Get set collide tries to modify the cache
-            // collideGetSet(tile12, T_A); // B -> C -> A
-            // collideGetSet(tile12, T_B); // no conversion, all run at the same time
-            // collideGetSet(tile12, T_B); // no conversion, all run at the same time
-            // collideGetSet(tile12, T_A); // B -> C -> A
-            // collideGetSet(tile12, T_B); // no conversion, all run at the same time
-            // //should finish with next await with 6 steps at this point, add two more and await end
-            // value = await collideGetSet(tile12, T_A); // B -> C -> A
-            // test.equal(typeAtoB, 3, "Conversion A->B not increased, not needed as all T_B requests resolve immediatelly.");
-            // test.equal(typeBtoC, 9, "Conversion B->C happened three times more.");
-            // test.equal(typeCtoA, 9, "Conversion C->A happened three times more.");
-            // test.equal(typeDtoA, 0, "Conversion D->A did not happen.");
-            // test.equal(typeCtoE, 0, "Conversion C->E did not happen.");
-            // test.equal(value, 13, "11+2 steps (writes are colliding, just single write will happen).");
-            //
-            // //shares cache with tile12
-            // value = await tile00.getData(T_A, false);
-            // test.equal(typeAtoB, 3, "Conversion A->B nor triggered.");
-            // test.equal(value, 13, "Value did not change.");
-            //
-            // //now set value with keeping origin
-            // await tile00.setData(42, T_D, true);
-            // test.equal(tile12.originalCacheKey, tile12.cacheKey, "Related tile not affected.");
-            // test.equal(tile00.originalCacheKey, tile12.originalCacheKey, "Cache data was modified, original kept.");
-            // test.notEqual(tile00.cacheKey, tile12.cacheKey, "Main cache keys changed.");
-            // const newCache = tile00.getCache();
-            // await newCache.transformTo(T_C);
-            // test.equal(typeDtoA, 1, "Conversion D->A happens first time.");
-            // test.equal(c12.data, 13, "Original cache value kept");
-            // test.equal(c12.type, T_A, "Original cache type kept");
-            // test.equal(c12, c00, "The same cache.");
-            //
-            // test.equal(typeAtoB, 4, "Conversion A->B triggered.");
-            // test.equal(newCache.type, T_C, "Original cache type kept");
-            // test.equal(newCache.data, 45, "42+3 steps happened.");
-            //
-            // //try again change in set data, now the cache gets overwritten
-            // await tile00.setData(42, T_B, true);
-            // test.equal(newCache.type, T_B, "Reset happened in place.");
-            // test.equal(newCache.data, 42, "Reset happened in place.");
-            //
-            // // Overwriting stress test with diff cache (see the same test as above, the same reasoning)
-            // collideGetSet(tile00, T_A); // B -> C -> A
-            // collideGetSet(tile00, T_B); // no conversion, all run at the same time
-            // collideGetSet(tile00, T_B); // no conversion, all run at the same time
-            // collideGetSet(tile00, T_A); // B -> C -> A
-            // collideGetSet(tile00, T_B); // no conversion, all run at the same time
-            // //should finish with next await with 6 steps at this point, add two more and await end
-            // value = await collideGetSet(tile00, T_A); // B -> C -> A
-            // test.equal(typeAtoB, 4, "Conversion A->B not increased.");
-            // test.equal(typeBtoC, 13, "Conversion B->C happened three times more.");
-            // //we converted D->C before, that's why C->A is one less
-            // test.equal(typeCtoA, 12, "Conversion C->A happened three times more.");
-            // test.equal(typeDtoA, 1, "Conversion D->A did not happen.");
-            // test.equal(typeCtoE, 0, "Conversion C->E did not happen.");
-            // test.equal(value, 44, "+2 writes value (writes collide, just one finishes last).");
-            //
-            // test.equal(c12.data, 13, "Original cache value kept");
-            // test.equal(c12.type, T_A, "Original cache type kept");
-            // test.equal(c12, c00, "The same cache.");
-            //
-            // //todo test destruction throughout the test above
-            // //tile00.unload();
+            // now set value with keeping origin
+            await tile00.setData(42, T_D);
+            const newCache = tile00.getCache(tile00._wcKey);
+            await newCache.transformTo(T_C); // D -> A -> B -> C
+            test.equal(typeDtoA, 1, "Conversion D->A happens first time.");
+            test.equal(tile12.originalCacheKey, tile12.cacheKey, "Related tile not affected.");
+            test.equal(tile00.originalCacheKey, tile12.originalCacheKey, "Cache data was modified, original kept.");
+            test.equal(tile00.cacheKey, tile12.cacheKey, "Main cache keys not changed.");
+
+            // tile restore has no effect on the result since tile00 gets overwritten by tile12.updateRenderTarget()
+            tile00.restore(true);
+            // tile 12 changes data both tile 00 and tile 12
+            tile12.updateRenderTarget();
+            let newMainCache12 = tile12.getCache();
+            let newMainCache00 = tile00.getCache();
+
+            test.equal(newMainCache12.data, 8, "Tile 12 main cache value is now 8 as inherited from working cache.");
+            test.equal(newMainCache00.data, 8, "Tile 00 also shares the same data as tile 12.");
+
+            tile00.updateRenderTarget();
+            test.equal(newMainCache12.data, 8, "No effect in update target.");
+            test.equal(newMainCache00.data, 8, "No effect in update target.");
+            test.notEqual(tile00.cacheKey, tile00.originalCacheKey, "Tiles have original type.");
+
+            // Overwriting stress test with diff cache (see the same test as above, the same reasoning)
+            // but now stress test from clean state (WC initialized with first call)
+            tile00.restore(true); //first we call restore so that set/get reads from original cache
+            await collideGetSet(tile00, T_C); // tile has no working cache, conversion from original A -> B -> C
+            test.equal(await tile00.getData(T_C), 8, "Data is now 8 (6 at original + 2 conversion steps).");
+
+            // initialization of working cache directly as different type
+            collideGetSet(tile00, T_B); // C -> A -> B
+            collideGetSet(tile00, T_A); // C -> A
+            collideGetSet(tile00, T_A); // C -> A
+            collideGetSet(tile00, T_C); // no change
+            //should finish with next await with 6 steps at this point, add two more and await end
+            value = await collideGetSet(tile00, T_B); // C -> A -> B
+            test.equal(typeAtoB, 12, "Conversion A->B +3");
+            test.equal(typeBtoC, 9, "Conversion B->C +2 (one here, one a bit above)");
+            test.equal(typeCtoA, 9, "Conversion C->A +4");
+            test.equal(typeDtoA, 1, "Conversion D->A did not happen.");
+            test.equal(typeCtoE, 0, "Conversion C->E did not happen.");
+
+            test.equal(value, 10, "6 original + 4 conversions value (last collide get set taken in action, rest values discarded).");
+
+            // tile restore has now effect since we swap order of updates
+            tile00.restore(true);
+            // tile 12 changes data both tile 00 and tile 12
+            tile00.updateRenderTarget();
+            newMainCache12 = tile12.getCache();
+            newMainCache00 = tile00.getCache();
+
+            test.equal(newMainCache12.data, 6, "Tile data is now 6 since we restored old data from tile00.");
+            test.equal(newMainCache12, newMainCache00, "Caches are equal.");
+
+            // we delete tile: original and main cache not freed, working yes
+            let cacheSize = tileCache.numCachesLoaded();
+            tile00.unload(true);
+            test.equal(tile00.getCacheSize(), 0, "No caches left.");
+            test.equal(await tile00.getData(T_A), undefined, "No data available.");
+            test.equal(tile00.loaded, false, "Tile in off state.");
+            test.equal(tile00.loading, false, "Tile no loading state.");
+            test.equal(tileCache.numCachesLoaded(), cacheSize, "Tile cache no change since original data shared.");
+
+            // we delete another tile, now original and main caches should be freed too
+            tile12.unload(true);
+            test.equal(tile12.getCacheSize(), 0, "No caches left.");
+            test.equal(await tile12.getData(T_A), undefined, "No data available.");
+            test.equal(tile12.loaded, false, "Tile in off state.");
+            test.equal(tile12.loading, false, "Tile no loading state.");
+            test.equal(tileCache.numCachesLoaded(), cacheSize - 1, "Tile cache shrunken by 1 since tile12 had only original data.");
+
+            done();
+        })();
+    });
+
+    QUnit.test('Tile API: basic conversion', function(test) {
+        const done = test.async();
+        const fakeViewer = MockSeadragon.getViewer(
+            MockSeadragon.getDrawer({
+                // tile in safe mode inspects the supported formats upon cache set
+                getSupportedDataFormats() {
+                    return [T_A, T_B, T_C, T_D, T_E];
+                }
+            })
+        );
+        const tileCache = fakeViewer.tileCache;
+        const fakeTiledImage0 = MockSeadragon.getTiledImage(fakeViewer);
+        const fakeTiledImage1 = MockSeadragon.getTiledImage(fakeViewer);
+
+        //load data: note that tests SETUP MORE CACHES than they might use: it tests that some other caches / tiles
+        // are not touched during the manipulation of unrelated caches / tiles
+        const tile00 = MockSeadragon.getTile('foo.jpg', fakeTiledImage0);
+        tile00.addCache(tile00.cacheKey, 0, T_A, false, false);
+        const tile01 = MockSeadragon.getTile('foo2.jpg', fakeTiledImage0);
+        tile01.addCache(tile01.cacheKey, 0, T_B, false, false);
+        const tile10 = MockSeadragon.getTile('foo3.jpg', fakeTiledImage1);
+        tile10.addCache(tile10.cacheKey, 0, T_C, false, false);
+        const tile11 = MockSeadragon.getTile('foo3.jpg', fakeTiledImage1);
+        tile11.addCache(tile11.cacheKey, 0, T_C, false, false);
+        const tile12 = MockSeadragon.getTile('foo.jpg', fakeTiledImage1);
+        tile12.addCache(tile12.cacheKey, 0, T_A, false, false);
+
+        //test set/get data in async env
+        (async function() {
+
+            // Tile10 VS Tile11 --> share cache (same key), collision update keeps last call sane
+            await tile10.setData(3, T_A);
+            await tile11.setData(5, T_C);
+
+            await tile10.updateRenderTarget();
+
+            test.equal(tile10.getCache().data, 3, "Tile 10 data used as main.");
+            test.equal(tile11.getCache().data, 3, "Tile 10 data used as main.");
+            test.equal(tile11.getCache().type, T_A, "Tile 10 data used as main.");
+            await tile11.updateRenderTarget();
+
+            test.equal(tile10.getCache().data, 5, "Tile 11 data used as main.");
+            test.equal(tile11.getCache().data, 5, "Tile 11 data used as main.");
+            test.equal(tile11.getCache().type, T_C, "Tile 11 data used as main.");
+
+            // Tile10 updated, reset -> OK
+            await tile10.setData(42, T_A);
+            tile10.restore();
+            await tile10.updateRenderTarget();
+            test.equal(tile10.getCache().data, 0, "Original data used as main: restore was called.");
+            test.equal(tile11.getCache().data, 0);
+            test.equal(tile11.getCache().type, T_C);
+
+
+            // UpdateTarget called on restore data
+            await tile11.setData(189, T_D);
+            await tile10.setData(-87, T_B);
+            tile10.restore();
+            await tile11.updateRenderTarget();
+
+            test.equal(tile11.getCache().data, 189, "New data reflected.");
+            test.equal(tile10.getCache().data, 189, "New data reflected also on connected tile.");
+            await tile10.updateRenderTarget();
+            test.equal(tile11.getCache().data, 189, "No effect: restore was called.");
+            test.equal(tile10.getCache().data, 189, "No effect: restore was called.");
+
+            let cacheSize = tileCache.numCachesLoaded();
+            tile11.unload(true);
+            test.equal(tile11.getCacheSize(), 0, "No caches left in tile11.");
+            test.equal(tileCache.numCachesLoaded(), cacheSize, "No caches freed: shared with tile12");
+
+            tile10.unload(true);
+            test.equal(tile10.getCacheSize(), 0, "No caches left in tile11.");
+            test.equal(tileCache.numCachesLoaded(), cacheSize - 2, "Two caches freed.");
+
+            tile01.unload(false);
+            test.equal(tileCache.numCachesLoaded(), cacheSize - 2, "No cache freed: zombie cache left.");
+
+            tile00.unload(true);
+            test.equal(tileCache.numCachesLoaded(), cacheSize - 2, "No cache freed: shared with tile12.");
+            tile12.unload(true);
+            test.equal(tileCache.numCachesLoaded(), 1, "One zombie cache left.");
 
             done();
         })();
@@ -424,32 +545,36 @@
 
         //test set/get data in async env
         (async function() {
-            // TODO FIX
+            test.equal(tileCache.numTilesLoaded(), 5, "We loaded 5 tiles");
+            test.equal(tileCache.numCachesLoaded(), 3, "We loaded 3 cache objects - three different urls");
+
+            const c00 = tile00.getCache(tile00.cacheKey);
+            const c12 = tile12.getCache(tile12.cacheKey);
+
+            //now test multi-cache within tile
+            const theTileKey = tile00.cacheKey;
+            tile00.setData(42, T_E);
+            test.equal(tile00.cacheKey, tile00.originalCacheKey, "Original cache still rendered.");
+            test.equal(theTileKey, tile00.originalCacheKey, "Original cache key preserved.");
+
+            tile00.updateRenderTarget();
+            test.notEqual(tile00.cacheKey, tile00.originalCacheKey, "New cache rendered.");
+
+            //now add artifically another record
+            tile00.addCache("my_custom_cache", 128, T_C);
+            test.equal(tileCache.numTilesLoaded(), 5, "We still loaded only 5 tiles.");
+            test.equal(tileCache.numCachesLoaded(), 5, "The cache has now 5 items (two new added already).");
+
+            test.equal(c00.getTileCount(), 2, "The cache still has only two tiles attached.");
+            test.equal(tile00.getCacheSize(), 3, "The tile has three cache objects (original data, main cache & custom.");
+            //related tile not affected
+            test.notEqual(tile12.cacheKey, tile12.originalCacheKey, "Original cache change reflected on shared caches.");
+            test.equal(tile12.originalCacheKey, theTileKey, "Original cache key also preserved.");
+            test.equal(c12.getTileCount(), 2, "The original data cache still has only two tiles attached.");
+            test.equal(tile12.getCacheSize(), 2, "Related tile cache has also two caches.");
+
+            //TODO fix test from here
             test.ok("TODO: FIX TEST SUITE FOR NEW CACHE SYSTEM");
-            done();
-            // test.equal(tileCache.numTilesLoaded(), 5, "We loaded 5 tiles");
-            // test.equal(tileCache.numCachesLoaded(), 3, "We loaded 3 cache objects");
-            //
-            // const c00 = tile00.getCache(tile00.cacheKey);
-            // const c12 = tile12.getCache(tile12.cacheKey);
-            //
-            // //now test multi-cache within tile
-            // const theTileKey = tile00.cacheKey;
-            // tile00.setData(42, T_E, true);
-            // test.ok(tile00.cacheKey !== tile00.originalCacheKey, "Original cache key differs.");
-            // test.equal(theTileKey, tile00.originalCacheKey, "Original cache key preserved.");
-            //
-            // //now add artifically another record
-            // tile00.addCache("my_custom_cache", 128, T_C);
-            // test.equal(tileCache.numTilesLoaded(), 5, "We still loaded only 5 tiles.");
-            // test.equal(tileCache.numCachesLoaded(), 5, "The cache has now 5 items.");
-            // test.equal(c00.getTileCount(), 2, "The cache still has only two tiles attached.");
-            // test.equal(tile00.getCacheSize(), 3, "The tile has three cache objects.");
-            // //related tile not really affected
-            // test.equal(tile12.cacheKey, tile12.originalCacheKey, "Original cache key not affected elsewhere.");
-            // test.equal(tile12.originalCacheKey, theTileKey, "Original cache key also preserved.");
-            // test.equal(c12.getTileCount(), 2, "The original data cache still has only two tiles attached.");
-            // test.equal(tile12.getCacheSize(), 1, "Related tile cache did not increase.");
             //
             // //add and delete cache nothing changes
             // tile00.addCache("my_custom_cache2", 128, T_C);
@@ -520,161 +645,155 @@
             // //now test tile destruction as zombie
             //
             // //now test tile cache sharing
-            // done();
+            done();
         })();
     });
 
     QUnit.test('Zombie Cache', function(test) {
         const done = test.async();
 
-        // TODO FIX
-        test.ok("TODO: FIX TEST SUITE FOR NEW CACHE SYSTEM");
-        done();
-        // //test jobs by coverage: fail if
-        // let jobCounter = 0, coverage = undefined;
-        // OpenSeadragon.ImageLoader.prototype.addJob = function (options) {
-        //     jobCounter++;
-        //     if (coverage) {
-        //         //old coverage of previous tiled image: if loaded, fail --> should be in cache
-        //         const coverageItem = coverage[options.tile.level][options.tile.x][options.tile.y];
-        //         test.ok(!coverageItem, "Attempt to add job for tile that is not in cache OK if previously not loaded.");
-        //     }
-        //     return originalJob.call(this, options);
-        // };
-        //
-        // let tilesFinished = 0;
-        // const tileCounter = function (event) {tilesFinished++;}
-        //
-        // const openHandler = function(event) {
-        //     event.item.allowZombieCache(true);
-        //
-        //     viewer.world.removeHandler('add-item', openHandler);
-        //     test.ok(jobCounter === 0, 'Initial state, no images loaded');
-        //
-        //     waitFor(() => {
-        //         if (tilesFinished === jobCounter && event.item._fullyLoaded) {
-        //             coverage = $.extend(true, {}, event.item.coverage);
-        //             viewer.world.removeAll();
-        //             return true;
-        //         }
-        //         return false;
-        //     });
-        // };
-        //
-        // let jobsAfterRemoval = 0;
-        // const removalHandler = function (event) {
-        //     viewer.world.removeHandler('remove-item', removalHandler);
-        //     test.ok(jobCounter > 0, 'Tiled image removed after 100 ms, should load some images.');
-        //     jobsAfterRemoval = jobCounter;
-        //
-        //     viewer.world.addHandler('add-item', reopenHandler);
-        //     viewer.addTiledImage({
-        //         tileSource: '/test/data/testpattern.dzi'
-        //     });
-        // }
-        //
-        // const reopenHandler = function (event) {
-        //     event.item.allowZombieCache(true);
-        //
-        //     viewer.removeHandler('add-item', reopenHandler);
-        //     test.equal(jobCounter, jobsAfterRemoval, 'Reopening image does not fetch any tiles imemdiatelly.');
-        //
-        //     waitFor(() => {
-        //         if (event.item._fullyLoaded) {
-        //             viewer.removeHandler('tile-unloaded', unloadTileHandler);
-        //             viewer.removeHandler('tile-loaded', tileCounter);
-        //
-        //             //console test needs here explicit removal to finish correctly
-        //             OpenSeadragon.ImageLoader.prototype.addJob = originalJob;
-        //             done();
-        //             return true;
-        //         }
-        //         return false;
-        //     });
-        // };
-        //
-        // const unloadTileHandler = function (event) {
-        //     test.equal(event.destroyed, false, "Tile unload event should not delete with zombies!");
-        // }
-        //
-        // viewer.world.addHandler('add-item', openHandler);
-        // viewer.world.addHandler('remove-item', removalHandler);
-        // viewer.addHandler('tile-unloaded', unloadTileHandler);
-        // viewer.addHandler('tile-loaded', tileCounter);
-        //
-        // viewer.open('/test/data/testpattern.dzi');
+        //test jobs by coverage: fail if cached coverage not fully re-stored without jobs
+        let jobCounter = 0, coverage = undefined;
+        OpenSeadragon.ImageLoader.prototype.addJob = function (options) {
+            jobCounter++;
+            if (coverage) {
+                //old coverage of previous tiled image: if loaded, fail --> should be in cache
+                const coverageItem = coverage[options.tile.level][options.tile.x][options.tile.y];
+                test.ok(!coverageItem, "Attempt to add job for tile that should be already in memory.");
+            }
+            return originalJob.call(this, options);
+        };
+
+        let tilesFinished = 0;
+        const tileCounter = function (event) {tilesFinished++;}
+
+        const openHandler = function(event) {
+            event.item.allowZombieCache(true);
+
+            viewer.world.removeHandler('add-item', openHandler);
+            test.ok(jobCounter === 0, 'Initial state, no images loaded');
+
+            waitFor(() => {
+                if (tilesFinished === jobCounter && event.item._fullyLoaded) {
+                    coverage = $.extend(true, {}, event.item.coverage);
+                    viewer.world.removeAll();
+                    return true;
+                }
+                return false;
+            });
+        };
+
+        let jobsAfterRemoval = 0;
+        const removalHandler = function (event) {
+            viewer.world.removeHandler('remove-item', removalHandler);
+            test.ok(jobCounter > 0, 'Tiled image removed after 100 ms, should load some images.');
+            jobsAfterRemoval = jobCounter;
+
+            viewer.world.addHandler('add-item', reopenHandler);
+            viewer.addTiledImage({
+                tileSource: '/test/data/testpattern.dzi'
+            });
+        }
+
+        const reopenHandler = function (event) {
+            event.item.allowZombieCache(true);
+
+            viewer.removeHandler('add-item', reopenHandler);
+            test.equal(jobCounter, jobsAfterRemoval, 'Reopening image does not fetch any tiles imemdiatelly.');
+
+            waitFor(() => {
+                if (event.item._fullyLoaded) {
+                    viewer.removeHandler('tile-unloaded', unloadTileHandler);
+                    viewer.removeHandler('tile-loaded', tileCounter);
+                    coverage = undefined;
+
+                    //console test needs here explicit removal to finish correctly
+                    OpenSeadragon.ImageLoader.prototype.addJob = originalJob;
+                    done();
+                    return true;
+                }
+                return false;
+            });
+        };
+
+        const unloadTileHandler = function (event) {
+            test.equal(event.destroyed, false, "Tile unload event should not delete with zombies!");
+        }
+
+        viewer.world.addHandler('add-item', openHandler);
+        viewer.world.addHandler('remove-item', removalHandler);
+        viewer.addHandler('tile-unloaded', unloadTileHandler);
+        viewer.addHandler('tile-loaded', tileCounter);
+
+        viewer.open('/test/data/testpattern.dzi');
     });
 
     QUnit.test('Zombie Cache Replace Item', function(test) {
         const done = test.async();
 
-        //TODO FIX
-        test.ok("TODO: FIX TEST SUITE FOR NEW CACHE SYSTEM");
-        done();
-        // //test jobs by coverage: fail if
-        // let jobCounter = 0, coverage = undefined;
-        // OpenSeadragon.ImageLoader.prototype.addJob = function (options) {
-        //     jobCounter++;
-        //     if (coverage) {
-        //         //old coverage of previous tiled image: if loaded, fail --> should be in cache
-        //         const coverageItem = coverage[options.tile.level][options.tile.x][options.tile.y];
-        //         if (!coverageItem) {
-        //             console.warn(coverage, coverage[options.tile.level][options.tile.x], options.tile);
-        //         }
-        //         test.ok(!coverageItem, "Attempt to add job for tile data that was previously loaded.");
-        //     }
-        //     return originalJob.call(this, options);
-        // };
-        //
-        // let tilesFinished = 0;
-        // const tileCounter = function (event) {tilesFinished++;}
-        //
-        // const openHandler = function(event) {
-        //     event.item.allowZombieCache(true);
-        //     viewer.world.removeHandler('add-item', openHandler);
-        //     viewer.world.addHandler('add-item', reopenHandler);
-        //
-        //     waitFor(() => {
-        //         if (tilesFinished === jobCounter && event.item._fullyLoaded) {
-        //             coverage = $.extend(true, {}, event.item.coverage);
-        //             viewer.addTiledImage({
-        //                 tileSource: '/test/data/testpattern.dzi',
-        //                 index: 0,
-        //                 replace: true
-        //             });
-        //             return true;
-        //         }
-        //         return false;
-        //     });
-        // };
-        //
-        // const reopenHandler = function (event) {
-        //     event.item.allowZombieCache(true);
-        //
-        //     viewer.removeHandler('add-item', reopenHandler);
-        //     waitFor(() => {
-        //         if (event.item._fullyLoaded) {
-        //             viewer.removeHandler('tile-unloaded', unloadTileHandler);
-        //             viewer.removeHandler('tile-loaded', tileCounter);
-        //
-        //             //console test needs here explicit removal to finish correctly
-        //             OpenSeadragon.ImageLoader.prototype.addJob = originalJob;
-        //             done();
-        //             return true;
-        //         }
-        //         return false;
-        //     });
-        // };
-        //
-        // const unloadTileHandler = function (event) {
-        //     test.equal(event.destroyed, false, "Tile unload event should not delete with zombies!");
-        // }
-        //
-        // viewer.world.addHandler('add-item', openHandler);
-        // viewer.addHandler('tile-unloaded', unloadTileHandler);
-        // viewer.addHandler('tile-loaded', tileCounter);
-        //
-        // viewer.open('/test/data/testpattern.dzi');
+        let jobCounter = 0, coverage = undefined;
+        OpenSeadragon.ImageLoader.prototype.addJob = function (options) {
+            jobCounter++;
+            if (coverage) {
+                //old coverage of previous tiled image: if loaded, fail --> should be in cache
+                const coverageItem = coverage[options.tile.level][options.tile.x][options.tile.y];
+                if (!coverageItem) {
+                    console.warn(coverage, coverage[options.tile.level][options.tile.x], options.tile);
+                }
+                test.ok(!coverageItem, "Attempt to add job for tile data that was previously loaded.");
+            }
+            return originalJob.call(this, options);
+        };
+
+        let tilesFinished = 0;
+        const tileCounter = function (event) {tilesFinished++;}
+
+        const openHandler = function(event) {
+            event.item.allowZombieCache(true);
+            viewer.world.removeHandler('add-item', openHandler);
+            viewer.world.addHandler('add-item', reopenHandler);
+
+            waitFor(() => {
+                if (tilesFinished === jobCounter && event.item._fullyLoaded) {
+                    coverage = $.extend(true, {}, event.item.coverage);
+                    viewer.addTiledImage({
+                        tileSource: '/test/data/testpattern.dzi',
+                        index: 0,
+                        replace: true
+                    });
+                    return true;
+                }
+                return false;
+            });
+        };
+
+        const reopenHandler = function (event) {
+            event.item.allowZombieCache(true);
+
+            viewer.removeHandler('add-item', reopenHandler);
+            waitFor(() => {
+                if (event.item._fullyLoaded) {
+                    viewer.removeHandler('tile-unloaded', unloadTileHandler);
+                    viewer.removeHandler('tile-loaded', tileCounter);
+
+                    //console test needs here explicit removal to finish correctly
+                    OpenSeadragon.ImageLoader.prototype.addJob = originalJob;
+                    done();
+                    return true;
+                }
+                return false;
+            });
+        };
+
+        const unloadTileHandler = function (event) {
+            test.equal(event.destroyed, false, "Tile unload event should not delete with zombies!");
+        }
+
+        viewer.world.addHandler('add-item', openHandler);
+        viewer.addHandler('tile-unloaded', unloadTileHandler);
+        viewer.addHandler('tile-loaded', tileCounter);
+
+        viewer.open('/test/data/testpattern.dzi');
     });
 
 })();
diff --git a/test/test.html b/test/test.html
index 5da55417..d7f19389 100644
--- a/test/test.html
+++ b/test/test.html
@@ -7,7 +7,7 @@
         window.QUnit = {
             config: {
                 //one minute per test timeout
-                testTimeout: 60000
+                testTimeout: 5000
             }
         };
     </script>