diff --git a/src/layer.js b/src/layer.js
index e7ffbfbc2..2b9756e3f 100644
--- a/src/layer.js
+++ b/src/layer.js
@@ -98,22 +98,59 @@ export class MapLayer extends HTMLElement {
       this.attachShadow({ mode: 'open' });
     }
     new Promise((resolve, reject) => {
-      this.addEventListener('extentload', () => {
-        // need to detect / handle layer.error here
-        resolve();
+      this.addEventListener(
+        'extentload',
+        (event) => {
+          event.stopPropagation();
+          if (event.detail.error) {
+            reject();
+          } else {
+            resolve();
+          }
+        },
+        { once: true }
+      );
+      this.addEventListener(
+        'changestyle',
+        function (e) {
+          e.stopPropagation();
+          this.src = e.detail.src;
+        },
+        { once: true }
+      );
+      this.addEventListener(
+        'changeprojection',
+        function (e) {
+          e.stopPropagation();
+          this.src = e.detail.href;
+        },
+        { once: true }
+      );
+      let base = this.baseURI ? this.baseURI : document.baseURI;
+      let opacity_value = this.hasAttribute('opacity')
+        ? this.getAttribute('opacity')
+        : '1.0';
+      this._layer = M.mapMLLayer(
+        this.src ? new URL(this.src, base).href : null,
+        this,
+        {
+          mapprojection: this.parentElement._map.options.projection,
+          opacity: opacity_value
+        }
+      );
+    })
+      .then(() => {
+        this._onLayerExtentLoad();
+        this._attachedToMap();
+        if (this._layerControl && !this.hidden) {
+          this._layerControl.addOrUpdateOverlay(this._layer, this.label);
+        }
+      })
+      .catch((e) => {
+        this.dispatchEvent(
+          new CustomEvent('error', { detail: { target: this } })
+        );
       });
-      //    this._layer.on('extentload', this._onLayerExtentLoad, this);
-      this._setUpEvents();
-      // add other event listeners possibly
-      this._ready(); // rename _ready
-    }).then(() => {
-      this._onLayerExtentLoad();
-      // refactor _attachedToMap
-      this._attachedToMap();
-      if (this._layerControl && !this.hidden) {
-        this._layerControl.addOrUpdateOverlay(this._layer, this.label);
-      }
-    });
   }
 
   adoptedCallback() {
@@ -202,18 +239,12 @@ export class MapLayer extends HTMLElement {
     if (this._layerControl) {
       this._layerControl.addOrUpdateOverlay(this._layer, this.label);
     }
-    if (!this._layer.error) {
-      // re-use 'loadedmetadata' event from HTMLMediaElement inteface, applied
-      // to MapML extent as metadata
-      // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/loadedmetadata_event
-      this.dispatchEvent(
-        new CustomEvent('loadedmetadata', { detail: { target: this } })
-      );
-    } else {
-      this.dispatchEvent(
-        new CustomEvent('error', { detail: { target: this } })
-      );
-    }
+    // re-use 'loadedmetadata' event from HTMLMediaElement inteface, applied
+    // to MapML extent as metadata
+    // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/loadedmetadata_event
+    this.dispatchEvent(
+      new CustomEvent('loadedmetadata', { detail: { target: this } })
+    );
   }
   _validateDisabled() {
     setTimeout(() => {
@@ -337,26 +368,6 @@ export class MapLayer extends HTMLElement {
       this.checked = this._layer._map.hasLayer(this._layer);
     }
   }
-  _ready() {
-    // the layer might not be attached to a map
-    // so we need a way for non-src based layers to establish what their
-    // zoom range, extent and projection are.  meta elements in content to
-    // allow the author to provide this explicitly are one way, they will
-    // be parsed from the second parameter here
-    // IE 11 did not have a value for this.baseURI for some reason
-    var base = this.baseURI ? this.baseURI : document.baseURI;
-    let opacity_value = this.hasAttribute('opacity')
-      ? this.getAttribute('opacity')
-      : '1.0';
-    this._layer = M.mapMLLayer(
-      this.src ? new URL(this.src, base).href : null,
-      this,
-      {
-        mapprojection: this.parentElement._map.options.projection,
-        opacity: opacity_value
-      }
-    );
-  }
   _attachedToMap() {
     // set i to the position of this layer element in the set of layers
     var i = 0,
@@ -414,16 +425,6 @@ export class MapLayer extends HTMLElement {
       this._layer.off();
     }
   }
-  _setUpEvents() {
-    this.addEventListener('changestyle', function (e) {
-      e.stopPropagation();
-      this.src = e.detail.src;
-    });
-    this.addEventListener('changeprojection', function (e) {
-      e.stopPropagation();
-      this.src = e.detail.href;
-    });
-  }
   zoomTo() {
     if (!this.extent) return;
     let map = this._layer._map,
diff --git a/src/mapml/layers/MapMLLayer.js b/src/mapml/layers/MapMLLayer.js
index 56c843b8c..70d0ebee6 100644
--- a/src/mapml/layers/MapMLLayer.js
+++ b/src/mapml/layers/MapMLLayer.js
@@ -1183,8 +1183,6 @@ export var MapMLLayer = L.Layer.extend({
       fetch(url, { headers: headers })
         .then((response) => {
           if (!response.ok) {
-            layer.error = true;
-            layer.fire('extentload', layer, true);
             throw new Error(`HTTP error! Status: ${response.status}`);
           }
           return response.text();
@@ -1193,7 +1191,11 @@ export var MapMLLayer = L.Layer.extend({
           fCallback(response, false);
         })
         .catch((response) => {
-          console.log('wut');
+          layer.error = true;
+          layer._layerEl.dispatchEvent(
+            new CustomEvent('extentload', { detail: layer })
+          );
+          console.log(`HTTP error! Status: ${response.message}`);
         });
     }
     function transcribe(element) {
@@ -1417,11 +1419,23 @@ export var MapMLLayer = L.Layer.extend({
       var mapml = !local
         ? new DOMParser().parseFromString(content, 'text/xml')
         : content;
+      if (
+        !local &&
+        (mapml.querySelector('parsererror') || !mapml.querySelector('mapml-'))
+      ) {
+        layer.error = true;
+        layer._layerEl.dispatchEvent(
+          new CustomEvent('extentload', { detail: layer })
+        );
+        throw new Error('Parser error');
+      }
       var base = new URL(
         mapml.querySelector('map-base')
           ? mapml.querySelector('map-base').getAttribute('href')
-          : mapml.baseURI,
-        mapml.baseURI
+          : local
+          ? mapml.baseURI
+          : layer._href,
+        layer._href
       ).href;
       layer._properties = {};
       if (mapml.querySelector && mapml.querySelector('map-feature'))
@@ -1442,8 +1456,10 @@ export var MapMLLayer = L.Layer.extend({
         layer._layerEl.parentElement._toggleControls();
       }
       layer.fire('extentload', layer, false);
+      // need this to enable processing by the <layer-> element connectedCallback
+      // processing
       layer._layerEl.dispatchEvent(
-        new CustomEvent('extentload', { detail: layer, bubbles: true })
+        new CustomEvent('extentload', { detail: layer })
       );
       // local functions
       function thinkOfAGoodName() {
diff --git a/src/web-map.js b/src/web-map.js
index ff03f80ae..ac72b1cbe 100644
--- a/src/web-map.js
+++ b/src/web-map.js
@@ -75,23 +75,13 @@ export class WebMap extends HTMLMapElement {
     }
   }
   get projection() {
-    return this.hasAttribute('projection')
+    return this.hasAttribute('projection') && M[this.getAttribute('projection')]
       ? this.getAttribute('projection')
       : 'OSMTILE';
   }
   set projection(val) {
     if (val && M[val]) {
       this.setAttribute('projection', val);
-      if (this._map && this._map.options.projection !== val) {
-        this._map.options.crs = M[val];
-        this._map.options.projection = val;
-        for (let layer of this.querySelectorAll('layer-')) {
-          layer.removeAttribute('disabled');
-          let reAttach = this.removeChild(layer);
-          this.appendChild(reAttach);
-        }
-        if (this._debug) for (let i = 0; i < 2; i++) this.toggleDebug();
-      } else this.dispatchEvent(new CustomEvent('createmap'));
     } else throw new Error('Undefined Projection');
   }
   get zoom() {
@@ -180,18 +170,16 @@ export class WebMap extends HTMLMapElement {
     // is because the mapml-viewer element has / can have a size of 0 up until after
     // something that happens between this point and the event handler executing
     // perhaps a browser rendering cycle??
-    this.addEventListener('createmap', this._createMap);
 
     let custom = !['CBMTILE', 'APSTILE', 'OSMTILE', 'WGS84'].includes(
       this.projection
     );
-    if (!custom) {
-      // this is worth a read, because dispatchEvent is synchronous
-      // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent
-      // In particular:
-      //   "All applicable event handlers are called and return before dispatchEvent() returns."
-      this.dispatchEvent(new CustomEvent('createmap'));
-    }
+    // this is worth a read, because dispatchEvent is synchronous
+    // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent
+    // In particular:
+    //   "All applicable event handlers are called and return before dispatchEvent() returns."
+    this._createMap();
+
     this._toggleStatic();
 
     /*
@@ -407,6 +395,22 @@ export class WebMap extends HTMLMapElement {
       case 'static':
         this._toggleStatic();
         break;
+      case 'projection':
+        if (newValue && M[newValue]) {
+          if (this._map && this._map.options.projection !== newValue) {
+            this._map.options.crs = M[newValue];
+            this._map.options.projection = newValue;
+            for (let layer of this.querySelectorAll('layer-')) {
+              layer.removeAttribute('disabled');
+              let reAttach = this.removeChild(layer);
+              this.appendChild(reAttach);
+            }
+            if (this._debug) for (let i = 0; i < 2; i++) this.toggleDebug();
+            this.zoomTo(this.lat, this.lon, this.zoom);
+            //this.dispatchEvent(new CustomEvent('projectionchange'));
+          }
+        }
+        break;
     }
   }
 
@@ -860,7 +864,7 @@ export class WebMap extends HTMLMapElement {
         this._updateMapCenter();
         this._addToHistory();
         this.dispatchEvent(
-          new CustomEvent('moveend', { detail: { target: this } })
+          new CustomEvent('map-moveend', { detail: { target: this } })
         );
       },
       this
diff --git a/test/e2e/api/domApi-HTMLLayerElement.test.js b/test/e2e/api/domApi-HTMLLayerElement.test.js
index 26f24c923..eab6c8be7 100644
--- a/test/e2e/api/domApi-HTMLLayerElement.test.js
+++ b/test/e2e/api/domApi-HTMLLayerElement.test.js
@@ -43,7 +43,9 @@ test.describe('HTMLLayerElement DOM API Tests', () => {
     let localWithTitleLabel = await page.evaluate(() => {
       return document.querySelector('#local-with-title').label;
     });
-    expect(localWithTitleLabel).toEqual('Layer name set via local map-title element - unsettable via HTMLLayerelement.label');
+    expect(localWithTitleLabel).toEqual(
+      'Layer name set via local map-title element - unsettable via HTMLLayerelement.label'
+    );
     let localWithTitleName = await page.evaluate(() => {
       let layer = document.querySelector('#local-with-title');
       return layer._layer.getName();
diff --git a/test/e2e/api/domApi-mapml-viewer.test.js b/test/e2e/api/domApi-mapml-viewer.test.js
index 27b68abcb..2ebf9fc77 100644
--- a/test/e2e/api/domApi-mapml-viewer.test.js
+++ b/test/e2e/api/domApi-mapml-viewer.test.js
@@ -140,10 +140,10 @@ test.describe('mapml-viewer DOM API Tests', () => {
     // locators avoid flaky tests, allegedly
     const viewer = await page.locator('mapml-viewer');
     await viewer.evaluate(() => {
-        let m = document.querySelector('mapml-viewer');
-        document.body.removeChild(m);
-        document.body.appendChild(m);
-      });
+      let m = document.querySelector('mapml-viewer');
+      document.body.removeChild(m);
+      document.body.appendChild(m);
+    });
     await page.waitForTimeout(200);
     expect(
       await viewer.evaluate(() => {
diff --git a/test/e2e/api/domApi-web-map.test.js b/test/e2e/api/domApi-web-map.test.js
index 27235531d..6d5c40877 100644
--- a/test/e2e/api/domApi-web-map.test.js
+++ b/test/e2e/api/domApi-web-map.test.js
@@ -133,10 +133,10 @@ test.describe('web-map DOM API Tests', () => {
     // locators avoid flaky tests, allegedly
     const viewer = await page.locator('map');
     await viewer.evaluate(() => {
-        let m = document.querySelector('map');
-        document.body.removeChild(m);
-        document.body.appendChild(m);
-      });
+      let m = document.querySelector('map');
+      document.body.removeChild(m);
+      document.body.appendChild(m);
+    });
     await page.waitForTimeout(200);
     expect(
       await viewer.evaluate(() => {
diff --git a/test/e2e/core/drag.test.js b/test/e2e/core/drag.test.js
index 2c5f0f2ed..8a94878fd 100644
--- a/test/e2e/core/drag.test.js
+++ b/test/e2e/core/drag.test.js
@@ -73,8 +73,8 @@ test.describe('UI Drag&Drop Test', () => {
       (span) => span.innerText
     );
     const layerIndex = await page.$eval(
-      '.leaflet-pane.leaflet-overlay-pane > div:nth-child(1)',
-      (div) => div.style.zIndex
+      '.leaflet-pane.leaflet-overlay-pane .mapml-templated-tile-container',
+      (div) => div.parentElement.parentElement.style.zIndex
     );
     const domLayer = await page.$eval(
       'body > map > layer-:nth-child(4)',
@@ -109,8 +109,8 @@ test.describe('UI Drag&Drop Test', () => {
       (span) => span.innerText
     );
     const layerIndex = await page.$eval(
-      '.leaflet-overlay-pane > div:nth-child(2)',
-      (div) => div.style.zIndex
+      '.leaflet-overlay-pane .mapml-static-tile-layer',
+      (div) => div.parentElement.style.zIndex
     );
     const domLayer = await page.$eval(
       'map > layer-:nth-child(3)',
diff --git a/test/e2e/mapml-viewer/mapml-viewer.test.js b/test/e2e/mapml-viewer/mapml-viewer.test.js
index 7c2b40f39..49adbf28c 100644
--- a/test/e2e/mapml-viewer/mapml-viewer.test.js
+++ b/test/e2e/mapml-viewer/mapml-viewer.test.js
@@ -120,12 +120,9 @@ test.describe('Playwright mapml-viewer Element Tests', () => {
         await page.$eval('body > mapml-viewer', (layer) =>
           layer.setAttribute('controlslist', 'nolayer')
         );
+        let layerControl = await page.locator('.leaflet-control-layers');
+        await expect(layerControl).toBeHidden();
 
-        let layerControlHidden = await page.$eval(
-          '.leaflet-top.leaflet-right',
-          (div) => div.firstChild.hidden
-        );
-        expect(layerControlHidden).toEqual(true);
         await page.click('body > mapml-viewer', { button: 'right' });
         // toggle controls
         await page.click('.mapml-contextmenu > button:nth-of-type(6)');
@@ -133,11 +130,7 @@ test.describe('Playwright mapml-viewer Element Tests', () => {
         // toggle controls
         await page.click('.mapml-contextmenu > button:nth-of-type(6)');
 
-        layerControlHidden = await page.$eval(
-          '.leaflet-top.leaflet-right',
-          (div) => div.firstChild.hidden
-        );
-        expect(layerControlHidden).toEqual(true);
+        await expect(layerControl).toBeHidden();
       });
     });
   });