[osgearth] 03/15: Imported Upstream version 2.6.0+dfsg

Bas Couwenberg sebastic at xs4all.nl
Sun Oct 26 16:19:40 UTC 2014


This is an automated email from the git hooks/post-receive script.

sebastic-guest pushed a commit to branch master
in repository osgearth.

commit fb50a436886a4060bdc855106825a43bcac4ea97
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Mon Oct 13 20:58:57 2014 +0200

    Imported Upstream version 2.6.0+dfsg
---
 CMakeLists.txt                                     |    45 +-
 CMakeModules/FindDuktape.cmake                     |    68 +
 CMakeModules/FindGEOS.cmake                        |     6 +-
 CMakeModules/FindJavaScriptCore.cmake              |     2 +-
 CMakeModules/FindLevelDB.cmake                     |    86 +
 CMakeModules/FindLibNoise.cmake                    |     2 +-
 CMakeModules/FindOSG.cmake                         |     8 +-
 CMakeModules/FindOsgEarth.cmake                    |    91 +
 CMakeModules/FindSilverLining.cmake                |   116 +
 CMakeModules/FindTriton.cmake                      |   116 +
 CMakeModules/FindV8.cmake                          |   213 +-
 CMakeModules/GetGitRevisionDescription.cmake       |     8 +-
 CMakeModules/OsgEarthMacroUtils.cmake              |     6 +-
 LICENSE.txt                                        |     2 +-
 README.txt                                         |     2 +-
 data/resources/textures_us/catalog.xml             |    15 +-
 .../commercial/10storymodernconcrete.jpg           |   Bin 16585 -> 20491 bytes
 .../textures_us/commercial/12storygovtmodern.jpg   |   Bin 21473 -> 22644 bytes
 .../commercial/15storybrownconcroffice2.jpg        |   Bin 25480 -> 20374 bytes
 .../commercial/15storyltbrownconcroffice3.jpg      |   Bin 27095 -> 21745 bytes
 .../commercial/16storyconcrglassgreymodern4.jpg    |   Bin 15728 -> 12318 bytes
 .../commercial/18storyconcrbrownoffice2.jpg        |   Bin 7662 -> 8286 bytes
 .../textures_us/commercial/18storyoffice.jpg       |   Bin 33012 -> 24905 bytes
 .../commercial/20storygreycncrglassmodern.jpg      |   Bin 19166 -> 16949 bytes
 .../textures_us/commercial/25storyBrownWide1.jpg   |   Bin 31227 -> 42743 bytes
 .../textures_us/commercial/30storyconcrbrown4.jpg  |   Bin 25146 -> 21841 bytes
 .../3storyIndustrial_concrglasswhite1.jpg          |   Bin 4562 -> 5811 bytes
 .../textures_us/commercial/40storymodern.jpg       |   Bin 21405 -> 32468 bytes
 .../textures_us/commercial/45storyglassmodern.jpg  |   Bin 14939 -> 12862 bytes
 .../textures_us/commercial/5storywhite.jpg         |   Bin 5509 -> 7282 bytes
 .../textures_us/commercial/7storymodernsq.jpg      |   Bin 7147 -> 8377 bytes
 .../commercial/US-dcofficeconcrwhite6-7st.jpg      |   Bin 22797 -> 24466 bytes
 .../commercial/US-dcofficeconcrwhite8st.jpg        |   Bin 35714 -> 37917 bytes
 data/resources/textures_us/misc/asphalt.jpg        |   Bin 0 -> 806389 bytes
 .../textures_us/residential/US-CityCondo-3st.jpg   |   Bin 8801 -> 9348 bytes
 .../residential/tiles/USUAE-8stTile_rep.jpg        |   Bin 22042 -> 18095 bytes
 data/resources/textures_us/rooftop/roof_misc2.jpg  |   Bin 11642 -> 9501 bytes
 data/resources/textures_us/rooftop/roof_misc3.jpg  |   Bin 53332 -> 16060 bytes
 data/resources/textures_us/rooftop/roof_misc4.jpg  |   Bin 8291 -> 6456 bytes
 data/resources/textures_us/rooftop/roof_misc5.jpg  |   Bin 12332 -> 9647 bytes
 .../textures_us/rooftop/tiled/roof_tiled1.jpg      |   Bin 5762 -> 5742 bytes
 .../textures_us/rooftop/tiled/roof_tiled3.jpg      |   Bin 5815 -> 5807 bytes
 docs/source/about.rst                              |    24 +-
 docs/source/data.rst                               |     4 +-
 docs/source/developer/utilities.rst                |    43 +-
 docs/source/faq.rst                                |    35 +-
 docs/source/references/colorfilters.rst            |     6 +-
 .../source/references/drivers/cache/filesystem.rst |    40 +
 docs/source/references/drivers/cache/index.rst     |    10 +
 docs/source/references/drivers/cache/leveldb.rst   |    39 +
 docs/source/references/drivers/effects/glsky.rst   |    14 +
 docs/source/references/drivers/effects/index.rst   |    10 +
 .../references/drivers/effects/silverlining.rst    |    29 +
 .../references/drivers/effects/simplesky.rst       |    24 +
 .../references/drivers/effects/sky_shared.rst      |     4 +
 docs/source/references/drivers/feature/wfs.rst     |     2 +
 docs/source/references/drivers/index.rst           |     3 +-
 .../drivers/model/feature_model_shared_props.rst   |     1 +
 docs/source/references/drivers/model/simple.rst    |     1 +
 docs/source/references/drivers/tile/colorramp.rst  |    35 +
 docs/source/references/drivers/tile/index.rst      |     2 +
 docs/source/references/drivers/tile/mbtiles.rst    |    26 +
 docs/source/references/drivers/tile/vpb.rst        |     1 +
 docs/source/references/earthfile.rst               |    81 +-
 docs/source/references/envvars.rst                 |    29 +-
 docs/source/references/symbology.rst               |     7 +
 docs/source/startup.rst                            |    32 +-
 docs/source/user/caching.rst                       |    41 +-
 docs/source/user/features.rst                      |    45 +-
 docs/source/user/tools.rst                         |    32 +-
 src/CMakeLists.txt                                 |     2 +-
 src/applications/CMakeLists.txt                    |    19 +-
 .../osgearth_annotation/osgearth_annotation.cpp    |    78 +-
 src/applications/osgearth_atlas/CMakeLists.txt     |     7 +
 src/applications/osgearth_atlas/osgearth_atlas.cpp |   289 +
 .../osgearth_backfill/osgearth_backfill.cpp        |     2 +-
 src/applications/osgearth_boundarygen/BoundaryUtil |    60 +-
 .../osgearth_boundarygen/BoundaryUtil.cpp          |   267 +-
 .../osgearth_boundarygen/VertexCollectionVisitor   |     2 +-
 .../VertexCollectionVisitor.cpp                    |     2 +-
 .../osgearth_boundarygen/boundarygen.cpp           |    27 +-
 src/applications/osgearth_city/osgearth_city.cpp   |   201 +-
 src/applications/osgearth_clamp/osgearth_clamp.cpp |     2 +-
 src/applications/osgearth_clipplane/CMakeLists.txt |     7 +
 .../osgearth_clipplane/osgearth_clipplane.cpp      |   130 +
 .../osgearth_colorfilter/osgearth_colorfilter.cpp  |    12 +-
 .../osgearth_controls/osgearth_controls.cpp        |    30 +-
 src/applications/osgearth_conv/CMakeLists.txt      |     7 +
 src/applications/osgearth_conv/osgearth_conv.cpp   |   405 +
 src/applications/osgearth_demo/CMakeLists.txt      |    25 +-
 src/applications/osgearth_demo/osgearth_demo.cpp   |    12 +-
 .../osgearth_elevation/osgearth_elevation.cpp      |    39 +-
 .../osgearth_featureeditor.cpp                     |     4 +-
 .../osgearth_featurefilter.cpp                     |     2 +-
 .../osgearth_featureinfo/osgearth_featureinfo.cpp  |     2 +-
 .../osgearth_featuremanip.cpp                      |     2 +-
 .../osgearth_featurequery.cpp                      |     4 +-
 .../osgearth_features/osgearth_features.cpp        |     2 +-
 src/applications/osgearth_fog/CMakeLists.txt       |     7 +
 .../osgearth_fog.cpp}                              |    41 +-
 .../osgearth_graticule/osgearth_graticule.cpp      |     8 +-
 .../osgearth_imageoverlay.cpp                      |     4 +-
 src/applications/osgearth_los/osgearth_los.cpp     |     2 +-
 src/applications/osgearth_manip/osgearth_manip.cpp |    78 +-
 src/applications/osgearth_map/osgearth_map.cpp     |     4 +-
 .../osgearth_measure/osgearth_measure.cpp          |     5 +-
 .../osgearth_minimap/osgearth_minimap.cpp          |     2 +-
 src/applications/osgearth_mrt/CMakeLists.txt       |     7 +
 src/applications/osgearth_mrt/osgearth_mrt.cpp     |   335 +
 .../osgearth_occlusionculling.cpp                  |     2 +-
 .../osgearth_overlayviewer.cpp                     |    40 +-
 .../osgearth_package/osgearth_package.cpp          |   831 +-
 .../osgearth_package_qt/CMakeLists.txt             |    27 +-
 src/applications/osgearth_package_qt/ExportDialog  |    74 +-
 .../osgearth_package_qt/ExportDialog.cpp           |   152 +-
 .../osgearth_package_qt/ExportDialog.ui            |    69 +-
 .../osgearth_package_qt/ExportProgress.cpp         |    53 +
 .../osgearth_package_qt/ExportProgress.h           |    28 +
 .../osgearth_package_qt/PackageQtMainWindow        |    79 +-
 .../osgearth_package_qt/SceneController.cpp        |    24 +-
 .../osgearth_package_qt/SceneController.h          |    12 +-
 .../osgearth_package_qt/TMSExporter.cpp            |   607 +-
 src/applications/osgearth_package_qt/TMSExporter.h |    87 +-
 src/applications/osgearth_package_qt/WaitDialog    |     5 +-
 .../osgearth_package_qt/WaitDialog.cpp             |     2 +-
 .../osgearth_package_qt/package_qt.cpp             |   294 +-
 src/applications/osgearth_qt/CMakeLists.txt        |    19 +-
 src/applications/osgearth_qt/DemoMainWindow        |     2 +-
 src/applications/osgearth_qt/osgearth_qt.cpp       |    22 +-
 src/applications/osgearth_qt_simple/CMakeLists.txt |    16 +-
 .../osgearth_qt_simple/osgearth_qt_simple.cpp      |    12 +-
 .../osgearth_qt_windows/CMakeLists.txt             |    15 +-
 .../osgearth_qt_windows/osgearth_qt_windows.cpp    |    13 +-
 src/applications/osgearth_seed/osgearth_seed.cpp   |   321 +-
 .../osgearth_sequencecontrol.cpp                   |     4 +-
 .../osgearth_shadercomp/osgearth_shadercomp.cpp    |   251 +-
 src/applications/osgearth_shadergen/CMakeLists.txt |     7 +
 .../osgearth_shadergen.cpp}                        |    74 +-
 src/applications/osgearth_shadow/CMakeLists.txt    |    14 -
 .../osgearth_shadow/osgearth_shadow.cpp            |   171 -
 .../osgearth_sharedlayer/osgearth_sharedlayer.cpp  |     3 +-
 .../osgearth_terraineffects.cpp                    |    34 +-
 .../osgearth_terrainprofile.cpp                    |     2 +-
 src/applications/osgearth_tfs/osgearth_tfs.cpp     |     2 +-
 .../osgearth_tileindex/osgearth_tileindex.cpp      |     2 +-
 .../osgearth_tilesource/osgearth_tilesource.cpp    |     2 +-
 src/applications/osgearth_toc/osgearth_toc.cpp     |     4 +-
 .../osgearth_tracks/osgearth_tracks.cpp            |     8 +-
 src/applications/osgearth_transform/CMakeLists.txt |     7 +
 .../osgearth_transform/osgearth_transform.cpp      |   163 +
 .../osgearth_version/osgearth_version.cpp          |     2 +-
 .../osgearth_viewer/osgearth_viewer.cpp            |    12 +-
 src/osgEarth/AlphaEffect                           |     5 +-
 src/osgEarth/AlphaEffect.cpp                       |    29 +-
 src/osgEarth/AutoScale                             |     2 +-
 src/osgEarth/AutoScale.cpp                         |     2 +-
 src/osgEarth/Bounds                                |     2 +-
 src/osgEarth/Bounds.cpp                            |     2 +-
 src/osgEarth/CMakeLists.txt                        |    36 +-
 src/osgEarth/Cache                                 |    35 +-
 src/osgEarth/Cache.cpp                             |     6 +-
 src/osgEarth/CacheBin                              |    59 +-
 src/osgEarth/CacheEstimator                        |     2 +-
 src/osgEarth/CacheEstimator.cpp                    |     2 +-
 src/osgEarth/CachePolicy                           |    11 +-
 src/osgEarth/CachePolicy.cpp                       |    40 +-
 src/osgEarth/CacheSeed                             |   101 +-
 src/osgEarth/CacheSeed.cpp                         |   353 +-
 src/osgEarth/Capabilities                          |    30 +-
 src/osgEarth/Capabilities.cpp                      |   109 +-
 src/osgEarth/ClampableNode.cpp                     |     2 +-
 src/osgEarth/ClampingTechnique                     |     2 +-
 src/osgEarth/ClampingTechnique.cpp                 |     6 +-
 src/osgEarth/ColorFilter                           |     2 +-
 src/osgEarth/ColorFilter.cpp                       |     2 +-
 src/osgEarth/Common                                |     2 +-
 src/osgEarth/CompositeTileSource                   |    37 +-
 src/osgEarth/CompositeTileSource.cpp               |   391 +-
 src/osgEarth/Config                                |     2 +-
 src/osgEarth/Config.cpp                            |    81 +-
 src/osgEarth/Containers                            |    13 +-
 src/osgEarth/Cube                                  |    11 +-
 src/osgEarth/Cube.cpp                              |    67 +-
 src/osgEarth/CullingUtils                          |    28 +-
 src/osgEarth/CullingUtils.cpp                      |    68 +-
 src/osgEarth/DPLineSegmentIntersector              |     2 +-
 src/osgEarth/DPLineSegmentIntersector.cpp          |     5 +-
 src/osgEarth/DateTime                              |    20 +-
 src/osgEarth/DateTime.cpp                          |   172 +-
 src/osgEarth/Decluttering                          |     2 +-
 src/osgEarth/Decluttering.cpp                      |     5 +-
 src/osgEarth/DepthOffset                           |    15 +-
 src/osgEarth/DepthOffset.cpp                       |   119 +-
 src/osgEarth/Draggers                              |     2 +-
 src/osgEarth/Draggers.cpp                          |     7 +-
 src/osgEarth/DrapeableNode                         |     7 +
 src/osgEarth/DrapeableNode.cpp                     |     9 +-
 src/osgEarth/DrapingTechnique                      |     2 +-
 src/osgEarth/DrapingTechnique.cpp                  |    18 +-
 src/osgEarth/DrawInstanced                         |    26 +-
 src/osgEarth/DrawInstanced.cpp                     |   200 +-
 src/osgEarth/ECEF                                  |     2 +-
 src/osgEarth/ECEF.cpp                              |     2 +-
 src/osgEarth/ElevationLOD                          |     2 +-
 src/osgEarth/ElevationLOD.cpp                      |     2 +-
 src/osgEarth/ElevationLayer                        |    35 +-
 src/osgEarth/ElevationLayer.cpp                    |   664 +-
 src/osgEarth/ElevationQuery                        |    26 +-
 src/osgEarth/ElevationQuery.cpp                    |   382 +-
 src/osgEarth/Export                                |     2 +-
 src/osgEarth/FadeEffect                            |     2 +-
 src/osgEarth/FadeEffect.cpp                        |     2 +-
 src/osgEarth/FileUtils                             |    19 +-
 src/osgEarth/FileUtils.cpp                         |   191 +-
 src/osgEarth/GeoCommon                             |    12 +-
 src/osgEarth/GeoData                               |    43 +-
 src/osgEarth/GeoData.cpp                           |    91 +-
 src/osgEarth/GeoMath                               |    30 +-
 src/osgEarth/GeoMath.cpp                           |   139 +-
 src/osgEarth/GeoTransform                          |    95 +
 src/osgEarth/GeoTransform.cpp                      |   128 +
 src/osgEarth/Geoid                                 |     2 +-
 src/osgEarth/Geoid.cpp                             |     6 +-
 src/osgEarth/HTTPClient                            |   780 +-
 src/osgEarth/HTTPClient.cpp                        |  2569 +-
 src/osgEarth/HeightFieldUtils                      |    24 +-
 src/osgEarth/HeightFieldUtils.cpp                  |    27 +-
 src/osgEarth/IOTypes                               |   502 +-
 src/osgEarth/IOTypes.cpp                           |     4 +-
 src/osgEarth/ImageLayer                            |    25 +-
 src/osgEarth/ImageLayer.cpp                        |   359 +-
 src/osgEarth/ImageMosaic                           |     2 +-
 src/osgEarth/ImageMosaic.cpp                       |     2 +-
 src/osgEarth/ImageToHeightFieldConverter           |     2 +-
 src/osgEarth/ImageToHeightFieldConverter.cpp       |     2 +-
 src/osgEarth/ImageUtils                            |    45 +-
 src/osgEarth/ImageUtils.cpp                        |   463 +-
 src/osgEarth/JsonUtils                             |     2 +-
 src/osgEarth/JsonUtils.cpp                         |     2 +-
 src/osgEarth/Layer                                 |     4 +-
 src/osgEarth/Layer.cpp                             |     2 +-
 src/osgEarth/LineFunctor                           |     2 +-
 src/osgEarth/LocalTangentPlane                     |     6 +-
 src/osgEarth/LocalTangentPlane.cpp                 |    43 +-
 src/osgEarth/Locators                              |     2 +-
 src/osgEarth/Locators.cpp                          |     2 +-
 src/osgEarth/Map                                   |    64 +-
 src/osgEarth/Map.cpp                               |    88 +-
 src/osgEarth/MapCallback                           |     2 +-
 src/osgEarth/MapCallback.cpp                       |     2 +-
 src/osgEarth/MapFrame                              |    41 +-
 src/osgEarth/MapFrame.cpp                          |   150 +-
 src/osgEarth/MapInfo                               |     2 +-
 src/osgEarth/MapInfo.cpp                           |     2 +-
 src/osgEarth/MapModelChange                        |     2 +-
 src/osgEarth/MapNode                               |    14 +-
 src/osgEarth/MapNode.cpp                           |    53 +-
 src/osgEarth/MapNodeObserver                       |     2 +-
 src/osgEarth/MapNodeOptions                        |     2 +-
 src/osgEarth/MapNodeOptions.cpp                    |     2 +-
 src/osgEarth/MapOptions                            |     6 +-
 src/osgEarth/MapOptions.cpp                        |     2 +-
 src/osgEarth/MaskLayer                             |    30 +-
 src/osgEarth/MaskLayer.cpp                         |    13 +-
 src/osgEarth/MaskNode                              |     2 +-
 src/osgEarth/MaskNode.cpp                          |     2 +-
 src/osgEarth/MaskSource                            |     9 +-
 src/osgEarth/MaskSource.cpp                        |     4 +-
 src/osgEarth/MemCache                              |    13 +-
 src/osgEarth/MemCache.cpp                          |    33 +-
 src/osgEarth/MimeTypes.cpp                         |     2 +-
 src/osgEarth/ModelLayer                            |    96 +-
 src/osgEarth/ModelLayer.cpp                        |   160 +-
 src/osgEarth/ModelSource                           |    42 +-
 src/osgEarth/ModelSource.cpp                       |    72 +-
 src/osgEarth/NodeUtils                             |    26 +-
 src/osgEarth/NodeUtils.cpp                         |     9 +-
 src/osgEarth/Notify                                |     6 +-
 src/osgEarth/Notify.cpp                            |     2 +-
 src/osgEarth/OverlayDecorator                      |     4 +-
 src/osgEarth/OverlayDecorator.cpp                  |   211 +-
 src/osgEarth/OverlayNode                           |     2 +-
 src/osgEarth/OverlayNode.cpp                       |    23 +-
 src/osgEarth/{AlphaEffect => PhongLightingEffect}  |    31 +-
 src/osgEarth/PhongLightingEffect.cpp               |   217 +
 src/osgEarth/Pickers                               |     2 +-
 src/osgEarth/Pickers.cpp                           |     2 +-
 src/osgEarth/PrimitiveIntersector                  |     6 +-
 src/osgEarth/PrimitiveIntersector.cpp              |    81 +-
 src/osgEarth/Profile                               |     6 +-
 src/osgEarth/Profile.cpp                           |   173 +-
 src/osgEarth/Progress                              |    18 +-
 src/osgEarth/Progress.cpp                          |     2 +-
 src/osgEarth/Random                                |     2 +-
 src/osgEarth/Random.cpp                            |     2 +-
 src/osgEarth/Registry                              |    78 +-
 src/osgEarth/Registry.cpp                          |   226 +-
 src/osgEarth/Revisioning                           |     2 +-
 src/osgEarth/Revisioning.cpp                       |     2 +-
 src/osgEarth/ShaderFactory                         |    61 +-
 src/osgEarth/ShaderFactory.cpp                     |   192 +-
 src/osgEarth/ShaderGenerator                       |   161 +-
 src/osgEarth/ShaderGenerator.cpp                   |   880 +-
 src/osgEarth/ShaderUtils                           |    38 +-
 src/osgEarth/ShaderUtils.cpp                       |   136 +-
 src/osgEarth/SharedSARepo                          |   120 +
 src/osgEarth/SparseTexture2DArray                  |     2 +-
 src/osgEarth/SparseTexture2DArray.cpp              |     2 +-
 src/osgEarth/SpatialReference                      |    23 +-
 src/osgEarth/SpatialReference.cpp                  |   126 +-
 src/osgEarth/StateSetCache                         |    33 +-
 src/osgEarth/StateSetCache.cpp                     |   279 +-
 src/osgEarth/StateSetLOD                           |    92 +
 src/osgEarth/StateSetLOD.cpp                       |    86 +
 src/osgEarth/StringUtils                           |    42 +-
 src/osgEarth/StringUtils.cpp                       |    47 +-
 src/osgEarth/TaskService                           |    30 +-
 src/osgEarth/TaskService.cpp                       |    95 +-
 src/osgEarth/Terrain                               |    68 +-
 src/osgEarth/Terrain.cpp                           |    40 +-
 src/osgEarth/TerrainEffect                         |     2 +-
 src/osgEarth/TerrainEngineNode                     |    38 +-
 src/osgEarth/TerrainEngineNode.cpp                 |   185 +-
 src/osgEarth/TerrainLayer                          |   114 +-
 src/osgEarth/TerrainLayer.cpp                      |   272 +-
 src/osgEarth/TerrainOptions                        |   131 +-
 src/osgEarth/TerrainOptions.cpp                    |    84 +-
 src/osgEarth/Tessellator                           |    45 +
 src/osgEarth/Tessellator.cpp                       |   401 +
 src/osgEarth/TextureCompositor                     |   253 +-
 src/osgEarth/TextureCompositor.cpp                 |   543 +-
 src/osgEarth/TextureCompositorMulti                |    75 -
 src/osgEarth/TextureCompositorMulti.cpp            |   560 -
 src/osgEarth/TextureCompositorTexArray             |    86 -
 src/osgEarth/TextureCompositorTexArray.cpp         |   476 -
 src/osgEarth/ThreadingUtils                        |     4 +-
 src/osgEarth/ThreadingUtils.cpp                    |     2 +-
 src/osgEarth/TileHandler                           |    60 +
 .../TileHandler.cpp}                               |    23 +-
 src/osgEarth/TileKey                               |    50 +-
 src/osgEarth/TileKey.cpp                           |    58 +-
 src/osgEarth/TileSource                            |   103 +-
 src/osgEarth/TileSource.cpp                        |   186 +-
 src/osgEarth/TileVisitor                           |   228 +
 src/osgEarth/TileVisitor.cpp                       |   497 +
 src/osgEarth/TimeControl                           |     2 +-
 src/osgEarth/TimeControl.cpp                       |     2 +-
 src/osgEarth/TraversalData                         |     4 +-
 src/osgEarth/TraversalData.cpp                     |    10 +-
 src/osgEarth/URI                                   |    56 +-
 src/osgEarth/URI.cpp                               |   130 +-
 src/osgEarth/Units                                 |     2 +-
 src/osgEarth/Units.cpp                             |    19 +-
 src/osgEarth/Utils                                 |    47 +-
 src/osgEarth/Utils.cpp                             |   216 +-
 src/osgEarth/Version                               |     8 +-
 src/osgEarth/Version.cpp                           |    12 +-
 src/osgEarth/VersionGit.cpp.in                     |     1 +
 src/osgEarth/VerticalDatum                         |     2 +-
 src/osgEarth/VerticalDatum.cpp                     |    30 +-
 src/osgEarth/Viewpoint                             |     2 +-
 src/osgEarth/Viewpoint.cpp                         |     2 +-
 src/osgEarth/VirtualProgram                        |   136 +-
 src/osgEarth/VirtualProgram.cpp                    |   632 +-
 src/osgEarth/XmlUtils                              |     2 +-
 src/osgEarth/XmlUtils.cpp                          |    27 +-
 src/osgEarth/optional                              |     2 +-
 src/osgEarth/tinyxml.cpp                           |    94 +-
 src/osgEarthAnnotation/AnnotationData              |     2 +-
 src/osgEarthAnnotation/AnnotationData.cpp          |     2 +-
 src/osgEarthAnnotation/AnnotationEditing           |     2 +-
 src/osgEarthAnnotation/AnnotationEditing.cpp       |     4 +-
 src/osgEarthAnnotation/AnnotationNode              |     5 +-
 src/osgEarthAnnotation/AnnotationNode.cpp          |    13 +-
 src/osgEarthAnnotation/AnnotationRegistry          |     2 +-
 src/osgEarthAnnotation/AnnotationRegistry.cpp      |     2 +-
 src/osgEarthAnnotation/AnnotationSettings          |     2 +-
 src/osgEarthAnnotation/AnnotationSettings.cpp      |     2 +-
 src/osgEarthAnnotation/AnnotationUtils             |     3 +-
 src/osgEarthAnnotation/AnnotationUtils.cpp         |    33 +-
 src/osgEarthAnnotation/CMakeLists.txt              |    18 +-
 src/osgEarthAnnotation/CircleNode                  |     2 +-
 src/osgEarthAnnotation/CircleNode.cpp              |     5 +-
 src/osgEarthAnnotation/Common                      |     2 +-
 src/osgEarthAnnotation/Decoration                  |     2 +-
 src/osgEarthAnnotation/Decoration.cpp              |     2 +-
 src/osgEarthAnnotation/EllipseNode                 |     2 +-
 src/osgEarthAnnotation/EllipseNode.cpp             |     5 +-
 src/osgEarthAnnotation/Export                      |     2 +-
 src/osgEarthAnnotation/FeatureEditing              |     2 +-
 src/osgEarthAnnotation/FeatureEditing.cpp          |     2 +-
 src/osgEarthAnnotation/FeatureNode                 |     2 +-
 src/osgEarthAnnotation/FeatureNode.cpp             |     2 +-
 src/osgEarthAnnotation/HighlightDecoration         |    22 +-
 src/osgEarthAnnotation/HighlightDecoration.cpp     |   137 +-
 src/osgEarthAnnotation/ImageOverlay                |     2 +-
 src/osgEarthAnnotation/ImageOverlay.cpp            |     6 +-
 src/osgEarthAnnotation/ImageOverlayEditor          |     2 +-
 src/osgEarthAnnotation/ImageOverlayEditor.cpp      |     2 +-
 src/osgEarthAnnotation/LabelNode                   |     4 +-
 src/osgEarthAnnotation/LabelNode.cpp               |    14 +-
 src/osgEarthAnnotation/LocalGeometryNode           |     2 +-
 src/osgEarthAnnotation/LocalGeometryNode.cpp       |     2 +-
 src/osgEarthAnnotation/LocalizedNode               |     4 +-
 src/osgEarthAnnotation/LocalizedNode.cpp           |     2 +-
 src/osgEarthAnnotation/ModelNode                   |     2 +-
 src/osgEarthAnnotation/ModelNode.cpp               |    15 +-
 src/osgEarthAnnotation/OrthoNode                   |     6 +-
 src/osgEarthAnnotation/OrthoNode.cpp               |     3 +-
 src/osgEarthAnnotation/PlaceNode                   |     4 +-
 src/osgEarthAnnotation/PlaceNode.cpp               |    30 +-
 src/osgEarthAnnotation/RectangleNode               |     2 +-
 src/osgEarthAnnotation/RectangleNode.cpp           |     2 +-
 src/osgEarthAnnotation/ScaleDecoration             |     2 +-
 src/osgEarthAnnotation/TrackNode                   |     2 +-
 src/osgEarthAnnotation/TrackNode.cpp               |    17 +-
 src/osgEarthDrivers/CMakeLists.txt                 |    30 +-
 src/osgEarthDrivers/agglite/AGGLiteOptions         |    15 +-
 .../agglite/AGGLiteRasterizerTileSource.cpp        |     6 +-
 src/osgEarthDrivers/arcgis/ArcGISOptions           |     9 +-
 src/osgEarthDrivers/arcgis/Extent.h                |     2 +-
 src/osgEarthDrivers/arcgis/MapService.cpp          |   154 +-
 src/osgEarthDrivers/arcgis/MapService.h            |     2 +-
 src/osgEarthDrivers/arcgis/ReaderWriterArcGIS.cpp  |    73 +-
 .../ReaderWriterArcGISMapCache.cpp                 |     5 +-
 src/osgEarthDrivers/bing/BingOptions               |     2 +-
 src/osgEarthDrivers/bing/BingTileSource.cpp        |   178 +-
 .../cache_filesystem/FileSystemCache               |     2 +-
 .../cache_filesystem/FileSystemCache.cpp           |   132 +-
 src/osgEarthDrivers/cache_leveldb/CMakeLists.txt   |    25 +
 src/osgEarthDrivers/cache_leveldb/LevelDBCache     |    77 +
 src/osgEarthDrivers/cache_leveldb/LevelDBCache.cpp |   227 +
 src/osgEarthDrivers/cache_leveldb/LevelDBCacheBin  |   138 +
 .../cache_leveldb/LevelDBCacheBin.cpp              |   668 +
 .../cache_leveldb/LevelDBCacheDriver.cpp           |    57 +
 .../cache_leveldb/LevelDBCacheOptions              |   114 +
 src/osgEarthDrivers/cache_leveldb/Tracker          |   113 +
 src/osgEarthDrivers/colorramp/CMakeLists.txt       |    15 +
 .../ColorRampOptions}                              |    59 +-
 .../colorramp/ColorRampTileSource.cpp              |   166 +
 src/osgEarthDrivers/debug/DebugOptions             |     2 +-
 src/osgEarthDrivers/debug/DebugTileSource.cpp      |     2 +-
 src/osgEarthDrivers/earth/EarthFileSerializer      |     2 +-
 src/osgEarthDrivers/earth/EarthFileSerializer1.cpp |     9 +-
 src/osgEarthDrivers/earth/EarthFileSerializer2.cpp |    14 +-
 src/osgEarthDrivers/earth/ReaderWriterOsgEarth.cpp |    13 +-
 .../engine_byo/BYOTerrainEngineDriver.cpp          |     2 +-
 .../engine_byo/BYOTerrainEngineNode                |     2 +-
 .../engine_byo/BYOTerrainEngineNode.cpp            |    11 +-
 .../engine_byo/BYOTerrainEngineOptions             |     2 +-
 src/osgEarthDrivers/engine_byo/Common              |     2 +-
 src/osgEarthDrivers/engine_mp/Common               |     8 +-
 .../engine_mp/DynamicLODScaleCallback              |     6 +-
 src/osgEarthDrivers/engine_mp/FileLocationCallback |    16 +-
 src/osgEarthDrivers/engine_mp/KeyNodeFactory       |    12 +-
 src/osgEarthDrivers/engine_mp/KeyNodeFactory.cpp   |     5 +-
 src/osgEarthDrivers/engine_mp/MPGeometry           |    23 +-
 src/osgEarthDrivers/engine_mp/MPGeometry.cpp       |   411 +-
 .../engine_mp/MPTerrainEngineDriver.cpp            |   295 +-
 src/osgEarthDrivers/engine_mp/MPTerrainEngineNode  |    28 +-
 .../engine_mp/MPTerrainEngineNode.cpp              |   486 +-
 .../engine_mp/MPTerrainEngineOptions               |    33 +-
 .../engine_mp/QuickReleaseGLObjects                |    12 +-
 src/osgEarthDrivers/engine_mp/SingleKeyNodeFactory |    24 +-
 .../engine_mp/SingleKeyNodeFactory.cpp             |   208 +-
 src/osgEarthDrivers/engine_mp/TerrainNode          |    12 +-
 src/osgEarthDrivers/engine_mp/TerrainNode.cpp      |     4 +-
 src/osgEarthDrivers/engine_mp/TileGroup            |    12 +-
 src/osgEarthDrivers/engine_mp/TileGroup.cpp        |     4 +-
 src/osgEarthDrivers/engine_mp/TileModel            |    35 +-
 src/osgEarthDrivers/engine_mp/TileModel.cpp        |   117 +-
 src/osgEarthDrivers/engine_mp/TileModelCompiler    |    19 +-
 .../engine_mp/TileModelCompiler.cpp                |  4009 +-
 src/osgEarthDrivers/engine_mp/TileModelFactory     |   157 +-
 src/osgEarthDrivers/engine_mp/TileModelFactory.cpp |   310 +-
 src/osgEarthDrivers/engine_mp/TileNode             |    13 +-
 src/osgEarthDrivers/engine_mp/TileNode.cpp         |    37 +-
 src/osgEarthDrivers/engine_mp/TileNodeRegistry     |    31 +-
 src/osgEarthDrivers/engine_mp/TileNodeRegistry.cpp |    30 +-
 src/osgEarthDrivers/engine_mp/TilePagedLOD         |    26 +-
 src/osgEarthDrivers/engine_mp/TilePagedLOD.cpp     |    67 +-
 .../engine_osgterrain/FileLocationCallback         |     2 +-
 .../MultiPassTerrainTechnique.cpp                  |    19 -
 .../engine_osgterrain/SinglePassTerrainTechnique   |     8 -
 .../engine_quadtree/FileLocationCallback           |     2 +-
 src/osgEarthDrivers/feature_ogr/FeatureCursorOGR   |     2 +-
 .../feature_ogr/FeatureCursorOGR.cpp               |    76 +-
 .../feature_ogr/FeatureSourceOGR.cpp               |    89 +-
 src/osgEarthDrivers/feature_ogr/OGRFeatureOptions  |    14 +-
 .../feature_tfs/FeatureSourceTFS.cpp               |     4 +-
 src/osgEarthDrivers/feature_tfs/TFSFeatureOptions  |     9 +-
 .../feature_wfs/FeatureSourceWFS.cpp               |    15 +-
 src/osgEarthDrivers/feature_wfs/WFSFeatureOptions  |    22 +-
 src/osgEarthDrivers/gdal/GDALOptions               |     2 +-
 src/osgEarthDrivers/gdal/ReaderWriterGDAL.cpp      |   525 +-
 src/osgEarthDrivers/kml/KML                        |     2 +-
 src/osgEarthDrivers/kml/KMLOptions                 |     2 +-
 src/osgEarthDrivers/kml/KMLReader                  |     2 +-
 src/osgEarthDrivers/kml/KMLReader.cpp              |     2 +-
 src/osgEarthDrivers/kml/KML_Common                 |     2 +-
 src/osgEarthDrivers/kml/KML_Container              |     2 +-
 src/osgEarthDrivers/kml/KML_Document               |     2 +-
 src/osgEarthDrivers/kml/KML_Document.cpp           |     2 +-
 src/osgEarthDrivers/kml/KML_Feature                |     2 +-
 src/osgEarthDrivers/kml/KML_Feature.cpp            |     2 +-
 src/osgEarthDrivers/kml/KML_Folder                 |     2 +-
 src/osgEarthDrivers/kml/KML_Folder.cpp             |     2 +-
 src/osgEarthDrivers/kml/KML_Geometry               |     2 +-
 src/osgEarthDrivers/kml/KML_Geometry.cpp           |     2 +-
 src/osgEarthDrivers/kml/KML_GroundOverlay          |     2 +-
 src/osgEarthDrivers/kml/KML_GroundOverlay.cpp      |     2 +-
 src/osgEarthDrivers/kml/KML_IconStyle              |     2 +-
 src/osgEarthDrivers/kml/KML_IconStyle.cpp          |     2 +-
 src/osgEarthDrivers/kml/KML_LabelStyle             |     2 +-
 src/osgEarthDrivers/kml/KML_LabelStyle.cpp         |     2 +-
 src/osgEarthDrivers/kml/KML_LineString             |     2 +-
 src/osgEarthDrivers/kml/KML_LineString.cpp         |     2 +-
 src/osgEarthDrivers/kml/KML_LineStyle              |     2 +-
 src/osgEarthDrivers/kml/KML_LineStyle.cpp          |     2 +-
 src/osgEarthDrivers/kml/KML_LinearRing             |     2 +-
 src/osgEarthDrivers/kml/KML_LinearRing.cpp         |     2 +-
 src/osgEarthDrivers/kml/KML_Model                  |     2 +-
 src/osgEarthDrivers/kml/KML_Model.cpp              |     2 +-
 src/osgEarthDrivers/kml/KML_MultiGeometry          |     2 +-
 src/osgEarthDrivers/kml/KML_MultiGeometry.cpp      |     2 +-
 src/osgEarthDrivers/kml/KML_NetworkLink.cpp        |    10 +-
 src/osgEarthDrivers/kml/KML_NetworkLinkControl     |     2 +-
 src/osgEarthDrivers/kml/KML_NetworkLinkControl.cpp |     2 +-
 src/osgEarthDrivers/kml/KML_Object                 |     2 +-
 src/osgEarthDrivers/kml/KML_Object.cpp             |     2 +-
 src/osgEarthDrivers/kml/KML_Overlay                |     2 +-
 src/osgEarthDrivers/kml/KML_Overlay.cpp            |     2 +-
 src/osgEarthDrivers/kml/KML_PhotoOverlay           |     2 +-
 src/osgEarthDrivers/kml/KML_PhotoOverlay.cpp       |     2 +-
 src/osgEarthDrivers/kml/KML_Placemark.cpp          |    34 +-
 src/osgEarthDrivers/kml/KML_Point                  |     2 +-
 src/osgEarthDrivers/kml/KML_Point.cpp              |     2 +-
 src/osgEarthDrivers/kml/KML_PolyStyle              |     2 +-
 src/osgEarthDrivers/kml/KML_PolyStyle.cpp          |    58 +-
 src/osgEarthDrivers/kml/KML_Polygon                |     2 +-
 src/osgEarthDrivers/kml/KML_Polygon.cpp            |     2 +-
 src/osgEarthDrivers/kml/KML_Root                   |     2 +-
 src/osgEarthDrivers/kml/KML_Root.cpp               |     2 +-
 src/osgEarthDrivers/kml/KML_Schema                 |     2 +-
 src/osgEarthDrivers/kml/KML_Schema.cpp             |     2 +-
 src/osgEarthDrivers/kml/KML_ScreenOverlay          |     2 +-
 src/osgEarthDrivers/kml/KML_ScreenOverlay.cpp      |     2 +-
 src/osgEarthDrivers/kml/KML_Style                  |     2 +-
 src/osgEarthDrivers/kml/KML_Style.cpp              |     2 +-
 src/osgEarthDrivers/kml/KML_StyleMap               |     2 +-
 src/osgEarthDrivers/kml/KML_StyleMap.cpp           |     2 +-
 src/osgEarthDrivers/kml/KML_StyleSelector          |     2 +-
 src/osgEarthDrivers/kml/KMZArchive                 |     2 +-
 src/osgEarthDrivers/kml/KMZArchive.cpp             |     2 +-
 src/osgEarthDrivers/kml/ReaderWriterKML.cpp        |     2 +-
 .../label_annotation/AnnotationLabelSource.cpp     |    24 +-
 .../label_overlay/OverlayLabelSource.cpp           |    27 +-
 .../mask_feature/FeatureMaskOptions                |     2 +-
 .../mask_feature/FeatureMaskSource.cpp             |    39 +-
 src/osgEarthDrivers/mbtiles/CMakeLists.txt         |     4 +-
 src/osgEarthDrivers/mbtiles/MBTilesOptions         |    41 +-
 src/osgEarthDrivers/mbtiles/MBTilesPlugin.cpp      |    61 +
 src/osgEarthDrivers/mbtiles/MBTilesTileSource      |    88 +
 src/osgEarthDrivers/mbtiles/MBTilesTileSource.cpp  |   596 +
 .../mbtiles/ReaderWriterMBTiles.cpp                |   310 -
 .../model_feature_geom/FeatureGeomModelOptions     |     2 +-
 .../model_feature_geom/FeatureGeomModelSource.cpp  |     2 +-
 .../FeatureStencilModelOptions                     |     2 +-
 .../FeatureStencilModelSource.cpp                  |     2 +-
 .../model_simple/SimpleModelOptions                |    11 +-
 .../model_simple/SimpleModelSource.cpp             |    91 +-
 src/osgEarthDrivers/noise/CMakeLists.txt           |    10 +-
 src/osgEarthDrivers/noise/NoiseDriver.cpp          |   321 +
 src/osgEarthDrivers/noise/NoiseOptions             |    25 +-
 src/osgEarthDrivers/noise/ReaderWriterNoise.cpp    |   318 -
 src/osgEarthDrivers/ocean_simple/CMakeLists.txt    |    25 +
 .../ElevationProxyImageLayer                       |    25 +-
 .../ocean_simple/ElevationProxyImageLayer.cpp      |    88 +
 .../ocean_simple/SimpleOceanDriver.cpp             |    67 +
 .../SimpleOceanNode}                               |    63 +-
 .../ocean_simple/SimpleOceanNode.cpp               |   275 +
 .../SimpleOceanOptions}                            |    87 +-
 .../SimpleOceanShaders}                            |    61 +-
 src/osgEarthDrivers/ocean_surface/CMakeLists.txt   |    22 -
 .../ocean_surface/ElevationProxyImageLayer.cpp     |    76 -
 src/osgEarthDrivers/ocean_surface/OceanCompositor  |    60 -
 .../ocean_surface/OceanCompositor.cpp              |   130 -
 .../ocean_surface/OceanSurfaceContainer.cpp        |   184 -
 .../ocean_surface/ReaderWriterOceanSurface.cpp     |    88 -
 src/osgEarthDrivers/ocean_triton/CMakeLists.txt    |    30 +
 src/osgEarthDrivers/ocean_triton/TritonContext     |    82 +
 src/osgEarthDrivers/ocean_triton/TritonContext.cpp |   141 +
 src/osgEarthDrivers/ocean_triton/TritonDrawable    |    80 +
 .../ocean_triton/TritonDrawable.cpp                |   535 +
 src/osgEarthDrivers/ocean_triton/TritonDriver.cpp  |    96 +
 src/osgEarthDrivers/ocean_triton/TritonNode        |    64 +
 src/osgEarthDrivers/ocean_triton/TritonNode.cpp    |    86 +
 src/osgEarthDrivers/ocean_triton/TritonOptions     |    88 +
 src/osgEarthDrivers/osg/OSGOptions                 |     2 +-
 src/osgEarthDrivers/osg/OSGTileSource.cpp          |    38 +-
 .../refresh/ReaderWriterRefresh.cpp                |     2 +-
 src/osgEarthDrivers/refresh/RefreshOptions         |     2 +-
 .../script_engine_duktape/CMakeLists.txt           |    42 +
 .../script_engine_duktape/DuktapeEngine            |    72 +
 .../script_engine_duktape/DuktapeEngine.cpp        |   277 +
 .../script_engine_duktape/JSGeometry               |   180 +
 .../script_engine_duktape/Plugin.cpp               |    59 +
 .../script_engine_duktape/duktape.c                | 65023 +++++++++++++++++++
 .../script_engine_duktape/duktape.h                |  3668 ++
 .../script_engine_v8/CMakeLists.txt                |     2 +-
 .../script_engine_v8/JSWrappers.cpp                |     4 +-
 .../script_engine_v8/JavascriptEngineV8            |     5 +-
 .../script_engine_v8/JavascriptEngineV8.cpp        |    62 +-
 src/osgEarthDrivers/sky_gl/CMakeLists.txt          |    22 +
 src/osgEarthDrivers/sky_gl/GLSkyDriver.cpp         |    62 +
 src/osgEarthDrivers/sky_gl/GLSkyNode               |    72 +
 src/osgEarthDrivers/sky_gl/GLSkyNode.cpp           |   146 +
 .../{yahoo/YahooOptions => sky_gl/GLSkyOptions}    |    47 +-
 src/osgEarthDrivers/sky_gl/GLSkyShaders            |   128 +
 .../sky_silverlining/CMakeLists.txt                |    32 +
 .../sky_silverlining/SilverLiningCloudsDrawable    |    62 +
 .../SilverLiningCloudsDrawable.cpp                 |    65 +
 .../sky_silverlining/SilverLiningContext           |   106 +
 .../sky_silverlining/SilverLiningContext.cpp       |   225 +
 .../sky_silverlining/SilverLiningDriver.cpp        |    84 +
 .../sky_silverlining/SilverLiningNode              |    68 +
 .../sky_silverlining/SilverLiningNode.cpp          |   164 +
 .../sky_silverlining/SilverLiningOptions           |   104 +
 .../sky_silverlining/SilverLiningSkyDrawable       |    58 +
 .../sky_silverlining/SilverLiningSkyDrawable.cpp   |   110 +
 src/osgEarthDrivers/sky_simple/CMakeLists.txt      |    22 +
 src/osgEarthDrivers/sky_simple/SimpleSkyDriver.cpp |    63 +
 src/osgEarthDrivers/sky_simple/SimpleSkyNode       |   122 +
 src/osgEarthDrivers/sky_simple/SimpleSkyNode.cpp   |   808 +
 src/osgEarthDrivers/sky_simple/SimpleSkyOptions    |    90 +
 src/osgEarthDrivers/sky_simple/SimpleSkyShaders    |   684 +
 src/osgEarthDrivers/splat_mask/CMakeLists.txt      |    14 +
 src/osgEarthDrivers/splat_mask/SplatMaskDriver.cpp |   205 +
 .../SplatMaskOptions}                              |    62 +-
 .../template_matclass/CMakeLists.txt               |    16 +
 .../template_matclass/TemplateMatClassDriver.cpp   |   163 +
 .../template_matclass/TemplateMatClassOptions      |    83 +
 .../tilecache/ReaderWriterTileCache.cpp            |     5 +-
 src/osgEarthDrivers/tilecache/TileCacheOptions     |     2 +-
 .../tileindex/ReaderWriterTileIndex.cpp            |     4 +-
 src/osgEarthDrivers/tileindex/TileIndexOptions     |     2 +-
 .../tileservice/ReaderWriterTileService.cpp        |     5 +-
 src/osgEarthDrivers/tileservice/TileServiceOptions |     2 +-
 src/osgEarthDrivers/tms/CMakeLists.txt             |     6 +-
 src/osgEarthDrivers/tms/ReaderWriterTMS.cpp        |   241 -
 src/osgEarthDrivers/tms/TMSOptions                 |     2 +-
 src/osgEarthDrivers/tms/TMSPlugin.cpp              |    66 +
 src/osgEarthDrivers/tms/TMSTileSource              |    82 +
 src/osgEarthDrivers/tms/TMSTileSource.cpp          |   310 +
 src/osgEarthDrivers/vdatum_egm2008/EGM2008.cpp     |     8 +-
 src/osgEarthDrivers/vdatum_egm2008/EGM2008Grid.h   |     2 +-
 src/osgEarthDrivers/vdatum_egm84/EGM84.cpp         |     8 +-
 src/osgEarthDrivers/vdatum_egm84/EGM84Grid.h       |     2 +-
 src/osgEarthDrivers/vdatum_egm96/EGM96.cpp         |    12 +-
 src/osgEarthDrivers/vdatum_egm96/EGM96Grid.h       |     2 +-
 src/osgEarthDrivers/vpb/ReaderWriterVPB.cpp        |   134 +-
 src/osgEarthDrivers/vpb/VPBOptions                 |     2 +-
 src/osgEarthDrivers/wcs/ReaderWriterWCS.cpp        |     2 +-
 src/osgEarthDrivers/wcs/WCS11Source.cpp            |    16 +-
 src/osgEarthDrivers/wcs/WCS11Source.h              |     2 +-
 src/osgEarthDrivers/wcs/WCSOptions                 |     2 +-
 src/osgEarthDrivers/wms/ReaderWriterWMS.cpp        |    22 +-
 src/osgEarthDrivers/wms/TileService                |     2 +-
 src/osgEarthDrivers/wms/TileService.cpp            |     2 +-
 src/osgEarthDrivers/wms/WMSOptions                 |     2 +-
 src/osgEarthDrivers/xyz/ReaderWriterXYZ.cpp        |    12 +-
 src/osgEarthDrivers/xyz/XYZOptions                 |     2 +-
 src/osgEarthDrivers/yahoo/ReaderWriterYahoo.cpp    |     4 +-
 src/osgEarthDrivers/yahoo/YahooOptions             |     2 +-
 src/osgEarthFeatures/AltitudeFilter                |     2 +-
 src/osgEarthFeatures/AltitudeFilter.cpp            |    21 +-
 src/osgEarthFeatures/BufferFilter                  |     2 +-
 src/osgEarthFeatures/BufferFilter.cpp              |     2 +-
 src/osgEarthFeatures/BuildGeometryFilter           |    14 +-
 src/osgEarthFeatures/BuildGeometryFilter.cpp       |   470 +-
 src/osgEarthFeatures/BuildTextFilter               |     2 +-
 src/osgEarthFeatures/BuildTextFilter.cpp           |     8 +-
 src/osgEarthFeatures/BuildTextOperator             |     2 +-
 src/osgEarthFeatures/BuildTextOperator.cpp         |     2 +-
 src/osgEarthFeatures/CMakeLists.txt                |     4 +-
 src/osgEarthFeatures/CentroidFilter                |     2 +-
 src/osgEarthFeatures/CentroidFilter.cpp            |     2 +-
 src/osgEarthFeatures/Common                        |     2 +-
 src/osgEarthFeatures/ConvertTypeFilter             |     2 +-
 src/osgEarthFeatures/ConvertTypeFilter.cpp         |     2 +-
 src/osgEarthFeatures/CropFilter                    |     2 +-
 src/osgEarthFeatures/CropFilter.cpp                |     2 +-
 src/osgEarthFeatures/ExtrudeGeometryFilter         |    90 +-
 src/osgEarthFeatures/ExtrudeGeometryFilter.cpp     |   949 +-
 src/osgEarthFeatures/Feature                       |     4 +-
 src/osgEarthFeatures/Feature.cpp                   |    12 +-
 src/osgEarthFeatures/FeatureCursor                 |     2 +-
 src/osgEarthFeatures/FeatureCursor.cpp             |     4 +-
 src/osgEarthFeatures/FeatureDisplayLayout          |     2 +-
 src/osgEarthFeatures/FeatureDisplayLayout.cpp      |     2 +-
 src/osgEarthFeatures/FeatureDrawSet                |     2 +-
 src/osgEarthFeatures/FeatureDrawSet.cpp            |     2 +-
 src/osgEarthFeatures/FeatureListSource             |     2 +-
 src/osgEarthFeatures/FeatureListSource.cpp         |     2 +-
 src/osgEarthFeatures/FeatureModelGraph             |    25 +-
 src/osgEarthFeatures/FeatureModelGraph.cpp         |   170 +-
 src/osgEarthFeatures/FeatureModelSource            |     7 +-
 src/osgEarthFeatures/FeatureModelSource.cpp        |    52 +-
 src/osgEarthFeatures/FeatureSource                 |     2 +-
 src/osgEarthFeatures/FeatureSource.cpp             |     2 +-
 src/osgEarthFeatures/FeatureSourceIndexNode        |     2 +-
 src/osgEarthFeatures/FeatureSourceIndexNode.cpp    |     2 +-
 src/osgEarthFeatures/FeatureTileSource             |     2 +-
 src/osgEarthFeatures/FeatureTileSource.cpp         |    20 +-
 src/osgEarthFeatures/Filter                        |     6 +-
 src/osgEarthFeatures/Filter.cpp                    |    76 +-
 src/osgEarthFeatures/FilterContext                 |     8 +-
 src/osgEarthFeatures/FilterContext.cpp             |    20 +-
 src/osgEarthFeatures/GeometryCompiler              |    27 +-
 src/osgEarthFeatures/GeometryCompiler.cpp          |   130 +-
 src/osgEarthFeatures/GeometryUtils                 |    14 +-
 src/osgEarthFeatures/GeometryUtils.cpp             |    25 +-
 src/osgEarthFeatures/LabelSource                   |     2 +-
 src/osgEarthFeatures/LabelSource.cpp               |     3 +-
 src/osgEarthFeatures/MeshClamper                   |     2 +-
 src/osgEarthFeatures/MeshClamper.cpp               |     2 +-
 src/osgEarthFeatures/OgrUtils                      |     8 +-
 src/osgEarthFeatures/OgrUtils.cpp                  |    32 +-
 src/osgEarthFeatures/OptimizerHints                |     2 +-
 src/osgEarthFeatures/OptimizerHints.cpp            |     2 +-
 src/osgEarthFeatures/PolygonizeLines               |     4 +-
 src/osgEarthFeatures/PolygonizeLines.cpp           |    85 +-
 src/osgEarthFeatures/ResampleFilter                |     2 +-
 src/osgEarthFeatures/ResampleFilter.cpp            |     2 +-
 src/osgEarthFeatures/ScaleFilter                   |     2 +-
 src/osgEarthFeatures/ScaleFilter.cpp               |     2 +-
 src/osgEarthFeatures/ScatterFilter                 |     2 +-
 src/osgEarthFeatures/ScatterFilter.cpp             |     2 +-
 src/osgEarthFeatures/Script                        |     2 +-
 src/osgEarthFeatures/ScriptEngine                  |    36 +-
 src/osgEarthFeatures/ScriptEngine.cpp              |    19 +-
 src/osgEarthFeatures/Session                       |    11 +-
 src/osgEarthFeatures/Session.cpp                   |    51 +-
 src/osgEarthFeatures/SubstituteModelFilter         |     2 +-
 src/osgEarthFeatures/SubstituteModelFilter.cpp     |    62 +-
 src/osgEarthFeatures/TessellateOperator            |     2 +-
 src/osgEarthFeatures/TessellateOperator.cpp        |     3 +-
 src/osgEarthFeatures/TextSymbolizer                |     2 +-
 src/osgEarthFeatures/TextSymbolizer.cpp            |    15 +-
 src/osgEarthFeatures/TransformFilter               |     2 +-
 src/osgEarthFeatures/TransformFilter.cpp           |     8 +-
 src/osgEarthFeatures/VirtualFeatureSource          |     2 +-
 src/osgEarthFeatures/VirtualFeatureSource.cpp      |     2 +-
 src/osgEarthQt/Actions                             |     2 +-
 src/osgEarthQt/AnnotationDialogs                   |     2 +-
 src/osgEarthQt/AnnotationDialogs.cpp               |     2 +-
 src/osgEarthQt/AnnotationListWidget                |     2 +-
 src/osgEarthQt/AnnotationListWidget.cpp            |     5 +-
 src/osgEarthQt/AnnotationToolbar                   |     2 +-
 src/osgEarthQt/AnnotationToolbar.cpp               |     2 +-
 src/osgEarthQt/CMakeLists.txt                      |    31 +-
 src/osgEarthQt/CollapsiblePairWidget               |     2 +-
 src/osgEarthQt/CollapsiblePairWidget.cpp           |     2 +-
 src/osgEarthQt/Common                              |     2 +-
 src/osgEarthQt/DataManager                         |     2 +-
 src/osgEarthQt/DataManager.cpp                     |     2 +-
 src/osgEarthQt/GuiActions                          |     2 +-
 src/osgEarthQt/LOSControlWidget                    |     2 +-
 src/osgEarthQt/LOSControlWidget.cpp                |     2 +-
 src/osgEarthQt/LOSCreationDialog                   |     2 +-
 src/osgEarthQt/LOSCreationDialog.cpp               |     2 +-
 src/osgEarthQt/LayerManagerWidget                  |     3 +-
 src/osgEarthQt/LayerManagerWidget.cpp              |     5 +-
 src/osgEarthQt/MapCatalogWidget                    |     2 +-
 src/osgEarthQt/MapCatalogWidget.cpp                |     4 +-
 src/osgEarthQt/TerrainProfileGraph                 |    11 +-
 src/osgEarthQt/TerrainProfileGraph.cpp             |    89 +-
 src/osgEarthQt/TerrainProfileWidget                |     9 +-
 src/osgEarthQt/TerrainProfileWidget.cpp            |    49 +-
 src/osgEarthQt/ViewWidget                          |     2 +-
 src/osgEarthQt/ViewWidget.cpp                      |     5 +-
 src/osgEarthQt/ViewerWidget                        |     2 +-
 src/osgEarthQt/ViewerWidget.cpp                    |     4 +-
 src/osgEarthQt/images.qrc                          |     1 +
 src/osgEarthQt/images/copy.png                     |   Bin 0 -> 341 bytes
 src/osgEarthSymbology/AltitudeSymbol               |     2 +-
 src/osgEarthSymbology/AltitudeSymbol.cpp           |    10 +-
 src/osgEarthSymbology/Color                        |     2 +-
 src/osgEarthSymbology/Color.cpp                    |     5 +-
 src/osgEarthSymbology/Common                       |     2 +-
 src/osgEarthSymbology/CssUtils                     |     2 +-
 src/osgEarthSymbology/CssUtils.cpp                 |     2 +-
 src/osgEarthSymbology/Expression                   |     2 +-
 src/osgEarthSymbology/Expression.cpp               |     2 +-
 src/osgEarthSymbology/ExtrusionSymbol              |     2 +-
 src/osgEarthSymbology/ExtrusionSymbol.cpp          |     5 +-
 src/osgEarthSymbology/Fill                         |     2 +-
 src/osgEarthSymbology/Fill.cpp                     |     2 +-
 src/osgEarthSymbology/GEOS                         |    15 +-
 src/osgEarthSymbology/GEOS.cpp                     |   318 +-
 src/osgEarthSymbology/Geometry                     |     2 +-
 src/osgEarthSymbology/Geometry.cpp                 |   103 +-
 src/osgEarthSymbology/GeometryFactory              |     2 +-
 src/osgEarthSymbology/GeometryFactory.cpp          |    15 +-
 src/osgEarthSymbology/GeometryRasterizer           |     2 +-
 src/osgEarthSymbology/GeometryRasterizer.cpp       |     2 +-
 src/osgEarthSymbology/IconResource                 |     2 +-
 src/osgEarthSymbology/IconResource.cpp             |     5 +-
 src/osgEarthSymbology/IconSymbol                   |     2 +-
 src/osgEarthSymbology/IconSymbol.cpp               |     5 +-
 src/osgEarthSymbology/InstanceResource             |     2 +-
 src/osgEarthSymbology/InstanceResource.cpp         |     2 +-
 src/osgEarthSymbology/InstanceSymbol               |    15 +-
 src/osgEarthSymbology/InstanceSymbol.cpp           |    14 +-
 src/osgEarthSymbology/LineSymbol                   |     7 +-
 src/osgEarthSymbology/LineSymbol.cpp               |    13 +-
 src/osgEarthSymbology/MarkerResource               |     2 +-
 src/osgEarthSymbology/MarkerResource.cpp           |    15 +-
 src/osgEarthSymbology/MarkerSymbol                 |     2 +-
 src/osgEarthSymbology/MarkerSymbol.cpp             |     5 +-
 src/osgEarthSymbology/MeshConsolidator             |     2 +-
 src/osgEarthSymbology/MeshConsolidator.cpp         |   159 +-
 src/osgEarthSymbology/MeshSubdivider               |     2 +-
 src/osgEarthSymbology/MeshSubdivider.cpp           |   110 +-
 src/osgEarthSymbology/ModelResource                |     2 +-
 src/osgEarthSymbology/ModelResource.cpp            |    27 +-
 src/osgEarthSymbology/ModelSymbol                  |     3 +-
 src/osgEarthSymbology/ModelSymbol.cpp              |    10 +-
 src/osgEarthSymbology/PointSymbol                  |     2 +-
 src/osgEarthSymbology/PointSymbol.cpp              |     8 +-
 src/osgEarthSymbology/PolygonSymbol                |     2 +-
 src/osgEarthSymbology/PolygonSymbol.cpp            |     5 +-
 src/osgEarthSymbology/Query                        |     2 +-
 src/osgEarthSymbology/Query.cpp                    |     2 +-
 src/osgEarthSymbology/RenderSymbol                 |    20 +-
 src/osgEarthSymbology/RenderSymbol.cpp             |    22 +-
 src/osgEarthSymbology/Resource                     |     2 +-
 src/osgEarthSymbology/Resource.cpp                 |     2 +-
 src/osgEarthSymbology/ResourceCache                |    53 +-
 src/osgEarthSymbology/ResourceCache.cpp            |   174 +-
 src/osgEarthSymbology/ResourceLibrary              |    13 +-
 src/osgEarthSymbology/ResourceLibrary.cpp          |    25 +-
 src/osgEarthSymbology/Skins                        |    81 +-
 src/osgEarthSymbology/Skins.cpp                    |   159 +-
 src/osgEarthSymbology/StencilVolumeNode            |     2 +-
 src/osgEarthSymbology/StencilVolumeNode.cpp        |     2 +-
 src/osgEarthSymbology/Stroke                       |     2 +-
 src/osgEarthSymbology/Stroke.cpp                   |    18 +-
 src/osgEarthSymbology/Style                        |     5 +-
 src/osgEarthSymbology/Style.cpp                    |     2 +-
 src/osgEarthSymbology/StyleSelector                |     2 +-
 src/osgEarthSymbology/StyleSelector.cpp            |     2 +-
 src/osgEarthSymbology/StyleSheet                   |    16 +-
 src/osgEarthSymbology/StyleSheet.cpp               |    33 +-
 src/osgEarthSymbology/Symbol                       |    14 +-
 src/osgEarthSymbology/Symbol.cpp                   |    20 +-
 src/osgEarthSymbology/Tags                         |     6 +-
 src/osgEarthSymbology/TextSymbol                   |     8 +-
 src/osgEarthSymbology/TextSymbol.cpp               |    11 +-
 .../{Formatter => ActivityMonitorTool}             |    33 +-
 src/osgEarthUtil/ActivityMonitorTool.cpp           |    58 +
 src/osgEarthUtil/AnnotationEvents                  |     2 +-
 src/osgEarthUtil/AnnotationEvents.cpp              |    23 +-
 src/osgEarthUtil/ArcGIS                            |     4 +-
 src/osgEarthUtil/ArcGIS.cpp                        |     2 +-
 src/osgEarthUtil/AtlasBuilder                      |    83 +
 src/osgEarthUtil/AtlasBuilder.cpp                  |   324 +
 src/osgEarthUtil/AutoClipPlaneHandler              |     2 +-
 src/osgEarthUtil/AutoClipPlaneHandler.cpp          |     2 +-
 src/osgEarthUtil/BrightnessContrastColorFilter     |     2 +-
 src/osgEarthUtil/BrightnessContrastColorFilter.cpp |     2 +-
 src/osgEarthUtil/CMYKColorFilter                   |     2 +-
 src/osgEarthUtil/CMYKColorFilter.cpp               |     2 +-
 src/osgEarthUtil/CMakeLists.txt                    |    35 +-
 src/osgEarthUtil/ChromaKeyColorFilter              |     2 +-
 src/osgEarthUtil/ChromaKeyColorFilter.cpp          |     2 +-
 src/osgEarthUtil/ClampCallback                     |     2 +-
 src/osgEarthUtil/ClampCallback.cpp                 |     2 +-
 src/osgEarthUtil/Common                            |     2 +-
 src/osgEarthUtil/Controls                          |    58 +-
 src/osgEarthUtil/Controls.cpp                      |   326 +-
 src/osgEarthUtil/DataScanner                       |     2 +-
 src/osgEarthUtil/DataScanner.cpp                   |     2 +-
 src/osgEarthUtil/DateTime                          |     3 +-
 src/osgEarthUtil/DetailTexture                     |    62 +-
 src/osgEarthUtil/DetailTexture.cpp                 |   378 +-
 src/osgEarthUtil/EarthManipulator                  |    73 +-
 src/osgEarthUtil/EarthManipulator.cpp              |   496 +-
 src/osgEarthUtil/Ephemeris                         |    60 +
 src/osgEarthUtil/Ephemeris.cpp                     |   322 +
 src/osgEarthUtil/ExampleResources                  |    22 +-
 src/osgEarthUtil/ExampleResources.cpp              |   302 +-
 src/osgEarthUtil/Export                            |     2 +-
 src/osgEarthUtil/FeatureManipTool                  |     2 +-
 src/osgEarthUtil/FeatureManipTool.cpp              |     3 +-
 src/osgEarthUtil/FeatureQueryTool                  |     2 +-
 src/osgEarthUtil/FeatureQueryTool.cpp              |    29 +-
 src/{osgEarth/AlphaEffect => osgEarthUtil/Fog}     |    60 +-
 src/osgEarthUtil/Fog.cpp                           |    98 +
 src/osgEarthUtil/Formatter                         |     2 +-
 src/osgEarthUtil/GLSLColorFilter                   |    18 +-
 src/osgEarthUtil/GLSLColorFilter.cpp               |    50 +-
 src/osgEarthUtil/GammaColorFilter                  |     2 +-
 src/osgEarthUtil/GammaColorFilter.cpp              |     2 +-
 src/osgEarthUtil/GeodeticGraticule                 |     2 +-
 src/osgEarthUtil/GeodeticGraticule.cpp             |     2 +-
 src/osgEarthUtil/HSLColorFilter                    |     2 +-
 src/osgEarthUtil/HSLColorFilter.cpp                |     2 +-
 src/osgEarthUtil/HTM                               |     2 +-
 src/osgEarthUtil/HTM.cpp                           |     2 +-
 src/osgEarthUtil/LODBlending                       |    10 +
 src/osgEarthUtil/LODBlending.cpp                   |   137 +-
 src/osgEarthUtil/LatLongFormatter                  |     2 +-
 src/osgEarthUtil/LatLongFormatter.cpp              |     8 +-
 src/osgEarthUtil/LineOfSight                       |     2 +-
 src/osgEarthUtil/LinearLineOfSight                 |     2 +-
 src/osgEarthUtil/LinearLineOfSight.cpp             |     2 +-
 src/osgEarthUtil/LogarithmicDepthBuffer            |    62 +
 src/osgEarthUtil/LogarithmicDepthBuffer.cpp        |   175 +
 src/osgEarthUtil/MGRSFormatter                     |     2 +-
 src/osgEarthUtil/MGRSFormatter.cpp                 |     2 +-
 src/osgEarthUtil/MGRSGraticule                     |     2 +-
 src/osgEarthUtil/MGRSGraticule.cpp                 |    23 +-
 src/osgEarthUtil/MeasureTool                       |     2 +-
 src/osgEarthUtil/MeasureTool.cpp                   |     2 +-
 src/osgEarthUtil/MouseCoordsTool                   |     2 +-
 src/osgEarthUtil/MouseCoordsTool.cpp               |    21 +-
 src/osgEarthUtil/Ocean                             |   123 +
 src/osgEarthUtil/Ocean.cpp                         |   196 +
 src/osgEarthUtil/PolyhedralLineOfSight             |     2 +-
 src/osgEarthUtil/PolyhedralLineOfSight.cpp         |     2 +-
 src/osgEarthUtil/RGBColorFilter                    |     2 +-
 src/osgEarthUtil/RGBColorFilter.cpp                |     2 +-
 src/osgEarthUtil/RadialLineOfSight                 |     2 +-
 src/osgEarthUtil/RadialLineOfSight.cpp             |     2 +-
 src/osgEarthUtil/ShadowUtils                       |    37 -
 src/osgEarthUtil/ShadowUtils.cpp                   |   207 -
 src/osgEarthUtil/Shadowing                         |   129 +
 src/osgEarthUtil/Shadowing.cpp                     |   381 +
 src/osgEarthUtil/SimplexNoise                      |   159 +
 src/osgEarthUtil/SimplexNoise.cpp                  |   556 +
 src/osgEarthUtil/Sky                               |   220 +
 src/osgEarthUtil/Sky.cpp                           |   204 +
 src/osgEarthUtil/SkyNode                           |   221 -
 src/osgEarthUtil/SkyNode.cpp                       |  1593 -
 src/osgEarthUtil/SpatialData                       |     2 +-
 src/osgEarthUtil/SpatialData.cpp                   |     2 +-
 src/osgEarthUtil/StarData                          |     2 +-
 src/osgEarthUtil/TFS                               |     4 +-
 src/osgEarthUtil/TFS.cpp                           |     2 +-
 src/osgEarthUtil/TFSPackager                       |     2 +-
 src/osgEarthUtil/TFSPackager.cpp                   |     5 +-
 src/osgEarthUtil/TMS                               |     4 +-
 src/osgEarthUtil/TMS.cpp                           |  1551 +-
 src/osgEarthUtil/TMSBackFiller                     |     2 +-
 src/osgEarthUtil/TMSBackFiller.cpp                 |     7 +-
 src/osgEarthUtil/TMSPackager                       |   350 +-
 src/osgEarthUtil/TMSPackager.cpp                   |  1043 +-
 src/osgEarthUtil/TerrainProfile                    |    19 +-
 src/osgEarthUtil/TerrainProfile.cpp                |    90 +-
 src/osgEarthUtil/TextureSplatter                   |   121 +
 src/osgEarthUtil/TextureSplatter.cpp               |   362 +
 src/osgEarthUtil/TileIndex                         |     2 +-
 src/osgEarthUtil/TileIndex.cpp                     |     2 +-
 src/osgEarthUtil/TileIndexBuilder                  |     2 +-
 src/osgEarthUtil/TileIndexBuilder.cpp              |     5 +-
 src/osgEarthUtil/UTMGraticule                      |    15 +-
 src/osgEarthUtil/UTMGraticule.cpp                  |    51 +-
 src/osgEarthUtil/WFS                               |     4 +-
 src/osgEarthUtil/WFS.cpp                           |     2 +-
 src/osgEarthUtil/WMS                               |     4 +-
 src/osgEarthUtil/WMS.cpp                           |     2 +-
 tests/annotation.earth                             |    26 +-
 tests/bing.earth                                   |    12 +-
 tests/boston.earth                                 |    92 +-
 tests/{boston.earth => boston_projected.earth}     |   103 +-
 tests/colorramp.earth                              |    20 +
 tests/datum_override.earth                         |     6 +-
 tests/feature_custom_filters.earth                 |     6 +-
 tests/feature_draped_lines.earth                   |     6 +-
 tests/feature_draped_polygons.earth                |    16 +-
 tests/feature_extrude.earth                        |    26 +-
 tests/feature_geom.earth                           |    10 +-
 tests/feature_labels.earth                         |     8 +-
 ...re_labels.earth => feature_labels_script.earth} |    12 +-
 tests/feature_levels_and_selectors.earth           |    94 +
 tests/feature_model_scatter.earth                  |    11 +-
 tests/feature_models.earth                         |    14 +-
 tests/feature_multilod.earth                       |    62 -
 tests/feature_population_cylinders.earth           |    91 +
 tests/feature_scripted_styling_2.earth             |     6 +-
 tests/feature_scripting.earth                      |    80 -
 tests/feature_stencil_line_draping.earth           |    46 -
 tests/feature_stencil_polygon_draping.earth        |   103 -
 tests/feature_tfs.earth                            |    10 +-
 tests/feature_tfs_scripting.earth                  |     8 +-
 tests/fractal_detail.earth                         |     8 +-
 tests/gdal_tiff.earth                              |     2 +-
 tests/glsl_filter.earth                            |    23 +-
 tests/layer_opacity.earth                          |    15 +-
 tests/lod_blending.earth                           |    17 +-
 tests/min_max_level.earth                          |    23 +-
 tests/noise.earth                                  |    13 +-
 tests/normalmap.earth                              |     2 +-
 tests/readymap.earth                               |     6 +-
 tests/silverlining.earth                           |    28 +
 tests/splat.earth                                  |   118 +
 tests/srtm.earth                                   |    18 -
 tests/stamen_toner.earth                           |    15 +
 tests/stamen_watercolor.earth                      |    15 +
 tests/triton.earth                                 |    36 +
 tests/vpb_with_inset.earth                         |    25 -
 tests/wms-t_nexrad_animated.earth                  |     7 -
 tests/yahoo_aerial.earth                           |    20 -
 tests/yahoo_maps.earth                             |    20 -
 1015 files changed, 110307 insertions(+), 21979 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 34cd375..fcde579 100755
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -8,6 +8,11 @@ if(COMMAND cmake_policy)
     # Works around warnings about escaped quotes in ADD_DEFINITIONS
     # statements.
     cmake_policy(SET CMP0005 OLD)
+
+    # disable autolinking to qtmain as we have our own main() functions (new in Qt 5.1)
+    if(NOT "${CMAKE_VERSION}" VERSION_LESS 2.8.11)
+        cmake_policy(SET CMP0020 OLD)
+    endif(NOT "${CMAKE_VERSION}" VERSION_LESS 2.8.11)
 endif(COMMAND cmake_policy)
 
 #
@@ -51,8 +56,11 @@ SET(THIRD_PARTY_DIR "" CACHE PATH "OSG 3rd-party dependency folder")
 SET(CMAKE_MODULE_PATH "${OSGEARTH_SOURCE_DIR}/CMakeModules;${CMAKE_MODULE_PATH}")
 
 # Attempt to detect the GIT revision number.
-include(GetGitRevisionDescription)
-get_git_head_revision(GIT_REFSPEC OSGEARTH_GIT_SHA1)
+OPTION(OSGEARTH_EMBED_GIT_SHA "Embeds the GIT SHA in the version code" OFF)
+IF (OSGEARTH_EMBED_GIT_SHA)
+  include(GetGitRevisionDescription)
+  get_git_head_revision(GIT_REFSPEC OSGEARTH_GIT_SHA1)
+ENDIF (OSGEARTH_EMBED_GIT_SHA)
 
 # IPHONE_PORT at tom
 # Trying to get CMake to generate an XCode IPhone project, current efforts are to get iphoneos sdk 3.1 working
@@ -114,17 +122,35 @@ FIND_PACKAGE(GEOS)
 FIND_PACKAGE(Sqlite3)
 FIND_PACKAGE(ZLIB)
 
+FIND_PACKAGE(LevelDB)
+
+FIND_PACKAGE(SilverLining)
+FIND_PACKAGE(Triton)
+
+# JavaScript Engines:
 SET(V8_DIR "" CACHE PATH "set to base V8 install path")
 FIND_PACKAGE(V8)
-
 FIND_PACKAGE(JavaScriptCore)
-FIND_PACKAGE(LibNoise)
 
-FIND_PACKAGE(Qt4)
-IF (QT4_FOUND)
-    INCLUDE(${QT_USE_FILE})
-    SET(QT_ALL_LIBRARIES ${QT_LIBRARIES} ${QT_QTCORE_LIBRARY} ${QT_QTWEBKIT_LIBRARY} ${QT_QTNETWORK_LIBRARY} ${QT_QTXML_LIBRARY} ${QT_QTOPENGL_LIBRARY})
-ENDIF (QT4_FOUND)
+SET (WITH_EXTERNAL_DUKTAPE FALSE CACHE BOOL "Use bundled or system wide version of Duktape")
+IF (WITH_EXTERNAL_DUKTAPE)
+    FIND_PACKAGE(Duktape)
+ENDIF (WITH_EXTERNAL_DUKTAPE)
+
+FIND_PACKAGE(Qt5Core QUIET)
+FIND_PACKAGE(Qt5Widgets QUIET)
+FIND_PACKAGE(Qt5Gui QUIET)
+FIND_PACKAGE(Qt5OpenGL QUIET)
+IF ( Qt5Core_FOUND AND Qt5Widgets_FOUND AND Qt5Gui_FOUND AND Qt5OpenGL_FOUND )
+    SET(QT_INCLUDES ${Qt5Widgets_INCLUDE_DIRS} ${Qt5OpenGL_INCLUDE_DIRS})
+ELSE()
+    FIND_PACKAGE(Qt4)
+    IF (QT4_FOUND)
+        INCLUDE(${QT_USE_FILE})
+		SET(QT_INCLUDES ${QT_INCLUDES} ${QT_INCLUDE_DIR} ${QT_QTCORE_INCLUDE_DIR} ${QT_QTGUI_INCLUDE_DIR}${QT_QTOPENGL_INCLUDE_DIR} )
+        SET(QT_ALL_LIBRARIES ${QT_LIBRARIES} ${QT_QTCORE_LIBRARY} ${QT_QTWEBKIT_LIBRARY} ${QT_QTNETWORK_LIBRARY} ${QT_QTXML_LIBRARY} ${QT_QTOPENGL_LIBRARY})
+    ENDIF (QT4_FOUND)
+ENDIF ()
 
 OPTION(OSGEARTH_USE_QT "Enable to use Qt (build Qt-dependent libraries, plugins and examples)" ON)
 
@@ -190,7 +216,6 @@ ENDIF(WIN32)
 ########################################################################################################
 
 # Common to all platforms:
-SET(OSG_DIR "" CACHE PATH "set to base osg install path")
 
 SET(CMAKE_DEBUG_POSTFIX  "d" CACHE STRING "add a postfix, usually d on windows")
 SET(CMAKE_RELEASE_POSTFIX "" CACHE STRING "add a postfix, usually empty on windows")
diff --git a/CMakeModules/FindDuktape.cmake b/CMakeModules/FindDuktape.cmake
new file mode 100644
index 0000000..b8db3fb
--- /dev/null
+++ b/CMakeModules/FindDuktape.cmake
@@ -0,0 +1,68 @@
+# Locate DUKTAPE
+# This module defines
+# DUKTAPE_LIBRARY
+# DUKTAPE_FOUND, if false, do not try to link to V8
+# DUKTAPE_INCLUDE_DIR, where to find the headers
+
+FIND_PATH(DUKTAPE_INCLUDE_DIR duktape.h
+    ${DUKTAPE_DIR}/include
+    $ENV{DUKTAPE_DIR}/include
+    $ENV{DUKTAPE_DIR}
+    ~/Library/Frameworks
+    /Library/Frameworks
+    /usr/local/include
+    /usr/include
+    /sw/include # Fink
+    /opt/local/include # DarwinPorts
+    /opt/csw/include # Blastwave
+    /opt/include
+    /usr/freeware/include
+    /devel
+)
+
+FIND_LIBRARY(DUKTAPE_LIBRARY_RELEASE
+	NAMES duktape
+	PATHS
+	${DUKTAPE_DIR}
+	${DUKTAPE_DIR}/lib
+	${DUKTAPE_DIR}/build/Release/lib
+	$ENV{DUKTAPE_DIR}
+	$ENV{DUKTAPE_DIR}/lib
+	~/Library/Frameworks
+	/Library/Frameworks
+	/usr/local/lib
+	/usr/lib
+	/sw/lib
+	/opt/local/lib
+	/opt/csw/lib
+	/opt/lib
+	/usr/freeware/lib64
+)
+
+FIND_LIBRARY(DUKTAPE_LIBRARY_DEBUG
+	NAMES duktaped
+	PATHS
+	${DUKTAPE_DIR}
+	${DUKTAPE_DIR}/lib
+	${DUKTAPE_DIR}/build/Debug/lib
+	$ENV{DUKTAPE_DIR}
+	$ENV{DUKTAPE_DIR}/lib
+	~/Library/Frameworks
+	/Library/Frameworks
+	/usr/local/lib
+	/usr/lib
+	/sw/lib
+	/opt/local/lib
+	/opt/csw/lib
+	/opt/lib
+	/usr/freeware/lib64
+)
+
+
+SET(DUKTAPE_FOUND "NO")
+IF(DUKTAPE_INCLUDE_DIR AND (DUKTAPE_LIBRARY_DEBUG OR DUKTAPE_LIBRARY_RELEASE))
+    SET(DUKTAPE_LIBRARY debug ${DUKTAPE_LIBRARY_DEBUG} optimized ${DUKTAPE_LIBRARY_RELEASE})
+    SET(DUKTAPE_FOUND "YES")
+ENDIF(DUKTAPE_INCLUDE_DIR AND (DUKTAPE_LIBRARY_DEBUG OR DUKTAPE_LIBRARY_RELEASE))
+
+
diff --git a/CMakeModules/FindGEOS.cmake b/CMakeModules/FindGEOS.cmake
index d4fbd8f..29054d1 100644
--- a/CMakeModules/FindGEOS.cmake
+++ b/CMakeModules/FindGEOS.cmake
@@ -5,13 +5,13 @@
 # GEOS_FOUND, if false, do not try to link to geos
 # GEOS_INCLUDE_DIR, where to find the headers
 
-FIND_PATH(GEOS_INCLUDE_DIR geos.h
-  $ENV{GDAL_DIR}
+FIND_PATH(GEOS_INCLUDE_DIR geos/geom/Geometry.h
+  $ENV{GEOS_DIR}
   NO_DEFAULT_PATH
     PATH_SUFFIXES include
 )
 
-FIND_PATH(GEOS_INCLUDE_DIR geos.h
+FIND_PATH(GEOS_INCLUDE_DIR geos/geom/Geometry.h
   PATHS
   ~/Library/Frameworks/geos/Headers
   /Library/Frameworks/geos/Headers
diff --git a/CMakeModules/FindJavaScriptCore.cmake b/CMakeModules/FindJavaScriptCore.cmake
index 1bca250..3877cd5 100644
--- a/CMakeModules/FindJavaScriptCore.cmake
+++ b/CMakeModules/FindJavaScriptCore.cmake
@@ -21,7 +21,7 @@ FIND_PATH(JAVASCRIPTCORE_INCLUDE_DIR JavaScriptCore.h
 )
 
 FIND_LIBRARY(JAVASCRIPTCORE_LIBRARY
-    NAMES libJavaScriptCore
+    NAMES libJavaScriptCore JavaScriptCore
     PATHS
     ${JAVASCRIPTCORE_DIR}
     ${JAVASCRIPTCORE_DIR}/lib
diff --git a/CMakeModules/FindLevelDB.cmake b/CMakeModules/FindLevelDB.cmake
new file mode 100644
index 0000000..0014325
--- /dev/null
+++ b/CMakeModules/FindLevelDB.cmake
@@ -0,0 +1,86 @@
+# Locate leveldb.
+# This module defines
+# LEVELDB_LIBRARY
+# LEVELDB_FOUND, if false, do not try to link to libnoise
+# LEVELDB_INCLUDE_DIR, where to find the headers
+
+FIND_PATH(LEVELDB_INCLUDE_DIR leveldb/db.h
+  $ENV{LEVELDB_DIR}
+  NO_DEFAULT_PATH
+    PATH_SUFFIXES include
+)
+
+FIND_PATH(LEVELDB_INCLUDE_DIR leveldb/db.h
+  PATHS
+  ~/Library/Frameworks/noise/Headers
+  /Library/Frameworks/noise/Headers
+  /usr/local/include/leveldb
+  /usr/local/include/leveldb
+  /usr/local/include
+  /usr/include/leveldb
+  /usr/include/leveldb
+  /usr/include
+  /sw/include/leveldb 
+  /sw/include/leveldb 
+  /sw/include # Fink
+  /opt/local/include/leveldb
+  /opt/local/include/leveldb
+  /opt/local/include # DarwinPorts
+  /opt/csw/include/leveldb
+  /opt/csw/include/leveldb
+  /opt/csw/include # Blastwave
+  /opt/include/leveldb
+  /opt/include/leveldb
+  /opt/include  
+)
+
+FIND_LIBRARY(LEVELDB_LIBRARY
+  NAMES libleveldb leveldb leveldb_static
+  PATHS
+    $ENV{LEVELDB_DIR}
+    NO_DEFAULT_PATH
+    PATH_SUFFIXES lib64 lib
+)
+
+FIND_LIBRARY(LEVELDB_LIBRARY
+  NAMES libleveldb leveldb leveldb_static
+  PATHS
+    ~/Library/Frameworks
+    /Library/Frameworks
+    /usr/local
+    /usr
+    /sw
+    /opt/local
+    /opt/csw
+    /opt
+    /usr/freeware    
+  PATH_SUFFIXES lib64 lib
+)
+FIND_LIBRARY(LEVELDB_LIBRARY_DEBUG
+  NAMES libleveldbd leveldbd leveldb_staticd
+  PATHS
+    $ENV{LEVELDB_DIR}
+    NO_DEFAULT_PATH
+    PATH_SUFFIXES lib64 lib
+)
+
+FIND_LIBRARY(LEVELDB_LIBRARY_DEBUG
+  NAMES libleveldbd leveldbd leveldb_staticd
+  PATHS
+    ~/Library/Frameworks
+    /Library/Frameworks
+    /usr/local
+    /usr
+    /sw
+    /opt/local
+    /opt/csw
+    /opt
+    /usr/freeware    
+  PATH_SUFFIXES lib64 lib
+)
+
+SET(LEVELDB_FOUND "NO")
+IF(LEVELDB_LIBRARY AND LEVELDB_INCLUDE_DIR)
+  SET(LEVELDB_FOUND "YES")
+ENDIF(LEVELDB_LIBRARY AND LEVELDB_INCLUDE_DIR)
+
diff --git a/CMakeModules/FindLibNoise.cmake b/CMakeModules/FindLibNoise.cmake
index 99d006b..edbbc63 100644
--- a/CMakeModules/FindLibNoise.cmake
+++ b/CMakeModules/FindLibNoise.cmake
@@ -43,7 +43,7 @@ FIND_LIBRARY(LIBNOISE_LIBRARY
 )
 
 FIND_LIBRARY(LIBNOISE_LIBRARY
-  NAMES libnoise
+  NAMES libnoise noise
   PATHS
     ~/Library/Frameworks
     /Library/Frameworks
diff --git a/CMakeModules/FindOSG.cmake b/CMakeModules/FindOSG.cmake
index 4b25f74..6b43069 100644
--- a/CMakeModules/FindOSG.cmake
+++ b/CMakeModules/FindOSG.cmake
@@ -18,6 +18,8 @@
 
 ###### headers ######
 
+SET(OSG_DIR "" CACHE PATH "Set to base OpenSceneGraph install path")
+
 MACRO( FIND_OSG_INCLUDE THIS_OSG_INCLUDE_DIR THIS_OSG_INCLUDE_FILE )
 
 FIND_PATH( ${THIS_OSG_INCLUDE_DIR} ${THIS_OSG_INCLUDE_FILE}
@@ -37,7 +39,6 @@ FIND_PATH( ${THIS_OSG_INCLUDE_DIR} ${THIS_OSG_INCLUDE_FILE}
         /Library/Frameworks
     PATH_SUFFIXES
         /include/
-		/inc/
 )
 
 ENDMACRO( FIND_OSG_INCLUDE THIS_OSG_INCLUDE_DIR THIS_OSG_INCLUDE_FILE )
@@ -75,8 +76,6 @@ FIND_LIBRARY(${MYLIBRARY}
         /build/lib64/
         /Build/lib/
         /Build/lib64/
-		/win/32/debug/lib/
-		/win/32/release/lib/		
      )
 
 ENDMACRO(FIND_OSG_LIBRARY LIBRARY LIBRARYNAME)
@@ -117,6 +116,9 @@ FIND_OSG_LIBRARY( OSGSHADOW_LIBRARY_DEBUG osgShadowd )
 FIND_OSG_LIBRARY( OSGMANIPULATOR_LIBRARY osgManipulator )
 FIND_OSG_LIBRARY( OSGMANIPULATOR_LIBRARY_DEBUG osgManipulatord )
 
+FIND_OSG_LIBRARY( OSGPARTICLE_LIBRARY osgParticle )
+FIND_OSG_LIBRARY( OSGPARTICLE_LIBRARY_DEBUG osgParticled )
+
 FIND_OSG_LIBRARY( OSGQT_LIBRARY osgQt )
 FIND_OSG_LIBRARY( OSGQT_LIBRARY_DEBUG osgQtd )
 
diff --git a/CMakeModules/FindOsgEarth.cmake b/CMakeModules/FindOsgEarth.cmake
new file mode 100644
index 0000000..0cb7daf
--- /dev/null
+++ b/CMakeModules/FindOsgEarth.cmake
@@ -0,0 +1,91 @@
+# This module defines
+
+# OSGEARTH_LIBRARY
+# OSGEARTH_FOUND, if false, do not try to link to osg
+# OSGEARTH_INCLUDE_DIRS, where to find the headers
+# OSGEARTH_INCLUDE_DIR, where to find the source headers
+
+# to use this module, set variables to point to the osg build
+# directory, and source directory, respectively
+# OSGEARTHDIR or OSGEARTH_SOURCE_DIR: osg source directory, typically OpenSceneGraph
+# OSGEARTH_DIR or OSGEARTH_BUILD_DIR: osg build directory, place in which you've
+#    built osg via cmake
+
+# Header files are presumed to be included like
+# #include <osg/PositionAttitudeTransform>
+# #include <osgUtil/SceneView>
+
+###### headers ######
+
+MACRO( FIND_OSGEARTH_INCLUDE THIS_OSGEARTH_INCLUDE_DIR THIS_OSGEARTH_INCLUDE_FILE )
+
+FIND_PATH( ${THIS_OSGEARTH_INCLUDE_DIR} ${THIS_OSGEARTH_INCLUDE_FILE}
+    PATHS
+        ${OSGEARTH_DIR}
+        $ENV{OSGEARTH_SOURCE_DIR}
+        $ENV{OSGEARTHDIR}
+        $ENV{OSGEARTH_DIR}
+        /usr/local/
+        /usr/
+        /sw/ # Fink
+        /opt/local/ # DarwinPorts
+        /opt/csw/ # Blastwave
+        /opt/
+        [HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session\ Manager\\Environment;OSGEARTH_ROOT]/
+        ~/Library/Frameworks
+        /Library/Frameworks
+    PATH_SUFFIXES
+        /include/
+)
+
+ENDMACRO( FIND_OSGEARTH_INCLUDE THIS_OSGEARTH_INCLUDE_DIR THIS_OSGEARTH_INCLUDE_FILE )
+
+FIND_OSGEARTH_INCLUDE( OSGEARTH_INCLUDE_DIR       osgEarth/Version )
+
+###### libraries ######
+
+MACRO( FIND_OSGEARTH_LIBRARY MYLIBRARY MYLIBRARYNAME )
+
+FIND_LIBRARY(${MYLIBRARY}
+    NAMES
+        ${MYLIBRARYNAME}
+    PATHS
+        ${OSGEARTH_DIR}
+        $ENV{OSGEARTH_BUILD_DIR}
+        $ENV{OSGEARTH_DIR}
+        $ENV{OSGEARTHDIR}
+        $ENV{OSG_ROOT}
+        ~/Library/Frameworks
+        /Library/Frameworks
+        /usr/local
+        /usr
+        /sw
+        /opt/local
+        /opt/csw
+        /opt
+        [HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session\ Manager\\Environment;OSGEARTH_ROOT]/lib
+        /usr/freeware
+    PATH_SUFFIXES
+        /lib/
+        /lib64/
+        /build/lib/
+        /build/lib64/
+        /Build/lib/
+        /Build/lib64/
+     )
+
+ENDMACRO(FIND_OSGEARTH_LIBRARY LIBRARY LIBRARYNAME)
+
+FIND_OSGEARTH_LIBRARY( OSGEARTH_LIBRARY osgEarth)
+FIND_OSGEARTH_LIBRARY( OSGEARTHFEATURES_LIBRARY osgEarthFeatures)
+FIND_OSGEARTH_LIBRARY( OSGEARTHUTIL_LIBRARY osgEarthUtil )
+FIND_OSGEARTH_LIBRARY( OSGEARTHQT_LIBRARY osgEarthQt )
+FIND_OSGEARTH_LIBRARY( OSGEARTHSYMBOLOGY_LIBRARY osgEarthSymbology )
+FIND_OSGEARTH_LIBRARY( OSGEARTHANNOTATION_LIBRARY osgEarthAnnotation )
+
+SET( OSGEARTH_FOUND "NO" )
+IF( OSGEARTH_LIBRARY AND OSGEARTH_INCLUDE_DIR )
+    SET( OSGEARTH_FOUND "YES" )
+    SET( OSGEARTH_INCLUDE_DIRS ${OSGEARTH_INCLUDE_DIR})
+    GET_FILENAME_COMPONENT( OSGEARTH_LIBRARIES_DIR ${OSGEARTH_LIBRARY} PATH )
+ENDIF( OSGEARTH_LIBRARY AND OSGEARTH_INCLUDE_DIR )
diff --git a/CMakeModules/FindSilverLining.cmake b/CMakeModules/FindSilverLining.cmake
new file mode 100644
index 0000000..c861efc
--- /dev/null
+++ b/CMakeModules/FindSilverLining.cmake
@@ -0,0 +1,116 @@
+# Locate SilverLining
+# This module defines
+# SILVERLINING_LIBRARY
+# SILVERLINING_FOUND, if false, do not try to link to SilverLining 
+# SILVERLINING_INCLUDE_DIR, where to find the headers
+#
+# $SILVERLINING_DIR is an environment variable that would
+# correspond to the ./configure --prefix=$SILVERLINING_DIR
+#
+# Created by Robert Hauck. 
+
+SET(SILVERLINING_DIR "" CACHE PATH "Location of SilverLining SDK")
+
+IF (MSVC90)
+	IF (CMAKE_CL_64)
+		SET(SILVERLINING_ARCH "vc9/x64")
+	ELSE (CMAKE_CL_64)
+		SET(SILVERLINING_ARCH "vc9/win32")
+	ENDIF (CMAKE_CL_64)
+ENDIF (MSVC90)
+
+IF (MSVC80)
+	IF (CMAKE_CL_64)
+		SET(SILVERLINING_ARCH "vc8/x64")
+	ELSE (CMAKE_CL_64)
+		SET(SILVERLINING_ARCH "vc8/win32")
+	ENDIF (CMAKE_CL_64)
+ENDIF (MSVC80)
+
+IF (MSVC10)
+	IF (CMAKE_CL_64)
+		SET(SILVERLINING_ARCH "vc10/x64")
+	ELSE (CMAKE_CL_64)
+		SET(SILVERLINING_ARCH "vc10/win32")
+	ENDIF (CMAKE_CL_64)
+ENDIF (MSVC10)
+
+IF (MSVC11)
+	IF (CMAKE_CL_64)
+		SET(SILVERLINING_ARCH "vc11/x64")
+	ELSE (CMAKE_CL_64)
+		SET(SILVERLINING_ARCH "vc11/win32")
+	ENDIF (CMAKE_CL_64)
+ENDIF (MSVC11)
+
+IF (MSVC71)
+	SET(SILVERLINING_ARCH "vc7")
+ENDIF (MSVC71)
+
+IF (MSVC60)
+	SET(SILVERLINING_ARCH "vc6")
+ENDIF (MSVC60)
+
+IF (UNIX)
+	SET(SILVERLINING_ARCH "linux")
+ENDIF (UNIX)
+
+FIND_PATH(SILVERLINING_INCLUDE_DIR Atmosphere.h
+    "${SILVERLINING_DIR}/Public Headers"
+    "$ENV{SILVERLINING_PATH}/Public Headers"
+    $ENV{SILVERLINING_PATH}
+    ${SILVERLINING_DIR}/include
+    $ENV{SILVERLINING_DIR}/include
+    $ENV{SILVERLINING_DIR}
+    /usr/local/include
+    /usr/include
+    /sw/include # Fink
+    /opt/local/include # DarwinPorts
+    /opt/csw/include # Blastwave
+    /opt/include
+    /usr/freeware/include
+    "C:/SilverLining SDK/Public Headers"
+)
+
+MACRO(FIND_SILVERLINING_LIBRARY MYLIBRARY MYLIBRARYNAME)
+
+    FIND_LIBRARY(${MYLIBRARY}
+    NAMES ${MYLIBRARYNAME}
+    PATHS
+		${SILVERLINING_DIR}/lib
+		$ENV{SILVERLINING_DIR}/lib
+		$ENV{SILVERLINING_DIR}
+		$ENV{SILVERLINING_PATH}/lib
+		/usr/local/lib
+		/usr/lib
+		/sw/lib
+		/opt/local/lib
+		/opt/csw/lib
+		/opt/lib
+		/usr/freeware/lib64
+        "C:/SilverLining SDK/lib"
+	PATH_SUFFIXES
+		${SILVERLINING_ARCH}
+    )
+
+ENDMACRO(FIND_SILVERLINING_LIBRARY MYLIBRARY MYLIBRARYNAME)
+
+
+FIND_SILVERLINING_LIBRARY(SILVERLINING_LIBRARY_RELEASE "SilverLining-MT-DLL;SilverLining;SilverLiningOpenGL")
+FIND_SILVERLINING_LIBRARY(SILVERLINING_LIBRARY_DEBUG "SilverLining-MTD-DLL;SilverLiningOpenGL")
+
+SET(SILVERLINING_FOUND FALSE)
+IF (SILVERLINING_INCLUDE_DIR AND SILVERLINING_LIBRARY_RELEASE AND SILVERLINING_LIBRARY_DEBUG)
+   SET(SILVERLINING_FOUND TRUE)
+   SET(SILVERLINING_LIBRARY debug ${SILVERLINING_LIBRARY_DEBUG} optimized ${SILVERLINING_LIBRARY_RELEASE})
+ENDIF (SILVERLINING_INCLUDE_DIR AND SILVERLINING_LIBRARY_RELEASE AND SILVERLINING_LIBRARY_DEBUG)
+
+IF (SILVERLINING_FOUND)
+   IF (NOT SILVERLINING_FIND_QUIETLY)
+      MESSAGE(STATUS "Found SilverLining: ${SILVERLINING_LIBRARY_RELEASE}")
+   ENDIF (NOT SILVERLINING_FIND_QUIETLY)
+ELSE (SILVERLINING_FOUND)
+   IF (SILVERLINING_FIND_REQUIRED)
+      MESSAGE(FATAL_ERROR "Could not find SilverLining")
+   ENDIF (SILVERLINING_FIND_REQUIRED)
+ENDIF (SILVERLINING_FOUND)
diff --git a/CMakeModules/FindTriton.cmake b/CMakeModules/FindTriton.cmake
new file mode 100644
index 0000000..91d495f
--- /dev/null
+++ b/CMakeModules/FindTriton.cmake
@@ -0,0 +1,116 @@
+# Find TRITON toolkit
+# This module defines
+# TRITON_FOUND
+# TRITON_INCLUDE_DIR
+# On windows:
+#   TRITON_LIBRARY_DEBUG
+#   TRITON_LIBRARY_RELEASE
+# On other platforms
+#   TRITON_LIBRARY
+#
+
+SET(TRITON_DIR "" CACHE PATH "Location of Triton SDK")
+
+IF (MSVC90)
+	IF (CMAKE_CL_64)
+		SET(TRITON_ARCH "vc9/x64")
+	ELSE (CMAKE_CL_64)
+		SET(TRITON_ARCH "vc9/win32")
+	ENDIF (CMAKE_CL_64)
+ENDIF (MSVC90)
+
+IF (MSVC80)
+	IF (CMAKE_CL_64)
+		SET(TRITON_ARCH "vc8/x64")
+	ELSE (CMAKE_CL_64)
+		SET(TRITON_ARCH "vc8/win32")
+	ENDIF (CMAKE_CL_64)
+ENDIF (MSVC80)
+
+IF (MSVC10)
+	IF (CMAKE_CL_64)
+		SET(TRITON_ARCH "vc10/x64")
+	ELSE (CMAKE_CL_64)
+		SET(TRITON_ARCH "vc10/win32")
+	ENDIF (CMAKE_CL_64)
+ENDIF (MSVC10)
+
+IF (MSVC11)
+	IF (CMAKE_CL_64)
+		SET(TRITON_ARCH "vc11/x64")
+	ELSE (CMAKE_CL_64)
+		SET(TRITON_ARCH "vc11/win32")
+	ENDIF (CMAKE_CL_64)
+ENDIF (MSVC11)
+
+IF (MSVC71)
+	SET(TRITON_ARCH "vc7")
+ENDIF (MSVC71)
+
+IF (MSVC60)
+	SET(TRITON_ARCH "vc6")
+ENDIF (MSVC60)
+
+IF (UNIX)
+	SET(TRITON_ARCH "linux")
+ENDIF (UNIX)
+
+FIND_PATH(TRITON_INCLUDE_DIR Triton.h
+    "${TRITON_DIR}/Public Headers"
+    "$ENV{TRITON_PATH}/Public Headers"
+    $ENV{TRITON_PATH}
+    ${TRITON_DIR}/include
+    $ENV{TRITON_DIR}/include
+    $ENV{TRITON_DIR}
+    /usr/local/include
+    /usr/include
+    /sw/include # Fink
+    /opt/local/include # DarwinPorts
+    /opt/csw/include # Blastwave
+    /opt/include
+    /usr/freeware/include
+    "C:/Triton SDK/Public Headers"
+)
+
+MACRO(FIND_TRITON_LIBRARY MYLIBRARY MYLIBRARYNAME)
+
+    FIND_LIBRARY(${MYLIBRARY}
+    NAMES ${MYLIBRARYNAME}
+    PATHS
+		${TRITON_DIR}/lib
+		$ENV{TRITON_DIR}/lib
+		$ENV{TRITON_DIR}
+		$ENV{TRITON_PATH}/lib
+		/usr/local/lib
+		/usr/lib
+		/sw/lib
+		/opt/local/lib
+		/opt/csw/lib
+		/opt/lib
+		/usr/freeware/lib64
+        "C:/Triton SDK/lib"
+	PATH_SUFFIXES
+		${TRITON_ARCH}
+    )
+
+ENDMACRO(FIND_TRITON_LIBRARY MYLIBRARY MYLIBRARYNAME)
+
+
+FIND_TRITON_LIBRARY(TRITON_LIBRARY_RELEASE "Triton-MT-DLL;Triton")
+FIND_TRITON_LIBRARY(TRITON_LIBRARY_DEBUG "Triton-MTD-DLL;Triton")
+
+SET(TRITON_FOUND FALSE)
+IF (TRITON_INCLUDE_DIR AND TRITON_LIBRARY_RELEASE AND TRITON_LIBRARY_DEBUG)
+   SET(TRITON_FOUND TRUE)
+   SET(TRITON_LIBRARY debug ${TRITON_LIBRARY_DEBUG} optimized ${TRITON_LIBRARY_RELEASE})
+ENDIF (TRITON_INCLUDE_DIR AND TRITON_LIBRARY_RELEASE AND TRITON_LIBRARY_DEBUG)
+
+IF (TRITON_FOUND)
+   IF (NOT TRITON_FIND_QUIETLY)
+      MESSAGE(STATUS "Found Triton: ${TRITON_LIBRARY_RELEASE}")
+   ENDIF (NOT TRITON_FIND_QUIETLY)
+ELSE (TRITON_FOUND)
+   IF (TRITON_FIND_REQUIRED)
+      MESSAGE(FATAL_ERROR "Could not find Triton")
+   ENDIF (TRITON_FIND_REQUIRED)
+ENDIF (TRITON_FOUND)
diff --git a/CMakeModules/FindV8.cmake b/CMakeModules/FindV8.cmake
index 9f5684d..dfb2d86 100644
--- a/CMakeModules/FindV8.cmake
+++ b/CMakeModules/FindV8.cmake
@@ -20,88 +20,143 @@ FIND_PATH(V8_INCLUDE_DIR v8.h
     /devel
 )
 
-FIND_LIBRARY(V8_BASE_LIBRARY
-    NAMES v8_base v8_base.ia32 libv8_base
-    PATHS
-    ${V8_DIR}
-    ${V8_DIR}/lib
-    ${V8_DIR}/build/Release/lib
-    $ENV{V8_DIR}
-    $ENV{V8_DIR}/lib
-    ~/Library/Frameworks
-    /Library/Frameworks
-    /usr/local/lib
-    /usr/lib
-    /sw/lib
-    /opt/local/lib
-    /opt/csw/lib
-    /opt/lib
-    /usr/freeware/lib64
-)
+# On non-Unix platforms (Mac and Windows specifically based on the forum),
+# V8 builds separate shared (or at least linkable) libraries for v8_base and v8_snapshot
+IF(NOT UNIX)
+    FIND_LIBRARY(V8_BASE_LIBRARY
+        NAMES v8_base v8_base.ia32 v8_base.x64 libv8_base
+        PATHS
+        ${V8_DIR}
+        ${V8_DIR}/lib
+        ${V8_DIR}/build/Release/lib
+        $ENV{V8_DIR}
+        $ENV{V8_DIR}/lib
+        ~/Library/Frameworks
+        /Library/Frameworks
+        /usr/local/lib
+        /usr/lib
+        /sw/lib
+        /opt/local/lib
+        /opt/csw/lib
+        /opt/lib
+        /usr/freeware/lib64
+    )
 
-FIND_LIBRARY(V8_BASE_LIBRARY_DEBUG
-    NAMES v8_base v8_base.ia32 libv8_base
-    PATHS
-    ${V8_DIR}
-    ${V8_DIR}/lib
-    ${V8_DIR}/build/Debug/lib
-    $ENV{V8_DIR}
-    $ENV{V8_DIR}/lib
-    ~/Library/Frameworks
-    /Library/Frameworks
-    /usr/local/lib
-    /usr/lib
-    /sw/lib
-    /opt/local/lib
-    /opt/csw/lib
-    /opt/lib
-    /usr/freeware/lib64
-)
+    FIND_LIBRARY(V8_BASE_LIBRARY_DEBUG
+        NAMES v8_base v8_base.ia32 v8_base.x64 libv8_base
+        PATHS
+        ${V8_DIR}
+        ${V8_DIR}/lib
+        ${V8_DIR}/build/Debug/lib
+        $ENV{V8_DIR}
+        $ENV{V8_DIR}/lib
+        ~/Library/Frameworks
+        /Library/Frameworks
+        /usr/local/lib
+        /usr/lib
+        /sw/lib
+        /opt/local/lib
+        /opt/csw/lib
+        /opt/lib
+        /usr/freeware/lib64
+    )
 
-FIND_LIBRARY(V8_SNAPSHOT_LIBRARY
-    NAMES v8_snapshot libv8_snapshot
-    PATHS
-    ${V8_DIR}
-    ${V8_DIR}/lib
-    ${V8_DIR}/build/Release/lib
-    $ENV{V8_DIR}
-    $ENV{V8_DIR}/lib
-    ~/Library/Frameworks
-    /Library/Frameworks
-    /usr/local/lib
-    /usr/lib
-    /sw/lib
-    /opt/local/lib
-    /opt/csw/lib
-    /opt/lib
-    /usr/freeware/lib64
-)
+    FIND_LIBRARY(V8_SNAPSHOT_LIBRARY
+        NAMES v8_snapshot libv8_snapshot
+        PATHS
+        ${V8_DIR}
+        ${V8_DIR}/lib
+        ${V8_DIR}/build/Release/lib
+        $ENV{V8_DIR}
+        $ENV{V8_DIR}/lib
+        ~/Library/Frameworks
+        /Library/Frameworks
+        /usr/local/lib
+        /usr/lib
+        /sw/lib
+        /opt/local/lib
+        /opt/csw/lib
+        /opt/lib
+        /usr/freeware/lib64
+    )
 
-FIND_LIBRARY(V8_SNAPSHOT_LIBRARY_DEBUG
-    NAMES v8_snapshot libv8_snapshot
-    PATHS
-    ${V8_DIR}
-    ${V8_DIR}/lib
-    ${V8_DIR}/build/Debug/lib
-    $ENV{V8_DIR}
-    $ENV{V8_DIR}/lib
-    ~/Library/Frameworks
-    /Library/Frameworks
-    /usr/local/lib
-    /usr/lib
-    /sw/lib
-    /opt/local/lib
-    /opt/csw/lib
-    /opt/lib
-    /usr/freeware/lib64
-)
+    FIND_LIBRARY(V8_SNAPSHOT_LIBRARY_DEBUG
+        NAMES v8_snapshot libv8_snapshot
+        PATHS
+        ${V8_DIR}
+        ${V8_DIR}/lib
+        ${V8_DIR}/build/Debug/lib
+        $ENV{V8_DIR}
+        $ENV{V8_DIR}/lib
+        ~/Library/Frameworks
+        /Library/Frameworks
+        /usr/local/lib
+        /usr/lib
+        /sw/lib
+        /opt/local/lib
+        /opt/csw/lib
+        /opt/lib
+        /usr/freeware/lib64
+    )
+
+# On Linux, there is just a libv8.so shared library built.
+# (well, there are pseudo-static libraries libv8_base.a and libv8_snapshot.a
+# but they don't seem to link correctly)
+ELSE()
+    FIND_LIBRARY(V8_LIBRARY
+        NAMES v8
+        PATHS
+        ${V8_DIR}
+        ${V8_DIR}/lib
+        ${V8_DIR}/build/Release/lib
+        # Having both architectures listed is problematic if both have been
+        # built (which is the default)
+        ${V8_DIR}/out/ia32.release/lib.target/
+        ${V8_DIR}/out/x64.release/lib.target/
+        $ENV{V8_DIR}
+        $ENV{V8_DIR}/lib
+        ~/Library/Frameworks
+        /Library/Frameworks
+        /usr/local/lib
+        /usr/lib
+        /sw/lib
+        /opt/local/lib
+        /opt/csw/lib
+        /opt/lib
+        /usr/freeware/lib64
+    )
+
+    FIND_LIBRARY(V8_LIBRARY_DEBUG
+        NAMES v8
+        PATHS
+        ${V8_DIR}
+        ${V8_DIR}/lib
+        ${V8_DIR}/build/Debug/lib
+        ${V8_DIR}/out/ia32.debug/lib.target/
+        ${V8_DIR}/out/x64.debug/lib.target/
+        $ENV{V8_DIR}
+        $ENV{V8_DIR}/lib
+        ~/Library/Frameworks
+        /Library/Frameworks
+        /usr/local/lib
+        /usr/lib
+        /sw/lib
+        /opt/local/lib
+        /opt/csw/lib
+        /opt/lib
+        /usr/freeware/lib64
+    )
+ENDIF(NOT UNIX)
 
+# icuuc and icui18n build fine on all platforms
 FIND_LIBRARY(V8_ICUUC_LIBRARY
     NAMES icuuc libicuuc
     PATHS
     ${V8_DIR}
     ${V8_DIR}/lib
     ${V8_DIR}/build/Release/lib
+    ${V8_DIR}/out/ia32.release/lib.target/
+    ${V8_DIR}/out/x64.release/lib.target/
     $ENV{V8_DIR}
     $ENV{V8_DIR}/lib
     ~/Library/Frameworks
@@ -121,6 +176,8 @@ FIND_LIBRARY(V8_ICUUC_LIBRARY_DEBUG
     ${V8_DIR}
     ${V8_DIR}/lib
     ${V8_DIR}/build/Debug/lib
+    ${V8_DIR}/out/ia32.debug/lib.target/
+    ${V8_DIR}/out/x64.debug/lib.target/
     $ENV{V8_DIR}
     $ENV{V8_DIR}/lib
     ~/Library/Frameworks
@@ -140,6 +197,8 @@ FIND_LIBRARY(V8_ICUI18N_LIBRARY
     ${V8_DIR}
     ${V8_DIR}/lib
     ${V8_DIR}/build/Release/lib
+    ${V8_DIR}/out/ia32.release/lib.target/
+    ${V8_DIR}/out/x64.release/lib.target/
     $ENV{V8_DIR}
     $ENV{V8_DIR}/lib
     ~/Library/Frameworks
@@ -159,6 +218,8 @@ FIND_LIBRARY(V8_ICUI18N_LIBRARY_DEBUG
     ${V8_DIR}
     ${V8_DIR}/lib
     ${V8_DIR}/build/Debug/lib
+    ${V8_DIR}/out/ia32.debug/lib.target/
+    ${V8_DIR}/out/x64.debug/lib.target/
     $ENV{V8_DIR}
     $ENV{V8_DIR}/lib
     ~/Library/Frameworks
@@ -173,8 +234,12 @@ FIND_LIBRARY(V8_ICUI18N_LIBRARY_DEBUG
 )
 
 SET(V8_FOUND "NO")
-IF(V8_BASE_LIBRARY AND V8_SNAPSHOT_LIBRARY AND V8_ICUUC_LIBRARY AND V8_ICUI18N_LIBRARY AND V8_INCLUDE_DIR)
+IF(NOT UNIX)
+    IF(V8_BASE_LIBRARY AND V8_SNAPSHOT_LIBRARY AND V8_ICUUC_LIBRARY AND V8_ICUI18N_LIBRARY AND V8_INCLUDE_DIR)
+        SET(V8_FOUND "YES")
+    ENDIF(V8_BASE_LIBRARY AND V8_SNAPSHOT_LIBRARY AND V8_ICUUC_LIBRARY AND V8_ICUI18N_LIBRARY AND V8_INCLUDE_DIR)
+ELSEIF(V8_LIBRARY AND V8_ICUUC_LIBRARY AND V8_ICUI18N_LIBRARY AND V8_INCLUDE_DIR)
     SET(V8_FOUND "YES")
-ENDIF(V8_BASE_LIBRARY AND V8_SNAPSHOT_LIBRARY AND V8_ICUUC_LIBRARY AND V8_ICUI18N_LIBRARY AND V8_INCLUDE_DIR)
+ENDIF(NOT UNIX)
 
 
diff --git a/CMakeModules/GetGitRevisionDescription.cmake b/CMakeModules/GetGitRevisionDescription.cmake
index 1bf0230..911af99 100644
--- a/CMakeModules/GetGitRevisionDescription.cmake
+++ b/CMakeModules/GetGitRevisionDescription.cmake
@@ -67,7 +67,13 @@ function(get_git_head_revision _refspecvar _hashvar)
 	configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in"
 		"${GIT_DATA}/grabRef.cmake"
 		@ONLY)
-	include("${GIT_DATA}/grabRef.cmake")
+        
+    if(EXISTS "${GIT_DATA}/grabRef.cmake")
+        include("${GIT_DATA}/grabRef.cmake")
+    else()
+        set(HEAD_REF  "noref")
+        set(HEAD_HASH "nohash")
+    endif()
 
 	set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE)
 	set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE)
diff --git a/CMakeModules/OsgEarthMacroUtils.cmake b/CMakeModules/OsgEarthMacroUtils.cmake
index 603bcc5..4d8bb70 100644
--- a/CMakeModules/OsgEarthMacroUtils.cmake
+++ b/CMakeModules/OsgEarthMacroUtils.cmake
@@ -96,9 +96,9 @@ MACRO(LINK_WITH_VARIABLES TRGTNAME)
 ENDMACRO(LINK_WITH_VARIABLES TRGTNAME)
 
 MACRO(LINK_INTERNAL TRGTNAME)
-    IF(${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} GREATER 4)
+    IF("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" GREATER 2.4)
         TARGET_LINK_LIBRARIES(${TRGTNAME} ${ARGN})
-    ELSE(${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} GREATER 4)
+    ELSE("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" GREATER 2.4)
         FOREACH(LINKLIB ${ARGN})
             IF(MSVC AND OSG_MSVC_VERSIONED_DLL)
                 #when using versioned names, the .dll name differ from .lib name, there is a problem with that:
@@ -111,7 +111,7 @@ MACRO(LINK_INTERNAL TRGTNAME)
                 TARGET_LINK_LIBRARIES(${TRGTNAME} optimized "${LINKLIB}${CMAKE_RELEASE_POSTFIX}" debug "${LINKLIB}${CMAKE_DEBUG_POSTFIX}")
             ENDIF(MSVC AND OSG_MSVC_VERSIONED_DLL)
         ENDFOREACH(LINKLIB)
-    ENDIF(${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} GREATER 4)
+    ENDIF("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" GREATER 2.4)
 ENDMACRO(LINK_INTERNAL TRGTNAME)
 
 MACRO(LINK_EXTERNAL TRGTNAME)
diff --git a/LICENSE.txt b/LICENSE.txt
index 830e7f0..7e79791 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -13,7 +13,7 @@ following exceptions and stipulations:
    the LGPL.
    
 3. The text herein comprises the definitive licensing information for the osgEarth
-   library, and supercedes any licensing text found in osgEarth source files,
+   library, and supersedes any licensing text found in osgEarth source files,
    header files, or documentation.
 
 
diff --git a/README.txt b/README.txt
index 64ac1d9..601627a 100644
--- a/README.txt
+++ b/README.txt
@@ -1,5 +1,5 @@
 osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-Copyright 2008-2013 Pelican Mapping
+Copyright 2008-2014 Pelican Mapping
 
 http://osgearth.org
 git://github.com/gwaldron/osgearth
diff --git a/data/resources/textures_us/catalog.xml b/data/resources/textures_us/catalog.xml
index eefff6d..3ee7f8b 100644
--- a/data/resources/textures_us/catalog.xml
+++ b/data/resources/textures_us/catalog.xml
@@ -1,6 +1,15 @@
 <?xml version="1.0" ?>
 <resources name="texlib-us">
 
+  <!-- tiled repeatables -->
+    <skin name="asphalt" tags="asphalt road rock">
+	    <url>misc/asphalt.jpg</url>
+		<tiled>true</tiled>
+		<image_width>20</image_width>
+		<image_height>30</image_height>
+		<texture_mode>modulate</texture_mode>
+	</skin>
+
   <!-- tiled barriers textures -->
 
     <skin name="fence" tags="barrier fence">
@@ -325,9 +334,9 @@
 
    <skin name="8stResditentialTileApt" tags="building tile residential apt us concrete">
         <url>residential/tiles/USUAE-8stTile_rep.jpg</url>
-        <image_width>19</image_width>
-        <min_object_height>21</min_object_height>
-        <max_object_height>29</max_object_height>
+        <image_width>15</image_width>
+        <min_object_height>14</min_object_height>
+        <max_object_height>21</max_object_height>
         <tiled>false</tiled>
    </skin>
 
diff --git a/data/resources/textures_us/commercial/10storymodernconcrete.jpg b/data/resources/textures_us/commercial/10storymodernconcrete.jpg
index def3d6e..aef1c53 100644
Binary files a/data/resources/textures_us/commercial/10storymodernconcrete.jpg and b/data/resources/textures_us/commercial/10storymodernconcrete.jpg differ
diff --git a/data/resources/textures_us/commercial/12storygovtmodern.jpg b/data/resources/textures_us/commercial/12storygovtmodern.jpg
index 48d5bed..9cbb60a 100644
Binary files a/data/resources/textures_us/commercial/12storygovtmodern.jpg and b/data/resources/textures_us/commercial/12storygovtmodern.jpg differ
diff --git a/data/resources/textures_us/commercial/15storybrownconcroffice2.jpg b/data/resources/textures_us/commercial/15storybrownconcroffice2.jpg
index 6466d89..73cbe06 100644
Binary files a/data/resources/textures_us/commercial/15storybrownconcroffice2.jpg and b/data/resources/textures_us/commercial/15storybrownconcroffice2.jpg differ
diff --git a/data/resources/textures_us/commercial/15storyltbrownconcroffice3.jpg b/data/resources/textures_us/commercial/15storyltbrownconcroffice3.jpg
index 6cee6d2..59613fc 100644
Binary files a/data/resources/textures_us/commercial/15storyltbrownconcroffice3.jpg and b/data/resources/textures_us/commercial/15storyltbrownconcroffice3.jpg differ
diff --git a/data/resources/textures_us/commercial/16storyconcrglassgreymodern4.jpg b/data/resources/textures_us/commercial/16storyconcrglassgreymodern4.jpg
index 3295a2c..0af283c 100644
Binary files a/data/resources/textures_us/commercial/16storyconcrglassgreymodern4.jpg and b/data/resources/textures_us/commercial/16storyconcrglassgreymodern4.jpg differ
diff --git a/data/resources/textures_us/commercial/18storyconcrbrownoffice2.jpg b/data/resources/textures_us/commercial/18storyconcrbrownoffice2.jpg
index c4dc858..6cf4cb6 100644
Binary files a/data/resources/textures_us/commercial/18storyconcrbrownoffice2.jpg and b/data/resources/textures_us/commercial/18storyconcrbrownoffice2.jpg differ
diff --git a/data/resources/textures_us/commercial/18storyoffice.jpg b/data/resources/textures_us/commercial/18storyoffice.jpg
index d42fab9..0804e3a 100644
Binary files a/data/resources/textures_us/commercial/18storyoffice.jpg and b/data/resources/textures_us/commercial/18storyoffice.jpg differ
diff --git a/data/resources/textures_us/commercial/20storygreycncrglassmodern.jpg b/data/resources/textures_us/commercial/20storygreycncrglassmodern.jpg
index dcaca4b..d875c73 100644
Binary files a/data/resources/textures_us/commercial/20storygreycncrglassmodern.jpg and b/data/resources/textures_us/commercial/20storygreycncrglassmodern.jpg differ
diff --git a/data/resources/textures_us/commercial/25storyBrownWide1.jpg b/data/resources/textures_us/commercial/25storyBrownWide1.jpg
index 8fe1d28..f0bb381 100644
Binary files a/data/resources/textures_us/commercial/25storyBrownWide1.jpg and b/data/resources/textures_us/commercial/25storyBrownWide1.jpg differ
diff --git a/data/resources/textures_us/commercial/30storyconcrbrown4.jpg b/data/resources/textures_us/commercial/30storyconcrbrown4.jpg
index d1a017b..f5cc80c 100644
Binary files a/data/resources/textures_us/commercial/30storyconcrbrown4.jpg and b/data/resources/textures_us/commercial/30storyconcrbrown4.jpg differ
diff --git a/data/resources/textures_us/commercial/3storyIndustrial_concrglasswhite1.jpg b/data/resources/textures_us/commercial/3storyIndustrial_concrglasswhite1.jpg
index 6d5019c..461bd5e 100644
Binary files a/data/resources/textures_us/commercial/3storyIndustrial_concrglasswhite1.jpg and b/data/resources/textures_us/commercial/3storyIndustrial_concrglasswhite1.jpg differ
diff --git a/data/resources/textures_us/commercial/40storymodern.jpg b/data/resources/textures_us/commercial/40storymodern.jpg
index 555bbdb..0ffa915 100644
Binary files a/data/resources/textures_us/commercial/40storymodern.jpg and b/data/resources/textures_us/commercial/40storymodern.jpg differ
diff --git a/data/resources/textures_us/commercial/45storyglassmodern.jpg b/data/resources/textures_us/commercial/45storyglassmodern.jpg
index 1642f60..cba52db 100644
Binary files a/data/resources/textures_us/commercial/45storyglassmodern.jpg and b/data/resources/textures_us/commercial/45storyglassmodern.jpg differ
diff --git a/data/resources/textures_us/commercial/5storywhite.jpg b/data/resources/textures_us/commercial/5storywhite.jpg
index e7730e0..b54f321 100644
Binary files a/data/resources/textures_us/commercial/5storywhite.jpg and b/data/resources/textures_us/commercial/5storywhite.jpg differ
diff --git a/data/resources/textures_us/commercial/7storymodernsq.jpg b/data/resources/textures_us/commercial/7storymodernsq.jpg
index e7fc96b..72f5f50 100644
Binary files a/data/resources/textures_us/commercial/7storymodernsq.jpg and b/data/resources/textures_us/commercial/7storymodernsq.jpg differ
diff --git a/data/resources/textures_us/commercial/US-dcofficeconcrwhite6-7st.jpg b/data/resources/textures_us/commercial/US-dcofficeconcrwhite6-7st.jpg
index 2409f2a..35b8799 100644
Binary files a/data/resources/textures_us/commercial/US-dcofficeconcrwhite6-7st.jpg and b/data/resources/textures_us/commercial/US-dcofficeconcrwhite6-7st.jpg differ
diff --git a/data/resources/textures_us/commercial/US-dcofficeconcrwhite8st.jpg b/data/resources/textures_us/commercial/US-dcofficeconcrwhite8st.jpg
index 1c2eed5..398dc06 100644
Binary files a/data/resources/textures_us/commercial/US-dcofficeconcrwhite8st.jpg and b/data/resources/textures_us/commercial/US-dcofficeconcrwhite8st.jpg differ
diff --git a/data/resources/textures_us/misc/asphalt.jpg b/data/resources/textures_us/misc/asphalt.jpg
new file mode 100644
index 0000000..8dbd190
Binary files /dev/null and b/data/resources/textures_us/misc/asphalt.jpg differ
diff --git a/data/resources/textures_us/residential/US-CityCondo-3st.jpg b/data/resources/textures_us/residential/US-CityCondo-3st.jpg
index dddc16e..0a2de9b 100644
Binary files a/data/resources/textures_us/residential/US-CityCondo-3st.jpg and b/data/resources/textures_us/residential/US-CityCondo-3st.jpg differ
diff --git a/data/resources/textures_us/residential/tiles/USUAE-8stTile_rep.jpg b/data/resources/textures_us/residential/tiles/USUAE-8stTile_rep.jpg
index 67772b1..bb9e42b 100644
Binary files a/data/resources/textures_us/residential/tiles/USUAE-8stTile_rep.jpg and b/data/resources/textures_us/residential/tiles/USUAE-8stTile_rep.jpg differ
diff --git a/data/resources/textures_us/rooftop/roof_misc2.jpg b/data/resources/textures_us/rooftop/roof_misc2.jpg
index 4f408fc..a808dfa 100644
Binary files a/data/resources/textures_us/rooftop/roof_misc2.jpg and b/data/resources/textures_us/rooftop/roof_misc2.jpg differ
diff --git a/data/resources/textures_us/rooftop/roof_misc3.jpg b/data/resources/textures_us/rooftop/roof_misc3.jpg
index 3ba3622..483115a 100644
Binary files a/data/resources/textures_us/rooftop/roof_misc3.jpg and b/data/resources/textures_us/rooftop/roof_misc3.jpg differ
diff --git a/data/resources/textures_us/rooftop/roof_misc4.jpg b/data/resources/textures_us/rooftop/roof_misc4.jpg
index f456941..c6661a8 100644
Binary files a/data/resources/textures_us/rooftop/roof_misc4.jpg and b/data/resources/textures_us/rooftop/roof_misc4.jpg differ
diff --git a/data/resources/textures_us/rooftop/roof_misc5.jpg b/data/resources/textures_us/rooftop/roof_misc5.jpg
index 3dc0708..3097fd7 100644
Binary files a/data/resources/textures_us/rooftop/roof_misc5.jpg and b/data/resources/textures_us/rooftop/roof_misc5.jpg differ
diff --git a/data/resources/textures_us/rooftop/tiled/roof_tiled1.jpg b/data/resources/textures_us/rooftop/tiled/roof_tiled1.jpg
index 4a2a235..6eec3a9 100644
Binary files a/data/resources/textures_us/rooftop/tiled/roof_tiled1.jpg and b/data/resources/textures_us/rooftop/tiled/roof_tiled1.jpg differ
diff --git a/data/resources/textures_us/rooftop/tiled/roof_tiled3.jpg b/data/resources/textures_us/rooftop/tiled/roof_tiled3.jpg
index f30d51f..44ea592 100644
Binary files a/data/resources/textures_us/rooftop/tiled/roof_tiled3.jpg and b/data/resources/textures_us/rooftop/tiled/roof_tiled3.jpg differ
diff --git a/docs/source/about.rst b/docs/source/about.rst
index c97d955..efecfc9 100644
--- a/docs/source/about.rst
+++ b/docs/source/about.rst
@@ -4,33 +4,25 @@ About the Project
 Introduction
 ------------
 
-osgEarth_ is a 3D mapping SDK for OpenSceneGraph_ applications.
-It's different than traditional terrain engines in an important way:
-osgEarth_ does not require you to build a 3D terrain model before you
-display it. 
-Instead, it will access the raw data sources at application run time and
-composite them into a 3D map *on the fly*.
-No terrain model is actually stored to disk, though it does use caching
-techniques to speed up the rendering of the map.
+osgEarth_ is a geospatial SDK and terrain engine for OpenSceneGraph_ applications.
 
 The goals of osgEarth_ are to:
 
 - Enable the development of 3D geospatial appliations on top of OpenSceneGraph_.
-- Make it as easy as possible to visualize terrian models and 3D maps.
+- Make it as easy as possible to visualize terrian models and imagery directly from source data.
 - Interoperate with open mapping standards, technologies, and data.
 
 
-**So if it for me?**
+**So is it for me?**
 
 So: does osgEarth replace the need for offline terrain database creation tools? In many cases it does.
 
 Consider using osgEarth_ if you need to:
 
     - Get a terrain base map up and running quickly and easily
-    - Access open-standards map data services like WMS, WCS, or TMS
-    - Integrate locally-stored data with web-service-based data
+    - Access open-standards map data services like WMS or TMS
+    - Integrate locally-stored data with web-service-based imagery
     - Incorporate new geospatial data layers at run-time
-    - Run in a "thin-client" environment
     - Deal with data that may change over time
     - Integrate with a commercial data provider
 
@@ -86,7 +78,7 @@ to testing, adding features, and fixing bugs.
 License
 -------
 
-`Pelican Mapping`_ licenses osgEarth_ under the LGPL_ free open source license. 
+osgEarth_ is licensed under the LGPL_ free open source license. 
 
 This means that:
 
@@ -114,9 +106,7 @@ That's it.
 Maintainers
 -----------
 
-`Pelican Mapping`_ maintains osgEarth_. We are located in the Washington, DC area.
-
-Pelican is Glenn_, Jason_, Jeff_, and Paul_.
+`Pelican Mapping`_ maintains osgEarth_.
 
 
 .. _osgEarth:        http://osgEarth.org
diff --git a/docs/source/data.rst b/docs/source/data.rst
index 12ac3e1..abe3d1a 100644
--- a/docs/source/data.rst
+++ b/docs/source/data.rst
@@ -18,7 +18,7 @@ Help us add useful sources of Free data to this list.
       transportation, structures, and land cover products for the US.
     
     * `NASA EOSDIS`_ - NASA's Global Imagery Browse Services (GIBS) replaces the agency's old
-      `JPL OnEarth`_ site for global imagery products like MODIS.
+      JPL OnEarth site for global imagery products like MODIS.
        
     * `NASA BlueMarble`_ - NASA's whole-earth imagery (including topography and bathymetry maps)
     
@@ -49,7 +49,7 @@ Help us add useful sources of Free data to this list.
     
 
 .. _CGIAR:                      http://srtm.csi.cgiar.org/
-.. _CGIAR Europoean mirror:     ftp://xftp.jrc.it/pub/srtmV4/
+.. _CGIAR European mirror:      ftp://xftp.jrc.it/pub/srtmV4/
 .. _DIVA-GIS:                   http://www.diva-gis.org/gData
 .. _GEBCO:                      http://www.gebco.net/
 .. _GLCF:                       http://glcf.umiacs.umd.edu/data/srtm/
diff --git a/docs/source/developer/utilities.rst b/docs/source/developer/utilities.rst
index f531f36..ef890cb 100644
--- a/docs/source/developer/utilities.rst
+++ b/docs/source/developer/utilities.rst
@@ -76,8 +76,9 @@ and image textures from one LOD to the next as you zoom in or out. Basic usage i
     mapnode->getTerrainEngine()->addEffect( effect );
 
 Caveats: It requires that the terrain elevation tile size dimensions be odd-numbered
-(e.g., 15x15). You can use the ``MapOptions::elevationTileSize`` property to configure
-this, or set ``elevation_tile_size`` in your earth file::
+(e.g., 17x17, which is the default.) You can use the ``MapOptions::elevationTileSize``
+property to configure this, or set ``elevation_tile_size`` in your earth file if you
+want to change it::
 
     <map>
         <options elevation_tile_size="15" ...
@@ -86,6 +87,44 @@ For a demo, run this example and zoom into a mountainous area::
 
     osgearth_viewer lod_blending.earth
 
+LOD blending supports the following properties (earth file and API):
+
+    :delay:            Time to wait before starting a blending transition (seconds)
+    :duration:         Duration of the blending transition (seconds)
+    :vertical_scale:   Factor by which to vertically scale the terrain (default = 1.0)
+    :blend_imagery:    Whether to blend imagery LODs (true)
+    :blend_elevation:  Whether to morph elevation LODs (true)
+
+
+Logarithmic Depth Buffer
+------------------------
+
+In whole-earth applications it's common that you want to see something up close (like
+an aircraft at altitude) while seeing the Earth and its horizon off in the distance.
+This poses a problem for modern graphic hardware because the standard depth buffer
+precision heavily favors objects closer to the camera, and viewing such a wide range
+of objects leads to "z-fighting" artifacts.
+
+The ``LogarithmicDepthBuffer`` is one way to solve this problem. It uses a shader to
+re-map the GPU's depth buffer values so they can be put to better use in this type
+of scenario.
+
+It's easy to install::
+
+    LogarithmicDepthBuffer logdepth;
+    logdepth->install( view->getCamera() );
+    
+Or you can activate it from ``osgearth_viewer`` or other examples::
+
+    osgearth_viewer --logdepth ...
+
+Since it does alter the projection-space coordinates of your geometry at draw time,
+you do need to be careful that you aren't doing anything ELSE in clip space in your
+own custom shaders that would conflict with this.
+
+(10-Jul-2014: Some osgEarth features are incompatible with the log depth buffer;
+namely, GPU clamping and Shadowing. Depth Offset works correctly though.)
+
 
 Formatters
 ----------
diff --git a/docs/source/faq.rst b/docs/source/faq.rst
index f947ffa..304a14a 100644
--- a/docs/source/faq.rst
+++ b/docs/source/faq.rst
@@ -17,7 +17,30 @@ Common Usage
 How do I place a 3D model on the map?
 .....................................
 
-    One way to position a 3D model is to use the ``ModelNode``. Here is the basic idea::
+    The most basic approach is to make a ``osg::Matrix`` so you can position
+    a model using your own ``osg::MatrixTransform``. You can use the ``GeoPoint``
+    class like so::
+    
+        GeoPoint point(latLong, -121.0, 34.0, 1000.0, ALTMODE_ABSOLUTE);
+        osg::Matrix matrix;
+        point.createLocalToWorld( matrix );
+        myMatrixTransform->setMatrix( matrix );
+
+    Another option is the ``osgEarth::GeoTransform`` class. It inherits from
+    ``osg::Transform`` so you can add your own nodes as children. ``GeoTransform``
+    can automatically convert coordinates as well, as long as you tell it 
+    about your map's terrain::
+
+        GeoTransform* xform = new GeoTransform();
+        ...
+        xform->setTerrain( mapNode->getTerrain() );
+        ...
+        GeoPoint point(srs, -121.0, 34.0, 1000.0, ALTMODE_ABSOLUTE);
+        xform->setPosition(point);
+
+    Finally, you can position a node by using the ``ModelNode`` from the
+    osgEarth::Annotation namespace. This is more complicated, but lets you
+    take advantage of symbology::
 
         using namespace osgEarth;
         using namespace osgEarth::Symbology;
@@ -39,14 +62,6 @@ How do I place a 3D model on the map?
         // Set its location.
         model->setPosition( GeoPoint(latLong, -121.0, 34.0, 1000.0, ALTMODE_ABSOLUTE) );
 
-    If you just want to make a ``osg::Matrix`` so you can position a model using your own 
-    ``osg::MatrixTransform``, you can use the ``GeoPoint`` class like so::
-    
-        GeoPoint point(latLong, -121.0, 34.0, 1000.0, ALTMODE_ABSOLUTE);
-        osg::Matrix matrix;
-        point.createLocalToWorld( matrix );
-        myMatrixTransform->setMatrix( matrix );
-
     Look at the ``osgearth_annotation.cpp`` sample for more inspiration.
     
 
@@ -64,7 +79,7 @@ How do make the terrain transparent?
     In code, this option is found in the ``MPTerrainEngineOptions`` class::
     
         #include <osgEarthDrivers/engine_mp/MPTerrainEngineOptions>
-        using namespace osgEarth::Drivers;
+        using namespace osgEarth::Drivers::MPTerrainEngine;
         ...
         MPTerrainEngineOptions options;
         options.color() = osg::Vec4(1,1,1,0);
diff --git a/docs/source/references/colorfilters.rst b/docs/source/references/colorfilters.rst
index 343c89c..a7290ae 100644
--- a/docs/source/references/colorfilters.rst
+++ b/docs/source/references/colorfilters.rst
@@ -1,9 +1,9 @@
 Color Filter Reference
 ======================
-A *color fitler* is an inline, GLSL processor for an ImageLayer. 
+A *color filter* is an inline, GLSL processor for an ImageLayer. 
 The osgEarth terrain engine runs each image tile through its layer's
 color filter as it's being rendered on the GPU. You can chain color
-filter together to form an image processing pipeline.
+filters together to form an image processing pipeline.
 
 osgEarth comes with several stock filters; you can create your own
 by implementing the ``osgEarth::ColorFilter`` interface.
@@ -40,7 +40,7 @@ For example, ``c="1.2"`` means to increase the contrast by 20%.
 
 ChromaKey
 ---------
-This filter matches color values are makes fragments to transparent,
+This filter matches color values to turn fragments transparent,
 providing a kind of "green-screen" effect::
 
     <chroma_key r="1.0" g="0.0" b="0.0" distance="0.1"/>
diff --git a/docs/source/references/drivers/cache/filesystem.rst b/docs/source/references/drivers/cache/filesystem.rst
new file mode 100644
index 0000000..4b5efd5
--- /dev/null
+++ b/docs/source/references/drivers/cache/filesystem.rst
@@ -0,0 +1,40 @@
+FileSystem Cache
+================
+This plugin caches terrain tiles, feature vectors, and other data
+to the local file system in a hierarchy of folders. Each cached
+data element is in a separate file, and may include an associated
+metadata file.
+
+Example usage::
+
+    <map>
+	    <options>
+            <cache driver="filesystem">
+	            <path>c:/osgearth_cache</path>
+            </cache>
+			...
+			
+Notes::
+
+    The ``filesystem`` cache stores each class of data in its own ``bin``.
+	Each ``bin`` has a separate directory under the root path. osgEarth
+	controls the naming of these bins, but you can use the ``cache_id``
+	property on map layers to customize the naming to some extent.
+	
+	This cache supports expiration, but does NOT support size limits --
+	there is to way to cap the size of the cache.
+	
+	Cache access is serialized since we are reading and writing
+	individual files on disk.
+	
+	Accessing the cache from more than one process at a time may cause
+	corruption.
+	
+	The actual format of cached data files is "black box" and may change
+	without notice. We do not intend for cached files to be used directly
+	or for other purposes.
+    
+Properties:
+
+    :path: Location of the root directory in which to store all cache
+	       bins and files.
diff --git a/docs/source/references/drivers/cache/index.rst b/docs/source/references/drivers/cache/index.rst
new file mode 100644
index 0000000..a4703d0
--- /dev/null
+++ b/docs/source/references/drivers/cache/index.rst
@@ -0,0 +1,10 @@
+Cache Drivers
+=============
+A *Cache Driver* is a plugin that provides terrain tile and feature 
+data caching to the local disk.
+
+.. toctree::
+   :maxdepth: 1
+
+   filesystem
+   leveldb
diff --git a/docs/source/references/drivers/cache/leveldb.rst b/docs/source/references/drivers/cache/leveldb.rst
new file mode 100644
index 0000000..77e47ce
--- /dev/null
+++ b/docs/source/references/drivers/cache/leveldb.rst
@@ -0,0 +1,39 @@
+LevelDB Cache
+=============
+This plugin caches terrain tiles, feature vectors, and other data
+to the local file system using the Google leveldb_ embedded key/value
+store library.
+
+Example usage::
+
+    <map>
+	    <options>
+            <cache driver      = "leveldb"
+                   path        = "c:/osgearth_cache"
+                   max_size_mb = "500" />
+            </cache>
+			...
+			
+The ``leveldb`` cache stores each class of data in its own *bin*.
+All bins are stored in the same directory, in the same database.
+We do this so we can impose a size limit on the entire database. Each
+record is timestamped; when the cache reaches the maximum size, it
+starts removing the oldest records first to make room.
+	
+Cache access is asynchronous and multi-threaded, but you may only 
+access a cache from one process at a time.
+	
+The actual format of cached data files is "black box" and may change
+without notice. We do not intend for cached files to be used directly
+or for other purposes.
+    
+Properties:
+
+    :path:        Location of the root directory in which to store all cache
+	              bins and data.
+    :max_size_mb: Maximum size of the cache in megabytes. The size is taken
+                  as a goal; there is no guarantee that the size of the cache
+                  will always be less than this value, but the driver will do
+                  its best to comply.
+
+.. _leveldb: https://github.com/pelicanmapping/leveldb
diff --git a/docs/source/references/drivers/effects/glsky.rst b/docs/source/references/drivers/effects/glsky.rst
new file mode 100644
index 0000000..129a1cb
--- /dev/null
+++ b/docs/source/references/drivers/effects/glsky.rst
@@ -0,0 +1,14 @@
+GL Sky
+======
+Sky model that implements OpenGL Phong shading.
+
+Example usage::
+
+    <map>
+        <options>
+            <sky driver  = "gl"
+			     hours   = "0.0"
+                 ambient = "0.05" />
+
+   
+.. include:: sky_shared.rst
diff --git a/docs/source/references/drivers/effects/index.rst b/docs/source/references/drivers/effects/index.rst
new file mode 100644
index 0000000..3c09ced
--- /dev/null
+++ b/docs/source/references/drivers/effects/index.rst
@@ -0,0 +1,10 @@
+Effects Drivers
+===============
+Plugins that implement special effects.
+
+.. toctree::
+   :maxdepth: 1
+
+   glsky
+   simplesky
+   silverlining
\ No newline at end of file
diff --git a/docs/source/references/drivers/effects/silverlining.rst b/docs/source/references/drivers/effects/silverlining.rst
new file mode 100644
index 0000000..2c30064
--- /dev/null
+++ b/docs/source/references/drivers/effects/silverlining.rst
@@ -0,0 +1,29 @@
+SilverLining Sky
+================
+Sky model that uses the SilverLining SDK from SunDog Software.
+
+SilverLining SDK requires a valid license code. Without a username and
+license code, the SDK will run in "demo mode" and will display a dialog box
+every five minutes.
+
+Example usage::
+
+    <map>
+        <options>
+            <sky driver = "silverlining"
+                 hours               = "0.0"
+                 ambient             = "0.05"
+                 user                = "myname"
+                 license_code        = "mycode"
+                 clouds              = "false"
+                 clouds_max_altitude = "0.0 />
+
+Properties:
+
+	:user:                 User name the SilverLining SDK license
+	:license_code:         License code the SilverLining SDK
+	:clouds:               Whether to render a local clouds layer
+	:clouds_max_altitude:  Maximumum camera altitude at which to start rendering
+	                       the clouds layer
+   
+.. include:: sky_shared.rst
diff --git a/docs/source/references/drivers/effects/simplesky.rst b/docs/source/references/drivers/effects/simplesky.rst
new file mode 100644
index 0000000..3d5b67d
--- /dev/null
+++ b/docs/source/references/drivers/effects/simplesky.rst
@@ -0,0 +1,24 @@
+Simple Sky
+==========
+Sky model that implements atmospheric scattering and lighting according to the
+Sam O'Neil GPU Gems article.
+
+Example usage::
+
+    <map>
+        <options>
+            <sky driver               = "simple"
+			     hours                = "0.0"
+                 ambient              = "0.05"
+				 atmospheric_lighting = "true" 
+				 exposure             = "3.0"  />
+
+Properties:
+
+    :atmospheric_lighting: Whether to apply the atmospheric scattering model to the scene
+	                       under the Sky node. If you set this to false, you will get 
+						   basic Phong lighting instead.
+    :exposure:             Exposure level to apply to the scattering model, which simulates
+	                       the wash-out effect of viewing terrain through the atmosphere.
+   
+.. include:: sky_shared.rst
diff --git a/docs/source/references/drivers/effects/sky_shared.rst b/docs/source/references/drivers/effects/sky_shared.rst
new file mode 100644
index 0000000..3415009
--- /dev/null
+++ b/docs/source/references/drivers/effects/sky_shared.rst
@@ -0,0 +1,4 @@
+Common Properties:
+
+    :hours:     Time of day; UTC hours [0..24]
+    :ambient:   Minimum ambient lighting level [0..1] to apply to dark areas of the terrain
diff --git a/docs/source/references/drivers/feature/wfs.rst b/docs/source/references/drivers/feature/wfs.rst
index c650f68..5725360 100644
--- a/docs/source/references/drivers/feature/wfs.rst
+++ b/docs/source/references/drivers/feature/wfs.rst
@@ -18,6 +18,8 @@ Properties:
     :typename:        WFS type name to access (i.e., the layer)
     :outputformat:    Format to return from the service; ``json`` or ``gml``
     :maxfeatures:     Maximum number of features to return for a query
+    :request_buffer:  The number of map units to buffer bounding box requests with to ensure that enough data is returned.
+                      This is useful when rendering buffered lines using the AGGLite driver.         
 
 
 .. _Web Feature Service:    http://en.wikipedia.org/wiki/Web_Feature_Service
diff --git a/docs/source/references/drivers/index.rst b/docs/source/references/drivers/index.rst
index fd0dbf9..670b14e 100644
--- a/docs/source/references/drivers/index.rst
+++ b/docs/source/references/drivers/index.rst
@@ -11,4 +11,5 @@ resource within osgEarth.
    model/index
    feature/index
    terrain/index
-   loaders/index
+   effects/index
+   cache/index
diff --git a/docs/source/references/drivers/model/feature_model_shared_props.rst b/docs/source/references/drivers/model/feature_model_shared_props.rst
index 3424669..70470b5 100644
--- a/docs/source/references/drivers/model/feature_model_shared_props.rst
+++ b/docs/source/references/drivers/model/feature_model_shared_props.rst
@@ -12,3 +12,4 @@ to those above):
     :lighting:              Whether to override and set the lighting mode on this layer (t/f)
     :max_granularity:       Anglular threshold at which to subdivide lines on a globe (degrees)
     :shader_policy:         Options for shader generation (see: `Shader Policy`_)
+	:use_texture_arrays:    Whether to use texture arrays for wall and roof skins if you're card supports them.  (default is ``true``)
diff --git a/docs/source/references/drivers/model/simple.rst b/docs/source/references/drivers/model/simple.rst
index abce08a..f23b09f 100644
--- a/docs/source/references/drivers/model/simple.rst
+++ b/docs/source/references/drivers/model/simple.rst
@@ -15,6 +15,7 @@ Properties:
     :url:       External model to load
     :location:  Map coordinates at which to place the model. SRS is that of
                 the containing map.
+    :paged:     If true, the model will be paged in when the camera is within the max range of the location.  If false the model is loaded immediately.
 
 Also see:
 
diff --git a/docs/source/references/drivers/tile/colorramp.rst b/docs/source/references/drivers/tile/colorramp.rst
new file mode 100644
index 0000000..04d9589
--- /dev/null
+++ b/docs/source/references/drivers/tile/colorramp.rst
@@ -0,0 +1,35 @@
+Color Ramp
+==========================================
+The Color Ramp plugin uses an underlying heightfield in addition to a color ramp
+file to generate RGBA images from single band datasets such as elevation or temperature.
+
+Example usage::
+
+    <image name="color ramp" driver="colorramp">
+        <elevation name="readymap_elevation" driver="tms">
+            <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
+        </elevation>
+        <ramp>..\data\colorramps\elevation.clr</ramp>
+    </image>
+
+Ramp files:
+
+A file that defines how values match to colors.  Each line should contain
+a value and the RGB color it's mapped to with values in the range 0-255
+
+For example::
+
+    0 255 0 0
+    1000 255 255 0
+    5000 0 0 255
+
+    
+Properties:
+
+    :elevation:         Definition of an elevation layer to sample.
+
+    :ramp:              Path to the ramp file to use to color the layer.
+    
+Also see:
+
+    ``colorramp.earth`` sample in the repo ``tests`` folder.
\ No newline at end of file
diff --git a/docs/source/references/drivers/tile/index.rst b/docs/source/references/drivers/tile/index.rst
index cc018bb..253c6ee 100644
--- a/docs/source/references/drivers/tile/index.rst
+++ b/docs/source/references/drivers/tile/index.rst
@@ -9,8 +9,10 @@ terrain engine. It can produce image tiles, elevation grid tiles, or both.
    agglite
    arcgis
    arcgis_map_cache
+   colorramp
    debug
    gdal
+   mbtiles
    noise
    osg
    tilecache
diff --git a/docs/source/references/drivers/tile/mbtiles.rst b/docs/source/references/drivers/tile/mbtiles.rst
new file mode 100644
index 0000000..34fef86
--- /dev/null
+++ b/docs/source/references/drivers/tile/mbtiles.rst
@@ -0,0 +1,26 @@
+MBTiles
+=========================
+This plugin reads data from an `MBTiles`_ file, which is an SQLite3 database that contains all the tile data in a single table.  This driver requires that you build osgEarth with SQLite3 support.
+
+Example usage::
+
+    <image name="haiti" driver="mbtiles">
+        <filename>../data/haiti-terrain-grey.mbtiles</filename>
+		<format>jpg</format>
+    </image>
+    
+Properties:
+
+    :filename:          The filename of the MBTiles file
+    :format:            The format of the imagery in the MBTiles file (jpeg, png, etc)
+    :compute_levels:    Whether or not to automatically compute the valid levels of the MBTiles file.
+                        By default this is true and will scan the table to determine the min/max.
+                        This can take time when first loading the file so if you know the levels of your file 
+                        up front you can set this to false and just use the min_level max_level settings of the tile source.
+       
+Also see:
+
+    ``mb_tiles.earth`` sample in the repo ``tests`` folder
+    
+
+.. _MBTiles:  https://www.mapbox.com/developers/mbtiles/
diff --git a/docs/source/references/drivers/tile/vpb.rst b/docs/source/references/drivers/tile/vpb.rst
index 1d016ef..046654b 100644
--- a/docs/source/references/drivers/tile/vpb.rst
+++ b/docs/source/references/drivers/tile/vpb.rst
@@ -13,6 +13,7 @@ Example usage::
 
     <image driver="vpb">
         <url>http://www.openscenegraph.org/data/earth_bayarea/earth.ive</url>
+        <profile>global-geodetic</profile>
         <primary_split_level>5</primary_split_level>
         <secondary_split_level>11</secondary_split_level>
         <directory_structure>nested</directory_structure>
diff --git a/docs/source/references/earthfile.rst b/docs/source/references/earthfile.rst
index ddd9617..154e010 100644
--- a/docs/source/references/earthfile.rst
+++ b/docs/source/references/earthfile.rst
@@ -44,10 +44,10 @@ the entire map.
     <map>
         <options lighting                 = "true"
                  elevation_interpolation  = "bilinear"
-                 elevation_tile_size      = "8"
+                 elevation_tile_size      = "17"
                  overlay_texture_size     = "4096"
                  overlay_blending         = "true"
-                 overlay_resolution_ratio = "5.0" >
+                 overlay_resolution_ratio = "3.0" >
 
             <:ref:`profile <Profile>`>
             <:ref:`proxy <ProxySettings>`>
@@ -67,9 +67,8 @@ the entire map.
 |                          |   :bilinear:    Linear interpolation in both axes                  |
 |                          |   :triangulate: Interp follows triangle slope                      |
 +--------------------------+--------------------------------------------------------------------+
-| elevation_tile_size      | Forces the number of posts to render for each terrain tile. By     |
-|                          | default, the engine will use the size of the largest available     |
-|                          | source.                                                            |
+| elevation_tile_size      | Sets the number of samples to render for each terrain tile         |
+|                          | (width and height)                                                 |
 +--------------------------+--------------------------------------------------------------------+
 | overlay_texture_size     | Sets the texture size to use for draping (projective texturing)    |
 +--------------------------+--------------------------------------------------------------------+
@@ -79,7 +78,10 @@ the entire map.
 | overlay_resolution_ratio | For draped geometry, the ratio of the resolution of the projective |
 |                          | texture near the camera versus the resolution far from the camera. |
 |                          | Increase the value to improve appearance close to the camera while |
-|                          | sacrificing appearance of farther geometry.                        |
+|                          | sacrificing appearance of farther geometry. NOTE: If you're using  |
+|                          | a camera manipulator that support roll, you will probably need to  |
+|                          | set this to 1.0; otherwise you will get draping artifacts! This is |
+|                          | a known issue.                                                     |
 +--------------------------+--------------------------------------------------------------------+
 
 
@@ -95,14 +97,14 @@ These options control the rendering of the terrain surface.
         <options>
             <terrain driver                = "mp"
                      lighting              = "true"
-                     skirt_ratio           = "0.05"
                      min_tile_range_factor = "6"
                      min_lod               = "0"
                      max_lod               = "23"
                      first_lod             = "0"
                      cluster_culling       = "true"
                      mercator_fast_path    = "true"
-                     blending              = "false" >
+                     blending              = "false"
+                     color                 = "#ffffffff" >
 
 +-----------------------+--------------------------------------------------------------------+
 | Property              | Description                                                        |
@@ -114,11 +116,10 @@ These options control the rendering of the terrain surface.
 | lighting              | Whether to enable GL_LIGHTING on the terrain. By default this is   |
 |                       | unset, meaning it will inherit the lighting mode of the scene.     |
 +-----------------------+--------------------------------------------------------------------+
-| skirt_ratio           | Ratio of the height of a terrain tile "skirt" to the extent of the |
-|                       | tile. The *skirt* is geometry that hides gaps between adjacent     |
-|                       | tiles with different levels of detail.                             |
-+-----------------------+--------------------------------------------------------------------+
-| min_tile_range_factor | Ratio of a tile's extent to its visibility range.                  |
+| min_tile_range_factor | Determines how close you need to be to a terrain tile for it to    |
+|                       | display. The value is the ratio of a tile's extent to its          |
+|                       | For example, if a tile is 10km is radius, and the MTRF=6, then the |
+|                       | tile will become visible at a range of about 60km.                 |
 +-----------------------+--------------------------------------------------------------------+
 | min_lod               | The lowest level of detail that the terrain is guaranteed to       |
 |                       | display, even if no source data is available at that LOD. The      |
@@ -143,6 +144,8 @@ These options control the rendering of the terrain surface.
 |                       | underlying geometry. This lets you make the globe partially        |
 |                       | transparent. This is handy for seeing underground objects.         |
 +-----------------------+--------------------------------------------------------------------+
+| color                 | Color on the underlying (untextures) terrain.                      |
++-----------------------+--------------------------------------------------------------------+
 
 
 
@@ -170,7 +173,8 @@ An *image layer* is a raster image overlaid on the map's geometry.
                shared         = "false"
                feather_pixels = "false"
                min_filter     = "LINEAR"
-               mag_filter     = "LINEAR" >
+               mag_filter     = "LINEAR" 
+               texture_compression = "auto" >
 
             <:ref:`cache_policy <CachePolicy>`>
             <:ref:`color_filters <ColorFilterChain>`>
@@ -208,6 +212,12 @@ An *image layer* is a raster image overlaid on the map's geometry.
 | max_resolution        | Maximum source data resolution at which to draw tiles. Value is    |
 |                       | units per pixel, in the native units of the source data.           |
 +-----------------------+--------------------------------------------------------------------+
+| max_data_level        | Maximum level of detail at which new source data is available to   |
+|                       | this image layer. Usually the driver will report this information. |
+|                       | But you may wish to limit it yourself. This is especially true for |
+|                       | some drivers that have no resolution limit, like a rasterization   |
+|                       | driver (agglite) for example.                                      |
++-----------------------+--------------------------------------------------------------------+
 | enabled               | Whether to include this layer in the map. You can only set this at |
 |                       | load time; it is just an easy way of "commenting out" a layer in   |
 |                       | the earth file.                                                    |
@@ -228,6 +238,8 @@ An *image layer* is a raster image overlaid on the map's geometry.
 | mag_filter            | OpenGL texture magnification filter to use for this layer.         |
 |                       | Options are the same as for ``min_filter`` above.                  |
 +-----------------------+--------------------------------------------------------------------+
+| texture_compression   | "auto" to compress textures on the GPU; "none" to disable.         |
++-----------------------+--------------------------------------------------------------------+
 
 
 .. _ElevationLayer:
@@ -247,7 +259,8 @@ will composite all elevation data into a single heightmap and use that to build
                    min_resolution = "100.0"
                    max_resolution = "0.0"
                    enabled        = "true"
-                   offset         = "false" >
+                   offset         = "false"
+                   nodata_policy  = "interpolate" >
 
 
 +-----------------------+--------------------------------------------------------------------+
@@ -276,6 +289,10 @@ will composite all elevation data into a single heightmap and use that to build
 | offset                | Indicates that the height values in this layer are relative        |
 |                       | offsets rather than true terrain height samples.                   |
 +-----------------------+--------------------------------------------------------------------+
+| nodata_policy         | What to do with "no data" values. Default is "interpolate" which   |
+|                       | will interpolate neighboring values to fill holes. Set it to "msl" |
+|                       | to replace "no data" samples with the current sea level value.     |
++-----------------------+--------------------------------------------------------------------+
 
 
 .. _ModelLayer:
@@ -287,8 +304,10 @@ A *Model Layer* renders non-terrain data, like vector features or external 3D mo
 .. parsed-literal::
 
     <map>
-        <model name   = "my model layer"
-               driver = "feature_geom"
+        <model name    = "my model layer"
+               driver  = "feature_geom"
+               enabled = true
+               visible = true >
 
 
 +-----------------------+--------------------------------------------------------------------+
@@ -307,6 +326,34 @@ A *Model Layer* renders non-terrain data, like vector features or external 3D mo
 | visible               | Whether to draw the layer.                                         |
 +-----------------------+--------------------------------------------------------------------+
 
+The Model Layer also allows you to define a cut-out mask. The terrain engine will cut a hole
+in the terrain surface matching a *boundary geometry* that you supply. You can use the tool
+*osgearth_boundarygen* to create such a geometry.
+
+This is useful if you have an external terrain model and you want to insert it into the 
+osgEarth terrain. The model MUST be in the same coordinate system as the terrain.
+
+.. parsed-literal::
+
+    <map>
+        <model ...>
+            <mask driver="feature">
+                <features driver="ogr">
+                    ...
+
+The Mask can take any polygon feature as input. You can specify masking geometry inline
+by using an inline geometry:
+
+.. parsed-literal::
+    
+    <features ...>
+        <geometry>POLYGON((120 42 0, 121 41 0, 121 40 0))</geometry>
+
+Or you use a shapefile or other feature source, in which case osgEarth will use the 
+*first* feature in the source.
+
+Refer to the *mask.earth* sample for an example.
+
 
 
 .. _Profile:
diff --git a/docs/source/references/envvars.rst b/docs/source/references/envvars.rst
index 3002365..be11509 100644
--- a/docs/source/references/envvars.rst
+++ b/docs/source/references/envvars.rst
@@ -7,6 +7,16 @@ Caching:
     :OSGEARTH_CACHE_PATH:   Sets up a cache at the specified folder (path)
     :OSGEARTH_CACHE_ONLY:   Directs osgEarth to ONLY use the cache and no data sources (set to 1)
     :OSGEARTH_NO_CACHE:     Directs osgEarth to NEVER use the cache (set to 1)
+    :OSGEARTH_CACHE_DRIVER: Sets the name of the plugin to use for caching (default is "filesystem")
+
+Threading/Performance:
+
+    :OSG_NUM_DATABASE_THREADS:      Sets the total number of threads that the OSG DatabasePager
+                                    will use to load terrain tiles and feature data tiles.
+    :OSG_NUM_HTTP_DATABASE_THREADS: Sets the number of threads in the Pager's thread pool (see
+                                    above) that should be used for "high-latency" operations.
+                                    (Usually this means operations that do not read data from
+                                    the cache, or are expected to take more time than average.)
 
 Debugging:
 
@@ -14,27 +24,26 @@ Debugging:
                                 console output. Values are ``DEBUG``, ``INFO``, ``NOTICE``,
                                 and ``WARN``. Default is ``NOTICE``. (This is distinct from
                                 OSG's notify level.)
+    :OSGEARTH_MP_PROFILE:       Dumps verbose profiling and timing data about the terrain engine's
+                                tile generator to the console.
     :OSGEARTH_MERGE_SHADERS:    Consolidate all shaders within a single shader program; this
                                 is required for GLES (mobile devices) and is therefore useful
-                                for testing. It also makes shader dumps more readable. (set to 1)
-    :OSGEARTH_DUMP_SHADERS:     Prints composited shader code to the console (set to 1).
-                                Setting this will also activate shader merging (above).
+                                for testing. (set to 1).
+    :OSGEARTH_DUMP_SHADERS:     Prints composed shader programs to the console (set to 1).
 
 Rendering:
 
-    :OSGEARTH_TERRAIN_ENGINE:     Sets the terrain engine driver to use; Default is ``mp``;
-                                  legacy options are ``quadtree`` or ``osgterrain``
     :OSGEARTH_DEFAULT_FONT:       Name of the default font to use for text symbology
     :OSGEARTH_MIN_STAR_MAGNITUDE: Smallest star magnitude to use in SkyNode
     
 Networking:
 
     :OSGEARTH_HTTP_DEBUG:                  Prints HTTP debugging messages (set to 1)
-    :OSGEARTH_SIMULATE_HTTP_RESPONSE_CODE: Simulates HTTP errors (set to HTTP response code)
     :OSGEARTH_HTTP_TIMEOUT:                Sets an HTTP timeout (seconds)
     :OSG_CURL_PROXY:                       Sets a proxy server for HTTP requests (string)
     :OSG_CURL_PROXYPORT:                   Sets a proxy port for HTTP proxy server (integer)
     :OSGEARTH_PROXYAUTH:                   Sets proxy authentication information (username:password)
+    :OSGEARTH_SIMULATE_HTTP_RESPONSE_CODE: Simulates HTTP errors (for debugging; set to HTTP response code)
 
 Misc:
 
@@ -42,11 +51,3 @@ Misc:
                                 create a PBUFFER-based graphics context for collecting
                                 GL support information. (set to 1)
 
-Performance:
-
-    :OSG_NUM_DATABASE_THREADS:      Sets the total number of threads that the OSG DatabasePager
-                                    will use to load terrain tiles and feature data tiles.
-    :OSG_NUM_HTTP_DATABASE_THREADS: Sets the number of threads in the Pager's thread pool (see
-                                    above) that should be used for "high-latency" operations.
-                                    (Usually this means operations that download data from the
-                                    network, hence the "HTTP" in the variable name.)
diff --git a/docs/source/references/symbology.rst b/docs/source/references/symbology.rst
index 7be2503..f342512 100644
--- a/docs/source/references/symbology.rst
+++ b/docs/source/references/symbology.rst
@@ -92,6 +92,13 @@ control the color and style of the vector data.
 |                       | Number of times to repeat each bit in |                            |
 |                       | the stippling pattern                 |                            |
 +-----------------------+---------------------------------------+----------------------------+
+| stroke-crease-angle   | When outlining extruded polygons,     | float degrees (0.0)        |
+|                       | only draw a post outline if the angle |                            |
+|                       | between the adjoining faces exceeds   |                            |
+|                       | this value. This has the effect of    |                            |
+|                       | only outlining corners that are       |                            |
+|                       | sufficiently "sharp".                 |                            |
++-----------------------+---------------------------------------+----------------------------+
 | point-fill            | Fill color for a point.               | HTML color                 |
 +-----------------------+---------------------------------------+----------------------------+
 | point-size            | Size for a GL point geometry          | float (1.0)                |
diff --git a/docs/source/startup.rst b/docs/source/startup.rst
index 57a813c..9e5641a 100644
--- a/docs/source/startup.rst
+++ b/docs/source/startup.rst
@@ -5,8 +5,7 @@ osgEarth is a cross-platform library. It uses the CMake_ build system.
 You will need **version 2.8** or newer. 
 (This is the same build system that OpenSceneGraph_ uses.)
 
-    NOTE: To build osgEarth for iOS see :doc:`ios`
-    
+    NOTE: To build osgEarth for **iOS** see :doc:`ios`
 
 Get the Source Code
 -------------------
@@ -30,23 +29,39 @@ Get the Source Code
 Get the Dependencies
 --------------------
 
-The following are **required dependencies**:
+**Required dependencies**:
 
     * OpenSceneGraph_ 3.0.1 or later, with the CURL plugin enabled.
     * GDAL_ 1.6 or later - Geospatial Data Abstraction Layer
     * CURL_ - HTTP transfer library (comes with OpenSceneGraph_ 3rd party library distros)
     
-These are the **optional depedencies**. osgEarth will compile without them,
-but some functionality will be missing:
+**Optional depedencies**: osgEarth will compile without them, but some functionality
+will be missing:
 
     * GEOS_ 3.2.0 or later - C++ library for topological operations.
       osgEarth uses GEOS to perform various geometry operations like buffering and intersections.
       If you plan to use vector feature data in osgEarth, you probably want this.
     
     * Minizip_ - ZIP file extractor; include this if you want to read KMZ files.
+      
+    * QT_ - Cross-platform UI framework. Point the ``QT_QMAKE_EXECUTABLE`` CMake variable
+      to the ``qmake.exe`` you want to use and CMake will populate all the other QT variables.
+
+    * LevelDB_ - Google's embedded key/value store. Include this if you want to build
+      osgEarth's optional "leveldb" cache driver.
+
+    * Duktape_ - Embedded JavaScript engine. Include this is you want to embed JavaScript
+      code in your earth files to control feature styling.
     
-    * V8_ - Google's JavaScript engine. Include this if you want to embed JavaScript code
-      in your earth files.
+**Deprecated dependencies**: osgEarth can still use these, but they will probably go away
+in the future:
+
+    * V8_ - Google's JavaScript engine. Include this if you're a Windows user and you want
+      to embed JavaScript code in your earth files. We recommend you use Duktape instead.
+      
+    * JavaScriptCore_ - Apple's JavaScript engine. Include this if you're an OSX or IOS user
+      and you want to embed JavaScript code in your earth files. We receommend you use
+      Duktape instead.
       
 **Optional: get pre-built dependencies**
 
@@ -98,4 +113,5 @@ Here are a few tips.
 .. _AlphaPixel:     http://openscenegraph.alphapixel.com/osg/downloads/openscenegraph-third-party-library-downloads
 .. _Mike Weiblen:   http://mew.cx/osg/
 .. _the forum:      http://forum.osgearth.org
-
+.. _LevelDB:        https://github.com/pelicanmapping/leveldb
+.. _Duktape:        http://duktape.org
diff --git a/docs/source/user/caching.rst b/docs/source/user/caching.rst
index 5f269d5..f3bb70a 100644
--- a/docs/source/user/caching.rst
+++ b/docs/source/user/caching.rst
@@ -81,18 +81,51 @@ but you can use the ``max_age`` property to tell it how long to treat an object
 Specify the maximum age in seconds. The example above will expire objects that are more
 than one hour old.
 
-
 Environment Variables
 ---------------------
 Sometimes it's more convenient to control caching from the environment,
-especially during development. Here are some environment variables you can use.
+especially during development.
+
+These variables override the cache policy properties:
 
-    :OSGEARTH_CACHE_PATH:    Root folder for a file system cache.
     :OSGEARTH_NO_CACHE:      Enables a ``no_cache`` policy for any osgEarth map. (set to 1)
     :OSGEARTH_CACHE_ONLY:    Enabled a ``cache_only`` policy for any osgEarth map. (set to 1)
     :OSGEARTH_CACHE_MAX_AGE: Set the cache to expire objects more than this number of seconds old.
 
-**Note**: environment variables *override* the cache settings in an *earth file*!
+These are not part of the cache policy, but instead control a particular cache implementation.
+
+    :OSGEARTH_CACHE_PATH:    Root folder for a cache. Setting this will enable caching for
+                             whichever cache driver is active.
+    :OSGEARTH_CACHE_DRIVER:  Set the name of the cache driver to use, e.g. ``filesystem`` or
+                             ``leveldb``.
+
+**Note**: environment variables *override* the cache settings in an *earth file*! See below.
+
+
+Precedence of Cache Policy Settings
+-----------------------------------
+Since you can set caching policies in various places, we need to establish
+precendence. Here are the rules.
+
+- **Map settings**. This is a cache policy set in the ``Map`` object on in the 
+  ``<map><options>`` block in an earth file. This sets the default cache policy for every
+  layer in the map. This is the weakest policy setting; it can be overridden by any of
+  the settings below.
+
+- **Layer settings**. This is a cache policy set in a ``ImageLayer`` or ``ElevationLayer``
+  object (or in the ``<map><image>`` or ``<map><elevation>`` block in an earth file).
+  This will override the top-level setting in the Map, but it will NOT override a cache
+  policy set by environment (see below). (It is also the ONLY way to override a driver
+  policy hint (see below), but it is rare that you every need to do this.)
+
+- **Environment variables**. These are read and stored in the Registry's
+  ``overrideCachePolicy`` and they will override the settings in the map or in a layer.
+  They will however NOT override driver policy hints.
+
+- **Driver policy hints**. Sometimes a driver will tell osgEarth to *never* cache
+  data that it provides, and osgEarth obeys. The only way to override this is to
+  expressly set a caching policy on the layer itself. (You will rarely have to 
+  worry about this.)
 
 
 Seeding the Cache
diff --git a/docs/source/user/features.rst b/docs/source/user/features.rst
index bda3900..450e3f7 100644
--- a/docs/source/user/features.rst
+++ b/docs/source/user/features.rst
@@ -358,7 +358,7 @@ Each level describes a level of detail. This is a camera range (between ``min_ra
 and ``max_range``) at which tiles in this level of detail are rendered. But how
 big is each tile? This is calculated based on the *tile range factor*.
 
-The ``tile_range_factor`` determines the size of a tile, based on the ``max_range``
+The ``tile_size_factor`` determines the size of a tile, based on the ``max_range``
 of the LOD. The tile range factor is the multiple of a tile's radius at which the
 LOD's ``max_range`` takes effect. In other words::
 
@@ -375,46 +375,3 @@ sending large batches of similar geometry to the graphics card, tweaking the
 tile size can help with performance and throughput. Unfortunately there's no way
 for osgEarth to know exactly what the "best" tile size will be in advance;
 so, you have the opportunity to tweak using this setting.
-
-Multiple Levels and Using Selectors
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You can have any number of levels -- but keep in mind that unlike the terrain
-imagery, feature data does NOT form a quadtree in which the higher LODs replace
-the lower ones. Rather, feature levels are independent of one another. So if you
-want to draw more detail as you zoom in closer, you have to use selectors to
-decide what you want to draw at each step.
-
-Here's an example. Say we're drawing roads.
-We have a shapefile in which the road type is stored in an attribute called ``roadtype`` ::
-
-   <layout>
-       <tile_size_factor>15.0</tile_size_factor>
-       <crop_features>true</crop_features>
-       <level name="highway" max_range="100000">
-          <selector class="highway">
-             <query>
-                <expr>roadtype = 'A'</expr>
-             </query>
-          </selector>
-       </level>
-       <level name="street" max_range="10000">
-          <selector class="street">
-             <query>
-                <expr>roadtype = 'B'</expr>
-             </query>
-          </selector>
-       </level>
-   </layout>
-
-   <styles>
-      highway {
-          stroke:       #ffff00;
-          stroke-width: 2.0;
-      }
-      street {
-          stroke:       #ffffff7f;
-          stroke-width: 1.0;
-      }
-   </styles>
-   
diff --git a/docs/source/user/tools.rst b/docs/source/user/tools.rst
index dffb64d..61c9551 100644
--- a/docs/source/user/tools.rst
+++ b/docs/source/user/tools.rst
@@ -81,7 +81,13 @@ The most common usage of osgearth_cache is to populate a cache in a non-interact
 | ``--estimate``                      | Print out an estimation of the number of tiles, disk space and     |
 |                                     | time it will take to perform this seed operation                   |
 +-------------------------------------+--------------------------------------------------------------------+
-| ``--threads``                       |The number of threads to use for the seed operation (default=1)     |
+| ``--mp``                            | Use multiprocessing to process the tiles.  Useful for GDAL         |
+|                                     | sources as this avoids the global GDAL lock                        |
++-------------------------------------+--------------------------------------------------------------------+
+| ``--mt``                            | Use multithreading to process the tiles.                           |
++-------------------------------------+--------------------------------------------------------------------+
+| ``--concurrency``                   | The number of threads or proceses to use if --mp or --mt           |
+|                                     | are provided                                                       | 
 +-------------------------------------+--------------------------------------------------------------------+
 | ``--min-level level``               | Lowest LOD level to seed (default=0)                               |
 +-------------------------------------+--------------------------------------------------------------------+
@@ -120,7 +126,11 @@ osgearth_package creates a redistributable `TMS`_ based package from an earth fi
 | ``--bounds xmin ymin xmax ymax``   | bounds to package (in map coordinates; default=entire map)         |
 |                                    | You can provide multiple bounds                                    |
 +------------------------------------+--------------------------------------------------------------------+
-| ``--max-level level``              | max LOD level for tiles (all layers; default=inf)                  |
+| ``--max-level level``              | max LOD level for tiles (all layers; default=5). Note: you can set |
+|                                    | this to a large number to get all available data (e.g., 99). This  |
+|                                    | works fine for files (like a GeoTIFF). But some data sources do    |
+|                                    | not report (or have) a maximum data level, so it's better to       |
+|                                    | specify a specific maximum.                                        |
 +------------------------------------+--------------------------------------------------------------------+
 | ``--out-earth earthfile``          | export an earth file referencing the new repo                      |
 +------------------------------------+--------------------------------------------------------------------+
@@ -136,6 +146,14 @@ osgearth_package creates a redistributable `TMS`_ based package from an earth fi
 | ``--db-options``                   | db options string to pass to the image writer                      |
 |                                    | in quotes (e.g., "JPEG_QUALITY 60")                                |
 +------------------------------------+--------------------------------------------------------------------+
+| ``--mp``                           | Use multiprocessing to process the tiles.  Useful for GDAL         |
+|                                    | sources as this avoids the global GDAL lock                        |
++------------------------------------+--------------------------------------------------------------------+
+| ``--mt``                           | Use multithreading to process the tiles.                           |
++------------------------------------+--------------------------------------------------------------------+
+| ``--concurrency``                  | The number of threads or proceses to use if --mp or --mt           |
+|                                    | are provided                                                       | 
++------------------------------------+--------------------------------------------------------------------+
 
 osgearth_tfs
 ------------
@@ -207,18 +225,18 @@ a specified higher level of detail.  For example, you can specify a max level of
 
 
 osgearth_boundarygen
------------------
+--------------------
 osgearth_boundarygen generates boundary geometry that you can use with an osgEarth <mask> layer in order to 
 stich an external model into the terrain.
 
 **Sample Usage**
 ::
-    osgearth_boundarygen model_file
+    osgearth_boundarygen model_file [options]
 
 +----------------------------------+--------------------------------------------------------------------+
 | Argument                         | Description                                                        |
 +==================================+====================================================================+
-| ``--out file_name``              | output file for boundary geometry( default is boundary.txt)        |
+| ``--out file_name``              | output file for boundary geometry (default is boundary.txt)        |
 +----------------------------------+--------------------------------------------------------------------+
 | ``--no-geocentric``              | Skip geocentric reprojection (for flat databases)                  |
 +----------------------------------+--------------------------------------------------------------------+
@@ -228,6 +246,10 @@ stich an external model into the terrain.
 +----------------------------------+--------------------------------------------------------------------+
 | ``--view``                       | show result in 3D window                                           |
 +----------------------------------+--------------------------------------------------------------------+
+| ``--tolerance`` N                | vertices less than this distance apart will be coalesced (0.005)   |
++----------------------------------+--------------------------------------------------------------------+
+| ``--precision`` N                | output coordinates will have this many significant digits (12)     |
++----------------------------------+--------------------------------------------------------------------+
 
 
 
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 2ee8c2b..2c4b0b8 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -18,7 +18,7 @@ IF(NOT OSG_BUILD_PLATFORM_IPHONE AND NOT OSG_BUILD_PLATFORM_IPHONE_SIMULATOR AND
 ADD_SUBDIRECTORY( applications )
 ENDIF()
 
-IF (QT4_FOUND AND NOT ANDROID AND OSGEARTH_USE_QT)
+IF (Qt5Widgets_FOUND OR QT4_FOUND AND NOT ANDROID AND OSGEARTH_USE_QT)
     ADD_SUBDIRECTORY(osgEarthQt)
     SET_PROPERTY(TARGET osgEarthQt PROPERTY FOLDER "Libs")
 ENDIF()
diff --git a/src/applications/CMakeLists.txt b/src/applications/CMakeLists.txt
index 82ec977..7afa5df 100644
--- a/src/applications/CMakeLists.txt
+++ b/src/applications/CMakeLists.txt
@@ -37,7 +37,7 @@ ADD_SUBDIRECTORY(osgearth_backfill)
 ADD_SUBDIRECTORY(osgearth_overlayviewer)
 ADD_SUBDIRECTORY(osgearth_version)
 ADD_SUBDIRECTORY(osgearth_tileindex)
-IF (QT4_FOUND AND NOT ANDROID AND OSGEARTH_USE_QT)
+IF (Qt5Widgets_FOUND OR QT4_FOUND AND NOT ANDROID AND OSGEARTH_USE_QT)
     ADD_SUBDIRECTORY(osgearth_package_qt)
 ENDIF()
 
@@ -56,7 +56,7 @@ ADD_SUBDIRECTORY(osgearth_terrainprofile)
 ADD_SUBDIRECTORY(osgearth_map)
 ADD_SUBDIRECTORY(osgearth_annotation)
 ADD_SUBDIRECTORY(osgearth_tracks)
-
+ADD_SUBDIRECTORY(osgearth_transform)
 
 IF(NOT ${OPENSCENEGRAPH_VERSION} VERSION_LESS "2.9.6")
     ADD_SUBDIRECTORY(osgearth_featureeditor)
@@ -79,16 +79,17 @@ ADD_SUBDIRECTORY(osgearth_minimap)
 #ADD_SUBDIRECTORY(osgearth_detailtex)
 ADD_SUBDIRECTORY(osgearth_sharedlayer)
 ADD_SUBDIRECTORY(osgearth_terraineffects)
+ADD_SUBDIRECTORY(osgearth_mrt)
+ADD_SUBDIRECTORY(osgearth_fog)
+ADD_SUBDIRECTORY(osgearth_shadergen)
+ADD_SUBDIRECTORY(osgearth_atlas)
+ADD_SUBDIRECTORY(osgearth_conv)
+ADD_SUBDIRECTORY(osgearth_clipplane)
 
-IF(NOT ${OPENSCENEGRAPH_VERSION} VERSION_LESS "3.1.0")
-    ADD_SUBDIRECTORY(osgearth_shadow)
-ENDIF()
-
-IF (QT4_FOUND AND NOT ANDROID AND OSGEARTH_USE_QT)
-#    ADD_SUBDIRECTORY(osgearth_qt)
+IF (Qt5Widgets_FOUND OR QT4_FOUND AND NOT ANDROID AND OSGEARTH_USE_QT)
+    ADD_SUBDIRECTORY(osgearth_qt)
     ADD_SUBDIRECTORY(osgearth_qt_simple)
     ADD_SUBDIRECTORY(osgearth_qt_windows)
     ADD_SUBDIRECTORY(osgearth_demo)
 ENDIF()
 
-#ADD_SUBDIRECTORY(osgearth_silverlining)
diff --git a/src/applications/osgearth_annotation/osgearth_annotation.cpp b/src/applications/osgearth_annotation/osgearth_annotation.cpp
index 60676bb..96d0bb2 100644
--- a/src/applications/osgearth_annotation/osgearth_annotation.cpp
+++ b/src/applications/osgearth_annotation/osgearth_annotation.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -118,11 +118,6 @@ struct ToggleNodeHandler : public ControlEventHandler
 int
 main(int argc, char** argv)
 {
-    // The HighlightDecoration requires you to allocate stencil planes,
-    // and will yell at you if you don't. You have to do this prior to creating
-    // your Viewer.
-    osg::DisplaySettings::instance()->setMinimumNumStencilBits( 2 );
-
     osg::Group* root = new osg::Group();
 
     // try to load an earth file.
@@ -131,11 +126,11 @@ main(int argc, char** argv)
     osgViewer::Viewer viewer(arguments);
     viewer.setCameraManipulator( new EarthManipulator() );
 
-    viewer.setCameraManipulator( new EarthManipulator() );
-
     // load an earth file and parse demo arguments
     osg::Node* node = MapNodeHelper().load(arguments, &viewer);
-    if ( !node ) return usage(argv);
+    if ( !node )
+        return usage(argv);
+
     root->addChild( node );
 
     // find the map node that we loaded.
@@ -152,7 +147,7 @@ main(int argc, char** argv)
     root->addChild( editorGroup );
     editorGroup->setNodeMask( 0 );
 
-    HBox* box = ControlCanvas::get(&viewer)->addControl( new HBox() );
+    HBox* box = ControlCanvas::getOrCreate(&viewer)->addControl( new HBox() );
     box->setChildSpacing( 5 );
     //Add a toggle button to toggle editing
     CheckBoxControl* editCheckbox = new CheckBoxControl( false );
@@ -184,9 +179,9 @@ main(int argc, char** argv)
         Style pin;
         pin.getOrCreate<IconSymbol>()->url()->setLiteral( "../data/placemark32.png" );
 
+        // bunch of pins:
         labelGroup->addChild( new PlaceNode(mapNode, GeoPoint(geoSRS, -74.00, 40.71), "New York"      , pin));
         labelGroup->addChild( new PlaceNode(mapNode, GeoPoint(geoSRS, -77.04, 38.85), "Washington, DC", pin));
-        //labelGroup->addChild( new PlaceNode(mapNode, GeoPoint(geoSRS, -87.65, 41.90), "Chicago"       , pin));
         labelGroup->addChild( new PlaceNode(mapNode, GeoPoint(geoSRS,-118.40, 33.93), "Los Angeles"   , pin));
         labelGroup->addChild( new PlaceNode(mapNode, GeoPoint(geoSRS, -71.03, 42.37), "Boston"        , pin));
         labelGroup->addChild( new PlaceNode(mapNode, GeoPoint(geoSRS,-157.93, 21.35), "Honolulu"      , pin));
@@ -195,12 +190,12 @@ main(int argc, char** argv)
         labelGroup->addChild( new PlaceNode(mapNode, GeoPoint(geoSRS, -80.28, 25.82), "Miami"         , pin));
         labelGroup->addChild( new PlaceNode(mapNode, GeoPoint(geoSRS,-117.17, 32.72), "San Diego"     , pin));
 
-        // test with an LOD, just for kicks:
+        // test with an LOD:
         osg::LOD* lod = new osg::LOD();
         lod->addChild( new PlaceNode(mapNode, GeoPoint(geoSRS, 14.68, 50.0), "Prague", pin), 0.0, 1e6);
         labelGroup->addChild( lod );
 
-
+        // absolute altitude:
         labelGroup->addChild( new PlaceNode(mapNode, GeoPoint(geoSRS, -87.65, 41.90, 1000, ALTMODE_ABSOLUTE), "Chicago"       , pin));
     }
 
@@ -271,39 +266,34 @@ main(int argc, char** argv)
 
     //--------------------------------------------------------------------
 
-    // A circle around New Orleans.
+    // Two circle segments around New Orleans.
     {
         Style circleStyle;
         circleStyle.getOrCreate<PolygonSymbol>()->fill()->color() = Color(Color::Cyan, 0.5);
-        //circleStyle.getOrCreate<LineSymbol>()->stroke()->color() = Color(Color::Cyan, 1.0);
-        //circleStyle.getOrCreate<LineSymbol>()->stroke()->width() = 6.0f;
-        //circleStyle.getOrCreate<ExtrusionSymbol>()->height() = 250000.0; // meters MSL
         circleStyle.getOrCreate<AltitudeSymbol>()->clamping() = AltitudeSymbol::CLAMP_TO_TERRAIN;
         circleStyle.getOrCreate<AltitudeSymbol>()->technique() = AltitudeSymbol::TECHNIQUE_DRAPE;
 
         CircleNode* circle = new CircleNode(
             mapNode,
             GeoPoint(geoSRS, -90.25, 29.98, 1000., ALTMODE_RELATIVE),
-            Linear(300, Units::KILOMETERS),
-            circleStyle, Angular(-45.0, Units::DEGREES), Angular(45.0, Units::DEGREES), true);
+            Distance(300, Units::KILOMETERS),
+            circleStyle, Angle(-45.0, Units::DEGREES), Angle(45.0, Units::DEGREES), true);
         annoGroup->addChild( circle );
 
         editorGroup->addChild( new CircleNodeEditor( circle ) );
     }
+
 	{
 		Style circleStyle;
 		circleStyle.getOrCreate<PolygonSymbol>()->fill()->color() = Color(Color::Red, 0.5);
-		//circleStyle.getOrCreate<LineSymbol>()->stroke()->color() = Color(Color::Red, 1.0);
-		//circleStyle.getOrCreate<LineSymbol>()->stroke()->width() = 6.0f;
-		//circleStyle.getOrCreate<ExtrusionSymbol>()->height() = 250000.0; // meters MSL
 		circleStyle.getOrCreate<AltitudeSymbol>()->clamping() = AltitudeSymbol::CLAMP_TO_TERRAIN;
 		circleStyle.getOrCreate<AltitudeSymbol>()->technique() = AltitudeSymbol::TECHNIQUE_DRAPE;
 
 		CircleNode* circle = new CircleNode(
 			mapNode,
 			GeoPoint(geoSRS, -90.25, 29.98, 1000., ALTMODE_RELATIVE),
-			Linear(300, Units::KILOMETERS),
-			circleStyle, Angular(45.0, Units::DEGREES), Angular(360.0 - 45.0, Units::DEGREES), true);
+			Distance(300, Units::KILOMETERS),
+			circleStyle, Angle(45.0, Units::DEGREES), Angle(360.0 - 45.0, Units::DEGREES), true);
 		annoGroup->addChild( circle );
 
 		editorGroup->addChild( new CircleNodeEditor( circle ) );
@@ -311,39 +301,43 @@ main(int argc, char** argv)
 
     //--------------------------------------------------------------------
 
-    // An draped ellipse around Miami.
+    // An extruded ellipse around Miami.
     {
         Style ellipseStyle;
         ellipseStyle.getOrCreate<PolygonSymbol>()->fill()->color() = Color(Color::Orange, 0.75);
-        //ellipseStyle.getOrCreate<LineSymbol>()->stroke()->color() = Color(Color::Orange, 0.75);
-        //ellipseStyle.getOrCreate<LineSymbol>()->stroke()->width() = 4.0f;
         ellipseStyle.getOrCreate<ExtrusionSymbol>()->height() = 250000.0; // meters MSL
         EllipseNode* ellipse = new EllipseNode(
             mapNode, 
             GeoPoint(geoSRS, -80.28, 25.82, 0.0, ALTMODE_RELATIVE),
-            Linear(500, Units::MILES),
-            Linear(100, Units::MILES),
-            Angular(0, Units::DEGREES),
-            ellipseStyle, Angular(45.0, Units::DEGREES), Angular(360.0 - 45.0, Units::DEGREES), true);
+            Distance(250, Units::MILES),
+            Distance(100, Units::MILES),
+            Angle   (0, Units::DEGREES),
+            ellipseStyle,
+            Angle(45.0, Units::DEGREES),
+            Angle(360.0 - 45.0, Units::DEGREES), 
+            true);
         annoGroup->addChild( ellipse );
         editorGroup->addChild( new EllipseNodeEditor( ellipse ) );
     }
 	{
 		Style ellipseStyle;
 		ellipseStyle.getOrCreate<PolygonSymbol>()->fill()->color() = Color(Color::Blue, 0.75);
-		//ellipseStyle.getOrCreate<LineSymbol>()->stroke()->color() = Color(Color::Blue, 0.75);
-		//ellipseStyle.getOrCreate<LineSymbol>()->stroke()->width() = 4.0f;
 		ellipseStyle.getOrCreate<ExtrusionSymbol>()->height() = 250000.0; // meters MSL
 		EllipseNode* ellipse = new EllipseNode(
 			mapNode, 
 			GeoPoint(geoSRS, -80.28, 25.82, 0.0, ALTMODE_RELATIVE),
-			Linear(500, Units::MILES),
-			Linear(100, Units::MILES),
-			Angular(0, Units::DEGREES),
-			ellipseStyle, Angular(-45.0, Units::DEGREES), Angular(45.0, Units::DEGREES), true);
+			Distance(250, Units::MILES),
+			Distance(100, Units::MILES),
+			Angle   (0, Units::DEGREES),
+			ellipseStyle, 
+            Angle(-40.0, Units::DEGREES), 
+            Angle(40.0, Units::DEGREES), 
+            true);
 		annoGroup->addChild( ellipse );
 		editorGroup->addChild( new EllipseNodeEditor( ellipse ) );
 	}
+    
+    //--------------------------------------------------------------------
 
     {
         // A rectangle around San Diego
@@ -354,15 +348,13 @@ main(int argc, char** argv)
         RectangleNode* rect = new RectangleNode(
             mapNode, 
             GeoPoint(geoSRS, -117.172, 32.721),
-            Linear(300, Units::KILOMETERS ),
-            Linear(600, Units::KILOMETERS ),
+            Distance(300, Units::KILOMETERS ),
+            Distance(600, Units::KILOMETERS ),
             rectStyle);
         annoGroup->addChild( rect );
 
         editorGroup->addChild( new RectangleNodeEditor( rect ) );
-    }
-
-    
+    }    
 
     //--------------------------------------------------------------------
 
@@ -389,7 +381,7 @@ main(int argc, char** argv)
 
     //--------------------------------------------------------------------
 
-    // an image overlay
+    // an image overlay.
     {
         ImageOverlay* imageOverlay = 0L;
         osg::Image* image = osgDB::readImageFile( "../data/USFLAG.TGA" );
diff --git a/src/applications/osgearth_atlas/CMakeLists.txt b/src/applications/osgearth_atlas/CMakeLists.txt
new file mode 100644
index 0000000..aadbb5e
--- /dev/null
+++ b/src/applications/osgearth_atlas/CMakeLists.txt
@@ -0,0 +1,7 @@
+INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} )
+
+SET(TARGET_LIBRARIES_VARS OSG_LIBRARY OSGDB_LIBRARY OSGUTIL_LIBRARY OSGVIEWER_LIBRARY OPENTHREADS_LIBRARY)
+
+SET(TARGET_SRC osgearth_atlas.cpp )
+
+SETUP_APPLICATION(osgearth_atlas)
\ No newline at end of file
diff --git a/src/applications/osgearth_atlas/osgearth_atlas.cpp b/src/applications/osgearth_atlas/osgearth_atlas.cpp
new file mode 100644
index 0000000..060d248
--- /dev/null
+++ b/src/applications/osgearth_atlas/osgearth_atlas.cpp
@@ -0,0 +1,289 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+#include <osgEarth/Notify>
+#include <osgEarth/XmlUtils>
+#include <osgEarth/ImageUtils>
+#include <osgEarthUtil/AtlasBuilder>
+#include <osgEarthSymbology/ResourceLibrary>
+#include <osgEarthSymbology/Skins>
+
+#include <osg/ArgumentParser>
+#include <osgDB/FileUtils>
+#include <osgDB/FileNameUtils>
+#include <osgDB/WriteFile>
+#include <osgDB/ReadFile>
+#include <osg/Geode>
+#include <osg/Geometry>
+#include <osg/Texture2DArray>
+#include <osgViewer/Viewer>
+#include <osgViewer/ViewerEventHandlers>
+#include <osgText/Text>
+
+#include <string>
+#include <set>
+
+#define LC "[atlas] "
+
+using namespace osgEarth;
+
+int
+usage(const char* msg, const char* name =0L)
+{
+    if ( msg )
+    {
+        OE_NOTICE << msg << std::endl;
+    }
+
+    if ( name )
+    {
+        OE_NOTICE
+            << "\n"
+            << name << " will compile an osgEarth resource catalog into a texture atlas."
+            << "\n"
+            << "\nUsage: " << name
+            << "\n"
+            << "\n      --build catalog.xml               : Build an atlas from the catalog"
+            << "\n        --size <x> <y>                  : Maximum size of atlas textures"
+            << "\n        --out-image <path>              : Output path for the atlas image (defaults to an OSGB file in"
+            << "\n                                          the working directory). The paths in the resulting catalog"
+            << "\n                                          file will point to this location using a relative path if possible."
+            << "\n        --aux <pattern> <r> <g> <b> <a> : Build an auxiliary atlas for files matching the pattern"
+            << "\n                                          \"filename_pattern.ext\", e.g., \"texture.jpg\" will match"
+            << "\n                                          \"texture_NML.jpg\" for pattern = \"NML\". The RGBA are the"
+            << "\n                                          default values to use when no match is found."
+            << "\n"
+            << "\n      --show  catalog.xml               : Display an atlas built with this tool"
+            << "\n        --layer <num>                   : Show layer <num> of the atlas (default = 0)"
+            << "\n        --labels                        : Label each atlas entry"
+            << "\n        --aux <pattern>                 : Show atlas matching this auxiliary file pattern"
+            << std::endl;
+    }
+
+    return 0;
+}
+
+//----------------------------------------------------------------------
+
+int
+build(osg::ArgumentParser& arguments)
+{
+    // the input resource catalog XML file:
+    std::string inCatalogPath;
+    if ( !arguments.read("--build", inCatalogPath) )
+        return usage("Missing required argument catalog file");
+
+    // the output texture atlas image path:
+    std::string inCatalogFile = osgDB::getSimpleFileName(inCatalogPath);
+
+    // check that the input file exists:
+    if ( !osgDB::fileExists(inCatalogPath) )
+        return usage("Input file not found");
+
+    // open the resource library.
+    osg::ref_ptr<osgEarth::Symbology::ResourceLibrary> lib =
+        new osgEarth::Symbology::ResourceLibrary("unnamed", inCatalogPath);
+
+    if ( !lib->initialize(0L) )
+        return usage("Error loading input catalog file");
+    
+    // build the atlas.
+    osgEarth::Util::AtlasBuilder        builder;
+    osgEarth::Util::AtlasBuilder::Atlas atlas;
+
+    // max x/y dimensions:
+    unsigned sizex, sizey;
+    if ( arguments.read("--size", sizex, sizey) )
+        builder.setSize( sizex, sizey );
+
+    // output location for image:
+    std::string outImageFile;
+    if ( !arguments.read("--out-image", outImageFile) )
+        outImageFile  = osgDB::getNameLessExtension(inCatalogFile) + "_atlas.osgb";
+        
+    // the output catalog file describing the texture atlas contents:
+    std::string outCatalogFile = osgDB::getSimpleFileName(outImageFile) + ".xml";
+
+    // auxiliary atlas patterns:
+    std::string pattern;
+    float r, g, b, a;
+    while(arguments.read("--aux", pattern, r, g, b, a))
+        builder.addAuxFilePattern(pattern, osg::Vec4f(r,g,b,a));
+
+    if ( !builder.build(lib.get(), outImageFile, atlas) )
+        return usage("Failed to build atlas");
+
+    // write the atlas images.
+    osgDB::writeImageFile(*atlas._images.begin()->get(), outImageFile);
+    OE_INFO << LC << "Wrote output image to \"" << outImageFile << "\"" << std::endl;
+    
+    // write any aux images.
+    const std::vector<std::string>& auxPatterns = builder.auxFilePatterns();
+    for(unsigned i=0; i<auxPatterns.size(); ++i)
+    {
+        std::string auxAtlasFile =
+            osgDB::getNameLessExtension(outImageFile) +
+            "_" + auxPatterns[i] + "." +
+            osgDB::getFileExtension(outImageFile);
+
+        osgDB::writeImageFile(*atlas._images[i+1].get(), auxAtlasFile);
+        
+        OE_INFO << LC << "Wrote auxiliary image to \"" << auxAtlasFile << "\"" << std::endl;
+    }
+
+    // write the new catalog:
+    osgEarth::XmlDocument catXML( atlas._lib->getConfig() );
+
+    std::ofstream catOut(outCatalogFile.c_str());
+    if ( !catOut.is_open() )
+        return usage("Failed to open output catalog file for writing");
+
+    catXML.store(catOut);
+    catOut.close();
+
+    OE_INFO << LC << "Wrote output catalog to \"" << outCatalogFile<< "\"" << std::endl;
+    return 0;
+}
+
+//----------------------------------------------------------------------
+
+int
+show(osg::ArgumentParser& arguments)
+{
+    // find the resource library file:
+    std::string inCatalogFile;
+    if ( !arguments.read("--show", inCatalogFile) )
+        return usage("Missing required catalog file name");
+
+    int layer = 0;
+    arguments.read("--layer", layer);
+
+    bool drawLabels;
+    drawLabels = arguments.read("--labels");
+
+    std::string auxPattern;
+    arguments.read("--aux", auxPattern);
+
+    // open the resource library:
+    osg::ref_ptr<osgEarth::Symbology::ResourceLibrary> lib =
+        new osgEarth::Symbology::ResourceLibrary("temp", osgEarth::URI(inCatalogFile) );
+    if ( lib->initialize(0L) == false )
+        return usage("Failed to load resource catalog");
+
+    // the atlas name is the library name without the extension. Not strictly true
+    // but true if you didn't rename it :)
+    std::string atlasFile = osgDB::getNameLessExtension(inCatalogFile);
+
+    // check for an auxiliary pattern:
+    if ( !auxPattern.empty() )
+    {
+        atlasFile =
+            osgDB::getNameLessExtension(atlasFile) +
+            "_" + auxPattern + "." +
+            osgDB::getFileExtension(atlasFile);
+    }
+
+    osg::Image* image = osgDB::readImageFile(atlasFile);
+    if ( !image )
+        return usage("Failed to load atlas image");
+
+    if ( layer > image->r()-1 )
+        return usage("Specified layer does not exist");
+
+    // geometry for the image layer:
+    std::vector<osg::ref_ptr<osg::Image> > images;
+    osgEarth::ImageUtils::flattenImage(image, images);
+    osg::Geode* geode = osg::createGeodeForImage(images[layer].get());
+
+    // geometry for the skins in that layer:
+    osg::Geode* geode2 = new osg::Geode();
+    osg::Geometry* geom = new osg::Geometry();
+    geode2->addDrawable(geom);
+    osg::Vec3Array* v = new osg::Vec3Array();
+    geom->setVertexArray( v );
+    osg::Vec4Array* c = new osg::Vec4Array(1);
+    (*c)[0].set(1,1,0,1);
+    geom->setColorArray(c);
+    geom->setColorBinding(geom->BIND_OVERALL);
+    osgEarth::Symbology::SkinResourceVector skins;
+    lib->getSkins(skins);
+    for(unsigned k=0; k<skins.size(); ++k)
+    {
+        if (skins[k]->imageLayer() == layer &&
+            skins[k]->isTiled() == false)
+        {
+            float x = -1.0f + 2.0*skins[k]->imageBiasS().value();
+            float y = -1.0f + 2.0*skins[k]->imageBiasT().value();
+            float s = 2.0*skins[k]->imageScaleS().value();
+            float t = 2.0*skins[k]->imageScaleT().value();
+
+            v->push_back(osg::Vec3(x,     -0.01f, y    ));
+            v->push_back(osg::Vec3(x + s, -0.01f, y    ));
+            v->push_back(osg::Vec3(x + s, -0.01f, y + t));
+            v->push_back(osg::Vec3(x,     -0.01f, y + t));
+
+            geom->addPrimitiveSet(new osg::DrawArrays(GL_LINE_LOOP, v->size()-4, 4));
+
+            if (drawLabels)
+            {
+                osgText::Text* label = new osgText::Text();
+                label->setText(skins[k]->name());
+                label->setPosition(osg::Vec3(x+0.5*s, -0.005f, y+0.5*t));
+                label->setAlignment(label->CENTER_CENTER);
+                label->setAutoRotateToScreen(true);
+                label->setCharacterSizeMode(label->SCREEN_COORDS);
+                label->setCharacterSize(20.0f);
+                label->setFont("arialbd.ttf");
+                geode2->addDrawable(label);
+            }
+        }
+    }
+
+    osg::Group* root = new osg::Group();
+    root->addChild( geode );
+    root->addChild( geode2 );
+
+    root->getOrCreateStateSet()->setMode(GL_LIGHTING, 0);
+    root->getOrCreateStateSet()->setMode(GL_CULL_FACE, 0);
+
+    osgViewer::Viewer viewer;
+    viewer.setSceneData( root );
+    viewer.addEventHandler(new osgViewer::StatsHandler());
+    viewer.run();
+    return 0;
+}
+
+
+int
+main(int argc, char** argv)
+{
+    osg::ArgumentParser arguments(&argc,argv);
+
+    // print usage info.
+    if ( arguments.read("--help") )
+        return usage(0L, argv[0]);
+
+    if (arguments.find("--build") >= 0)
+        return build(arguments);
+
+    if (arguments.find("--show") >= 0)
+        return show(arguments);
+
+    return usage(0L, argv[0]);
+}
diff --git a/src/applications/osgearth_backfill/osgearth_backfill.cpp b/src/applications/osgearth_backfill/osgearth_backfill.cpp
index 28ad8ac..3f97d12 100644
--- a/src/applications/osgearth_backfill/osgearth_backfill.cpp
+++ b/src/applications/osgearth_backfill/osgearth_backfill.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/applications/osgearth_boundarygen/BoundaryUtil b/src/applications/osgearth_boundarygen/BoundaryUtil
index abc33a1..080fcc4 100644
--- a/src/applications/osgearth_boundarygen/BoundaryUtil
+++ b/src/applications/osgearth_boundarygen/BoundaryUtil
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -25,35 +25,45 @@
 class BoundaryUtil
 {
 public:
-  /* Use the vertices of the given node to calculate a boundary via the
-   * findHull() method.
-   */
-  static osg::Vec3dArray* getBoundary(osg::Node* modelNode, bool geocentric=true, bool convexHull=false);
+    BoundaryUtil();
 
-  /* Finds the convex hull for the given points using the Andrew's monotone
-   * chain algorithm. Returns an ordered set of points defining the hull
-   */
-  static osg::Vec3dArray* findHull(osg::Vec3dArray& points);
+    /** Sets the tolerance when combining close verts. Default = 0.01 */
+    static void setTolerance(double meters);
+    static double getTolerance() { return _tolerance; }
 
-  static osg::Vec3dArray* findMeshBoundary(osg::Node* modelNode, bool geocentric=true);
+    /**
+     * Use the vertices of the given node to calculate a boundary via the
+     * findHull() method.
+     */
+    static osg::Vec3dArray* getBoundary(osg::Node* modelNode, bool geocentric=true, bool convexHull=false);
 
-  static bool simpleBoundaryTest(const osg::Vec3dArray& boundary);
+    /**
+     * Finds the convex hull for the given points using the Andrew's monotone
+     * chain algorithm. Returns an ordered set of points defining the hull
+     */
+    static osg::Vec3dArray* findHull(osg::Vec3dArray& points);
+
+    static osg::Vec3dArray* findMeshBoundary(osg::Node* modelNode, bool geocentric=true);
+
+    static bool simpleBoundaryTest(const osg::Vec3dArray& boundary);
 
 protected:
-  /* Returns an array containing the points sorted first by x and then by y */
-  static osg::Vec3dArray* hullPresortPoints(osg::Vec3dArray& points);
-
-  /* Tests if a point is Left|On|Right of an infinite line
-   *   Returns: >0 for P2 left of the line through P0 and P1
-   *            0 for P2 on the line
-   *            <0 for P2 right of the line
-   *
-   * Implementation based on method from softSurfer (www.softsurfer.com)
-   */
-  static inline float isLeft(osg::Vec3d P0, osg::Vec3d P1, osg::Vec3d P2)
-  {
-    return (P1.x() - P0.x())*(P2.y() - P0.y()) - (P2.x() - P0.x())*(P1.y() - P0.y());
-  }
+    /* Returns an array containing the points sorted first by x and then by y */
+    static osg::Vec3dArray* hullPresortPoints(osg::Vec3dArray& points);
+
+    /* Tests if a point is Left|On|Right of an infinite line
+    *   Returns: >0 for P2 left of the line through P0 and P1
+    *            0 for P2 on the line
+    *            <0 for P2 right of the line
+    *
+    * Implementation based on method from softSurfer (www.softsurfer.com)
+    */
+    static inline float isLeft(osg::Vec3d P0, osg::Vec3d P1, osg::Vec3d P2)
+    {
+        return (P1.x() - P0.x())*(P2.y() - P0.y()) - (P2.x() - P0.x())*(P1.y() - P0.y());
+    }
+
+    static double _tolerance;
 };
 
 #endif // BOUNDARY_UTIL
diff --git a/src/applications/osgearth_boundarygen/BoundaryUtil.cpp b/src/applications/osgearth_boundarygen/BoundaryUtil.cpp
index 8546a5a..e4812ff 100644
--- a/src/applications/osgearth_boundarygen/BoundaryUtil.cpp
+++ b/src/applications/osgearth_boundarygen/BoundaryUtil.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -22,8 +22,12 @@
 #include <algorithm>
 #include <osg/Geode>
 #include <osg/Geometry>
+#include <osg/Point>
 #include <osg/TriangleIndexFunctor>
+#include <osg/ComputeBoundsVisitor>
 #include <osgEarthSymbology/Geometry>
+#include <osgEarth/SpatialReference>
+#include <osgDB/WriteFile>
 #include <map>
 #include <vector>
 #include <set>
@@ -37,6 +41,14 @@ bool presortCompare (osg::Vec3d i, osg::Vec3d j)
   return i.x() < j.x();
 }
 
+double BoundaryUtil::_tolerance = 0.005;
+
+void
+BoundaryUtil::setTolerance(double value)
+{
+    _tolerance = value;
+}
+
 /* Use the vertices of the given node to calculate a boundary via the
  * findHull() method.
  */
@@ -241,8 +253,22 @@ osg::Vec3dArray* BoundaryUtil::hullPresortPoints(osg::Vec3dArray& points)
 
 namespace
 {
-    typedef std::set<osg::Vec3d> VertexSet; 
-    typedef VertexSet::iterator  Index;
+    // custom comparator for VertexSet.
+    struct VertexLess
+    {
+        bool operator()(const osg::Vec3d& lhs, const osg::Vec3d& rhs) const
+        {
+            double dx = lhs.x() - rhs.x();
+            if ( dx < 0.0 && dx < -BoundaryUtil::getTolerance() ) return true;
+            if ( dx > 0.0 && dx >  BoundaryUtil::getTolerance() ) return false;
+
+            double dy = lhs.y() - rhs.y();
+            return (dy < 0.0 && dy < -BoundaryUtil::getTolerance());
+        }
+    };
+
+    typedef std::set<osg::Vec3d, VertexLess> VertexSet;
+    typedef VertexSet::iterator Index;
 
     // custom comparator for Index so we can use it at a std::map key
     struct IndexLess : public std::less<Index> {
@@ -260,12 +286,15 @@ namespace
     struct TopologyGraph
     {
         TopologyGraph()
-          : _minY( _verts.end() ) { }
-
-        VertexSet _verts;    // set of unique verts in the topology (rotated into XY plane)
-        osg::Quat _rot;      // rotates a geocentric vert into the XY plane
-        EdgeMap   _edgeMap;  // maps each vert to all the verts with which it shares an edge
-        Index     _minY;     // points to the vert with the minimum Y coordinate (in XY plane)
+          : _minY( _verts.end() ), _totalVerts(0), _srs(0L) { }
+
+        unsigned     _totalVerts;  // total number of verts encountered
+        VertexSet    _vertsWorld;  // 
+        VertexSet    _verts;       // set of unique verts in the topology (rotated into XY plane)
+        EdgeMap      _edgeMap;     // maps each vert to all the verts with which it shares an edge
+        Index        _minY;        // points to the vert with the minimum Y coordinate (in XY plane)
+        osg::Matrixd _world2plane; // matrix that transforms into a localized XY plane
+        const osgEarth::SpatialReference* _srs;
     };
 
     typedef std::map<unsigned,Index> UniqueMap;
@@ -286,43 +315,55 @@ namespace
             Index i2 = add( v2 );
 
             // add to the edge list for each of these verts
-            _topology->_edgeMap[i0].insert( i1 );
-            _topology->_edgeMap[i0].insert( i2 );
-            _topology->_edgeMap[i1].insert( i0 );
-            _topology->_edgeMap[i1].insert( i2 );
-            _topology->_edgeMap[i2].insert( i0 );
-            _topology->_edgeMap[i2].insert( i1 );
+            if ( i0 != i1 ) _topology->_edgeMap[i0].insert( i1 );
+            if ( i0 != i2 ) _topology->_edgeMap[i0].insert( i2 );
+            if ( i1 != i0 ) _topology->_edgeMap[i1].insert( i0 );
+            if ( i1 != i2 ) _topology->_edgeMap[i1].insert( i2 );
+            if ( i2 != i0 ) _topology->_edgeMap[i2].insert( i0 );
+            if ( i2 != i1 ) _topology->_edgeMap[i2].insert( i1 );
         }
 
         Index add( unsigned v )
         {
-            // first see if we already added this vert:
+            // first see if we already added the vert at this index.
             UniqueMap::iterator i = _uniqueMap.find( v );
             if ( i == _uniqueMap.end() )
             {
-              // no, so transform it into world coordinates, and rotate it into the XY plane
-              osg::Vec3d spVert = (*_vertexList)[v];
-              osg::Vec3d local = _topology->_rot * (spVert * _local2world);
-
-              // insert it into the unique vert list
-              std::pair<VertexSet::iterator,bool> f = _topology->_verts.insert( local );
-              if ( f.second )
-              {
-                // this is a new location, so check it to see if it is the new "lowest" point:
-                if ( _topology->_minY == _topology->_verts.end() || local.y() < _topology->_minY->y() )
-                  _topology->_minY = f.first;
-
-                // store in the uniqueness map to prevent duplication
+                // no, so transform it into world coordinates, and rotate it into the XY plane
+                osg::Vec3d vert = (*_vertexList)[v];
+                osg::Vec3d world = vert * _local2world;
+                osg::Vec3d plane = world;
+
+                if ( _topology->_srs )
+                {
+                    const osgEarth::SpatialReference* ecef = _topology->_srs->getECEF();
+                    ecef->transform(world, _topology->_srs, plane);
+                }
+                else
+                {
+                    plane = world * _topology->_world2plane;
+                }
+
+                // insert it into the unique vert list
+                std::pair<VertexSet::iterator,bool> f = _topology->_verts.insert( plane );
+                if ( f.second ) // insert succedded
+                {
+                    // this is a new location, so check it to see if it is the new "southernmost" point:
+                    if ( _topology->_minY == _topology->_verts.end() || plane.y() < _topology->_minY->y() )
+                    {
+                        _topology->_minY = f.first;
+                    }
+                }
+
+                // store in the uniqueness map so we don't process the same index again
                 _uniqueMap[ v ] = f.first;
-              }
-             
-              // return the index of the vert.
-              return f.first;
+
+                // return the index of the vert.
+                return f.first;
             }
             else
             {
-              // return the index of the vert.
-              return i->second;
+                return i->second;
             }
         }
     };
@@ -368,18 +409,59 @@ namespace
             osg::Vec3Array* vertexList = dynamic_cast<osg::Vec3Array*>(geometry->getVertexArray());
             if ( vertexList )
             {
-                osg::TriangleIndexFunctor<TopologyBuilder> _builder;
-                _builder._topology = &_topology;
-                _builder._vertexList = vertexList;
+                osg::TriangleIndexFunctor<TopologyBuilder> builder;
+                builder._topology = &_topology;
+                builder._vertexList = vertexList;
                 if ( !_matrixStack.empty() )
-                  _builder._local2world = _matrixStack.back();
-                geometry->accept( _builder );
+                    builder._local2world = _matrixStack.back();
+                _topology._totalVerts += vertexList->size();
+                geometry->accept( builder );
             }
         }
 
         std::vector<osg::Matrixd> _matrixStack;
         TopologyGraph&            _topology;
     };
+
+    void dumpPointCloud(TopologyGraph& t)
+    {
+        osg::Vec3Array* v = new osg::Vec3Array();
+        osg::DrawElementsUInt* lines = new osg::DrawElementsUInt(GL_LINES);
+        unsigned index = 0;
+        unsigned minyindex = 0;
+        std::map<Index,unsigned, IndexLess> order;
+        for(Index i = t._verts.begin(); i != t._verts.end(); ++i, ++index)
+        {
+            v->push_back( *i );
+            if ( i == t._minY )
+                minyindex = index;
+            order[i] = index;
+        }
+        index = 0;
+        for(Index i = t._verts.begin(); i != t._verts.end(); ++i, ++index)
+        {
+            IndexSet& edges = t._edgeMap[i];
+            for(IndexSet::iterator j=edges.begin(); j!=edges.end(); ++j)
+            {
+                lines->push_back(order[i]);
+                lines->push_back(order[*j]);
+            }
+        }
+        osg::Geometry* g = new osg::Geometry();
+        g->setVertexArray(v);
+        g->addPrimitiveSet(new osg::DrawArrays(GL_POINTS, 0, v->size()));
+        g->addPrimitiveSet(lines);
+        g->getOrCreateStateSet()->setAttributeAndModes(new osg::Point(3));
+        osg::Geode* n = new osg::Geode();
+        n->addDrawable(g);
+        osg::Geometry* g2 = new osg::Geometry();
+        g2->setVertexArray(v);
+        g2->addPrimitiveSet(new osg::DrawArrays(GL_POINTS, minyindex, 1));
+        g2->getOrCreateStateSet()->setAttributeAndModes(new osg::Point(10));
+        n->addDrawable(g2);
+        osgDB::writeNodeFile(*n, "mesh.osg");            
+        n->unref();
+    }
 }
 
 //------------------------------------------------------------------------
@@ -389,13 +471,16 @@ BoundaryUtil::findMeshBoundary( osg::Node* node, bool geocentric )
 {
     // the normal defines the XY plane in which to search for a boundary
     osg::Vec3d normal(0,0,1);
+    osg::Vec3d center;
 
     if ( geocentric )
     {
         // define the XY plane based on the normal to the center of the dataset:
         osg::BoundingSphere bs = node->getBound();
-        normal = bs.center();
+        center = bs.center();
+        normal = center;
         normal.normalize();
+        OE_DEBUG << "Normal = " << normal.x() << ", " << normal.y() << ", " << normal.z() << std::endl;
     }
 
     osg::ref_ptr<osg::Vec3dArray> _result = new osg::Vec3dArray();
@@ -403,14 +488,23 @@ BoundaryUtil::findMeshBoundary( osg::Node* node, bool geocentric )
     // first build a topology graph from the node.
     TopologyGraph topology;
 
-    // set up a quat that will rotate geometry into our XY plane
-    if ( normal != osg::Vec3(0,0,1) )
-        topology._rot.makeRotate( normal, osg::Vec3d(0,0,1) );
+    // set up a transform that will localize geometry into an XY plane
+    if ( geocentric )
+    {
+        topology._world2plane.makeRotate(normal, osg::Vec3d(0,0,1));
+        topology._world2plane.preMultTranslate(-center);
+
+        // if this is set, use mercator projection instead of a simple geolocation
+        //topology._srs = osgEarth::SpatialReference::get("spherical-mercator");
+    }
 
     // build the topology
     BuildTopologyVisitor buildTopoVisitor(topology);
     node->accept( buildTopoVisitor );
 
+    OE_DEBUG << "Found " << topology._verts.size() << " unique verts" << std::endl;
+    dumpPointCloud(topology);
+
     // starting with the minimum-Y vertex (which is guaranteed to be in the boundary)
     // traverse the outside of the point set. Do this by sorting all the edges by
     // their angle relative to the vector to the previous point. The vector with the
@@ -420,67 +514,95 @@ BoundaryUtil::findMeshBoundary( osg::Node* node, bool geocentric )
     Index vptr      = topology._minY;
     Index vptr_prev = topology._verts.end();
 
+    IndexSet visited;
+
     while( true )
     {
         // store this vertex in the result set:
         _result->push_back( *vptr );
 
+        if ( _result->size() == 56 )
+        {
+            int asd=0;
+        }
+
         // pull up the next 2D vertex (XY plane):
-        osg::Vec2d vert( vptr->x(), vptr->y() );
+        osg::Vec2d vert ( vptr->x(), vptr->y() );
 
-        // construct the "base" vector that points from the current point back
-        // to the previous point; or to -X in the initial case
+        // construct the "base" vector that points from the previous 
+        // point to the current point; or to -X in the initial case
         osg::Vec2d base;
         if ( vptr_prev == topology._verts.end() )
             base.set( -1, 0 );
         else
-            base = osg::Vec2d( vptr_prev->x(), vptr_prev->y() ) - vert;
+            base = vert - osg::Vec2d( vptr_prev->x(), vptr_prev->y() );
             
         // pull up the edge set for this vertex:
         IndexSet& edges = topology._edgeMap[vptr];
 
         // find the edge with the minimun delta angle to the base vector
-        double minAngle = DBL_MAX;
-        Index  minEdge  = topology._verts.end();
+        double bestScore = DBL_MAX;
+        Index  bestEdge  = topology._verts.end();
+        
+        OE_DEBUG << "VERTEX (" << 
+            vptr->x() << ", " << vptr->y() << ", " << vptr->z() 
+            << ") has " << edges.size() << " edges..."
+            << std::endl;
 
         for( IndexSet::iterator e = edges.begin(); e != edges.end(); ++e )
         {
             // don't go back from whence we just came
             if ( *e == vptr_prev )
-              continue;
+                continue;
+
+            // never return to a vert we've already visited
+            if ( visited.find(*e) != visited.end() )
+                continue;
 
             // calculate the angle between the base vector and the current edge:
             osg::Vec2d edgeVert( (*e)->x(), (*e)->y() );
             osg::Vec2d edge = edgeVert - vert;
 
-            double baseAngle = atan2(base.y(), base.x());
-            double edgeAngle = atan2(edge.y(), edge.x());
-
-            double outsideAngle = baseAngle - edgeAngle;
+            base.normalize();
+            edge.normalize();
+            double cross = base.x()*edge.y() - base.y()*edge.x();
+            double dot   = base * edge;
+            double score = dot;
 
-            // normalize it to [0..360)
-            if ( outsideAngle < 0.0 )
-              outsideAngle += 2.0*osg::PI;
+            if ( cross < 0.0 )
+            {
+                double diff = 2.0-(score+1.0);
+                score = 1.0 + diff;
+            }
 
-            // see it is qualifies as the new minimum angle
-            if ( outsideAngle < minAngle )
+            OE_DEBUG << "   check: " << (*e)->x() << ", " << (*e)->y() << ", " << (*e)->z() << std::endl;
+            OE_DEBUG << "   base = " << base.x() << ", " << base.y() << std::endl;
+            OE_DEBUG << "   edge = " << edge.x() << ", " << edge.y() << std::endl;
+            OE_DEBUG << "   crs = " << cross << ", dot = " << dot << ", score = " << score << std::endl;
+            
+            if ( score < bestScore )
             {
-                minAngle = outsideAngle;
-                minEdge = *e;
+                bestScore = score;
+                bestEdge = *e;
             }
         }
 
-        if ( minEdge == topology._verts.end() )
+        if ( bestEdge == topology._verts.end() )
         {
             // this will probably never happen
-            osg::notify(osg::WARN) << "Illegal state - bailing" << std::endl;
-            return 0L;
+            osg::notify(osg::WARN) << "Illegal state - reached a dead end!" << std::endl;
+            break;
         }
 
+        // store the previous:
         vptr_prev = vptr;
 
         // follow the chosen edge around the outside of the geometry:
-        vptr = minEdge;
+        OE_DEBUG << "   BEST SCORE = " << bestScore << std::endl;
+        vptr = bestEdge;
+
+        // record this vert so we don't visit it again.
+        visited.insert( vptr );
 
         // once we make it all the way around, we're done:
         if ( vptr == topology._minY )
@@ -488,10 +610,17 @@ BoundaryUtil::findMeshBoundary( osg::Node* node, bool geocentric )
     }
 
     // un-rotate the results from the XY plane back to their original frame:
-    osg::Quat invRot = topology._rot.inverse();
-    for( osg::Vec3dArray::iterator i = _result->begin(); i != _result->end(); ++i )
+    if ( topology._srs )
+    {
+        const osgEarth::SpatialReference* ecef = topology._srs->getECEF();
+        topology._srs->transform(_result->asVector(), ecef);
+    }
+    else
     {
-      (*i) = invRot * (*i);
+        osg::Matrix plane2world;
+        plane2world.invert( topology._world2plane );
+        for( osg::Vec3dArray::iterator i = _result->begin(); i != _result->end(); ++i )
+            (*i) = (*i) * plane2world;
     }
 
     return _result.release();
diff --git a/src/applications/osgearth_boundarygen/VertexCollectionVisitor b/src/applications/osgearth_boundarygen/VertexCollectionVisitor
index 4cd68b6..b79a07c 100644
--- a/src/applications/osgearth_boundarygen/VertexCollectionVisitor
+++ b/src/applications/osgearth_boundarygen/VertexCollectionVisitor
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/applications/osgearth_boundarygen/VertexCollectionVisitor.cpp b/src/applications/osgearth_boundarygen/VertexCollectionVisitor.cpp
index 5080365..6fb8b71 100644
--- a/src/applications/osgearth_boundarygen/VertexCollectionVisitor.cpp
+++ b/src/applications/osgearth_boundarygen/VertexCollectionVisitor.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/applications/osgearth_boundarygen/boundarygen.cpp b/src/applications/osgearth_boundarygen/boundarygen.cpp
index 749b23a..f4d5e12 100644
--- a/src/applications/osgearth_boundarygen/boundarygen.cpp
+++ b/src/applications/osgearth_boundarygen/boundarygen.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -33,6 +33,7 @@
 #include <osgGA/GUIEventHandler>
 #include <osgViewer/Viewer>
 #include <osgViewer/ViewerEventHandlers>
+#include <osgUtil/Optimizer>
 
 
 int usage( char** argv, const std::string& msg )
@@ -43,11 +44,13 @@ int usage( char** argv, const std::string& msg )
     << "Generates boundary geometry that you can use with an osgEarth <mask> layer in order\n"
     << "to stitch an external model into the terrain.\n\n"
     << "USAGE: " << argv[0] << " [options] model_file\n"
-    << "           --out file_name     : output file for boundary geometry (default is boundary.txt)\n"
-    << "           --no-geocentric     : skip geocentric reprojection (for flat databases)\n"
-    << "           --convex-hull       : calculate a convex hull instead of a full boundary\n"
-    << "           --verbose           : print progress to console\n"
-    << "           --view              : show result in 3D window\n"
+    << "           --out <file_name>    : output file for boundary geometry (default is boundary.txt)\n"
+    << "           --tolerance <meters> : tolerance for combining similar verts along a boundary (default = 0.005)\n"
+    << "           --precision <n>      : output precision of boundary coords (default=12)\n"
+    << "           --no-geocentric      : skip geocentric reprojection (for flat databases)\n"
+    << "           --convex-hull        : calculate a convex hull instead of a full boundary\n"
+    << "           --verbose            : print progress to console\n"
+    << "           --view               : show result in 3D window\n"
     << std::endl;
 
   
@@ -62,6 +65,13 @@ int main(int argc, char** argv)
     if (!arguments.read("--out", outFile))
       outFile = "boundary.txt";
 
+    double tolerance;
+    if (arguments.read("--tolerance", tolerance))
+        BoundaryUtil::setTolerance(tolerance);
+
+    int precision = 12;
+    arguments.read("--precision", precision);
+
     bool geocentric = !arguments.read("--no-geocentric");
     bool verbose = arguments.read("--verbose");
     bool convexOnly = arguments.read("--convex-hull");
@@ -104,7 +114,10 @@ int main(int argc, char** argv)
             if (verbose)
               std::cout << "  hull[" << i << "] == " << lon << ", " << lat << ", " << height << std::endl;
 
-            outStream << std::setiosflags(std::ios_base::fixed) << (i > 0 ? ", " : "") << lon << " " << lat << " " << height;
+            outStream
+                //<< std::fixed //std::setiosflags(std::ios_base::fixed)
+                << std::setprecision(precision)
+                << (i > 0 ? ", " : "") << lon << " " << lat << " " << height;
           }
 
           outStream << "))";
diff --git a/src/applications/osgearth_city/osgearth_city.cpp b/src/applications/osgearth_city/osgearth_city.cpp
index 48e0ad6..3d9947a 100644
--- a/src/applications/osgearth_city/osgearth_city.cpp
+++ b/src/applications/osgearth_city/osgearth_city.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -20,11 +20,14 @@
 #include <osgGA/GUIEventHandler>
 #include <osgGA/StateSetManipulator>
 #include <osgViewer/Viewer>
+
 #include <osgEarth/MapNode>
+
 #include <osgEarthUtil/ExampleResources>
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/AutoClipPlaneHandler>
-#include <osgEarthUtil/SkyNode>
+#include <osgEarthUtil/LogarithmicDepthBuffer>
+
 #include <osgEarthDrivers/tms/TMSOptions>
 #include <osgEarthDrivers/xyz/XYZOptions>
 #include <osgEarthDrivers/feature_ogr/OGRFeatureOptions>
@@ -36,6 +39,22 @@ using namespace osgEarth::Features;
 using namespace osgEarth::Symbology;
 using namespace osgEarth::Util;
 
+#define IMAGERY_URL      "http://readymap.org/readymap/tiles/1.0.0/22/"
+#define ELEVATION_URL    "http://readymap.org/readymap/tiles/1.0.0/9/"
+#define BUILDINGS_URL    "../data/boston_buildings_utm19.shp"
+#define RESOURCE_LIB_URL "../data/resources/textures_us/catalog.xml"
+#define STREETS_URL      "../data/boston-scl-utm19n-meters.shp"
+#define PARKS_URL        "../data/boston-parks.shp"
+#define TREE_MODEL_URL   "../data/loopix/tree4.osgb"
+
+// forward declarations.
+void addImagery  (Map* map);
+void addElevation(Map* map);
+void addBuildings(Map* map);
+void addStreets  (Map* map);
+void addParks    (Map* map);
+
+
 /**
  * This code example effectively duplicates the "boston.earth" sample,
  * demonstrating how to create a 3D city model in osgEarth.
@@ -50,31 +69,88 @@ main(int argc, char** argv)
     // create the map.
     Map* map = new Map();
 
+    addImagery( map );
+    addElevation( map );
+    addBuildings( map );
+    addStreets( map );
+    addParks( map );
+
+    // initialize a viewer:
+    osgViewer::Viewer viewer(arguments);
+    
+    EarthManipulator* manip = new EarthManipulator();
+    viewer.setCameraManipulator( manip );
+
+    osg::Group* root = new osg::Group();
+    viewer.setSceneData( root );
+
+    // make the map scene graph:
+    MapNode* mapNode = new MapNode( map );
+    root->addChild( mapNode );
+    
+    // Process cmdline args.
+    MapNodeHelper helper;
+    helper.configureView( &viewer );
+    helper.parse(mapNode, arguments, &viewer, root, new LabelControl("City Demo"));
+
+    // zoom to a good startup position
+    manip->setViewpoint( Viewpoint(
+        -71.0763, 42.34425, 0,   // longitude, latitude, altitude
+         24.261, -21.6, 3450.0), // heading, pitch, range
+       5.0 );                    // duration
+
+    // This will mitigate near clip plane issues if you zoom in close to the ground:
+    LogarithmicDepthBuffer buf;
+    buf.install( viewer.getCamera() );
+
+    return viewer.run();
+}
+
+void addImagery(Map* map)
+{
     // add a TMS imagery layer:
     TMSOptions imagery;
-    imagery.url() = "http://readymap.org/readymap/tiles/1.0.0/22/";
+    imagery.url() = IMAGERY_URL;
     map->addImageLayer( new ImageLayer("ReadyMap imagery", imagery) );
+}
+
+
+void addElevation(Map* map)
+{
+    // add a TMS elevation layer:
+    TMSOptions elevation;
+    elevation.url() = ELEVATION_URL;
+    map->addElevationLayer( new ElevationLayer("ReadyMap elevation", elevation) );
+}
 
+
+void addBuildings(Map* map)
+{
     // create a feature source to load the building footprint shapefile.
     OGRFeatureOptions feature_opt;
     feature_opt.name() = "buildings";
-    feature_opt.url() = "../data/boston_buildings_utm19.shp";
+    feature_opt.url() = BUILDINGS_URL;
     feature_opt.buildSpatialIndex() = true;
     
     // a style for the building data:
     Style buildingStyle;
     buildingStyle.setName( "buildings" );
 
+    // Extrude the shapes into 3D buildings.
     ExtrusionSymbol* extrusion = buildingStyle.getOrCreate<ExtrusionSymbol>();
     extrusion->heightExpression() = NumericExpression( "3.5 * max( [story_ht_], 1 )" );
     extrusion->flatten() = true;
     extrusion->wallStyleName() = "building-wall";
     extrusion->roofStyleName() = "building-roof";
-    extrusion->wallGradientPercentage() = 0.8;
 
     PolygonSymbol* poly = buildingStyle.getOrCreate<PolygonSymbol>();
     poly->fill()->color() = Color::White;
 
+    // Clamp the buildings to the terrain.
+    AltitudeSymbol* alt = buildingStyle.getOrCreate<AltitudeSymbol>();
+    alt->clamping() = alt->CLAMP_TO_TERRAIN;
+    alt->binding() = alt->BINDING_VERTEX;
+
     // a style for the wall textures:
     Style wallStyle;
     wallStyle.setName( "building-wall" );
@@ -99,10 +175,12 @@ main(int argc, char** argv)
     styleSheet->addStyle( roofStyle );
     
     // load a resource library that contains the building textures.
-    ResourceLibrary* reslib = new ResourceLibrary( "us_resources", "../data/resources/textures_us/catalog.xml" );
+    ResourceLibrary* reslib = new ResourceLibrary( "us_resources", RESOURCE_LIB_URL );
     styleSheet->addResourceLibrary( reslib );
 
-    // set up a paging layout for incremental loading.
+    // set up a paging layout for incremental loading. The tile size factor and
+    // the visibility range combine to determine the tile size, such that
+    // tile radius = max range / tile size factor.
     FeatureDisplayLayout layout;
     layout.tileSizeFactor() = 52.0;
     layout.addLevel( FeatureLevel(0.0f, 20000.0f, "buildings") );
@@ -114,29 +192,104 @@ main(int argc, char** argv)
     fgm_opt.layout() = layout;
 
     map->addModelLayer( new ModelLayer( "buildings", fgm_opt ) );
+}
 
 
-    // initialize a viewer:
-    osgViewer::Viewer viewer(arguments);
-    EarthManipulator* manip = new EarthManipulator();
-    viewer.setCameraManipulator( manip );
+void addStreets(Map* map)
+{
+    // create a feature source to load the street shapefile.
+    OGRFeatureOptions feature_opt;
+    feature_opt.name() = "streets";
+    feature_opt.url() = STREETS_URL;
+    feature_opt.buildSpatialIndex() = true;
 
-    // make the map scene graph:
-    osg::Group* root = new osg::Group();
-    viewer.setSceneData( root );
+    // a resampling filter will ensure that the length of each segment falls
+    // within the specified range. That can be helpful to avoid cropping 
+    // very long lines segments.
+    feature_opt.filters().push_back( new ResampleFilter(0.0, 25.0) );
 
-    MapNode* mapNode = new MapNode( map );
-    root->addChild( mapNode );
+    // a style:
+    Style style;
+    style.setName( "streets" );
+
+    // Render the data as translucent yellow lines that are 7.5m wide.
+    LineSymbol* line = style.getOrCreate<LineSymbol>();
+    line->stroke()->color() = Color(Color::Yellow, 0.5f);
+    line->stroke()->width() = 7.5f;
+    line->stroke()->widthUnits() = Units::METERS;
+
+    // Clamp the lines to the terrain.
+    AltitudeSymbol* alt = style.getOrCreate<AltitudeSymbol>();
+    alt->clamping() = alt->CLAMP_TO_TERRAIN;
+
+    // Apply a depth offset to avoid z-fighting. The "min bias" is the minimum
+    // apparent offset (towards the camera) of the geometry from its actual position.
+    // The value here was chosen empirically by tweaking the "oe_doff_min_bias" uniform.
+    RenderSymbol* render = style.getOrCreate<RenderSymbol>();
+    render->depthOffset()->minBias() = 6.6f;
+
+    // Set up a paging layout. The tile size factor and the visibility range combine
+    // to determine the tile size, such that tile radius = max range / tile size factor.
+    FeatureDisplayLayout layout;
+    layout.tileSizeFactor() = 7.5f;
+    layout.maxRange()       = 5000.0f;
+
+    // create a model layer that will render the buildings according to our style sheet.
+    FeatureGeomModelOptions fgm_opt;
+    fgm_opt.featureOptions() = feature_opt;
+    fgm_opt.layout() = layout;
+    fgm_opt.styles() = new StyleSheet();
+    fgm_opt.styles()->addStyle( style );
+
+    map->addModelLayer( new ModelLayer("streets", fgm_opt) );
+}
+
+
+void addParks(Map* map)
+{
+    // create a feature source to load the shapefile.
+    OGRFeatureOptions feature_opt;
+    feature_opt.name() = "parks";
+    feature_opt.url() = PARKS_URL;
+    feature_opt.buildSpatialIndex() = true;
+
+    // a style:
+    Style style;
+    style.setName( "parks" );
+
+    // Render the data using point-model substitution, which replaces each point
+    // in the feature geometry with an instance of a 3D model. Since the input
+    // data are polygons, the PLACEMENT_RANDOM directive below will scatter
+    // points within the polygon boundary at the specified density.
+    ModelSymbol* model = style.getOrCreate<ModelSymbol>();
+    model->url()->setLiteral(TREE_MODEL_URL);
+    model->scale()->setLiteral( 0.2 );
+    model->placement() = model->PLACEMENT_RANDOM;
+    model->density() = 3000.0f; // instances per sqkm
     
-    // Process cmdline args
-    MapNodeHelper helper;
-    helper.configureView( &viewer );
-    helper.parse(mapNode, arguments, &viewer, root, new LabelControl("City Demo"));
+    // Clamp to the terrain:
+    AltitudeSymbol* alt = style.getOrCreate<AltitudeSymbol>();
+    alt->clamping() = alt->CLAMP_TO_TERRAIN;
 
-    // zoom to a good startup position
-    manip->setViewpoint( Viewpoint(-71.0763, 42.34425, 0, 24.261, -21.6, 3450.0), 5.0 );
+    // Since the tree model contains alpha components, we will discard any data
+    // that's sufficiently transparent; this will prevent depth-sorting anomolies
+    // common when rendering lots of semi-transparent objects.
+    RenderSymbol* render = style.getOrCreate<RenderSymbol>();
+    render->minAlpha() = 0.15f;
 
-    viewer.getCamera()->addCullCallback( new AutoClipPlaneCullCallback(mapNode) );
+    // Set up a paging layout. The tile size factor and the visibility range combine
+    // to determine the tile size, such that tile radius = max range / tile size factor.
+    FeatureDisplayLayout layout;
+    layout.tileSizeFactor() = 3.0f;
+    layout.maxRange()       = 2000.0f;
 
-    return viewer.run();
+    // create a model layer that will render the buildings according to our style sheet.
+    FeatureGeomModelOptions fgm_opt;
+    fgm_opt.featureOptions() = feature_opt;
+    fgm_opt.layout() = layout;
+    fgm_opt.styles() = new StyleSheet();
+    fgm_opt.styles()->addStyle( style );
+    fgm_opt.compilerOptions().instancing() = true;
+
+    map->addModelLayer( new ModelLayer("parks", fgm_opt) );
 }
diff --git a/src/applications/osgearth_clamp/osgearth_clamp.cpp b/src/applications/osgearth_clamp/osgearth_clamp.cpp
index ff9c4d8..185fab6 100644
--- a/src/applications/osgearth_clamp/osgearth_clamp.cpp
+++ b/src/applications/osgearth_clamp/osgearth_clamp.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/applications/osgearth_clipplane/CMakeLists.txt b/src/applications/osgearth_clipplane/CMakeLists.txt
new file mode 100644
index 0000000..47d33f4
--- /dev/null
+++ b/src/applications/osgearth_clipplane/CMakeLists.txt
@@ -0,0 +1,7 @@
+INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} )
+SET(TARGET_LIBRARIES_VARS OSG_LIBRARY OSGDB_LIBRARY OSGUTIL_LIBRARY OSGVIEWER_LIBRARY OPENTHREADS_LIBRARY)
+
+SET(TARGET_SRC osgearth_clipplane.cpp )
+
+#### end var setup  ###
+SETUP_APPLICATION(osgearth_clipplane)
\ No newline at end of file
diff --git a/src/applications/osgearth_clipplane/osgearth_clipplane.cpp b/src/applications/osgearth_clipplane/osgearth_clipplane.cpp
new file mode 100644
index 0000000..457023a
--- /dev/null
+++ b/src/applications/osgearth_clipplane/osgearth_clipplane.cpp
@@ -0,0 +1,130 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+#include <osgViewer/Viewer>
+#include <osgEarth/Notify>
+#include <osgEarthUtil/EarthManipulator>
+#include <osgEarthUtil/ExampleResources>
+#include <osgEarth/CullingUtils>
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/MapNode>
+#include <osgEarth/CullingUtils>
+#include <osg/ClipNode>
+#include <osg/ClipPlane>
+
+#define LC "[viewer] "
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+/**
+ * Demonstrates the use of an osg::ClipNode and osg::ClipPlane
+ * objects to do clipping based on the visible horizon. This technique
+ * can be useful when you want to draw geometry with depth testing
+ * disabled, but you don't want it showing through the earth.
+ *
+ * Try this with "feature_clip_plane.earth" for a demo.
+ */
+int
+usage(const char* name)
+{
+    OE_NOTICE 
+        << "\nUsage: " << name << " feature_clip_plane.earth" << std::endl
+        << MapNodeHelper().usage() << std::endl;
+
+    return 0;
+}
+
+// Vertex shader to activate clip planes in GLSL:
+const char* clipvs =
+    "#version 110 \n"
+    "void oe_clip_vert(inout vec4 vertex_view) { \n"
+    "   gl_ClipVertex = vertex_view; \n"
+    "}\n";
+
+int
+main(int argc, char** argv)
+{
+    osg::ArgumentParser arguments(&argc,argv);
+
+    // help?
+    if ( arguments.read("--help") )
+        return usage(argv[0]);
+
+    // set up a viewer:
+    osgViewer::Viewer viewer(arguments);
+    viewer.getDatabasePager()->setUnrefImageDataAfterApplyPolicy( false, false );
+
+    // install our default manipulator (do this before calling load)
+    viewer.setCameraManipulator( new EarthManipulator() );
+
+    // load an earth file, and support all or our example command-line options
+    // and earth file <external> tags    
+    osg::Node* node = MapNodeHelper().load( arguments, &viewer );
+    if ( node )
+    {
+        // Make a root group:
+        osg::Group* root = new osg::Group();
+        root->addChild( node );
+        viewer.setSceneData( root );
+
+        // Install a ClipNode. The ClipNode establishes positional state so it
+        // doesn't need to parent anything. In this case it needs to be at the
+        // top of the scene graph since out clip plane calculator assumes 
+        // you're in world space.
+        osg::ClipNode* clipNode = new osg::ClipNode();
+        root->addChild( clipNode );
+        
+        // By default, the clip node will activate any clip planes you add to it
+        // for its subgraph. Our clip node doesn't parent anything, but we include
+        // this to demonstrate how you would disable that:
+        clipNode->getOrCreateStateSet()->setMode(GL_CLIP_PLANE0, 0);
+        
+        // Create a ClipPlane we will use to clip to the visible horizon:
+        osg::ClipPlane* cp = new osg::ClipPlane();
+        clipNode->addClipPlane( cp );
+
+        // This cull callback will recalcuate the position of the clipping plane
+        // each frame based on the camera.
+        const osgEarth::SpatialReference* srs = osgEarth::MapNode::get(node)->getMapSRS();
+        clipNode->addCullCallback( new ClipToGeocentricHorizon(srs, cp) );
+
+        // We also need a shader that will activate clipping in GLSL.
+        VirtualProgram* vp = VirtualProgram::getOrCreate(root->getOrCreateStateSet());
+        vp->setFunction("oe_clip_vert", clipvs, ShaderComp::LOCATION_VERTEX_VIEW);
+
+        // Now everything is set up. The last thing to do is: anywhere in your
+        // scene graph that you want to activate the clipping plane, set the 
+        // corresponding mode on, like so:
+        //
+        // node->getOrCreateStateSet()->setMode(GL_CLIP_PLANE0, osg::StateAttribute::ON);
+        //
+        // If you are using symbology, you can use RenderSymbol::clipPlane(). Or in 
+        // the earth file, for example:
+        //
+        //    render-depth-test: false;
+        //    render-clip-plane: 0;
+
+        return viewer.run();
+    }
+    else
+    {
+        return usage(argv[0]);
+    }
+}
diff --git a/src/applications/osgearth_colorfilter/osgearth_colorfilter.cpp b/src/applications/osgearth_colorfilter/osgearth_colorfilter.cpp
index 4b3bfa7..bb137fa 100644
--- a/src/applications/osgearth_colorfilter/osgearth_colorfilter.cpp
+++ b/src/applications/osgearth_colorfilter/osgearth_colorfilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -28,16 +28,18 @@
 #include <osgEarthUtil/HSLColorFilter>
 #include <osgEarthUtil/RGBColorFilter>
 #include <osgEarthUtil/ChromaKeyColorFilter>
+#include <osgEarthSymbology/Color>
 
 using namespace osgEarth;
 using namespace osgEarth::Util;
 using namespace osgEarth::Util::Controls;
+using namespace osgEarth::Symbology;
 
 
 Container*
 createControlPanel(osgViewer::View* view)
 {
-    ControlCanvas* canvas = ControlCanvas::get( view, true );
+    ControlCanvas* canvas = ControlCanvas::getOrCreate(view);
     VBox* vbox = canvas->addControl(new VBox());
     vbox->setChildSpacing(10);
     return vbox;
@@ -98,7 +100,7 @@ namespace HSL
         s_layerBox->setVertAlign( Control::ALIGN_TOP );
 
         // Title:
-        s_layerBox->setControl( 0, 0, new LabelControl(Stringify()<<"Layer "<<i, Color::Yellow) );
+        s_layerBox->setControl( 0, 0, new LabelControl(Stringify()<<"Layer "<<i, osg::Vec4(1,1,0,1)));
         
         // Hue:
         LabelControl* hLabel = new LabelControl( "Hue" );      
@@ -138,8 +140,8 @@ namespace HSL
 
         // Reset button
         LabelControl* resetButton = new LabelControl( "Reset" );
-        resetButton->setBackColor( Color::Gray );
-        resetButton->setActiveColor( Color::Blue );
+        resetButton->setBackColor( osg::Vec4(0.5,0.5,0.5,1) );
+        resetButton->setActiveColor( osg::Vec4(0.5,0.5,1,1) );
         resetButton->addEventHandler( new ResetHSL(hAdjust, sAdjust, lAdjust) );
         s_layerBox->setControl( 1, 4, resetButton );
     }
diff --git a/src/applications/osgearth_controls/osgearth_controls.cpp b/src/applications/osgearth_controls/osgearth_controls.cpp
index 6804d2c..eadd9b0 100644
--- a/src/applications/osgearth_controls/osgearth_controls.cpp
+++ b/src/applications/osgearth_controls/osgearth_controls.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -48,7 +48,7 @@ int main(int argc, char** argv)
         root->addChild( node );
 
     // create a surface to house the controls
-    ControlCanvas* cs = ControlCanvas::get( &viewer );
+    ControlCanvas* cs = ControlCanvas::getOrCreate( &viewer );
 
     viewer.setSceneData( root );
     viewer.setCameraManipulator( new osgEarth::Util::EarthManipulator );
@@ -90,14 +90,6 @@ struct RotateImage : public ControlEventHandler
     }
 };
 
-struct SetImageOpacity : public ControlEventHandler
-{
-    void onValueChanged( Control* control, float value )
-    {
-        s_imageControl->setOpacity( value );
-    }
-};
-
 void
 createControls( ControlCanvas* cs )
 {
@@ -111,7 +103,7 @@ createControls( ControlCanvas* cs )
         center->setVertAlign( Control::ALIGN_CENTER );
 
         // Add an image:
-        osg::ref_ptr<osg::Image> image = osgDB::readImageFile("http://demo.pelicanmapping.com/rmweb/readymap_logo.png");
+        osg::ref_ptr<osg::Image> image = osgDB::readImageFile("http://osgearth.org/images/octocat-icon.png");
         if ( image.valid() )
         {
             s_imageControl = new ImageControl( image.get() );
@@ -145,22 +137,6 @@ createControls( ControlCanvas* cs )
         }
         center->addControl( rotateBox );
 
-        // Opacity slider:
-        HBox* opacityBox = new HBox();
-        opacityBox->setChildVertAlign( Control::ALIGN_CENTER );
-        opacityBox->setHorizFill( true );
-        opacityBox->setBackColor( Color::Green );
-        {
-            opacityBox->addControl( new LabelControl("Opacity: ") );
-
-            HSliderControl* opacitySlider = new HSliderControl( 0.0, 1.0, 1.0 );
-            opacitySlider->addEventHandler( new SetImageOpacity() );
-            opacitySlider->setHeight( 8.0f );
-            opacitySlider->setHorizFill( true, 200 );
-            opacityBox->addControl( opacitySlider );
-        }
-        center->addControl( opacityBox );
-
         cs->addControl( center );
     }
 
diff --git a/src/applications/osgearth_conv/CMakeLists.txt b/src/applications/osgearth_conv/CMakeLists.txt
new file mode 100644
index 0000000..a77d703
--- /dev/null
+++ b/src/applications/osgearth_conv/CMakeLists.txt
@@ -0,0 +1,7 @@
+INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} )
+SET(TARGET_LIBRARIES_VARS OSG_LIBRARY OSGDB_LIBRARY OSGUTIL_LIBRARY OPENTHREADS_LIBRARY)
+
+SET(TARGET_SRC osgearth_conv.cpp )
+
+#### end var setup  ###
+SETUP_APPLICATION(osgearth_conv)
\ No newline at end of file
diff --git a/src/applications/osgearth_conv/osgearth_conv.cpp b/src/applications/osgearth_conv/osgearth_conv.cpp
new file mode 100644
index 0000000..b486e53
--- /dev/null
+++ b/src/applications/osgearth_conv/osgearth_conv.cpp
@@ -0,0 +1,405 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#define LC "[osgearth_conv] "
+
+#include <osgEarth/Notify>
+#include <osgEarth/Profile>
+#include <osgEarth/TileSource>
+#include <osgEarth/TileHandler>
+#include <osgEarth/TileVisitor>
+#include <osg/ArgumentParser>
+#include <osg/Timer>
+#include <iomanip>
+
+using namespace osgEarth;
+
+// documentation
+int usage(char** argv)
+{
+    std::cout
+        << "Converts tiles from one format to another.\n\n"
+        << argv[0]
+        << "\n    --in [prop_name] [prop_value]       : set an input property"
+        << "\n    --out [prop_name] [prop_value]      : set an output property"
+        << "\n    --elevation                         : convert as elevation data (default is image)"
+        << "\n    --profile [profile def]             : set an output profile (optional; default = same as input)"
+        << "\n    --min-level [int]                   : minimum level of detail"
+        << "\n    --max-level [int]                   : maximum level of detail"
+        << std::endl;
+        
+    return 0;
+}
+
+
+// TileHandler that copies images from one tilesource to another.
+struct TileSourceToTileSource : public TileHandler
+{
+    TileSourceToTileSource(TileSource* source, TileSource* dest, bool heightFields)
+        : _source(source), _dest(dest), _heightFields(heightFields)
+    {
+        //nop
+    }
+
+    bool handleTile(const TileKey& key, const TileVisitor& tv)
+    {
+        bool ok = false;
+        if (_heightFields)
+        {
+            osg::ref_ptr<osg::HeightField> hf = _source->createHeightField(key);
+            if ( hf.valid() )
+                ok = _dest->storeHeightField(key, hf.get(), 0L);
+        }
+        else
+        {
+            osg::ref_ptr<osg::Image> image = _source->createImage(key);
+            if ( image.valid() )
+                ok = _dest->storeImage(key, image.get(), 0L);
+        }
+        return ok;
+    }
+    
+    bool hasData(const TileKey& key) const
+    {
+        return _source->hasData(key);
+    }
+
+    TileSource* _source;
+    TileSource* _dest;
+    bool        _heightFields;
+};
+
+
+// TileHandler that copies images from an ImageLayer to a TileSource.
+// This will automatically handle any mosaicing and reprojection that is
+// necessary to translate from one Profile/SRS to another.
+struct ImageLayerToTileSource : public TileHandler
+{
+    ImageLayerToTileSource(ImageLayer* source, TileSource* dest)
+        : _source(source), _dest(dest)
+    {
+        //nop
+    }
+
+    bool handleTile(const TileKey& key, const TileVisitor& tv)
+    {
+        bool ok = false;
+        GeoImage image = _source->createImage(key);
+        if ( image.valid() )
+            ok = _dest->storeImage(key, image.getImage(), 0L);
+        return ok;
+    }
+    
+    bool hasData(const TileKey& key) const
+    {
+        return _source->getTileSource()->hasData(key);
+    }
+
+    osg::ref_ptr<ImageLayer> _source;
+    TileSource*              _dest;
+};
+
+
+// TileHandler that copies images from an ElevationLayer to a TileSource.
+// This will automatically handle any mosaicing and reprojection that is
+// necessary to translate from one Profile/SRS to another.
+struct ElevationLayerToTileSource : public TileHandler
+{
+    ElevationLayerToTileSource(ElevationLayer* source, TileSource* dest)
+        : _source(source), _dest(dest)
+    {
+        //nop
+    }
+
+    bool handleTile(const TileKey& key, const TileVisitor& tv)
+    {
+        bool ok = false;
+        GeoHeightField hf = _source->createHeightField(key, 0L);
+        if ( hf.valid() )
+            ok = _dest->storeHeightField(key, hf.getHeightField(), 0L);
+        return ok;
+    }
+    
+    bool hasData(const TileKey& key) const
+    {
+        return _source->getTileSource()->hasData(key);
+    }
+
+    osg::ref_ptr<ElevationLayer> _source;
+    TileSource*                  _dest;
+};
+
+
+// Custom progress reporter
+struct ProgressReporter : public osgEarth::ProgressCallback
+{
+    bool reportProgress(double             current, 
+                        double             total, 
+                        unsigned           currentStage,
+                        unsigned           totalStages,
+                        const std::string& msg )
+    {
+        _mutex.lock();
+
+        float percentage = current/total*100.0f;
+        std::cout 
+            << std::fixed
+            << std::setprecision(1) << "\r" 
+            << (int)current << "/" << (int)total
+            << " (" << percentage << "%)"
+            << "                        "
+            << std::flush;
+
+        if ( percentage >= 100.0f )
+            std::cout << std::endl;
+
+        _mutex.unlock();
+
+        return false;
+    }
+
+    Threading::Mutex _mutex;
+};
+
+
+/**
+ * Command-line tool that copies the contents of one TileSource
+ * to another. All arguments are Config name/value pairs, so you need
+ * to look in the header file for each driver's Options structure for
+ * options :)
+ *
+ * Example: copy a GDAL file to an MBTiles repo:
+ *
+ *   osgearth_conv
+ *      --in driver gdal
+ *      --in url world.tif
+ *      --out driver mbtiles
+ *      --out filename world.db
+ *
+ * The "in" properties come from the GDALOptions getConfig method. The
+ * "out" properties come from the MBTilesOptions getConfig method.
+ *
+ * Other arguments:
+ *
+ *      --elevation           : convert as elevation data (instead of image data)
+ *      --profile [profile]   : reproject to the target profile, e.g. "wgs84"
+ *      --min-level [int]     : min level of detail to copy
+ *      --max-level [int]     : max level of detail to copy
+ *      --threads [n]         : threads to use (may crash. Careful.)
+ *
+ *      --extents [minLat] [minLong] [maxLat] [maxLong] : Lat/Long extends to copy (*)
+ *
+ * Of course, the output driver must support writing (by implementing
+ * the ReadWriteTileSource interface).
+ */
+int
+main(int argc, char** argv)
+{
+    osg::ArgumentParser args(&argc,argv);
+
+    if ( argc == 1 )
+        return usage(argv);
+
+    typedef std::map<std::string,std::string> KeyValue;
+    std::string key, value;
+
+    // collect input configuration:
+    Config inConf;
+    while( args.read("--in", key, value) )
+        inConf.set(key, value);
+
+    TileSourceOptions inOptions(inConf);
+    osg::ref_ptr<TileSource> input = TileSourceFactory::create(inOptions);
+    if ( !input.valid() )
+    {
+        OE_WARN << LC << "Failed to open input" << std::endl;
+        return -1;
+    }
+
+    TileSource::Status inputStatus = input->open();
+    if ( inputStatus.isError() )
+    {
+        OE_WARN << LC << "Error initializing input" << std::endl;
+        return -1;
+    }
+
+    // collect output configuration:
+    Config outConf;
+    while( args.read("--out", key, value) )
+        outConf.set(key, value);
+
+    // heightfields?
+    bool heightFields = args.read("--heightfield") || args.read("--hf") || args.read("--elevation");
+    if ( heightFields )
+        OE_INFO << LC << "Converting heightfield tiles" << std::endl;
+    else
+        OE_INFO << LC << "Converting image tiles" << std::endl;
+
+    // are we changing profiles?
+    osg::ref_ptr<const Profile> outputProfile = input->getProfile();
+    std::string profileString;
+    bool isSameProfile = true;
+
+    if ( args.read("--profile", profileString) )
+    {
+        outputProfile = Profile::create(profileString);
+        if ( !outputProfile.valid() || !outputProfile->isOK() )
+        {
+            OE_WARN << LC << "Output profile is not recognized" << std::endl;
+            return -1;
+        }
+        isSameProfile = outputProfile->isHorizEquivalentTo(input->getProfile());
+    }
+
+    // set the output profile.
+    ProfileOptions profileOptions = outputProfile->toProfileOptions();
+    outConf.add("profile", profileOptions.getConfig());
+
+    // open the output tile source:
+    TileSourceOptions outOptions(outConf);
+    osg::ref_ptr<TileSource> output = TileSourceFactory::create(outOptions);
+    if ( !output.valid() )
+    {
+        OE_WARN << LC << "Failed to open output" << std::endl;
+        return -1;
+    }
+
+    TileSource::Status outputStatus = output->open(TileSource::MODE_WRITE | TileSource::MODE_CREATE);
+    if ( outputStatus.isError() )
+    {
+        OE_WARN << LC << "Error initializing output" << std::endl;
+        return -1;
+    }
+
+    // Dump out some stuff...
+    OE_NOTICE << LC << "FROM:\n"
+        << inConf.toJSON(true)
+        << std::endl;
+
+    OE_NOTICE << LC << "TO:\n"
+        << outConf.toJSON(true)
+        << std::endl;
+
+    // create the visitor.
+    osg::ref_ptr<TileVisitor> visitor;
+
+    unsigned numThreads = 1;
+    if (args.read("--threads", numThreads))
+    {
+        MultithreadedTileVisitor* mtv = new MultithreadedTileVisitor();
+        mtv->setNumThreads( numThreads < 1 ? 1 : numThreads );
+        visitor = mtv;
+    }
+    else
+    {
+        visitor = new TileVisitor();
+    }
+
+    // If the profiles are identical, just use a tile copier.
+    if ( isSameProfile )
+    {
+        OE_NOTICE << LC << "Profiles match - initiating simple tile copy" << std::endl;
+        visitor->setTileHandler( new TileSourceToTileSource(input.get(), output.get(), heightFields) );
+    }
+    else
+    {
+        OE_NOTICE << LC << "Profiles differ - initiating tile transformation" << std::endl;
+
+        if (heightFields)
+        {
+            ElevationLayer* layer = new ElevationLayer(ElevationLayerOptions(), input.get());
+            if ( !layer->getProfile() || !layer->getProfile()->isOK() )
+            {
+                OE_WARN << LC << "Input profile is not valid" << std::endl;
+                return -1;
+            }
+            visitor->setTileHandler( new ElevationLayerToTileSource(layer, output.get()) );
+        }
+        else
+        {
+            ImageLayer* layer = new ImageLayer(ImageLayerOptions(), input.get());
+            if ( !layer->getProfile() || !layer->getProfile()->isOK() )
+            {
+                OE_WARN << LC << "Input profile is not valid" << std::endl;
+                return -1;
+            }
+            visitor->setTileHandler( new ImageLayerToTileSource(layer, output.get()) );
+        }
+    }
+    
+    // Set the level limits:
+    unsigned minLevel = ~0;
+    bool minLevelSet = args.read("--min-level", minLevel);
+
+    unsigned maxLevel = 0;
+    bool maxLevelSet = args.read("--max-level", maxLevel);
+
+    // figure out the max source level:
+    if ( !minLevelSet || !maxLevelSet )
+    {
+        for(DataExtentList::const_iterator i = input->getDataExtents().begin();
+            i != input->getDataExtents().end();
+            ++i)
+        {
+            if ( !maxLevelSet && i->maxLevel().isSet() && i->maxLevel().value() > maxLevel )
+                maxLevel = i->maxLevel().value();
+            if ( !minLevelSet && i->minLevel().isSet() && i->minLevel().value() < minLevel )
+                minLevel = i->minLevel().value();
+        }
+    }
+       
+    if ( minLevel < ~0 )
+    {
+        visitor->setMinLevel( minLevel );
+    }
+
+    if ( maxLevel > 0 )
+    {
+        maxLevel = outputProfile->getEquivalentLOD( input->getProfile(), maxLevel );
+        visitor->setMaxLevel( maxLevel );
+        OE_NOTICE << LC << "Calculated max level = " << maxLevel << std::endl;
+    }
+
+    // set the extents:
+    double minlat, minlon, maxlat, maxlon;
+    while( args.read("--extents", minlat, minlon, maxlat, maxlon) )
+    {
+        GeoExtent extent(SpatialReference::get("wgs84"), minlon, minlat, maxlon, maxlat);
+        visitor->addExtent( extent );
+    }
+
+    // Ready!!!
+    std::cout << "Working..." << std::endl;
+
+    visitor->setProgressCallback( new ProgressReporter() );
+
+    osg::Timer_t t0 = osg::Timer::instance()->tick();
+
+    visitor->run( outputProfile.get() );
+
+    osg::Timer_t t1 = osg::Timer::instance()->tick();
+
+    std::cout
+        << "Time = " 
+        << std::fixed
+        << std::setprecision(1)
+        << osg::Timer::instance()->delta_s(t0, t1)
+        << " seconds." << std::endl;
+
+    return 0;
+}
diff --git a/src/applications/osgearth_demo/CMakeLists.txt b/src/applications/osgearth_demo/CMakeLists.txt
index a5a0cea..e1fc9f2 100644
--- a/src/applications/osgearth_demo/CMakeLists.txt
+++ b/src/applications/osgearth_demo/CMakeLists.txt
@@ -1,11 +1,23 @@
-INCLUDE( ${QT_USE_FILE} )
-
 INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} ${QT_INCLUDES})
 
 SET(MOC_HDRS
 )
 
-QT4_WRAP_CPP( MOC_SRCS ${MOC_HDRS} OPTIONS "-f" )
+IF(Qt5Widgets_FOUND)
+    QT5_WRAP_CPP( MOC_SRCS ${MOC_HDRS} OPTIONS "-f" )
+	SET(TARGET_ADDED_LIBRARIES
+        osgEarthQt
+    )
+ELSE()
+    INCLUDE( ${QT_USE_FILE} )
+    QT4_WRAP_CPP( MOC_SRCS ${MOC_HDRS} OPTIONS "-f" )
+	SET(TARGET_ADDED_LIBRARIES
+        osgEarthQt
+        ${QT_QTCORE_LIBRARY}
+        ${QT_QTGUI_LIBRARY}
+        ${QT_QTOPENGL_LIBRARY}
+    )
+ENDIF()
 
 SET(TARGET_H
     ${LIB_QT_RCS}
@@ -17,12 +29,5 @@ SET(TARGET_SRC
     osgearth_demo.cpp
 )
 
-SET(TARGET_ADDED_LIBRARIES
-    osgEarthQt
-    ${QT_QTCORE_LIBRARY}
-    ${QT_QTGUI_LIBRARY}
-    ${QT_QTOPENGL_LIBRARY}
-)
-
 #### end var setup  ###
 SETUP_APPLICATION(osgearth_demo)
diff --git a/src/applications/osgearth_demo/osgearth_demo.cpp b/src/applications/osgearth_demo/osgearth_demo.cpp
index f7a44b9..5bb4579 100644
--- a/src/applications/osgearth_demo/osgearth_demo.cpp
+++ b/src/applications/osgearth_demo/osgearth_demo.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -25,9 +25,9 @@
 #include <osgEarthQt/ViewerWidget>
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
-#include <QtGui/QApplication>
-#include <QtGui/QMainWindow>
-#include <QtGui/QStatusBar>
+#include <QApplication>
+#include <QMainWindow>
+#include <QStatusBar>
 
 #ifdef Q_WS_X11
 #include <X11/Xlib.h>
@@ -84,7 +84,7 @@ main(int argc, char** argv)
     grid->setControl( 0, 3, new LabelControl("Go to:"));
     grid->setControl( 1, 3, new LabelControl("Double-click"));
 
-    ControlCanvas::get(&viewer)->addControl(vbox);
+    ControlCanvas::getOrCreate(&viewer)->addControl(vbox);
 
     // Load an earth file
     osg::Node* node = MapNodeHelper().load(arguments, &viewer);
@@ -108,7 +108,7 @@ main(int argc, char** argv)
     win.setCentralWidget( viewerWidget );
     win.setGeometry(100, 100, 1024, 800);
 
-    win.statusBar()->showMessage(QString("osgEarth.   Terrain on Demand.   Copyright 2013 Pelican Mapping.   Please visit http://osgearth.org"));
+    win.statusBar()->showMessage(QString("osgEarth.  Copyright 2014 Pelican Mapping.   Please visit http://osgearth.org"));
 
     win.show();
     app.exec();
diff --git a/src/applications/osgearth_elevation/osgearth_elevation.cpp b/src/applications/osgearth_elevation/osgearth_elevation.cpp
index df40b4d..67a4097 100644
--- a/src/applications/osgearth_elevation/osgearth_elevation.cpp
+++ b/src/applications/osgearth_elevation/osgearth_elevation.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -41,6 +41,7 @@ static LabelControl*  s_posLabel    = 0L;
 static LabelControl*  s_vdaLabel    = 0L;
 static LabelControl*  s_mslLabel    = 0L;
 static LabelControl*  s_haeLabel    = 0L;
+static LabelControl*  s_mapLabel    = 0L;
 static LabelControl*  s_resLabel    = 0L;
 
 
@@ -63,14 +64,17 @@ struct QueryElevationHandler : public osgGA::GUIEventHandler
 
         // look under the mouse:
         osg::Vec3d world;
-        if ( _terrain->getWorldCoordsUnderMouse(view, x, y, world) )
+        osgUtil::LineSegmentIntersector::Intersections hits;
+        if ( view->computeIntersections(x, y, hits) )
         {
+            world = hits.begin()->getWorldIntersectPoint();
+
             // convert to map coords:
             GeoPoint mapPoint;
             mapPoint.fromWorld( _terrain->getSRS(), world );
 
             // do an elevation query:
-            double query_resolution = 0.1; // 1/10th of a degree
+            double query_resolution = 0; // 1/10th of a degree
             double out_hamsl        = 0.0;
             double out_resolution   = 0.0;
 
@@ -97,8 +101,14 @@ struct QueryElevationHandler : public osgGA::GUIEventHandler
                 s_mslLabel->setText( Stringify() << out_hamsl );
                 s_haeLabel->setText( Stringify() << mapPointGeodetic.z() );
                 s_resLabel->setText( Stringify() << out_resolution );
+
                 yes = true;
             }
+
+            // finally, get a normal ISECT HAE point.
+            GeoPoint isectPoint;
+            isectPoint.fromWorld( _terrain->getSRS()->getGeodeticSRS(), world );
+            s_mapLabel->setText( Stringify() << isectPoint.alt() );
         }
 
         if (!yes)
@@ -112,7 +122,8 @@ struct QueryElevationHandler : public osgGA::GUIEventHandler
 
     bool handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa )
     {
-        if ( ea.getEventType() == osgGA::GUIEventAdapter::MOVE )
+        if (ea.getEventType() == osgGA::GUIEventAdapter::MOVE &&
+            aa.asView()->getFrameStamp()->getFrameNumber() % 10 == 0)
         {
             osgViewer::View* view = static_cast<osgViewer::View*>(aa.asView());
             update( ea.getX(), ea.getY(), view );
@@ -143,6 +154,10 @@ int main(int argc, char** argv)
     }
 
     osg::Group* root = new osg::Group();
+    viewer.setSceneData( root );
+    
+    // install the programmable manipulator.
+    viewer.setCameraManipulator( new osgEarth::Util::EarthManipulator() );
 
     // The MapNode will render the Map object in the scene graph.
     root->addChild( s_mapNode );
@@ -153,22 +168,24 @@ int main(int argc, char** argv)
     grid->setControl(0,1,new LabelControl("Vertical Datum:"));
     grid->setControl(0,2,new LabelControl("Height (MSL):"));
     grid->setControl(0,3,new LabelControl("Height (HAE):"));
-    grid->setControl(0,4,new LabelControl("Resolution:"));
+    grid->setControl(0,4,new LabelControl("Isect  (HAE):"));
+    grid->setControl(0,5,new LabelControl("Resolution:"));
 
     s_posLabel = grid->setControl(1,0,new LabelControl(""));
     s_vdaLabel = grid->setControl(1,1,new LabelControl(""));
     s_mslLabel = grid->setControl(1,2,new LabelControl(""));
     s_haeLabel = grid->setControl(1,3,new LabelControl(""));
-    s_resLabel = grid->setControl(1,4,new LabelControl(""));
+    s_mapLabel = grid->setControl(1,4,new LabelControl(""));
+    s_resLabel = grid->setControl(1,5,new LabelControl(""));
 
     const SpatialReference* mapSRS = s_mapNode->getMapSRS();
     s_vdaLabel->setText( mapSRS->getVerticalDatum() ? 
         mapSRS->getVerticalDatum()->getName() : 
         Stringify() << "geodetic (" << mapSRS->getEllipsoid()->getName() << ")" );
 
-    ControlCanvas::get(&viewer,true)->addControl(grid);
-
-    viewer.setSceneData( root );
+    ControlCanvas* canvas = new ControlCanvas();    
+    root->addChild(canvas);
+    canvas->addControl( grid );
 
     // An event handler that will respond to mouse clicks:
     viewer.addEventHandler( new QueryElevationHandler() );
@@ -178,9 +195,5 @@ int main(int argc, char** argv)
     viewer.addEventHandler(new osgViewer::WindowSizeHandler());
     viewer.addEventHandler(new osgGA::StateSetManipulator(viewer.getCamera()->getOrCreateStateSet()));
 
-    // install the programmable manipulator.
-    if ( s_mapNode->getMap()->isGeocentric() )
-        viewer.setCameraManipulator( new osgEarth::Util::EarthManipulator() );
-
     return viewer.run();
 }
diff --git a/src/applications/osgearth_featureeditor/osgearth_featureeditor.cpp b/src/applications/osgearth_featureeditor/osgearth_featureeditor.cpp
index 11b91c4..0ce3033 100644
--- a/src/applications/osgearth_featureeditor/osgearth_featureeditor.cpp
+++ b/src/applications/osgearth_featureeditor/osgearth_featureeditor.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -218,7 +218,7 @@ int main(int argc, char** argv)
     s_root->addChild( s_featureNode );
 
     //Setup the controls
-    ControlCanvas* canvas = ControlCanvas::get( &viewer );
+    ControlCanvas* canvas = ControlCanvas::getOrCreate( &viewer );
     s_root->addChild( canvas );
     Grid *toolbar = createToolBar( );
     canvas->addControl( toolbar );
diff --git a/src/applications/osgearth_featurefilter/osgearth_featurefilter.cpp b/src/applications/osgearth_featurefilter/osgearth_featurefilter.cpp
index b4398af..de3115c 100644
--- a/src/applications/osgearth_featurefilter/osgearth_featurefilter.cpp
+++ b/src/applications/osgearth_featurefilter/osgearth_featurefilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/applications/osgearth_featureinfo/osgearth_featureinfo.cpp b/src/applications/osgearth_featureinfo/osgearth_featureinfo.cpp
index 86cebaa..6d99d37 100644
--- a/src/applications/osgearth_featureinfo/osgearth_featureinfo.cpp
+++ b/src/applications/osgearth_featureinfo/osgearth_featureinfo.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/applications/osgearth_featuremanip/osgearth_featuremanip.cpp b/src/applications/osgearth_featuremanip/osgearth_featuremanip.cpp
index 8875071..cd2cadf 100644
--- a/src/applications/osgearth_featuremanip/osgearth_featuremanip.cpp
+++ b/src/applications/osgearth_featuremanip/osgearth_featuremanip.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/applications/osgearth_featurequery/osgearth_featurequery.cpp b/src/applications/osgearth_featurequery/osgearth_featurequery.cpp
index 4aee9fd..6da05e2 100644
--- a/src/applications/osgearth_featurequery/osgearth_featurequery.cpp
+++ b/src/applications/osgearth_featurequery/osgearth_featurequery.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -84,7 +84,7 @@ main(int argc, char** argv)
             FeatureQueryTool* tool = new FeatureQueryTool( mapNode );
             viewer.addEventHandler( tool );
 
-            VBox* readout = ControlCanvas::get(&viewer)->addControl( new VBox() );
+            VBox* readout = ControlCanvas::getOrCreate(&viewer)->addControl( new VBox() );
             readout->setHorizAlign( Control::ALIGN_RIGHT );
             readout->setBackColor( Color(Color::Black,0.8) );
             tool->addCallback( new FeatureReadoutCallback(readout) );
diff --git a/src/applications/osgearth_features/osgearth_features.cpp b/src/applications/osgearth_features/osgearth_features.cpp
index 7ce2b65..e666ae1 100644
--- a/src/applications/osgearth_features/osgearth_features.cpp
+++ b/src/applications/osgearth_features/osgearth_features.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/applications/osgearth_fog/CMakeLists.txt b/src/applications/osgearth_fog/CMakeLists.txt
new file mode 100644
index 0000000..7e66e05
--- /dev/null
+++ b/src/applications/osgearth_fog/CMakeLists.txt
@@ -0,0 +1,7 @@
+INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} )
+SET(TARGET_LIBRARIES_VARS OSG_LIBRARY OSGDB_LIBRARY OSGUTIL_LIBRARY OSGVIEWER_LIBRARY OPENTHREADS_LIBRARY)
+
+SET(TARGET_SRC osgearth_fog.cpp )
+
+#### end var setup  ###
+SETUP_APPLICATION(osgearth_fog)
\ No newline at end of file
diff --git a/src/applications/osgearth_viewer/osgearth_viewer.cpp b/src/applications/osgearth_fog/osgearth_fog.cpp
similarity index 62%
copy from src/applications/osgearth_viewer/osgearth_viewer.cpp
copy to src/applications/osgearth_fog/osgearth_fog.cpp
index de97bee..e2ceacf 100644
--- a/src/applications/osgearth_viewer/osgearth_viewer.cpp
+++ b/src/applications/osgearth_fog/osgearth_fog.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -19,15 +19,17 @@
 
 #include <osg/Notify>
 #include <osgViewer/Viewer>
+#include <osgEarth/MapNode>
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/ExampleResources>
-#include <osgEarthAnnotation/ModelNode>
+#include <osgEarthUtil/Fog>
+#include <osg/Fog>
+
 
 #define LC "[viewer] "
 
 using namespace osgEarth;
 using namespace osgEarth::Util;
-using namespace osgEarth::Annotation;
 
 int
 usage(const char* name)
@@ -62,16 +64,45 @@ main(int argc, char** argv)
 
     // load an earth file, and support all or our example command-line options
     // and earth file <external> tags    
-    osg::Node* node = MapNodeHelper().load( arguments, &viewer );
+    osg::Node* node = MapNodeHelper().load( arguments, &viewer );    
     if ( node )
     {
+        MapNode* mapNode = MapNode::findMapNode( node );
+
+        FogEffect* fogEffect = new FogEffect;
+        fogEffect->attach( node->getOrCreateStateSet() );
+        
+        float maxDensity = 0.000125; 
+        float fogStartHeight = 10000.0f;        
+
+        // Setup a Fog state attribute
+        osg::Vec4 fogColor(0.66f, 0.7f, 0.81f, 1.0f);
+        osg::Fog* fog = new osg::Fog;        
+        fog->setColor( fogColor ); //viewer.getCamera()->getClearColor() );                
+        fog->setDensity( 0 );
+        node->getOrCreateStateSet()->setAttributeAndModes( fog, osg::StateAttribute::ON );                
+
         viewer.setSceneData( node );
 
         // configure the near/far so we don't clip things that are up close
         viewer.getCamera()->setNearFarRatio(0.00002);
         viewer.getCamera()->setSmallFeatureCullingPixelSize(-1.0f);
 
-        viewer.run();
+        while (!viewer.done())
+        {
+            // Get the height above the terrain
+            osg::Vec3d eye, center, up;
+            viewer.getCamera()->getViewMatrixAsLookAt(eye,center, up);
+            GeoPoint map;
+            map.fromWorld( mapNode->getMapSRS(), eye );            
+            
+            // Compute the fog density based on the camera height
+            float ratio = ((fogStartHeight - map.z()) / fogStartHeight);
+            ratio = osg::clampBetween(ratio, 0.0f, 1.0f);
+            float density = ratio * maxDensity;
+            fog->setDensity( density );            
+            viewer.frame();
+        }        
     }
     else
     {
diff --git a/src/applications/osgearth_graticule/osgearth_graticule.cpp b/src/applications/osgearth_graticule/osgearth_graticule.cpp
index afc3d85..1fa0306 100644
--- a/src/applications/osgearth_graticule/osgearth_graticule.cpp
+++ b/src/applications/osgearth_graticule/osgearth_graticule.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -95,8 +95,12 @@ main(int argc, char** argv)
     }
 
     // mouse coordinate readout:
+    ControlCanvas* canvas = new ControlCanvas();
+    root->addChild( canvas );
+
     LabelControl* readout = new LabelControl();
-    ControlCanvas::get( &viewer, true )->addControl( readout );
+    canvas->addControl( readout );
+
     MouseCoordsTool* tool = new MouseCoordsTool( mapNode );
     tool->addCallback( new MouseCoordsLabelCallback(readout, formatter) );
     viewer.addEventHandler( tool );
diff --git a/src/applications/osgearth_imageoverlay/osgearth_imageoverlay.cpp b/src/applications/osgearth_imageoverlay/osgearth_imageoverlay.cpp
index e151c2d..5db8eae 100644
--- a/src/applications/osgearth_imageoverlay/osgearth_imageoverlay.cpp
+++ b/src/applications/osgearth_imageoverlay/osgearth_imageoverlay.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -45,7 +45,7 @@ static Grid* s_layerBox = NULL;
 osg::Node*
 createControlPanel( osgViewer::View* view )
 {
-    ControlCanvas* canvas = ControlCanvas::get( view );
+    ControlCanvas* canvas = ControlCanvas::getOrCreate( view );
 
     // the outer container:
     s_layerBox = new Grid();
diff --git a/src/applications/osgearth_los/osgearth_los.cpp b/src/applications/osgearth_los/osgearth_los.cpp
index 53a247e..6ca4386 100644
--- a/src/applications/osgearth_los/osgearth_los.cpp
+++ b/src/applications/osgearth_los/osgearth_los.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/applications/osgearth_manip/osgearth_manip.cpp b/src/applications/osgearth_manip/osgearth_manip.cpp
index 9d5c5be..6a7ef1d 100644
--- a/src/applications/osgearth_manip/osgearth_manip.cpp
+++ b/src/applications/osgearth_manip/osgearth_manip.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -22,11 +22,13 @@
 #include <osg/Notify>
 #include <osg/Timer>
 #include <osg/ShapeDrawable>
+#include <osg/PositionAttitudeTransform>
 #include <osgGA/StateSetManipulator>
 #include <osgGA/GUIEventHandler>
 #include <osgViewer/Viewer>
 #include <osgViewer/ViewerEventHandlers>
 #include <osgEarth/GeoMath>
+#include <osgEarth/GeoTransform>
 #include <osgEarth/MapNode>
 #include <osgEarth/TerrainEngineNode>
 #include <osgEarth/Viewpoint>
@@ -35,7 +37,6 @@
 #include <osgEarthUtil/Controls>
 #include <osgEarthUtil/ExampleResources>
 #include <osgEarthAnnotation/AnnotationUtils>
-#include <osgEarthAnnotation/LocalGeometryNode>
 #include <osgEarthAnnotation/LabelNode>
 #include <osgEarthSymbology/Style>
 
@@ -259,13 +260,21 @@ namespace
      */
     struct Simulator : public osgGA::GUIEventHandler
     {
-        Simulator( osg::Group* root, EarthManipulator* manip, MapNode* mapnode )
-            : _manip(manip), _mapnode(mapnode), _lat0(55.0), _lon0(45.0), _lat1(-55.0), _lon1(-45.0)
+        Simulator( osg::Group* root, EarthManipulator* manip, MapNode* mapnode, osg::Node* model)
+            : _manip(manip), _mapnode(mapnode), _model(model), _lat0(55.0), _lon0(45.0), _lat1(-55.0), _lon1(-45.0)
         {
-            osg::Node* geode = AnnotationUtils::createSphere( 25.0, osg::Vec4(1,.7,.4,1) );
+            if ( !model )
+            { 
+                _model = AnnotationUtils::createSphere( 250.0, osg::Vec4(1,.7,.4,1) );
+            }
             
-            _xform = new osg::MatrixTransform();
-            _xform->addChild( geode );
+            _xform = new GeoTransform();
+            _xform->setTerrain(mapnode->getTerrain());
+
+            _pat = new osg::PositionAttitudeTransform();
+            _pat->addChild( _model );
+
+            _xform->addChild( _pat );
 
             _cam = new osg::Camera();
             _cam->setRenderOrder( osg::Camera::NESTED_RENDER, 1 );
@@ -288,21 +297,19 @@ namespace
                 double t = fmod( osg::Timer::instance()->time_s(), 600.0 ) / 600.0;
                 double lat, lon;
                 GeoMath::interpolate( D2R*_lat0, D2R*_lon0, D2R*_lat1, D2R*_lon1, t, lat, lon );
-                GeoPoint p( SpatialReference::create("wgs84"), R2D*lon, R2D*lat, 25000.0, ALTMODE_ABSOLUTE );
-                osg::Vec3d world;
-                p.toWorld( world );
-                _xform->setMatrix( osg::Matrix::translate(world) );
+                GeoPoint p( SpatialReference::create("wgs84"), R2D*lon, R2D*lat, 2500.0 );
+                double bearing = GeoMath::bearing(D2R*_lat1, D2R*_lon1, lat, lon);
+                _xform->setPosition( p );
+                _pat->setAttitude(osg::Quat(bearing, osg::Vec3d(0,0,1)));
                 _label->setPosition( p );
             }
             else if ( ea.getEventType() == ea.KEYDOWN && ea.getKey() == 't' )
-            {
-                _manip->setTetherNode( _manip->getTetherNode() ? 0L : _xform.get() );
-                if ( _manip->getTetherNode() )
-                {
-                    _manip->getSettings()->setArcViewpointTransitions( false );
-                    _manip->setViewpoint(Viewpoint(osg::Vec3d(0,0,0), 45, -25, 250000));
-                    _manip->getSettings()->setArcViewpointTransitions( true );
-                }
+            {                                
+                _manip->getSettings()->setTetherMode(osgEarth::Util::EarthManipulator::TETHER_CENTER_AND_HEADING);
+                _manip->setTetherNode( _manip->getTetherNode() ? 0L : _xform.get(), 2.0 );
+                Viewpoint vp = _manip->getViewpoint();
+                vp.setRange(5000);
+                _manip->setViewpoint(vp);
                 return true;
             }
             return false;
@@ -311,9 +318,11 @@ namespace
         MapNode*                           _mapnode;
         EarthManipulator*                  _manip;
         osg::ref_ptr<osg::Camera>          _cam;
-        osg::ref_ptr<osg::MatrixTransform> _xform;
+        osg::ref_ptr<GeoTransform>         _xform;
+        osg::ref_ptr<osg::PositionAttitudeTransform> _pat;
         double                             _lat0, _lon0, _lat1, _lon1;
         LabelNode*                         _label;
+        osg::Node*                         _model;
     };
 }
 
@@ -321,7 +330,13 @@ namespace
 int main(int argc, char** argv)
 {
     osg::ArgumentParser arguments(&argc,argv);
-    osg::DisplaySettings::instance()->setMinimumNumStencilBits( 8 );
+
+    if (arguments.read("--help") || argc==1)
+    {
+        OE_WARN << "Usage: " << argv[0] << " [earthFile] [--model modelToLoad]"
+            << std::endl;
+        return 0;
+    }
 
     osgViewer::Viewer viewer(arguments);
 
@@ -355,10 +370,16 @@ int main(int argc, char** argv)
         }
     }
 
+    // user model?
+    osg::Node* model = 0L;
+    std::string modelFile;
+    if (arguments.read("--model", modelFile))
+        model = osgDB::readNodeFile(modelFile);
+
     // Simulator for tethering:
-    viewer.addEventHandler( new Simulator(root, manip, mapNode) );
+    viewer.addEventHandler( new Simulator(root, manip, mapNode, model) );
     manip->getSettings()->getBreakTetherActions().push_back( EarthManipulator::ACTION_PAN );
-    manip->getSettings()->getBreakTetherActions().push_back( EarthManipulator::ACTION_GOTO );
+    manip->getSettings()->getBreakTetherActions().push_back( EarthManipulator::ACTION_GOTO );    
 
 
     viewer.setSceneData( root );
@@ -368,7 +389,7 @@ int main(int argc, char** argv)
         osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON,
         osgGA::GUIEventAdapter::MODKEY_SHIFT );
 
-    manip->getSettings()->setArcViewpointTransitions( true );
+    manip->getSettings()->setArcViewpointTransitions( true );    
     
     viewer.addEventHandler(new FlyToViewpointHandler( manip ));
     viewer.addEventHandler(new LockAzimuthHandler('u', manip));
@@ -376,5 +397,12 @@ int main(int argc, char** argv)
     viewer.addEventHandler(new ToggleArcViewpointTransitionsHandler('a', manip));
     viewer.addEventHandler(new ToggleThrowingHandler('z', manip));
 
-    return viewer.run();
+    while(!viewer.done())
+    {
+        viewer.frame();
+
+        // simulate slow frame rate
+        //OpenThreads::Thread::microSleep(1000*1000);
+    }
+    return 0;
 }
diff --git a/src/applications/osgearth_map/osgearth_map.cpp b/src/applications/osgearth_map/osgearth_map.cpp
index 89e9802..d9f6f96 100644
--- a/src/applications/osgearth_map/osgearth_map.cpp
+++ b/src/applications/osgearth_map/osgearth_map.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -43,7 +43,7 @@ main(int argc, char** argv)
 
     // add a TMS imager layer:
     TMSOptions imagery;
-    imagery.url() = "http://readymaps.org/readymap/tiles/1.0.0/7/";
+    imagery.url() = "http://readymap.org/readymap/tiles/1.0.0/7/";
     map->addImageLayer( new ImageLayer("Imagery", imagery) );
 
     // add a TMS elevation layer:
diff --git a/src/applications/osgearth_measure/osgearth_measure.cpp b/src/applications/osgearth_measure/osgearth_measure.cpp
index 2d07978..c068fee 100644
--- a/src/applications/osgearth_measure/osgearth_measure.cpp
+++ b/src/applications/osgearth_measure/osgearth_measure.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -29,7 +29,6 @@
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/AutoClipPlaneHandler>
 #include <osgEarthUtil/Controls>
-#include <osgEarthUtil/SkyNode>
 #include <osgEarthUtil/MouseCoordsTool>
 #include <osgEarthUtil/ExampleResources>
 #include <osgEarthSymbology/Color>
@@ -131,7 +130,7 @@ main(int argc, char** argv)
     viewer.addEventHandler( measureTool );
 
     //Create some controls to interact with the measuretool
-    ControlCanvas* canvas = new ControlCanvas( &viewer );
+    ControlCanvas* canvas = new ControlCanvas();
     root->addChild( canvas );
     canvas->setNodeMask( 0x1 << 1 );
 
diff --git a/src/applications/osgearth_minimap/osgearth_minimap.cpp b/src/applications/osgearth_minimap/osgearth_minimap.cpp
index bed8e6a..a6ac340 100644
--- a/src/applications/osgearth_minimap/osgearth_minimap.cpp
+++ b/src/applications/osgearth_minimap/osgearth_minimap.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/applications/osgearth_mrt/CMakeLists.txt b/src/applications/osgearth_mrt/CMakeLists.txt
new file mode 100644
index 0000000..0e2a47c
--- /dev/null
+++ b/src/applications/osgearth_mrt/CMakeLists.txt
@@ -0,0 +1,7 @@
+INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} )
+SET(TARGET_LIBRARIES_VARS OSG_LIBRARY OSGDB_LIBRARY OSGUTIL_LIBRARY OSGVIEWER_LIBRARY OPENTHREADS_LIBRARY)
+
+SET(TARGET_SRC osgearth_mrt.cpp )
+
+#### end var setup  ###
+SETUP_APPLICATION(osgearth_mrt)
\ No newline at end of file
diff --git a/src/applications/osgearth_mrt/osgearth_mrt.cpp b/src/applications/osgearth_mrt/osgearth_mrt.cpp
new file mode 100644
index 0000000..ce5610e
--- /dev/null
+++ b/src/applications/osgearth_mrt/osgearth_mrt.cpp
@@ -0,0 +1,335 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+#include <osg/Notify>
+#include <osg/Geode>
+#include <osg/Geometry>
+#include <osg/TextureRectangle>
+#include <osgGA/GUIEventHandler>
+#include <osgViewer/Viewer>
+#include <osgEarth/VirtualProgram>
+#include <osgEarthUtil/EarthManipulator>
+#include <osgEarthUtil/ExampleResources>
+
+#define LC "[viewer] "
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+// shared data.
+struct App
+{
+    osg::TextureRectangle* gcolor;
+    osg::TextureRectangle* gnormal;
+    osg::TextureRectangle* gdepth;
+};
+
+
+osg::Node*
+createMRTPass(App& app, osg::Node* sceneGraph)
+{
+    osg::Camera* rtt = new osg::Camera();
+    rtt->setRenderOrder(osg::Camera::PRE_RENDER);
+    rtt->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
+    rtt->setViewport(0, 0, app.gcolor->getTextureWidth(), app.gcolor->getTextureHeight());
+    rtt->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+    rtt->attach(osg::Camera::BufferComponent(osg::Camera::COLOR_BUFFER0), app.gcolor);
+    rtt->attach(osg::Camera::BufferComponent(osg::Camera::COLOR_BUFFER1), app.gnormal);
+    rtt->attach(osg::Camera::BufferComponent(osg::Camera::COLOR_BUFFER2), app.gdepth);
+
+    static const char* vertSource =
+        "varying float mrt_depth;\n"
+        "void oe_mrt_vertex(inout vec4 vertexClip)\n"
+        "{\n"
+        "    mrt_depth = (vertexClip.z/vertexClip.w)*0.5+1.0;\n"
+        "}\n";
+
+    static const char* fragSource =
+        "varying float mrt_depth;\n"
+        "varying vec3 oe_Normal; \n"
+        "void oe_mrt_fragment(inout vec4 color)\n"
+        "{\n"
+        "    gl_FragData[0] = color; \n"
+        "    gl_FragData[1] = vec4((oe_Normal+1.0)/2.0,1.0);\n"
+        "    gl_FragData[2] = vec4(mrt_depth,mrt_depth,mrt_depth,1.0); \n"
+        "}\n";
+
+    VirtualProgram* vp = VirtualProgram::getOrCreate( rtt->getOrCreateStateSet() );
+    vp->setFunction( "oe_mrt_vertex",   vertSource, ShaderComp::LOCATION_VERTEX_CLIP );
+    vp->setFunction( "oe_mrt_fragment", fragSource, ShaderComp::LOCATION_FRAGMENT_OUTPUT );
+
+    rtt->addChild( sceneGraph );
+    return rtt;
+}
+
+osg::Node*
+createFramebufferQuad(App& app)
+{
+    float w = (float)app.gcolor->getTextureWidth();
+    float h = (float)app.gcolor->getTextureHeight();
+
+    osg::Geometry* g = new osg::Geometry();
+    g->setSupportsDisplayList( false );
+    
+    osg::Vec3Array* v = new osg::Vec3Array();
+    v->push_back(osg::Vec3(-w/2, -h/2, 0));
+    v->push_back(osg::Vec3( w/2, -h/2, 0));
+    v->push_back(osg::Vec3( w/2,  h/2, 0));
+    v->push_back(osg::Vec3(-w/2,  h/2, 0));
+    g->setVertexArray(v);
+
+    osg::Vec2Array* t = new osg::Vec2Array();
+    t->push_back(osg::Vec2(0,0));
+    t->push_back(osg::Vec2(w,0));
+    t->push_back(osg::Vec2(w,h));
+    t->push_back(osg::Vec2(0,h));
+    g->setTexCoordArray(0, t);
+
+    osg::Vec4Array* c = new osg::Vec4Array();
+    c->push_back(osg::Vec4(1,1,1,1));
+    g->setColorArray(c);
+    g->setColorBinding(osg::Geometry::BIND_OVERALL);
+
+    g->addPrimitiveSet(new osg::DrawArrays(GL_QUADS, 0, 4));
+
+    osg::Geode* geode = new osg::Geode();
+    geode->addDrawable( g );
+
+    return geode;
+}
+
+osg::Node*
+createFramebufferPass(App& app)
+{
+    osg::Node* quad = createFramebufferQuad(app);
+    
+    osg::StateSet* stateset = quad->getOrCreateStateSet();
+
+    static const char* vertSource =
+        "varying vec4 texcoord;\n"
+        "void effect_vert(inout vec4 vertexView)\n"
+        "{\n"
+        "    texcoord = gl_MultiTexCoord0; \n"
+        "}\n";
+
+    static const char* fragSource =
+        "#version 120\n"
+        "#extension GL_ARB_texture_rectangle : enable\n"
+        "uniform sampler2DRect gcolor;\n"
+        "uniform sampler2DRect gnormal;\n"
+        "uniform sampler2DRect gdepth;\n"
+        "varying vec4 texcoord;\n"
+
+        "void effect_frag(inout vec4 color)\n"
+        "{\n"
+        "    color = texture2DRect(gcolor, texcoord.st); \n"
+        "    float depth = texture2DRect(gdepth, texcoord.st).r; \n"
+        "    vec3 normal = texture2DRect(gnormal,texcoord.st).xyz *2.0-1.0; \n"
+
+        // sample radius in pixels:
+        "    float e = 5.0; \n"
+
+        // sample the normals around our pixel and find the approximate
+        // deviation from our center normal:
+        "    vec3 avgNormal =\n"
+        "       texture2DRect(gnormal, texcoord.st+vec2( e, e)).xyz + \n"
+        "       texture2DRect(gnormal, texcoord.st+vec2(-e, e)).xyz + \n"
+        "       texture2DRect(gnormal, texcoord.st+vec2(-e,-e)).xyz + \n"
+        "       texture2DRect(gnormal, texcoord.st+vec2( e,-e)).xyz + \n"
+        "       texture2DRect(gnormal, texcoord.st+vec2( 0, e)).xyz + \n"
+        "       texture2DRect(gnormal, texcoord.st+vec2( e, 0)).xyz + \n"
+        "       texture2DRect(gnormal, texcoord.st+vec2( 0,-e)).xyz + \n"
+        "       texture2DRect(gnormal, texcoord.st+vec2(-e, 0)).xyz;  \n"
+        "    avgNormal = normalize((avgNormal/8.0)*2.0-1.0); \n"
+        "    float deviation = clamp(dot(normal, avgNormal),0.0,1.0); \n"
+
+        // set a blur factor based on the normal deviation, so that we
+        // blur more around edges.
+        "    e = 2.5 * (1.0-deviation); \n"
+
+        "    vec4 blurColor = \n"
+        "       color + \n"
+        "       texture2DRect(gcolor, texcoord.st+vec2( e, e)) + \n"
+        "       texture2DRect(gcolor, texcoord.st+vec2(-e, e)) + \n"
+        "       texture2DRect(gcolor, texcoord.st+vec2(-e,-e)) + \n"
+        "       texture2DRect(gcolor, texcoord.st+vec2( e,-e)) + \n"
+        "       texture2DRect(gcolor, texcoord.st+vec2( 0, e)) + \n"
+        "       texture2DRect(gcolor, texcoord.st+vec2( e, 0)) + \n"
+        "       texture2DRect(gcolor, texcoord.st+vec2( 0,-e)) + \n"
+        "       texture2DRect(gcolor, texcoord.st+vec2(-e, 0));  \n"
+        "    blurColor /= 9.0; \n"
+
+        // blur the color and darken the edges at the same time
+        "    color.rgb = blurColor.rgb * deviation; \n"
+        "}\n";
+
+    VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
+    vp->setFunction("effect_vert", vertSource, ShaderComp::LOCATION_VERTEX_VIEW);
+    vp->setFunction("effect_frag", fragSource, ShaderComp::LOCATION_FRAGMENT_COLORING);
+
+    stateset->setTextureAttributeAndModes(0, app.gcolor, 1);
+    stateset->addUniform(new osg::Uniform("gcolor", 0));
+    stateset->setTextureAttributeAndModes(1, app.gnormal, 1);
+    stateset->addUniform(new osg::Uniform("gnormal", 1));
+    stateset->setTextureAttributeAndModes(2, app.gdepth, 1);
+    stateset->addUniform(new osg::Uniform("gdepth", 2));
+    stateset->setMode( GL_LIGHTING, 0 );
+
+    float w = app.gcolor->getTextureWidth();
+    float h = app.gcolor->getTextureHeight();
+
+    osg::Camera* camera = new osg::Camera();
+    camera->setReferenceFrame( osg::Transform::ABSOLUTE_RF );
+    camera->setViewMatrix( osg::Matrix::identity() );
+    camera->setProjectionMatrixAsOrtho2D( -w/2, (-w/2)+w, -h/2, (-h/2)+h );
+
+    camera->addChild( quad );
+    return camera;
+}
+
+
+void
+createRenderTargets(App& app, unsigned width, unsigned height)
+{
+    app.gcolor = new osg::TextureRectangle();
+    app.gcolor->setTextureSize(width, height);
+    app.gcolor->setInternalFormat(GL_RGBA);
+    app.gcolor->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::LINEAR);
+    app.gcolor->setFilter(osg::Texture2D::MAG_FILTER,osg::Texture2D::LINEAR);
+
+    app.gnormal = new osg::TextureRectangle();
+    app.gnormal->setTextureSize(width, height);
+    app.gnormal->setInternalFormat(GL_RGB);
+    app.gnormal->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::NEAREST);
+    app.gnormal->setFilter(osg::Texture2D::MAG_FILTER,osg::Texture2D::NEAREST);
+
+    app.gdepth = new osg::TextureRectangle();
+    app.gdepth->setTextureSize(width, height);
+    app.gdepth->setInternalFormat(GL_LUMINANCE);
+    app.gdepth->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::NEAREST);
+    app.gdepth->setFilter(osg::Texture2D::MAG_FILTER,osg::Texture2D::NEAREST);
+}
+
+
+int
+usage(const char* name)
+{
+    OE_NOTICE 
+        << "\nUsage: " << name << " file.earth" << std::endl
+        << MapNodeHelper().usage() << std::endl;
+
+    return 0;
+}
+
+struct RTTIntersectionTest : public osgGA::GUIEventHandler
+{
+    osgViewer::View* _view;
+    osg::Node*       _node;
+
+    virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa, osg::Object*, osg::NodeVisitor*)
+    {
+        if ( ea.getEventType() == ea.PUSH )
+        {
+            // mouse click from [-1...1]
+            float nx = ea.getXnormalized();
+            float ny = ea.getYnormalized();
+
+            // clicked point in clip space:
+            osg::Vec3d pn( nx, ny, -1 ); // on near plane
+            osg::Vec3d pf( nx, ny,  1 ); // on far plane
+
+            OE_NOTICE << "clip: nx=" << nx << ", ny=" << ny << std::endl;
+            
+            // take the view matrix as-is:
+            osg::Matrix view = _view->getCamera()->getViewMatrix();
+
+            // adjust projection matrix to include entire earth:
+            double fovy, ar, zn, zf;
+            _view->getCamera()->getProjectionMatrix().getPerspective(fovy, ar, zn, zf);
+            osg::Matrix proj;
+            proj.makePerspective(fovy, ar, 1.0, 1e10);
+
+            // Invert the MVP to transform points from clip to model space:
+            osg::Matrix MVP = view * proj;
+            osg::Matrix invMVP;
+            invMVP.invert(MVP);
+
+            pn = pn * invMVP;
+            pf = pf * invMVP;
+
+            OE_NOTICE << "model: near = " << pn.x() << ", " << pn.y() << ", " << pn.z() << std::endl;
+            OE_NOTICE << "model: far  = " << pf.x() << ", " << pf.y() << ", " << pf.z() << std::endl;
+
+            // Intersect in model space.
+            osgUtil::LineSegmentIntersector* lsi = new osgUtil::LineSegmentIntersector(
+                osgUtil::Intersector::MODEL, pn, pf );
+
+            lsi->setIntersectionLimit( lsi->LIMIT_NEAREST );
+
+            osgUtil::IntersectionVisitor iv( lsi ); 
+            
+            _node->accept( iv );
+
+            if ( lsi->containsIntersections() )
+            {
+                osg::Vec3d p = lsi->getIntersections().begin()->getWorldIntersectPoint();
+                OE_NOTICE << "i = " << p.x() << ", " << p.y() << ", " << p.z() << std::endl;
+            }
+        }
+        return false;
+    }
+};
+
+int
+main(int argc, char** argv)
+{
+    osg::ArgumentParser arguments(&argc,argv);
+    osgViewer::Viewer viewer(arguments);
+    viewer.setCameraManipulator( new EarthManipulator() );
+
+    osg::Group* root = new osg::Group();
+
+    osg::Node* node = MapNodeHelper().load( arguments, &viewer );
+    if ( node )
+    {
+        App app;
+        createRenderTargets( app, 1280, 1024 );
+
+        osg::Node* pass1 = createMRTPass(app, node);
+        root->addChild( pass1 );
+
+        osg::Node* pass2 = createFramebufferPass(app);
+        root->addChild( pass2 );
+
+        // demonstrate scene intersection when using MRT/RTT.
+        RTTIntersectionTest* isect = new RTTIntersectionTest();
+        isect->_view = &viewer;
+        isect->_node = node;
+        viewer.addEventHandler( isect );
+
+        viewer.setSceneData( root );
+
+        viewer.run();
+    }
+    else
+    {
+        return usage(argv[0]);
+    }
+    return 0;
+}
diff --git a/src/applications/osgearth_occlusionculling/osgearth_occlusionculling.cpp b/src/applications/osgearth_occlusionculling/osgearth_occlusionculling.cpp
index e86eaba..064a31f 100644
--- a/src/applications/osgearth_occlusionculling/osgearth_occlusionculling.cpp
+++ b/src/applications/osgearth_occlusionculling/osgearth_occlusionculling.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/applications/osgearth_overlayviewer/osgearth_overlayviewer.cpp b/src/applications/osgearth_overlayviewer/osgearth_overlayviewer.cpp
index 09672a9..a8c2b66 100644
--- a/src/applications/osgearth_overlayviewer/osgearth_overlayviewer.cpp
+++ b/src/applications/osgearth_overlayviewer/osgearth_overlayviewer.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -21,11 +21,15 @@
 #include <osg/Depth>
 #include <osg/LineWidth>
 #include <osgGA/StateSetManipulator>
+#include <osgGA/AnimationPathManipulator>
 #include <osgViewer/CompositeViewer>
+#include <osgViewer/ViewerEventHandlers>
 #include <osgEarth/OverlayDecorator>
+#include <osgEarth/MapNode>
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/ExampleResources>
 #include <osgEarthUtil/Controls>
+#include <osgEarthSymbology/Color>
 
 #define LC "[viewer] "
 
@@ -123,8 +127,7 @@ namespace
 void
 setupOverlayView( osgViewer::View* view, osg::Group* parent, MapNode* mapNode )
 {
-
-    ControlCanvas* canvas = ControlCanvas::get(view, true);
+    ControlCanvas* canvas = ControlCanvas::getOrCreate(view);
 
     VBox* v = canvas->addControl(new VBox());
     v->setBackColor( Color(Color::Black,0.75) );
@@ -166,19 +169,42 @@ main(int argc, char** argv)
     osgViewer::CompositeViewer viewer(arguments);
     viewer.setThreadingModel( osgViewer::CompositeViewer::SingleThreaded );
 
+    // query the screen size.
+    osg::GraphicsContext::ScreenIdentifier si;
+    si.readDISPLAY();
+    if ( si.displayNum < 0 ) si.displayNum = 0;
+    osg::GraphicsContext::WindowingSystemInterface* wsi = osg::GraphicsContext::getWindowingSystemInterface();
+    unsigned width, height;
+    wsi->getScreenResolution( si, width, height );
+    unsigned b = 50;
+
     osgViewer::View* mainView = new osgViewer::View();
     mainView->getCamera()->setNearFarRatio(0.00002);
-    mainView->setCameraManipulator( new EarthManipulator() );
-    mainView->setUpViewInWindow( 50, 50, 600, 600 );
+    EarthManipulator* em = new EarthManipulator();
+    em->getSettings()->setMinMaxPitch(-90, 0);
+    mainView->setCameraManipulator( em );
+    //mainView->setUpViewInWindow( 50, 50, 600, 600 );
+    mainView->setUpViewInWindow( b, b, (width/2)-b*2, (height-b*4) );
     viewer.addView( mainView );
 
     osgViewer::View* overlayView = new osgViewer::View();
     overlayView->getCamera()->setNearFarRatio(0.00002);
-    overlayView->setCameraManipulator( new EarthManipulator() );
-    overlayView->setUpViewInWindow( 700, 50, 600, 600 );
+    EarthManipulator* overlayEM = new EarthManipulator();
+    overlayEM->getSettings()->setCameraProjection(overlayEM->PROJ_ORTHOGRAPHIC);
+    overlayView->setCameraManipulator( overlayEM );
+    
+    //overlayView->setUpViewInWindow( 700, 50, 600, 600 );
+    overlayView->setUpViewInWindow( (width/2), b, (width/2)-b*2, (height-b*4) );
     overlayView->addEventHandler(new osgGA::StateSetManipulator(overlayView->getCamera()->getOrCreateStateSet()));
     viewer.addView( overlayView );
 
+    std::string pathfile;
+    double animationSpeed = 1.0;
+    if (arguments.read("-p", pathfile))
+    {
+        mainView->setCameraManipulator( new osgGA::AnimationPathManipulator(pathfile) );
+    }
+
     osg::Node* node = MapNodeHelper().load( arguments, mainView );
     if ( node )
     {
diff --git a/src/applications/osgearth_package/osgearth_package.cpp b/src/applications/osgearth_package/osgearth_package.cpp
index 3c17b1e..d2b8c82 100644
--- a/src/applications/osgearth_package/osgearth_package.cpp
+++ b/src/applications/osgearth_package/osgearth_package.cpp
@@ -1,342 +1,489 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#include <osg/io_utils>
-#include <osgDB/FileNameUtils>
-#include <osgDB/FileUtils>
-#include <osgDB/WriteFile>
-
-#include <osgEarth/Common>
-#include <osgEarth/Map>
-#include <osgEarth/MapNode>
-#include <osgEarth/Registry>
-#include <osgEarth/StringUtils>
-#include <osgEarth/HTTPClient>
-#include <osgEarthUtil/TMSPackager>
-#include <osgEarthDrivers/tms/TMSOptions>
-
-#include <iostream>
-#include <sstream>
-#include <iterator>
-
-using namespace osgEarth;
-using namespace osgEarth::Util;
-using namespace osgEarth::Drivers;
-
-#define LC "[osgearth_package] "
-
-
-/** Prints an error message, usage information, and returns -1. */
-int
-usage( const std::string& msg = "" )
-{
-    if ( !msg.empty() )
-    {
-        std::cout << msg << std::endl;
-    }
-
-    std::cout
-        << std::endl
-        << "USAGE: osgearth_package <earth_file>" << std::endl
-        << std::endl
-        << "         --tms                              : make a TMS repo\n"
-        << "            <earth_file>                    : earth file defining layers to export (required)\n"
-        << "            --out <path>                    : root output folder of the TMS repo (required)\n"
-        << "            [--bounds xmin ymin xmax ymax]* : bounds to package (in map coordinates; default=entire map)\n"
-        << "            [--max-level <num>]             : max LOD level for tiles (all layers; default=inf)\n"
-        << "            [--out-earth <earthfile>]       : export an earth file referencing the new repo\n"
-        << "            [--ext <extension>]             : overrides the image file extension (e.g. jpg)\n"
-        << "            [--overwrite]                   : overwrite existing tiles\n"
-        << "            [--keep-empties]                : writes out fully transparent image tiles (normally discarded)\n"
-        << "            [--continue-single-color]       : continues to subdivide single color tiles, subdivision typicall stops on single color images\n"
-        << "            [--db-options]                : db options string to pass to the image writer in quotes (e.g., \"JPEG_QUALITY 60\")\n"
-        << std::endl
-        << "         [--quiet]               : suppress progress output" << std::endl;
-
-    return -1;
-}
-
-
-/** Prints a message and returns a non-error return code. */
-int
-message( const std::string& msg )
-{
-    if ( !msg.empty() )
-    {
-        std::cout << msg << std::endl << std::endl;
-    }
-    return 0;
-}
-
-
-/** Finds an argument with the specified extension. */
-std::string
-findArgumentWithExtension( osg::ArgumentParser& args, const std::string& ext )
-{
-    for( int i=0; i<args.argc(); ++i )
-    {
-        std::string arg( args.argv()[i] );
-        if ( endsWith( toLower(trim(arg)), ".earth" ) )
-            return arg;
-    }
-    return "";
-}
-
-
-/** Packages an image layer as a TMS folder. */
-int
-makeTMS( osg::ArgumentParser& args )
-{
-    // see if the user wants to override the type extension (imagery only)
-    std::string extension;
-    args.read( "--ext", extension );
-
-    // verbosity?
-    bool verbose = !args.read( "--quiet" );
-
-    // find a .earth file on the command line
-    std::string earthFile = findArgumentWithExtension(args, ".earth");
- /*   if ( earthFile.empty() )
-        return usage( "Missing required .earth file" );
-        */
-    // folder to which to write the TMS archive.
-    std::string rootFolder;
-    if ( !args.read( "--out", rootFolder ) )
-        rootFolder = Stringify() << earthFile << ".tms_repo";
-
-    // whether to overwrite existing tile files
-    bool overwrite = false;
-    if ( args.read("--overwrite") )
-        overwrite = true;
-
-    // write out an earth file
-    std::string outEarth;
-    args.read("--out-earth", outEarth);
-
-    std::string dbOptions;
-    args.read("--db-options", dbOptions);
-    std::string::size_type n = 0;
-    while ((n=dbOptions.find('"', n))!=dbOptions.npos)
-    {
-        dbOptions.erase(n,1);
-    }
-
-    osg::ref_ptr<osgDB::Options> options = new osgDB::Options(dbOptions);
-
-
-    std::vector< Bounds > bounds;
-    // restrict packaging to user-specified bounds.    
-    double xmin=DBL_MAX, ymin=DBL_MAX, xmax=DBL_MIN, ymax=DBL_MIN;
-    while (args.read("--bounds", xmin, ymin, xmax, ymax ))
-    {        
-        Bounds b;
-        b.xMin() = xmin, b.yMin() = ymin, b.xMax() = xmax, b.yMax() = ymax;
-        bounds.push_back( b );
-    }
-
-    // max level to which to generate
-    unsigned maxLevel = ~0;
-    args.read( "--max-level", maxLevel );
-
-    // whether to keep 'empty' tiles
-    bool keepEmpties = args.read("--keep-empties");    
-
-    bool continueSingleColor = args.read("--continue-single-color");
-
-    // load up the map
-    osg::ref_ptr<MapNode> mapNode = MapNode::load( args );
-    if ( !mapNode.valid() )
-        return usage( "Failed to load a valid .earth file" );
-
-    // create a folder for the output
-    osgDB::makeDirectory(rootFolder);
-    if ( !osgDB::fileExists(rootFolder) )
-        return usage("Failed to create root output folder" );
-
-    Map* map = mapNode->getMap();
-
-    // fire up a packager:
-    TMSPackager packager( map->getProfile(), options);
-
-    packager.setVerbose( verbose );
-    packager.setOverwrite( overwrite );
-    packager.setKeepEmptyImageTiles( keepEmpties );
-    packager.setSubdivideSingleColorImageTiles( continueSingleColor );
-
-    if ( maxLevel != ~0 )
-        packager.setMaxLevel( maxLevel );
-
-    if (bounds.size() > 0)
-    {
-        for (unsigned int i = 0; i < bounds.size(); ++i)
-        {
-            Bounds b = bounds[i];            
-            if ( b.isValid() )
-                packager.addExtent( GeoExtent(map->getProfile()->getSRS(), b) );
-        }
-    }    
-
-    
-    // new map for an output earth file if necessary.
-    osg::ref_ptr<Map> outMap = 0L;
-    if ( !outEarth.empty() )
-    {
-        // copy the options from the source map first
-        outMap = new Map(map->getInitialMapOptions());
-    }
-
-    // establish the output path of the earth file, if applicable:
-    std::string outEarthFile = osgDB::concatPaths(rootFolder, osgDB::getSimpleFileName(outEarth));
-
-    // package any image layers that are enabled:
-    ImageLayerVector imageLayers;
-    map->getImageLayers( imageLayers );
-
-    unsigned counter = 0;
-    
-    for( ImageLayerVector::iterator i = imageLayers.begin(); i != imageLayers.end(); ++i, ++counter )
-    {
-        ImageLayer* layer = i->get();
-        if ( layer->getImageLayerOptions().enabled() == true )
-        {
-            std::string layerFolder = toLegalFileName( layer->getName() );
-            if ( layerFolder.empty() )
-                layerFolder = Stringify() << "image_layer_" << counter;
-
-            if ( verbose )
-            {
-                OE_NOTICE << LC << "Packaging image layer \"" << layerFolder << "\"" << std::endl;
-            }
-
-            std::string layerRoot = osgDB::concatPaths( rootFolder, layerFolder );
-            TMSPackager::Result r = packager.package( layer, layerRoot, 0L, extension );
-            if ( r.ok )
-            {
-                // save to the output map if requested:
-                if ( outMap.valid() )
-                {
-                    // new TMS driver info:
-                    TMSOptions tms;
-                    tms.url() = URI(
-                        osgDB::concatPaths(layerFolder, "tms.xml"),
-                        outEarthFile );
-
-                    ImageLayerOptions layerOptions( layer->getName(), tms );
-                    layerOptions.mergeConfig( layer->getInitialOptions().getConfig(true) );
-                    layerOptions.cachePolicy() = CachePolicy::NO_CACHE;
-
-                    outMap->addImageLayer( new ImageLayer(layerOptions) );
-                }
-            }
-            else
-            {
-                OE_WARN << LC << r.message << std::endl;
-            }
-        }
-        else if ( verbose )
-        {
-            OE_NOTICE << LC << "Skipping disabled layer \"" << layer->getName() << "\"" << std::endl;
-        }
-    }
-
-    // package any elevation layers that are enabled:
-    counter = 0;
-    ElevationLayerVector elevationLayers;
-    map->getElevationLayers( elevationLayers );
-
-    for( ElevationLayerVector::iterator i = elevationLayers.begin(); i != elevationLayers.end(); ++i, ++counter )
-    {
-        ElevationLayer* layer = i->get();
-        if ( layer->getElevationLayerOptions().enabled() == true )
-        {
-            std::string layerFolder = toLegalFileName( layer->getName() );
-            if ( layerFolder.empty() )
-                layerFolder = Stringify() << "elevation_layer_" << counter;
-
-            if ( verbose )
-            {
-                OE_NOTICE << LC << "Packaging elevation layer \"" << layerFolder << "\"" << std::endl;
-            }
-
-            std::string layerRoot = osgDB::concatPaths( rootFolder, layerFolder );
-            TMSPackager::Result r = packager.package( layer, layerRoot );
-
-            if ( r.ok )
-            {
-                // save to the output map if requested:
-                if ( outMap.valid() )
-                {
-                    // new TMS driver info:
-                    TMSOptions tms;
-                    tms.url() = URI(
-                        osgDB::concatPaths(layerFolder, "tms.xml"),
-                        outEarthFile );
-
-                    ElevationLayerOptions layerOptions( layer->getName(), tms );
-                    layerOptions.mergeConfig( layer->getInitialOptions().getConfig(true) );
-                    layerOptions.cachePolicy() = CachePolicy::NO_CACHE;
-
-                    outMap->addElevationLayer( new ElevationLayer(layerOptions) );
-                }
-            }
-            else
-            {
-                OE_WARN << LC << r.message << std::endl;
-            }
-        }
-        else if ( verbose )
-        {
-            OE_NOTICE << LC << "Skipping disabled layer \"" << layer->getName() << "\"" << std::endl;
-        }
-    }
-
-    // Finally, write an earth file if requested:
-    if ( outMap.valid() )
-    {
-        MapNodeOptions outNodeOptions = mapNode->getMapNodeOptions();
-        osg::ref_ptr<MapNode> outMapNode = new MapNode(outMap.get(), outNodeOptions);
-        if ( !osgDB::writeNodeFile(*outMapNode.get(), outEarthFile) )
-        {
-            OE_WARN << LC << "Error writing earth file to \"" << outEarthFile << "\"" << std::endl;
-        }
-        else if ( verbose )
-        {
-            OE_NOTICE << LC << "Wrote earth file to \"" << outEarthFile << "\"" << std::endl;
-        }
-    }
-
-    return 0;
-}
-
-/**
- * Data packaging tool for osgEarth.
- */
-int
-main(int argc, char** argv)
-{
-    osg::ArgumentParser args(&argc,argv);
-
-    HTTPClient::setUserAgent( "osgearth_package/2.2" );
-
-    if ( args.read("--tms") )
-        return makeTMS(args);
-
-    else
-        return usage();
-}
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+#include <osg/io_utils>
+#include <osgDB/FileNameUtils>
+#include <osgDB/FileUtils>
+#include <osgDB/WriteFile>
+
+#include <osgEarth/Common>
+#include <osgEarth/Map>
+#include <osgEarth/MapNode>
+#include <osgEarth/Registry>
+#include <osgEarth/StringUtils>
+#include <osgEarth/HTTPClient>
+#include <osgEarth/TileVisitor>
+#include <osgEarthUtil/TMSPackager>
+#include <osgEarthDrivers/feature_ogr/OGRFeatureOptions>
+#include <osgEarthDrivers/tms/TMSOptions>
+
+#include <iostream>
+#include <sstream>
+#include <iterator>
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+using namespace osgEarth::Drivers;
+
+#define LC "[osgearth_package] "
+
+
+/** Prints an error message, usage information, and returns -1. */
+int
+usage( const std::string& msg = "" )
+{
+    if( !msg.empty() )
+    {
+        std::cout << msg << std::endl;
+    }
+
+    std::cout
+        << std::endl
+        << "USAGE: osgearth_package <earth_file>" << std::endl
+        << std::endl
+        << "         --tms                              : make a TMS repo\n"
+        << "            <earth_file>                    : earth file defining layers to export (required)\n"
+        << "            --out <path>                    : root output folder of the TMS repo (required)\n"
+        << "            [--bounds xmin ymin xmax ymax]* : bounds to package (in map coordinates; default=entire map)\n"
+        << "            [--max-level <num>]             : max LOD level for tiles (all layers; default=inf)\n"
+        << "            [--out-earth <earthfile>]       : export an earth file referencing the new repo\n"
+        << "            [--ext <extension>]             : overrides the image file extension (e.g. jpg)\n"
+        << "            [--overwrite]                   : overwrite existing tiles\n"
+        << "            [--keep-empties]                : writes out fully transparent image tiles (normally discarded)\n"
+        << "            [--continue-single-color]       : continues to subdivide single color tiles, subdivision typicall stops on single color images\n"
+        << "            [--elevation-pixel-depth]       : pixeldepth for elevations\n"
+        << "            [--db-options]                : db options string to pass to the image writer in quotes (e.g., \"JPEG_QUALITY 60\")\n"
+        << "            [--mp]                          ; Use multiprocessing to process the tiles.  Useful for GDAL sources as this avoids the global GDAL lock" << std::endl
+        << "            [--mt]                          ; Use multithreading to process the tiles." << std::endl
+        << "            [--concurrency]                 ; The number of threads or proceses to use if --mp or --mt are provided." << std::endl
+        << std::endl
+        << "         [--quiet]               : suppress progress output" << std::endl;
+
+    return -1;
+}
+
+
+/** Prints a message and returns a non-error return code. */
+int
+message( const std::string& msg )
+{
+    if( !msg.empty() )
+    {
+        std::cout << msg << std::endl << std::endl;
+    }
+    return 0;
+}
+
+
+/** Finds an argument with the specified extension. */
+std::string
+findArgumentWithExtension( osg::ArgumentParser& args, const std::string& ext )
+{
+    for( int i = 0; i < args.argc(); ++i )
+    {
+        std::string arg( args.argv()[i] );
+        if( endsWith( toLower( trim( arg ) ), ".earth" ) )
+            return arg;
+    }
+    return "";
+}
+
+
+/** Packages an image layer as a TMS folder. */
+int
+makeTMS( osg::ArgumentParser& args )
+{
+    osgDB::Registry::instance()->getReaderWriterForExtension("png");
+    osgDB::Registry::instance()->getReaderWriterForExtension("jpg");
+    osgDB::Registry::instance()->getReaderWriterForExtension("tiff");
+
+    //Read the min level
+    unsigned int minLevel = 0;
+    while (args.read("--min-level", minLevel));
+
+    //Read the max level
+    unsigned int maxLevel = 5;
+    while (args.read("--max-level", maxLevel));
+    
+
+    std::vector< Bounds > bounds;
+    // restrict packaging to user-specified bounds.    
+    double xmin=DBL_MAX, ymin=DBL_MAX, xmax=DBL_MIN, ymax=DBL_MIN;
+    while (args.read("--bounds", xmin, ymin, xmax, ymax ))
+    {        
+        Bounds b;
+        b.xMin() = xmin, b.yMin() = ymin, b.xMax() = xmax, b.yMax() = ymax;
+        bounds.push_back( b );
+    }    
+
+    std::string tileList;
+    while (args.read( "--tiles", tileList ) );
+
+    bool verbose = args.read("--verbose");
+
+    unsigned int batchSize = 0;
+    args.read("--batchsize", batchSize);
+
+    // Read the concurrency level
+    unsigned int concurrency = 0;
+    args.read("-c", concurrency);
+    args.read("--concurrency", concurrency);
+
+    bool writeXML = true;
+
+    // load up the map
+    osg::ref_ptr<MapNode> mapNode = MapNode::load( args );
+    if( !mapNode.valid() )
+        return usage( "Failed to load a valid .earth file" );
+
+
+    // Read in an index shapefile
+    std::string index;
+    while (args.read("--index", index))
+    {        
+        //Open the feature source
+        OGRFeatureOptions featureOpt;
+        featureOpt.url() = index;        
+
+        osg::ref_ptr< FeatureSource > features = FeatureSourceFactory::create( featureOpt );
+        features->initialize();
+        features->getFeatureProfile();
+
+        osg::ref_ptr< FeatureCursor > cursor = features->createFeatureCursor();
+        while (cursor.valid() && cursor->hasMore())
+        {
+            osg::ref_ptr< Feature > feature = cursor->nextFeature();
+            osgEarth::Bounds featureBounds = feature->getGeometry()->getBounds();
+            GeoExtent ext( feature->getSRS(), featureBounds );
+            ext = ext.transform( mapNode->getMapSRS() );
+            bounds.push_back( ext.bounds() );            
+        }
+    }
+
+    // see if the user wants to override the type extension (imagery only)
+    std::string extension;
+    args.read( "--ext", extension );
+
+    // find a .earth file on the command line
+    std::string earthFile = findArgumentWithExtension( args, ".earth" );
+    
+    // folder to which to write the TMS archive.
+    std::string rootFolder;
+    if( !args.read( "--out", rootFolder ) )
+        rootFolder = Stringify() << earthFile << ".tms_repo";
+
+    // whether to overwrite existing tile files
+    //TODO:  Support
+    bool overwrite = false;
+    if( args.read( "--overwrite" ) )
+        overwrite = true;
+
+    // write out an earth file
+    std::string outEarth;
+    args.read( "--out-earth", outEarth );
+
+    std::string dbOptions;
+    args.read( "--db-options", dbOptions );
+    std::string::size_type n = 0;
+    while( (n = dbOptions.find( '"', n )) != dbOptions.npos )
+    {
+        dbOptions.erase( n, 1 );
+    }
+
+    osg::ref_ptr<osgDB::Options> options = new osgDB::Options( dbOptions );
+
+    // whether to keep 'empty' tiles    
+    bool keepEmpties = args.read( "--keep-empties" );
+
+    //TODO:  Single color
+    bool continueSingleColor = args.read( "--continue-single-color" );
+
+    // elevation pixel depth
+    unsigned elevationPixelDepth = 32;
+    args.read( "--elevation-pixel-depth", elevationPixelDepth );
+    
+    // create a folder for the output
+    osgDB::makeDirectory( rootFolder );
+    if( !osgDB::fileExists( rootFolder ) )
+        return usage( "Failed to create root output folder" );
+
+    int imageLayerIndex = -1;
+    args.read("--image", imageLayerIndex);
+
+    int elevationLayerIndex = -1;
+    args.read("--elevation", elevationLayerIndex);
+    
+    Map* map = mapNode->getMap();
+
+
+    osg::ref_ptr< TileVisitor > visitor;
+
+    // If we are given a task file, load it up and create a new TileKeyListVisitor
+    if (!tileList.empty())
+    {        
+        TaskList tasks( mapNode->getMap()->getProfile() );
+        tasks.load( tileList );
+
+        TileKeyListVisitor* v = new TileKeyListVisitor();
+        v->setKeys( tasks.getKeys() );
+        visitor = v;     
+        // This process is a lowly worker, and shouldn't write out the XML file.
+        writeXML = false;
+    }
+
+    // If we dont' have a visitor create one.
+    if (!visitor.valid())
+    {
+        if (args.read("--mt"))
+        {
+            // Create a multithreaded visitor
+            MultithreadedTileVisitor* v = new MultithreadedTileVisitor();
+            if (concurrency > 0)
+            {
+                v->setNumThreads(concurrency);
+            }
+            visitor = v;            
+        }
+        else if (args.read("--mp"))
+        {
+            // Create a multiprocess visitor
+            MultiprocessTileVisitor* v = new MultiprocessTileVisitor();
+            if (concurrency > 0)
+            {
+                v->setNumProcesses(concurrency);
+                OE_NOTICE << "Set num processes " << concurrency << std::endl;
+            }
+
+            if (batchSize > 0)
+            {            
+                v->setBatchSize(batchSize);
+            }
+
+
+            // Try to find the earth file
+            std::string earthFile;
+            for(int pos=1;pos<args.argc();++pos)
+            {
+                if (!args.isOption(pos))
+                {
+                    earthFile  = args[ pos ];
+                    break;
+                }
+            }
+
+            v->setEarthFile( earthFile );
+
+            visitor = v;            
+        }
+        else
+        {
+            // Create a single thread visitor
+            visitor = new TileVisitor();            
+        }        
+    }
+
+    osg::ref_ptr< ProgressCallback > progress = new ConsoleProgressCallback();
+
+    if (verbose)
+    {
+        visitor->setProgressCallback( progress );
+    }
+
+    visitor->setMinLevel( minLevel );
+    visitor->setMaxLevel( maxLevel );        
+
+
+    for (unsigned int i = 0; i < bounds.size(); i++)
+    {
+        GeoExtent extent(mapNode->getMapSRS(), bounds[i]);
+        OE_DEBUG << "Adding extent " << extent.toString() << std::endl;                
+        visitor->addExtent( extent );
+    }    
+
+
+    // Setup a TMSPackager with all the options.
+    TMSPackager packager;
+    packager.setExtension(extension);
+    packager.setVisitor(visitor);
+    packager.setDestination(rootFolder);    
+    packager.setElevationPixelDepth(elevationPixelDepth);
+    packager.setWriteOptions(options);    
+    packager.setOverwrite(overwrite);
+    packager.setKeepEmpties(keepEmpties);
+
+
+    // new map for an output earth file if necessary.
+    osg::ref_ptr<Map> outMap = 0L;
+    if( !outEarth.empty() )
+    {
+        // copy the options from the source map first
+        outMap = new Map( map->getInitialMapOptions() );
+    }
+
+    std::string outEarthFile = osgDB::concatPaths( rootFolder, osgDB::getSimpleFileName( outEarth ) );
+    
+
+    // Package an individual image layer
+    if (imageLayerIndex >= 0)
+    {        
+        ImageLayer* layer = map->getImageLayerAt(imageLayerIndex);
+        if (layer)
+        {
+            packager.run(layer, map);
+            if (writeXML)
+            {
+                packager.writeXML(layer, map);
+            }
+        }
+        else
+        {
+            std::cout << "Failed to find an image layer at index " << imageLayerIndex << std::endl;
+            return 1;
+        }
+    }
+    // Package an individual elevation layer
+    else if (elevationLayerIndex >= 0)
+    {        
+        ElevationLayer* layer = map->getElevationLayerAt(elevationLayerIndex);
+        if (layer)
+        {
+            packager.run(layer, map);
+            if (writeXML)
+            {
+                packager.writeXML(layer, map );
+            }
+        }
+        else
+        {
+            std::cout << "Failed to find an elevation layer at index " << elevationLayerIndex << std::endl;
+            return 1;
+        }
+    }
+    else
+    {        
+        // Package all the ImageLayer's
+        for (unsigned int i = 0; i < map->getNumImageLayers(); i++)
+        {            
+            ImageLayer* layer = map->getImageLayerAt(i);        
+            OE_NOTICE << "Packaging " << layer->getName() << std::endl;
+            osg::Timer_t start = osg::Timer::instance()->tick();
+            packager.run(layer, map);
+            osg::Timer_t end = osg::Timer::instance()->tick();
+            if (verbose)
+            {
+                OE_NOTICE << "Completed seeding layer " << layer->getName() << " in " << prettyPrintTime( osg::Timer::instance()->delta_s( start, end ) ) << std::endl;
+            }                
+
+            if (writeXML)
+            {
+                packager.writeXML(layer, map);
+            }
+
+            // save to the output map if requested:
+            if( outMap.valid() )
+            {
+                std::string layerFolder = toLegalFileName( packager.getLayerName() );
+
+                // new TMS driver info:
+                TMSOptions tms;
+                tms.url() = URI(
+                    osgDB::concatPaths( layerFolder, "tms.xml" ),
+                    outEarthFile );
+
+                ImageLayerOptions layerOptions( packager.getLayerName(), tms );
+                layerOptions.mergeConfig( layer->getInitialOptions().getConfig( true ) );
+                layerOptions.cachePolicy() = CachePolicy::NO_CACHE;
+
+                outMap->addImageLayer( new ImageLayer( layerOptions ) );
+            }
+        }    
+
+        // Package all the ElevationLayer's
+        for (unsigned int i = 0; i < map->getNumElevationLayers(); i++)
+        {            
+            ElevationLayer* layer = map->getElevationLayerAt(i);        
+            OE_NOTICE << "Packaging " << layer->getName() << std::endl;
+            osg::Timer_t start = osg::Timer::instance()->tick();
+            packager.run(layer, map);
+            osg::Timer_t end = osg::Timer::instance()->tick();
+            if (verbose)
+            {
+                OE_NOTICE << "Completed seeding layer " << layer->getName() << " in " << prettyPrintTime( osg::Timer::instance()->delta_s( start, end ) ) << std::endl;
+            }      
+            if (writeXML)
+            {
+                packager.writeXML(layer, map);
+            }
+
+            // save to the output map if requested:
+            if( outMap.valid() )
+            {
+                std::string layerFolder = toLegalFileName( packager.getLayerName() );
+
+                // new TMS driver info:
+                TMSOptions tms;
+                tms.url() = URI(
+                    osgDB::concatPaths( layerFolder, "tms.xml" ),
+                    outEarthFile );
+
+                ElevationLayerOptions layerOptions( packager.getLayerName(), tms );
+                layerOptions.mergeConfig( layer->getInitialOptions().getConfig( true ) );
+                layerOptions.cachePolicy() = CachePolicy::NO_CACHE;
+
+                outMap->addElevationLayer( new ElevationLayer( layerOptions ) );
+            }
+        }
+
+    }
+
+    // Write out an earth file if it was requested
+    // Finally, write an earth file if requested:
+    if( outMap.valid() )
+    {
+        MapNodeOptions outNodeOptions = mapNode->getMapNodeOptions();
+        osg::ref_ptr<MapNode> outMapNode = new MapNode( outMap.get(), outNodeOptions );
+        if( !osgDB::writeNodeFile( *outMapNode.get(), outEarthFile ) )
+        {
+            OE_WARN << LC << "Error writing earth file to \"" << outEarthFile << "\"" << std::endl;
+        }
+        else if( verbose )
+        {
+            OE_NOTICE << LC << "Wrote earth file to \"" << outEarthFile << "\"" << std::endl;
+        }
+    }
+
+    return 0;
+}
+
+/**
+ * Data packaging tool for osgEarth.
+ */
+int
+main( int argc, char** argv )
+{
+    osg::ArgumentParser args( &argc, argv );
+
+    HTTPClient::setUserAgent( "osgearth_package/2.2" );
+
+    if( args.read( "--tms" ) )
+        return makeTMS( args );
+
+    else
+        return usage();
+}
diff --git a/src/applications/osgearth_package_qt/CMakeLists.txt b/src/applications/osgearth_package_qt/CMakeLists.txt
index 7f7a9a6..ba6ceb0 100644
--- a/src/applications/osgearth_package_qt/CMakeLists.txt
+++ b/src/applications/osgearth_package_qt/CMakeLists.txt
@@ -1,5 +1,3 @@
-INCLUDE( ${QT_USE_FILE} )
-
 INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} ${QT_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR})
 
 SET(UI_FILES
@@ -16,17 +14,27 @@ set(LIB_QT_RCS
     images.qrc
 )
 
-QT4_ADD_RESOURCES( LIB_RC_SRCS ${LIB_QT_RCS} )
-
-QT4_WRAP_UI( UI_HDRS ${UI_FILES} )
-
-QT4_WRAP_CPP( UI_SRCS ${UI_HDRS} )
-
-QT4_WRAP_CPP( MOC_SRCS ${MOC_HDRS} OPTIONS "-f" )
+IF(Qt5Widgets_FOUND)
+    QT5_ADD_RESOURCES( LIB_RC_SRCS ${LIB_QT_RCS} )
+    QT5_WRAP_UI( UI_HDRS ${UI_FILES} )
+    QT5_WRAP_CPP( UI_SRCS ${UI_HDRS} )
+    IF(Qt5Widgets_VERSION VERSION_LESS 5.2.0)
+        QT5_WRAP_CPP( MOC_SRCS ${MOC_HDRS} OPTIONS "-f" )
+    ELSE()
+        QT5_WRAP_CPP( MOC_SRCS ${MOC_HDRS} )
+    ENDIF()
+ELSE()
+    INCLUDE( ${QT_USE_FILE} )
+    QT4_ADD_RESOURCES( LIB_RC_SRCS ${LIB_QT_RCS} )
+    QT4_WRAP_UI( UI_HDRS ${UI_FILES} )
+    QT4_WRAP_CPP( UI_SRCS ${UI_HDRS} )
+    QT4_WRAP_CPP( MOC_SRCS ${MOC_HDRS} OPTIONS "-f" )
+ENDIF()
 
 SET(TARGET_H
     PackageQtMainWindow
     ExportDialog
+    ExportProgress.h
     WaitDialog
     SceneController.h
     TMSExporter.h
@@ -39,6 +47,7 @@ SET(TARGET_SRC
     ${MOC_SRCS}
     ${LIB_RC_SRCS}
     ExportDialog.cpp
+    ExportProgress.cpp
     SceneController.cpp
     TMSExporter.cpp
     WaitDialog.cpp
diff --git a/src/applications/osgearth_package_qt/ExportDialog b/src/applications/osgearth_package_qt/ExportDialog
index a2ee942..34c6d45 100644
--- a/src/applications/osgearth_package_qt/ExportDialog
+++ b/src/applications/osgearth_package_qt/ExportDialog
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -21,47 +21,65 @@
 #define TILER_TOOL_EXPORTDIALOG 1
 
 #include "ui_ExportDialog.h"
+#include "TMSExporter.h"
+
+#include <osgEarth/GeoData>
 
 namespace PackageQt
 {
-  class ExportDialog : public QDialog
-  {
-	  Q_OBJECT
+    class ExportDialog : public QDialog
+    {
+        Q_OBJECT
+
+    public:
+        ExportDialog(osgEarth::MapNode* mapNode, const std::string& dir, const osgEarth::Bounds& bounds);
+
+        std::string getExportPath() const { return _ui.exportPathEdit->text().toUtf8().data(); }
+
+        bool exportEarthFile() const { return _ui.earthFileCheckBox->isChecked(); }
+        std::string getEarthFilePath() const { return _ui.earthFilePathEdit->text().toUtf8().data(); }
 
-	  public:
-      ExportDialog(const std::string& dir="", const std::string& boundsString="");
+        bool maxLevelEnabled() const { return _ui.maxLevelCheckBox->isChecked(); }
+        int getMaxLevel() const { return _ui.maxLevelCheckBox->isChecked() ? _ui.maxLevelSpinBox->value() : ~0; }
 
-      std::string getExportPath() const { return _ui.exportPathEdit->text().toUtf8().data(); }
+        int getConcurrency() const { return _ui.concurrencySpinBox->value(); }
 
-      bool exportEarthFile() const { return _ui.earthFileCheckBox->isChecked(); }
-      std::string getEarthFilePath() const { return _ui.earthFilePathEdit->text().toUtf8().data(); }
+        bool overwriteExisting() const { return _ui.overwriteCheckBox->isChecked(); }
 
-      bool maxLevelEnabled() const { return _ui.maxLevelCheckBox->isChecked(); }
-      int getMaxLevel() const { return _ui.maxLevelCheckBox->isChecked() ? _ui.maxLevelSpinBox->value() : ~0; }
+        bool keepEmpties() const { return _ui.keepEmptiesCheckBox->isChecked(); }
 
-      //bool overrideExtension() const { return _ui.extensionCheckBox->isChecked(); }
-      //std::string getExtension() const { return _ui.extensionComboBox->currentText().toUtf8().data(); }
+        bool useBounds() const { return _ui.boundsCheckBox->isChecked(); }
 
-      bool overwriteExisting() const { return _ui.overwriteCheckBox->isChecked(); }
+        TMSExporter::ProcessingMode getProcessingMode() const
+        {          
+            if (_ui.rbModeMT->isChecked()) return TMSExporter::MODE_MULTITHREADED;
+            if (_ui.rbModeMP->isChecked()) return TMSExporter::MODE_MULTIPROCESS;
+            return TMSExporter::MODE_SINGLE;
+        }
 
-      bool keepEmpties() const { return _ui.keepEmptiesCheckBox->isChecked(); }
 
-      bool useBounds() const { return _ui.boundsCheckBox->isChecked(); }
+        private slots:
+            void showExportBrowse();
+            void updateEarthFilePathEdit();
+            void updateMaxLevelSpinBox();
+            void validateAndAccept();
+            void updateMode(bool checked);
+            void maxLevelChanged(int value);
+            void concurrencyChanged(int value);
 
-	  private slots:
-		  void showExportBrowse();
-      void updateEarthFilePathEdit();
-      void updateMaxLevelSpinBox();
-      //void updateExtensionComboBox();
-      void validateAndAccept();
+    private:		
 
-	  private:		
-		  Ui::ExportDialog _ui;
-      
-		  void initUi(const std::string& dir, const std::string& boundsString);
-  };
+        void updateEstimate();
+        Ui::ExportDialog _ui;
+
+        void initUi(const std::string& dir);
+
+        osgEarth::Bounds _bounds;
+        osg::ref_ptr< osgEarth::MapNode > _mapNode;
+    };
 }
 
 
 
-#endif //TILER_TOOL_EXPORTDIALOG
\ No newline at end of file
+#endif //TILER_TOOL_EXPORTDIALOG
+
diff --git a/src/applications/osgearth_package_qt/ExportDialog.cpp b/src/applications/osgearth_package_qt/ExportDialog.cpp
index ddc0b01..5a9c7f6 100644
--- a/src/applications/osgearth_package_qt/ExportDialog.cpp
+++ b/src/applications/osgearth_package_qt/ExportDialog.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -21,15 +21,20 @@
 
 #include <QFileDialog>
 
+#include <osgEarth/CacheEstimator>
+
+using namespace osgEarth;
 using namespace PackageQt;
 
 
-ExportDialog::ExportDialog(const std::string& dir, const std::string& boundsString)
+ExportDialog::ExportDialog(osgEarth::MapNode* mapNode, const std::string& dir, const osgEarth::Bounds& bounds):
+_mapNode(mapNode),
+_bounds(bounds)
 {
-  initUi(dir, boundsString);
+  initUi(dir);
 }
 
-void ExportDialog::initUi(const std::string& dir, const std::string& boundsString)
+void ExportDialog::initUi(const std::string& dir)
 {
 	_ui.setupUi(this);
 
@@ -37,19 +42,29 @@ void ExportDialog::initUi(const std::string& dir, const std::string& boundsStrin
 
   _ui.exportPathEdit->setText(tr(dir.c_str()));
 
-  if (boundsString.length() > 0)
+  if (_bounds.width() > 0 && _bounds.height() > 0)
   {
-    _ui.boundsLabel->setText(tr(boundsString.c_str()));
-    _ui.boundsLabel->setEnabled(true);
-    _ui.boundsCheckBox->setEnabled(true);
-    _ui.boundsCheckBox->setChecked(true);
+      std::stringstream ss;
+      ss << "LL( " << _bounds.yMin() << ", " << _bounds.xMin() << " ) UR( " << _bounds.yMax() << ", " << _bounds.xMax() << " )";
+      _ui.boundsLabel->setText(tr(ss.str().c_str()));
+      _ui.boundsLabel->setEnabled(true);
+      _ui.boundsCheckBox->setEnabled(true);
+      _ui.boundsCheckBox->setChecked(true);
   }
 
+  _ui.concurrencySpinBox->setValue(OpenThreads::GetNumberOfProcessors());
+
+  updateEstimate();
+
   QObject::connect(_ui.exportPathBrowseButton, SIGNAL(clicked()), this, SLOT(showExportBrowse()));
   QObject::connect(_ui.earthFileCheckBox, SIGNAL(toggled(bool)), this, SLOT(updateEarthFilePathEdit()));
-	QObject::connect(_ui.maxLevelCheckBox, SIGNAL(toggled(bool)), this, SLOT(updateMaxLevelSpinBox()));
-//  QObject::connect(_ui.extensionCheckBox, SIGNAL(toggled(bool)), this, SLOT(updateExtensionComboBox()));
+  QObject::connect(_ui.maxLevelCheckBox, SIGNAL(toggled(bool)), this, SLOT(updateMaxLevelSpinBox()));
   QObject::connect(_ui.okButton, SIGNAL(clicked()), this, SLOT(validateAndAccept()));
+  QObject::connect(_ui.rbModeMP, SIGNAL(toggled(bool)), this, SLOT(updateMode(bool)));
+  QObject::connect(_ui.rbModeMT, SIGNAL(toggled(bool)), this, SLOT(updateMode(bool)));
+  QObject::connect(_ui.rbModeSingle, SIGNAL(toggled(bool)), this, SLOT(updateMode(bool)));
+  QObject::connect(_ui.maxLevelSpinBox, SIGNAL(valueChanged(int)), this, SLOT(maxLevelChanged(int)));
+  QObject::connect(_ui.concurrencySpinBox, SIGNAL(valueChanged(int)), this, SLOT(concurrencyChanged(int)));
 }
 
 void ExportDialog::showExportBrowse()
@@ -62,16 +77,6 @@ void ExportDialog::showExportBrowse()
 		_ui.exportPathEdit->setText(dir);
 }
 
-//void ExportDialog::showEarthFileBrowse()
-//{
-//  QString path = QFileDialog::getSaveFileName(this, tr("Earth File"),
-//    _ui.earthFilePathEdit->text().length() > 0 ? _ui.earthFilePathEdit->text() : QDir::homePath() + QDir::separator() + "out.earth",
-//    tr("Earth Files (*.earth)"));
-//
-//	if (!path.isNull())
-//		_ui.earthFilePathEdit->setText(path);
-//}
-
 void ExportDialog::updateEarthFilePathEdit()
 {
   _ui.earthFilePathEdit->setEnabled(_ui.earthFileCheckBox->isChecked());
@@ -80,13 +85,9 @@ void ExportDialog::updateEarthFilePathEdit()
 void ExportDialog::updateMaxLevelSpinBox()
 {
   _ui.maxLevelSpinBox->setEnabled(_ui.maxLevelCheckBox->isChecked());
+  updateEstimate();
 }
 
-//void ExportDialog::updateExtensionComboBox()
-//{
-//  _ui.extensionComboBox->setEnabled(_ui.extensionCheckBox->isChecked());
-//}
-
 void ExportDialog::validateAndAccept()
 {
   std::string errMsg = "ERROR: ";
@@ -105,4 +106,101 @@ void ExportDialog::validateAndAccept()
   }
 
   _ui.errorLabel->setText(QString(errMsg.c_str()));
-}
\ No newline at end of file
+}
+
+void ExportDialog::updateMode(bool checked)
+{
+    bool multi = _ui.rbModeMP->isChecked() || _ui.rbModeMT->isChecked();
+    _ui.concurrencySpinBox->setEnabled(multi);
+    updateEstimate();
+}
+
+void ExportDialog::maxLevelChanged(int value)
+{    
+    updateEstimate();
+}
+
+void ExportDialog::concurrencyChanged(int value)
+{    
+    updateEstimate();
+}
+
+void ExportDialog::updateEstimate()
+{
+    CacheEstimator est;    
+    est.setProfile(_mapNode->getMap()->getProfile());
+    if (useBounds() && _bounds.width() > 0 && _bounds.height() > 0)
+    {
+        est.addExtent(GeoExtent(_mapNode->getMapSRS(), _bounds));
+    }    
+
+    int maxLevel = 10;
+    if (maxLevelEnabled())
+    {
+        maxLevel = getMaxLevel();        
+    }
+    else
+    {
+        // Determine the max level from the layers
+        maxLevel = 0;
+        for (unsigned int i = 0; i < _mapNode->getMap()->getNumImageLayers(); i++)
+        {
+            osgEarth::ImageLayer* layer = _mapNode->getMap()->getImageLayerAt(i);
+            if (layer)
+            {
+                osgEarth::TileSource* ts = layer->getTileSource();
+                if (ts)
+                {
+                    for (DataExtentList::iterator itr = ts->getDataExtents().begin(); itr != ts->getDataExtents().end(); itr++)
+                    {
+                        if (itr->maxLevel().isSet() && itr->maxLevel().value() > maxLevel)
+                        {
+                            maxLevel = itr->maxLevel().value();
+                        }
+                    }
+                }
+            }
+        }
+
+        for (unsigned int i = 0; i < _mapNode->getMap()->getNumElevationLayers(); i++)
+        {
+            osgEarth::ElevationLayer* layer = _mapNode->getMap()->getElevationLayerAt(i);
+            if (layer)
+            {
+                osgEarth::TileSource* ts = layer->getTileSource();
+                if (ts)
+                {
+                    for (DataExtentList::iterator itr = ts->getDataExtents().begin(); itr != ts->getDataExtents().end(); itr++)
+                    {
+                        if (itr->maxLevel().isSet() && itr->maxLevel().value() > maxLevel)
+                        {
+                            maxLevel = itr->maxLevel().value();
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    est.setMaxLevel(maxLevel);    
+
+    std::stringstream buf;
+    double totalSeconds = est.getTotalTimeInSeconds();
+    TMSExporter::ProcessingMode mode = getProcessingMode();
+    // If we are using multiple threads or processes assume it will scale linearly.
+    if (mode == TMSExporter::MODE_MULTIPROCESS || mode == TMSExporter::MODE_MULTITHREADED)
+    {
+        totalSeconds /= (double)getConcurrency();
+    }
+
+    // Adjust everything by the # of layers
+    unsigned int numLayers = _mapNode->getMap()->getNumImageLayers() + _mapNode->getMap()->getNumElevationLayers();
+    totalSeconds *= (double)numLayers;
+    unsigned int numTiles = est.getNumTiles() * numLayers;
+    double sizeMB = est.getSizeInMB() * (double)numLayers;
+
+    std::string timeString = prettyPrintTime(totalSeconds);
+    buf << "Estimate: Max level=" << maxLevel << "  " << numTiles << " tiles.  " << sizeMB << " MB.  " << timeString;
+    _ui.estimateLabel->setText(QString::fromStdString(buf.str()));
+}
+
diff --git a/src/applications/osgearth_package_qt/ExportDialog.ui b/src/applications/osgearth_package_qt/ExportDialog.ui
index 4d97da1..1a7e712 100644
--- a/src/applications/osgearth_package_qt/ExportDialog.ui
+++ b/src/applications/osgearth_package_qt/ExportDialog.ui
@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>565</width>
-    <height>298</height>
+    <height>357</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -199,6 +199,66 @@
         </item>
        </layout>
       </item>
+      <item>
+       <widget class="QWidget" name="widget" native="true">
+        <layout class="QHBoxLayout" name="horizontalLayout_4">
+         <item>
+          <widget class="QLabel" name="label_2">
+           <property name="text">
+            <string>Concurrency</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QSpinBox" name="concurrencySpinBox">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+           <property name="minimum">
+            <number>1</number>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QRadioButton" name="rbModeSingle">
+           <property name="text">
+            <string>Single Threaded</string>
+           </property>
+           <property name="checked">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QRadioButton" name="rbModeMT">
+           <property name="text">
+            <string>Multithreaded</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QRadioButton" name="rbModeMP">
+           <property name="text">
+            <string>Multiprocess</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <spacer name="horizontalSpacer_2">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>40</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+        </layout>
+       </widget>
+      </item>
      </layout>
     </widget>
    </item>
@@ -216,6 +276,13 @@
     </spacer>
    </item>
    <item>
+    <widget class="QLabel" name="estimateLabel">
+     <property name="text">
+      <string>Estimate</string>
+     </property>
+    </widget>
+   </item>
+   <item>
     <widget class="QLabel" name="errorLabel">
      <property name="text">
       <string/>
diff --git a/src/applications/osgearth_package_qt/ExportProgress.cpp b/src/applications/osgearth_package_qt/ExportProgress.cpp
new file mode 100644
index 0000000..cd2b36c
--- /dev/null
+++ b/src/applications/osgearth_package_qt/ExportProgress.cpp
@@ -0,0 +1,53 @@
+#include "ExportProgress.h"
+
+#include <sstream>
+
+ExportProgressCallback::ExportProgressCallback(QMainWindow* parent, QProgressDialog* dialog)
+    : _parent(parent), _dialog(dialog)
+{
+}
+
+ExportProgressCallback::~ExportProgressCallback()
+{
+}
+
+bool ExportProgressCallback::reportProgress(double current, double total, unsigned currentStage, unsigned totalStages, const std::string& msg)
+{
+    if (_dialog)
+    {
+        int percentComplete = (current / total) * 100;                
+        QMetaObject::invokeMethod(_dialog, "setValue", Qt::QueuedConnection, Q_ARG(int, percentComplete));        
+
+        // Update the status label
+        std::stringstream buf;
+        buf << _status << "\n" << (int)current << " of " << (int)total;        
+        QString qstatus = QString::fromStdString(buf.str());
+        QMetaObject::invokeMethod(_dialog, "setLabelText", Qt::BlockingQueuedConnection, Q_ARG(const QString&, qstatus) );        
+
+        if (_dialog->wasCanceled())
+        {
+            OE_NOTICE << "Returning true from reportProgress" << std::endl;
+            return true;
+        }
+    }
+
+    return false;
+}
+
+void ExportProgressCallback::setStatus(const std::string& status)
+{    
+    _status = status;
+}
+
+
+void ExportProgressCallback::complete()
+{    
+    bool userCanceled = _dialog->wasCanceled();
+    QMetaObject::invokeMethod(_dialog, "close", Qt::QueuedConnection);
+
+    // Only show the cancel message if the user didn't cancel the export
+    //if (!userCanceled)
+    {
+        QMetaObject::invokeMethod(_parent, "showExportResult", Qt::BlockingQueuedConnection);    
+    }
+}
diff --git a/src/applications/osgearth_package_qt/ExportProgress.h b/src/applications/osgearth_package_qt/ExportProgress.h
new file mode 100644
index 0000000..be4ce5e
--- /dev/null
+++ b/src/applications/osgearth_package_qt/ExportProgress.h
@@ -0,0 +1,28 @@
+#ifndef TILER_TOOL_EXPORT_PROGRESS_H
+#define TILER_TOOL_EXPORT_PROGRESS_H 1
+
+#include <osgEarth/Progress>
+#include <QMainWindow>
+#include <QMetaObject>
+#include <QProgressDialog>
+
+class ExportProgressCallback : public osgEarth::ProgressCallback
+{
+public:
+    ExportProgressCallback(QMainWindow* parent, QProgressDialog* dialog);
+
+    virtual ~ExportProgressCallback();
+
+    bool reportProgress(double current, double total, unsigned currentStage, unsigned totalStages, const std::string& msg);
+
+    void setStatus(const std::string& status);
+
+    void complete();
+
+private:
+    QMainWindow* _parent;
+    QProgressDialog* _dialog;    
+    std::string _status;
+};
+
+#endif
\ No newline at end of file
diff --git a/src/applications/osgearth_package_qt/PackageQtMainWindow b/src/applications/osgearth_package_qt/PackageQtMainWindow
index 91f7df6..5095748 100644
--- a/src/applications/osgearth_package_qt/PackageQtMainWindow
+++ b/src/applications/osgearth_package_qt/PackageQtMainWindow
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -39,46 +39,18 @@
 #include <QMetaObject>
 #include <QProgressDialog>
 #include <QToolBar>
+#include <QFileDialog>
+#include <QMessageBox>
 
 #include "SceneController.h"
 #include "TMSExporter.h"
 #include "ExportDialog"
 #include "WaitDialog"
+#include "ExportProgress.h"
 
 namespace
 {
-  class ExportProgressCallback : public osgEarth::ProgressCallback
-  {
-  public:
-    ExportProgressCallback(QProgressDialog* dialog)
-      : _dialog(dialog)
-    {
-    }
-
-    virtual ~ExportProgressCallback() { }
-
-    bool reportProgress(double current, double total, unsigned currentStage, unsigned totalStages, const std::string& msg)
-    {
-      if (_dialog)
-      {
-        QMetaObject::invokeMethod(_dialog, "setValue", Qt::QueuedConnection, Q_ARG(int, (current / total) * 100));
-
-        if (_dialog->wasCanceled())
-          return true;
-      }
-
-      return false;
-    }
-
-    void onCompleted()
-    {
-      QMetaObject::invokeMethod(_dialog, "close", Qt::QueuedConnection);
-    }
-
-  private:
-    QProgressDialog* _dialog;
-  };
-
+ 
   struct SceneBoundsSetCallback : public PackageQt::BoundsSetCallback
   {
     SceneBoundsSetCallback(QAction* toggleAction) : _action(toggleAction) { }
@@ -198,6 +170,7 @@ private slots:
 
           osgEarth::Drivers::GDALOptions layerOpt;
           layerOpt.url() = osgEarth::URI(filePath.toStdString());
+          layerOpt.tileSize() = 15;
           
           std::string fileName = osgDB::getSimpleFileName(filePath.toStdString());
           osg::ref_ptr<osgEarth::ElevationLayer> newLayer = new osgEarth::ElevationLayer(osgEarth::ElevationLayerOptions(fileName, layerOpt));
@@ -217,21 +190,27 @@ private slots:
     {
         if (_exporter)
         {
-          PackageQt::ExportDialog exportDialog(_lastDir.toStdString(), _controller->getBoundsString());
+            osgEarth::Bounds bounds(_controller->getBoundsLL().x(), _controller->getBoundsLL().y(),
+                                    _controller->getBoundsUR().x(), _controller->getBoundsUR().y());
+          PackageQt::ExportDialog exportDialog(_controller->mapNode(), _lastDir.toStdString(), bounds);
+         
 	        if (exportDialog.exec() == QDialog::Accepted)
 	        {
             //QProgressDialog* progress = new QProgressDialog(tr("Exporting. Please wait."), tr("Cancel"), 0, 100, this, Qt::CustomizeWindowHint|Qt::WindowTitleHint|Qt::WindowStaysOnTopHint);
-            QProgressDialog* progress = new QProgressDialog(tr("Processing layers..."), QString(), 0, 100, this, Qt::CustomizeWindowHint|Qt::WindowTitleHint|Qt::WindowStaysOnTopHint);
+            QProgressDialog* progress = new QProgressDialog(tr("Processing layers..."), tr("Cancel"), 0, 100, this, Qt::CustomizeWindowHint|Qt::WindowTitleHint|Qt::WindowStaysOnTopHint);
             progress->setAttribute(Qt::WA_DeleteOnClose, true);
             progress->setWindowTitle(tr("Exporting"));
-            progress->setValue(0);
-            _exporter->setProgressCallback(new ExportProgressCallback(progress));
+            progress->setValue(0);                        
+            progress->setAutoClose(false);
+            _exporter->setProgressCallback(new ExportProgressCallback(this, progress));
 
             progress->setWindowModality(Qt::WindowModal);
             progress->show();
 
             _exporter->setKeepEmpties(exportDialog.keepEmpties());
             _exporter->setMaxLevel(exportDialog.getMaxLevel());
+            _exporter->setConcurrency(exportDialog.getConcurrency());
+            _exporter->setProcessingMode(exportDialog.getProcessingMode() );
 
             osg::Vec2d ll = _controller->getBoundsLL();
             osg::Vec2d ur = _controller->getBoundsUR();
@@ -239,10 +218,11 @@ private slots:
             std::vector<osgEarth::Bounds> bounds;
             if (exportDialog.useBounds() && (ur - ll).length() > 0.0)
               bounds.push_back(osgEarth::Bounds(ll.x(), ll.y(), ur.x(), ur.y()));
-
+            
             _exportThread = new TMSExporterWorkerThread(
               _exporter, 
               _controller->mapNode(),
+              _controller->getEarthFilePath(),
               exportDialog.getExportPath(),
               bounds,
               (exportDialog.exportEarthFile() ? exportDialog.getEarthFilePath() : ""),
@@ -254,6 +234,26 @@ private slots:
         }
     }
 
+    void showExportResult()
+    {
+      if (_exporter->getProgressCallback()->isCanceled())
+      {        
+          std::string message = "Error exporting package";
+          if (!_exporter->getProgressCallback()->message().empty())
+          {
+              message = _exporter->getProgressCallback()->message();
+          }
+          QMessageBox::warning(this, tr("Error"), tr(message.c_str()));
+      }
+      else
+      {
+          double timeS = _exporter->getExportTime();          
+          std::stringstream message;
+          message << "The export finished successfully in " << osgEarth::prettyPrintTime(timeS);          
+          QMessageBox::information(this, tr("Complete"), QString::fromStdString(message.str()));
+      }
+    }
+
     void getBoundingBox(bool checked)
     {
       if (!_controller)
@@ -414,4 +414,5 @@ private:
     QString _lastDir;
 };
 
-}
\ No newline at end of file
+}
+
diff --git a/src/applications/osgearth_package_qt/SceneController.cpp b/src/applications/osgearth_package_qt/SceneController.cpp
index 3a43543..96c8050 100644
--- a/src/applications/osgearth_package_qt/SceneController.cpp
+++ b/src/applications/osgearth_package_qt/SceneController.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -22,12 +22,9 @@
 #include <osgEarth/Common>
 #include <osgEarth/Map>
 #include <osgEarth/MapNode>
-//#include <osgEarth/StringUtils>
 #include <osgEarthAnnotation/FeatureNode>
 #include <osgEarthUtil/Controls>
 #include <osgEarthUtil/ExampleResources>
-//#include <osgEarthUtil/SkyNode>
-
 
 
 using namespace PackageQt;
@@ -121,7 +118,7 @@ SceneController::SceneController(osg::Group* root, osgViewer::View* view, const
   if (_root.valid() && _view.valid())
   {
     //install a canvas for any UI controls we plan to create
-    _canvas = osgEarth::Util::Controls::ControlCanvas::get(_view, false);
+    _canvas = osgEarth::Util::Controls::ControlCanvas::getOrCreate(_view);
 
     _controlContainer = _canvas->addControl( new osgEarth::Util::Controls::VBox() );
     _controlContainer->setBackColor( osgEarth::Util::Controls::Color(osgEarth::Util::Controls::Color::Black, 0.8) );
@@ -179,8 +176,10 @@ osg::Node* SceneController::loadEarthFile(const std::string& url)
   {
     _mapNode = osgEarth::MapNode::findMapNode( _earthNode );
     if (_mapNode.valid())
-    {
+    {        
       _map = _mapNode->getMap();
+      _earthFilePath = url;
+      OE_NOTICE << "Set earth file path to " << _earthFilePath << std::endl;
 
       //const osgEarth::Config& externals = _mapNode->externalConfig();
 
@@ -279,17 +278,4 @@ void SceneController::setBounds(const osgEarth::GeoPoint& p1, const osgEarth::Ge
       _bboxNode->setFeature(feature);
     }
   }
-}
-
-std::string SceneController::getBoundsString()
-{
-  std::string str = "";
-  if ((_boundsUR - _boundsLL).length() > 0.0)
-  {
-    std::stringstream ss;
-    ss << "LL( " << _boundsLL.y() << ", " << _boundsLL.x() << " ) UR( " << _boundsUR.y() << ", " << _boundsUR.x() << " )";
-    str = ss.str();
-  }
-
-  return str;
 }
\ No newline at end of file
diff --git a/src/applications/osgearth_package_qt/SceneController.h b/src/applications/osgearth_package_qt/SceneController.h
index a3cfa78..1e1d604 100644
--- a/src/applications/osgearth_package_qt/SceneController.h
+++ b/src/applications/osgearth_package_qt/SceneController.h
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -27,7 +27,6 @@
 #include <osgEarthAnnotation/FeatureNode>
 #include <osgEarthUtil/Controls>
 #include <osgEarthUtil/ExampleResources>
-//#include <osgEarthUtil/SkyNode>
 
 namespace PackageQt
 {
@@ -54,8 +53,9 @@ namespace PackageQt
 
     void setBounds(const osgEarth::GeoPoint& p1, const osgEarth::GeoPoint& p2);
     const osg::Vec2d &getBoundsLL() { return _boundsLL; }
-    const osg::Vec2d &getBoundsUR() { return _boundsUR; }
-    std::string getBoundsString();
+    const osg::Vec2d &getBoundsUR() { return _boundsUR; }   
+
+    const std::string getEarthFilePath() const { return _earthFilePath; }
 
   private:
 
@@ -76,7 +76,9 @@ namespace PackageQt
     osg::ref_ptr<osgEarth::Annotation::FeatureNode> _bboxNode;
     osg::ref_ptr<osgGA::GUIEventHandler> _guiHandler;
     osg::ref_ptr<BoundsSetCallback> _boundsCallback;
+    std::string _earthFilePath;
   };
 }
 
-#endif //TILER_TOOL_SCENECONTROLLER_H
\ No newline at end of file
+#endif //TILER_TOOL_SCENECONTROLLER_H
+
diff --git a/src/applications/osgearth_package_qt/TMSExporter.cpp b/src/applications/osgearth_package_qt/TMSExporter.cpp
index cc155bd..a1f2aef 100644
--- a/src/applications/osgearth_package_qt/TMSExporter.cpp
+++ b/src/applications/osgearth_package_qt/TMSExporter.cpp
@@ -1,346 +1,261 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#include "TMSExporter.h"
-
-#include <osg/io_utils>
-#include <osgDB/FileNameUtils>
-#include <osgDB/FileUtils>
-#include <osgDB/WriteFile>
-
-#include <osgEarth/Common>
-#include <osgEarth/Map>
-#include <osgEarth/MapNode>
-#include <osgEarth/Registry>
-#include <osgEarth/StringUtils>
-#include <osgEarth/HTTPClient>
-#include <osgEarthUtil/TMSPackager>
-#include <osgEarthDrivers/tms/TMSOptions>
-
-#include <iostream>
-#include <sstream>
-#include <iterator>
-
-using namespace PackageQt;
-using namespace osgEarth;
-using namespace osgEarth::Util;
-using namespace osgEarth::Drivers;
-
-#define LC "[TMSExporter] "
-
-namespace
-{
-  /** Packaging task for a single layer. */
-  struct PackageLayer
-  {
-    void init(osgEarth::Map* map, osgEarth::ImageLayer* layer, osgDB::Options* options, const std::string& rootFolder, const std::string& layerFolder, bool verbose, bool overwrite, bool keepEmpties, unsigned int maxLevel, const std::string& extension, osgEarth::ProgressCallback* callback, std::vector< osgEarth::Bounds >& bounds)
-    {
-      _map = map;
-      _imageLayer = layer;
-      _options = options;
-      _rootFolder = rootFolder;
-      _layerFolder = layerFolder;
-      _verbose = verbose;
-      _overwrite = overwrite;
-      _keepEmpties = keepEmpties;
-      _maxLevel = maxLevel;
-      _extension = extension;
-      _bounds = bounds;
-      _callback = callback;
-      _packageResult.ok = false;
-    }
-
-    void init(osgEarth::Map* map, osgEarth::ElevationLayer* layer, osgDB::Options* options, const std::string& rootFolder, const std::string& layerFolder, bool verbose, bool overwrite, bool keepEmpties, unsigned int maxLevel, const std::string& extension, osgEarth::ProgressCallback* callback, std::vector< osgEarth::Bounds >& bounds)
-    {
-      _map = map;
-      _elevationLayer = layer;
-      _options = options;
-      _rootFolder = rootFolder;
-      _layerFolder = layerFolder;
-      _verbose = verbose;
-      _overwrite = overwrite;
-      _keepEmpties = keepEmpties;
-      _maxLevel = maxLevel;
-      _extension = extension;
-      _bounds = bounds;
-      _callback = callback;
-      _packageResult.ok = false;
-    }
-
-    void execute()
-    {
-      TMSPackager packager( _map->getProfile(), _options);
-
-      packager.setVerbose( _verbose );
-      packager.setOverwrite( _overwrite );
-      packager.setKeepEmptyImageTiles( _keepEmpties );
-
-      if ( _maxLevel != ~0 )
-          packager.setMaxLevel( _maxLevel );
-
-      if (_bounds.size() > 0)
-      {
-          for (unsigned int i = 0; i < _bounds.size(); ++i)
-          {
-              Bounds b = _bounds[i];            
-              if ( b.isValid() )
-                  packager.addExtent( osgEarth::GeoExtent(_map->getProfile()->getSRS(), b) );
-          }
-      }
-
-      std::string layerRoot = osgDB::concatPaths( _rootFolder, _layerFolder );
-
-      if (_imageLayer.valid())
-      {
-        if (_verbose)
-          OE_NOTICE << LC << "Packaging image layer \"" << _layerFolder << "\"" << std::endl;
-
-        _packageResult = packager.package( _imageLayer.get(), layerRoot, _callback, _extension );
-      }
-      else if (_elevationLayer.valid())
-      {
-        if (_verbose)
-          OE_NOTICE << LC << "Packaging elevation layer \"" << _layerFolder << "\"" << std::endl;
-
-        _packageResult = packager.package( _elevationLayer.get(), layerRoot, _callback );
-      }
-    }
-
-    osg::ref_ptr<osgEarth::Map> _map;
-    osg::ref_ptr<osgEarth::ImageLayer> _imageLayer;
-    osg::ref_ptr<osgEarth::ElevationLayer> _elevationLayer;
-    osg::ref_ptr<osgDB::Options> _options;
-    std::string _rootFolder;
-    std::string _layerFolder;
-    bool _verbose;
-    bool _overwrite;
-    bool _keepEmpties;
-    unsigned int _maxLevel;
-    std::string _extension;
-    std::vector< osgEarth::Bounds > _bounds;
-    osg::ref_ptr<osgEarth::ProgressCallback> _callback;
-    TMSPackager::Result _packageResult;
-  };
-}
-
-
-TMSExporter::TMSExporter(const std::string& log)
-: _dbOptions(""), _maxLevel(~0), _keepEmpties(false), _errorMessage("")
-{
-  unsigned num = 2 * OpenThreads::GetNumberOfProcessors();
-  _taskService = new osgEarth::TaskService("TMS Packager", num);
-}
-
-/** Packages image and elevation layers as a TMS. */
-int TMSExporter::exportTMS(MapNode* mapNode, const std::string& path, std::vector< osgEarth::Bounds >& bounds, const std::string& outEarth, bool overwrite, const std::string& extension)
-{
-  if ( !mapNode )
-  {
-    _errorMessage = "Invalid MapNode";
-    if (_progress.valid()) _progress->onCompleted();
-    return 0;
-  }
-
-  // folder to which to write the TMS archive.
-  std::string rootFolder = path;
-
-  osg::ref_ptr<osgDB::Options> options = new osgDB::Options(_dbOptions);
-
-  // create a folder for the output
-  osgDB::makeDirectory(rootFolder);
-  if ( !osgDB::fileExists(rootFolder) )
-  {
-    _errorMessage = "Failed to create root output folder";
-    if (_progress.valid()) _progress->onCompleted();
-    return 0;
-  }
-
-  Map* map = mapNode->getMap();
-
-  // new map for an output earth file if necessary.
-  osg::ref_ptr<Map> outMap = 0L;
-  if ( !outEarth.empty() )
-  {
-      // copy the options from the source map first
-      outMap = new Map(map->getInitialMapOptions());
-  }
-
-  // establish the output path of the earth file, if applicable:
-  std::string outEarthName = osgDB::getSimpleFileName(outEarth);
-  if (outEarthName.length() > 0 && osgEarth::toLower(osgDB::getFileExtension(outEarthName)) != "earth")
-    outEarthName += ".earth";
-
-  std::string outEarthFile = osgDB::concatPaths(rootFolder, outEarthName);
-  
-
-  // semaphore and tasks collection for multithreading
-  osgEarth::Threading::MultiEvent semaphore;
-  osgEarth::TaskRequestVector tasks;
-  int taskCount = 0;
-
-
-  // package any image layers that are enabled and visible
-  ImageLayerVector imageLayers;
-  map->getImageLayers( imageLayers );
-
-  unsigned imageCount = 0;
-  for( ImageLayerVector::iterator i = imageLayers.begin(); i != imageLayers.end(); ++i, ++imageCount )
-  {
-      ImageLayer* layer = i->get();
-
-      if ( layer->getEnabled() && layer->getVisible() )
-      {
-          std::string layerFolder = toLegalFileName( layer->getName() );
-          if ( layerFolder.empty() )
-              layerFolder = Stringify() << "image_layer_" << imageCount;
-
-          ParallelTask<PackageLayer>* task = new ParallelTask<PackageLayer>( &semaphore );
-          task->init(map, layer, options, rootFolder, layerFolder, true, overwrite, _keepEmpties, _maxLevel, extension, new PackageLayerProgressCallback(this, taskCount), bounds);
-          tasks.push_back(task);
-          taskCount++;
-      }
-  }
-
-  // package any elevation layers that are enabled and visible
-  ElevationLayerVector elevationLayers;
-  map->getElevationLayers( elevationLayers );
-
-  int elevCount = 0;
-  for( ElevationLayerVector::iterator i = elevationLayers.begin(); i != elevationLayers.end(); ++i, ++elevCount )
-  {
-      ElevationLayer* layer = i->get();
-      if ( layer->getEnabled() && layer->getVisible() )
-      {
-          std::string layerFolder = toLegalFileName( layer->getName() );
-          if ( layerFolder.empty() )
-              layerFolder = Stringify() << "elevation_layer_" << elevCount;
-
-          ParallelTask<PackageLayer>* task = new ParallelTask<PackageLayer>( &semaphore );
-          task->init(map, layer, options, rootFolder, layerFolder, true, overwrite, _keepEmpties, _maxLevel, extension, new PackageLayerProgressCallback(this, taskCount), bounds);
-          tasks.push_back(task);
-          taskCount++;
-      }
-  }
-
-
-  // Run all the tasks in parallel
-  _totalTasks = taskCount;
-  _percentComplete = 0.0;
-
-  semaphore.reset( _totalTasks );
-  _taskProgress = std::vector<double>(_totalTasks, 0.0);
-
-  for( TaskRequestVector::iterator i = tasks.begin(); i != tasks.end(); ++i )
-        _taskService->add( i->get() );
-
-  // Wait for them to complete
-  semaphore.wait();
-
-
-  // Add successfully packaged layers to the new map object and
-  // write out the .earth file (if requested)
-  if (outMap.valid())
-  {
-    for( TaskRequestVector::iterator i = tasks.begin(); i != tasks.end(); ++i )
-    {
-      PackageLayer* p = dynamic_cast<PackageLayer*>(i->get());
-      if (p)
-      {
-        if (p->_packageResult.ok)
-        {
-          TMSOptions tms;
-          tms.url() = URI(osgDB::concatPaths(p->_layerFolder, "tms.xml"), outEarthFile );
-
-          if (p->_imageLayer.valid())
-          {
-            ImageLayerOptions layerOptions( p->_imageLayer->getName(), tms );
-            layerOptions.mergeConfig( p->_imageLayer->getInitialOptions().getConfig(true) );
-            layerOptions.cachePolicy() = CachePolicy::NO_CACHE;
-
-            outMap->addImageLayer( new ImageLayer(layerOptions) );
-          }
-          else
-          {
-            ElevationLayerOptions layerOptions( p->_elevationLayer->getName(), tms );
-            layerOptions.mergeConfig( p->_elevationLayer->getInitialOptions().getConfig(true) );
-            layerOptions.cachePolicy() = CachePolicy::NO_CACHE;
-
-            outMap->addElevationLayer( new ElevationLayer(layerOptions) );
-          }
-        }
-        else
-        {
-          OE_WARN << LC << p->_packageResult.message << std::endl;
-        }
-      }
-    }
-  }
-
-  if ( outMap.valid() )
-  {
-      MapNodeOptions outNodeOptions = mapNode->getMapNodeOptions();
-      osg::ref_ptr<MapNode> outMapNode = new MapNode(outMap.get(), outNodeOptions);
-      if ( !osgDB::writeNodeFile(*outMapNode.get(), outEarthFile) )
-      {
-          OE_WARN << LC << "Error writing earth file to \"" << outEarthFile << "\"" << std::endl;
-      }
-      else
-      {
-          OE_NOTICE << LC << "Wrote earth file to \"" << outEarthFile << "\"" << std::endl;
-      }
-  }
-
-
-  // Mark the progress callback as completed
-  if (_progress.valid()) _progress->onCompleted();
-
-  return elevCount + imageCount;
-}
-
-void TMSExporter::packageTaskProgress(int id, double complete)
-{
-  if (_progress.valid())
-  {
-    OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _m );
-
-    _percentComplete += complete - _taskProgress[id];
-    _taskProgress[id] = complete;
-
-    _progress->reportProgress(_percentComplete, _totalTasks);
-  }
-}
-
-void TMSExporter::packageTaskComplete(int id)
-{
-  if (_progress.valid())
-  {
-    OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _m );
-
-    _percentComplete += 1.0 - _taskProgress[id];
-    _taskProgress[id] = 1.0;
-
-    //if ( _progress.valid() && (_progress->isCanceled() || _progress->reportProgress(_completedTasks, _totalTasks)) )
-    //{
-    //    //Canceled
-    //}
-
-    _progress->reportProgress(_percentComplete, _totalTasks);
-  }
-}
-
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+#include "TMSExporter.h"
+
+#include <osg/io_utils>
+#include <osgDB/FileNameUtils>
+#include <osgDB/FileUtils>
+#include <osgDB/WriteFile>
+
+#include <osgEarth/Common>
+#include <osgEarth/Map>
+#include <osgEarth/MapNode>
+#include <osgEarth/Registry>
+#include <osgEarth/StringUtils>
+#include <osgEarth/FileUtils>
+#include <osgEarth/HTTPClient>
+#include <osgEarthUtil/TMSPackager>
+#include <osgEarthDrivers/tms/TMSOptions>
+
+#include <iostream>
+#include <sstream>
+#include <iterator>
+
+using namespace PackageQt;
+using namespace osgEarth;
+using namespace osgEarth::Util;
+using namespace osgEarth::Drivers;
+
+#define LC "[TMSExporter] "
+
+TMSExporter::TMSExporter()
+    : _dbOptions(""),
+    _maxLevel(~0),
+    _keepEmpties(false),
+    _concurrency(1),
+    _mode(MODE_SINGLE),
+    _totalTimeS(0.0)
+{  
+}
+
+unsigned int TMSExporter::getConcurrency() const
+{
+    return _concurrency;
+}
+
+void TMSExporter::setConcurrency(unsigned int concurrency)
+{
+    _concurrency = concurrency;
+}
+
+double TMSExporter::getExportTime()
+{
+    return _totalTimeS;
+}
+
+/** Packages image and elevation layers as a TMS. */
+int TMSExporter::exportTMS(MapNode* mapNode, const std::string& earthFilePath, const std::string& path, std::vector< osgEarth::Bounds >& bounds, const std::string& outEarth, bool overwrite, const std::string& extension)
+{   
+    _totalTimeS = 0.0;
+    osg::Timer_t startTime = osg::Timer::instance()->tick();
+
+    Map* map = mapNode->getMap();
+
+    // new map for an output earth file if necessary.
+    osg::ref_ptr<Map> outMap = 0L;
+    if ( !outEarth.empty() )
+    {
+        // copy the options from the source map first
+        outMap = new Map(map->getInitialMapOptions());
+    }
+
+    // establish the output path of the earth file, if applicable:
+    std::string outEarthName = osgDB::getSimpleFileName(outEarth);
+    if (outEarthName.length() > 0 && osgEarth::toLower(osgDB::getFileExtension(outEarthName)) != "earth")
+        outEarthName += ".earth";
+
+    std::string outEarthFile = osgDB::concatPaths(path, outEarthName);
+
+
+    // Create the TMS packager.
+    TMSPackager packager;
+
+    std::string tmpEarth;
+
+    // Setup the visitor with the concurrency level and processing mode if it's set.
+    if (_concurrency > 1)
+    {
+        if (_mode == MODE_MULTIPROCESS)
+        {
+            // Write out a temp earth file so the processes can work on it.
+            // Determine the output path.  If we loaded from an earth file write out the temp file right next to it so relative paths will work the same.
+            // Otherwise use the temp path
+            OE_NOTICE << "Earth file path " << earthFilePath << std::endl;
+            if (!earthFilePath.empty())
+            {
+                std::string root = osgDB::getFilePath(earthFilePath);
+                root += "/";
+                tmpEarth = getTempName(root, ".earth");
+            }
+            else
+            {
+                tmpEarth = getTempName(getTempPath(), ".earth");
+            }
+            OE_INFO << "Writing to " << tmpEarth << std::endl;
+            osgDB::writeNodeFile(*mapNode, tmpEarth );         
+            MultiprocessTileVisitor* v = new MultiprocessTileVisitor();
+            v->setEarthFile(tmpEarth);
+            v->setNumProcesses(_concurrency);
+            packager.setVisitor(v);
+        }
+        else if (_mode == MODE_MULTITHREADED)
+        {
+            MultithreadedTileVisitor* v = new MultithreadedTileVisitor();          
+            v->setNumThreads(_concurrency);
+            packager.setVisitor(v);          
+        }
+    }
+
+    // Make the output directory if it doesn't exist  
+    osgDB::makeDirectory(path);
+    packager.setDestination(path);
+
+    // Setup the osgDB options for the packager.
+    osg::ref_ptr<osgDB::Options> options = new osgDB::Options(_dbOptions);
+    packager.setWriteOptions( options.get() );
+
+    // Add all the bounds
+    for (unsigned int i = 0; i < bounds.size(); i++)
+    {
+        packager.getTileVisitor()->addExtent( osgEarth::GeoExtent(map->getProfile()->getSRS(), bounds[i]));
+    }
+    packager.setExtension(extension);  
+    packager.setOverwrite(overwrite);
+    packager.getTileVisitor()->setProgressCallback( _progress.get() );
+    packager.getTileVisitor()->setMaxLevel(_maxLevel);
+
+    // Compute the total number of layers we are going to operate on.
+    unsigned int totalLayers = map->getNumImageLayers() + map->getNumElevationLayers();  
+
+    unsigned int layerNum = 1;
+
+    // Package each image layer
+    for (unsigned int i = 0; i < map->getNumImageLayers(); i++)
+    {            
+        // Don't continue if the export has been canceled
+        if (_progress->isCanceled())
+        {
+            break;
+        }
+        osg::ref_ptr< ImageLayer > layer = map->getImageLayerAt(i);      
+        std::stringstream buf;
+        buf << "Packaging " << layer->getName() << " (" << layerNum << " of " << totalLayers << ")";
+        OE_NOTICE << buf.str() << std::endl;
+        _progress->setStatus(buf.str());
+        packager.run(layer.get(), map);
+        packager.writeXML(layer.get(), map);
+        if (outMap)
+        {
+            std::string layerFolder = toLegalFileName( packager.getLayerName() );
+
+            // new TMS driver info:
+            TMSOptions tms;
+            tms.url() = URI(
+                osgDB::concatPaths( layerFolder, "tms.xml" ),
+                outEarthFile );
+
+            ImageLayerOptions layerOptions( packager.getLayerName(), tms );
+            layerOptions.mergeConfig( layer->getInitialOptions().getConfig( true ) );
+            layerOptions.cachePolicy() = CachePolicy::NO_CACHE;
+
+            outMap->addImageLayer( new ImageLayer( layerOptions ) );
+        }
+        layerNum++;
+    }
+
+    // Package each elevation layer
+    for (unsigned int i = 0; i < map->getNumElevationLayers(); i++)
+    {
+        // Don't continue if the export has been canceled
+        if (_progress->isCanceled())
+        {
+            break;
+        }
+
+        osg::ref_ptr< ElevationLayer > layer = map->getElevationLayerAt(i);      
+        std::stringstream buf;
+        buf << "Packaging " << layer->getName() << " (" << layerNum << " of " << totalLayers << ")";
+        OE_NOTICE << buf.str() << std::endl;
+        _progress->setStatus(buf.str());
+        packager.run(layer.get(), map);
+        packager.writeXML(layer.get(), map);
+
+        if( outMap.valid() )
+        {
+            std::string layerFolder = toLegalFileName( packager.getLayerName() );
+
+            // new TMS driver info:
+            TMSOptions tms;
+            tms.url() = URI(
+                osgDB::concatPaths( layerFolder, "tms.xml" ),
+                outEarthFile );
+
+            ElevationLayerOptions layerOptions( packager.getLayerName(), tms );
+            layerOptions.mergeConfig( layer->getInitialOptions().getConfig( true ) );
+            layerOptions.cachePolicy() = CachePolicy::NO_CACHE;
+
+            outMap->addElevationLayer( new ElevationLayer( layerOptions ) );
+        }
+
+        layerNum++;
+    }
+
+    // Don't continue if the export has been canceled
+    if (!_progress->isCanceled())
+    {
+        // Write out an earth file if it was requested
+        // Finally, write an earth file if requested:
+        if( outMap.valid() )
+        {
+            MapNodeOptions outNodeOptions = mapNode->getMapNodeOptions();
+            osg::ref_ptr<MapNode> outMapNode = new MapNode( outMap.get(), outNodeOptions );
+            if( !osgDB::writeNodeFile( *outMapNode.get(), outEarthFile ) )
+            {
+                OE_WARN << LC << "Error writing earth file to \"" << outEarthFile << "\"" << std::endl;
+            }
+        }
+    }    
+
+    osg::Timer_t endTime = osg::Timer::instance()->tick();
+
+    _totalTimeS = osg::Timer::instance()->delta_s(startTime, endTime );
+
+
+    // Tell the progress dialog that we're finished and it can close
+    _progress->complete();
+
+    // Remove the temp earth file.
+    if (!tmpEarth.empty())
+    {
+        remove(tmpEarth.c_str());
+    }
+
+    return 0;
+}
\ No newline at end of file
diff --git a/src/applications/osgearth_package_qt/TMSExporter.h b/src/applications/osgearth_package_qt/TMSExporter.h
index 421adc3..f3ce6f5 100644
--- a/src/applications/osgearth_package_qt/TMSExporter.h
+++ b/src/applications/osgearth_package_qt/TMSExporter.h
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -25,19 +25,20 @@
 #include <osgEarth/Map>
 #include <osgEarth/MapNode>
 #include <osgEarth/Progress>
-#include <osgEarth/TaskService>
+#include "ExportProgress.h"
 
 namespace PackageQt
 {
-  class PackageLayerProgressCallback;
-
+  /**
+   * Shim between the QT exporter and the TMSPackager
+   */
   class TMSExporter
   {
-  public:
+  public:    
 
-    TMSExporter(const std::string& log="log.txt");
+    TMSExporter();    
 
-    int exportTMS(osgEarth::MapNode* mapNode, const std::string& path, std::vector< osgEarth::Bounds >& bounds, const std::string& outEarth="", bool overwrite=false, const std::string& extension="");
+    int exportTMS(osgEarth::MapNode* mapNode, const std::string& earthFilePath, const std::string& path, std::vector< osgEarth::Bounds >& bounds, const std::string& outEarth="", bool overwrite=false, const std::string& extension="");
 
     std::string getDBOptions() { return _dbOptions; }
     void setDBOptions(const std::string& options) { _dbOptions = options; }
@@ -46,46 +47,52 @@ namespace PackageQt
 
     bool getKeepEmpties() { return _keepEmpties; }
     void setKeepEmpties(bool keep) { _keepEmpties = keep; }
+  
+    ExportProgressCallback* getProgressCallback() const { return _progress; }
+    void setProgressCallback(ExportProgressCallback* progress) { _progress = progress; }
 
-    std::string getErrorMessage() { return _errorMessage; }
+    unsigned int getConcurrency() const;
+    void setConcurrency(unsigned int concurrency);
 
-    void setProgressCallback(osgEarth::ProgressCallback* progress) { _progress = progress ? progress : new osgEarth::ProgressCallback; }
+    double getExportTime();
 
-  protected:
-    friend class PackageLayerProgressCallback;
+    enum ProcessingMode {
+        MODE_SINGLE,
+        MODE_MULTITHREADED,
+        MODE_MULTIPROCESS        
+    };
 
-    void packageTaskProgress(int id, double percentComplete);
-    void packageTaskComplete(int id);
+    ProcessingMode getProcessingMode() const { return _mode;}
+    void setProcessingMode(ProcessingMode mode) { _mode = mode; }
 
   private:
 
     std::string _dbOptions;
     unsigned _maxLevel;
     bool _keepEmpties;
-    std::string _errorMessage;
+    unsigned int _concurrency;
+    ProcessingMode _mode;
+
+    OpenThreads::Mutex _mutex;       
 
-    OpenThreads::Mutex _m;
+    osg::ref_ptr<ExportProgressCallback> _progress;
 
-    osg::ref_ptr<osgEarth::TaskService> _taskService;
-    int _totalTasks;
-    double _percentComplete;
-    std::vector<double> _taskProgress;
-    osg::ref_ptr<osgEarth::ProgressCallback> _progress;
+    double _totalTimeS;
   };
 
 
   class TMSExporterWorkerThread : public osg::Referenced, public OpenThreads::Thread
   {
   public:
-    TMSExporterWorkerThread(TMSExporter* exporter, osgEarth::MapNode* mapNode, const std::string& path, std::vector< osgEarth::Bounds >& bounds, const std::string& outEarth="", bool overwrite=false, const std::string& extension="")
-      : OpenThreads::Thread(), _exporter(exporter), _mapNode(mapNode), _path(path), _bounds(bounds), _outEarth(outEarth), _overwrite(overwrite), _extension(extension)
+    TMSExporterWorkerThread(TMSExporter* exporter, osgEarth::MapNode* mapNode, const std::string& earthFilePath, const std::string& path, std::vector< osgEarth::Bounds >& bounds, const std::string& outEarth="", bool overwrite=false, const std::string& extension="")
+      : OpenThreads::Thread(), _exporter(exporter), _mapNode(mapNode), _earthFilePath(earthFilePath), _path(path), _bounds(bounds), _outEarth(outEarth), _overwrite(overwrite), _extension(extension)
     { }
 
     void run()
     {
       if (_exporter)
       {
-        _exporter->exportTMS(_mapNode, _path, _bounds, _outEarth, _overwrite, _extension);
+        _exporter->exportTMS(_mapNode, _earthFilePath, _path, _bounds, _outEarth, _overwrite, _extension);        
       }
     }
 
@@ -97,37 +104,9 @@ namespace PackageQt
     std::string _outEarth;
     bool _overwrite;
     std::string _extension;
-  };
-
-
-  class PackageLayerProgressCallback : public osgEarth::ProgressCallback
-  {
-  public:
-    PackageLayerProgressCallback(TMSExporter* exporter, int id)
-      : _exporter(exporter), _id(id)
-    {
-    }
-
-    virtual ~PackageLayerProgressCallback() { }
-
-    bool reportProgress(double current, double total, unsigned currentStage, unsigned totalStages, const std::string& msg)
-    {
-      if (_exporter)
-        _exporter->packageTaskProgress(_id, current / total);
-
-      return false;
-    }
-
-    void onCompleted()
-    {
-      if (_exporter)
-        _exporter->packageTaskComplete(_id);
-    }
-
-  private:
-    TMSExporter* _exporter;
-    int _id;
+    std::string _earthFilePath;
   };
 }
 
-#endif //TILER_TOOL_TMSEXPORTER_H
\ No newline at end of file
+#endif //TILER_TOOL_TMSEXPORTER_H
+
diff --git a/src/applications/osgearth_package_qt/WaitDialog b/src/applications/osgearth_package_qt/WaitDialog
index fdf727b..c59d64f 100644
--- a/src/applications/osgearth_package_qt/WaitDialog
+++ b/src/applications/osgearth_package_qt/WaitDialog
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -36,4 +36,5 @@ public:
 
 }
 
-#endif //TILER_TOOL_WAITDIALOG_H
\ No newline at end of file
+#endif //TILER_TOOL_WAITDIALOG_H
+
diff --git a/src/applications/osgearth_package_qt/WaitDialog.cpp b/src/applications/osgearth_package_qt/WaitDialog.cpp
index 11da2bc..3f353ff 100644
--- a/src/applications/osgearth_package_qt/WaitDialog.cpp
+++ b/src/applications/osgearth_package_qt/WaitDialog.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/applications/osgearth_package_qt/package_qt.cpp b/src/applications/osgearth_package_qt/package_qt.cpp
index d6ac11e..330ca59 100644
--- a/src/applications/osgearth_package_qt/package_qt.cpp
+++ b/src/applications/osgearth_package_qt/package_qt.cpp
@@ -1,142 +1,152 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-* GNU Lesser General Public License for more details.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-
-#include <osg/Notify>
-#include <osgDB/FileUtils>
-#include <osgGA/StateSetManipulator>
-#include <osgViewer/ViewerEventHandlers>
-#include <osgEarth/ImageUtils>
-#include <osgEarth/MapNode>
-#include <osgEarthQt/ViewerWidget>
-#include <osgEarthQt/LayerManagerWidget>
-#include <osgEarthQt/MapCatalogWidget>
-#include <osgEarthQt/DataManager>
-#include <osgEarthUtil/EarthManipulator>
-
-#include <QtGui/QApplication>
-
-#include "PackageQtMainWindow"
-#include "SceneController.h"
-#include "TMSExporter.h"
-
-#ifdef Q_WS_X11
-#include <X11/Xlib.h>
-#endif
-
-using namespace PackageQt;
-using namespace osgEarth::Util;
-using namespace osgEarth::Util::Controls;
-
-
-/** Finds an argument with the specified extension. */
-std::string
-findArgumentWithExtension( osg::ArgumentParser& args, const std::string& ext )
-{
-    for( int i=0; i<args.argc(); ++i )
-    {
-        std::string arg( args.argv()[i] );
-        if ( endsWith( toLower(trim(arg)), ".earth" ) )
-            return arg;
-    }
-    return "";
-}
-
-int main(int argc, char** argv)
-{
-  HTTPClient::setUserAgent( "osgearth_package_qt/1.0" );
-
-  //setup log file
-#ifdef _WIN32
-  const char *appData = getenv("APPDATA");
-#else
-  const char *appData = "/tmp";
-#endif
-
-  std::string logDir = std::string(appData) + "/osgEarthPackageQt";
-  if (!osgDB::fileExists(logDir))
-    osgDB::makeDirectory(logDir);
-
-  std::string logPath = logDir + "/log.txt";
-  std::ofstream* log = new std::ofstream( logPath.c_str() );
-  std::cout.rdbuf( log->rdbuf() );
-  std::cerr.rdbuf( log->rdbuf() );
-
-  if (getenv("OSGEARTH_PACKAGE_LOGGING") != 0)
-  {
-    std::string level( getenv("OSGEARTH_PACKAGE_LOGGING") );
-    if ( level == "INFO" )
-      osgEarth::setNotifyLevel( osg::INFO );
-    else if ( level == "DEBUG" )
-      osgEarth::setNotifyLevel( osg::DEBUG_INFO );
-  }
-  else
-  {
-    osgEarth::setNotifyLevel( osg::INFO );
-  }
-
-  osg::DisplaySettings::instance()->setMinimumNumStencilBits(8);
-
-  #ifdef Q_WS_X11
-  XInitThreads();
-  #endif
-
-  QApplication app(argc, argv);
-
-  osg::ref_ptr<osg::Group> root = new osg::Group();
-
-  //create ViewWidget and get views collection
-  osgEarth::QtGui::ViewerWidget* viewerWidget = new osgEarth::QtGui::ViewerWidget( root );
-  osgEarth::QtGui::ViewVector views;
-  viewerWidget->getViews( views );
-
-  osg::ref_ptr<osgViewer::View> mainView;
-  if (views.size() > 0)
-    mainView = views[0];
-
-  if (mainView.valid())
-  {
-    mainView->getCamera()->setNearFarRatio(0.00002);
-    mainView->addEventHandler( new osgGA::StateSetManipulator(mainView->getCamera()->getOrCreateStateSet()) );
-    mainView->addEventHandler( new osgViewer::StatsHandler() );
-  }
-
-  //create the SceneController, if no earth file is specified a blank
-  //globe will be loaded
-  osg::ArgumentParser args(&argc,argv);
-  std::string earthFile = findArgumentWithExtension(args, ".earth");
-  SceneController controller(root, mainView, earthFile);
-
-  //create the TMSExporter and main window
-  TMSExporter exporter;
-  PackageQtMainWindow appWin(viewerWidget, &controller, &exporter);
-  appWin.setGeometry(100, 100, 1280, 800);
-  appWin.show();
-
-  //return app.exec();
-  int ret = app.exec();
-
-  //TODO: move somewhere smarter
-  if (log)
-  {
-      log->close();
-      delete log;
-  }
-
-  return ret;
-}
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+#include <osg/Notify>
+#include <osgDB/FileUtils>
+#include <osgGA/StateSetManipulator>
+#include <osgViewer/ViewerEventHandlers>
+#include <osgEarth/ImageUtils>
+#include <osgEarth/MapNode>
+#include <osgEarthQt/ViewerWidget>
+#include <osgEarthQt/LayerManagerWidget>
+#include <osgEarthQt/MapCatalogWidget>
+#include <osgEarthQt/DataManager>
+#include <osgEarthUtil/EarthManipulator>
+
+#include <QApplication>
+
+#include "PackageQtMainWindow"
+#include "SceneController.h"
+#include "TMSExporter.h"
+
+#ifdef Q_WS_X11
+#include <X11/Xlib.h>
+#endif
+
+using namespace PackageQt;
+using namespace osgEarth::Util;
+using namespace osgEarth::Util::Controls;
+
+
+/** Finds an argument with the specified extension. */
+std::string
+findArgumentWithExtension( osg::ArgumentParser& args, const std::string& ext )
+{
+    for( int i=0; i<args.argc(); ++i )
+    {
+        std::string arg( args.argv()[i] );
+        if ( endsWith( toLower(trim(arg)), ".earth" ) )
+            return arg;
+    }
+    return "";
+}
+
+int main(int argc, char** argv)
+{
+  HTTPClient::setUserAgent( "osgearth_package_qt/1.0" );
+
+  //setup log file
+#ifdef _WIN32
+  const char *appData = getenv("APPDATA");
+#else
+  const char *appData = "/tmp";
+#endif
+
+  std::string logDir = std::string(appData) + "/osgEarthPackageQt";
+  if (!osgDB::fileExists(logDir))
+    osgDB::makeDirectory(logDir);
+
+  /*
+  std::string logPath = logDir + "/log.txt";
+  std::ofstream* log = new std::ofstream( logPath.c_str() );
+  std::cout.rdbuf( log->rdbuf() );
+  std::cerr.rdbuf( log->rdbuf() );
+
+
+  if (getenv("OSGEARTH_PACKAGE_LOGGING") != 0)
+  {
+    std::string level( getenv("OSGEARTH_PACKAGE_LOGGING") );
+    if ( level == "INFO" )
+      osgEarth::setNotifyLevel( osg::INFO );
+    else if ( level == "DEBUG" )
+      osgEarth::setNotifyLevel( osg::DEBUG_INFO );
+  }
+  else
+  {
+    osgEarth::setNotifyLevel( osg::INFO );
+  }
+  */
+
+  osg::DisplaySettings::instance()->setMinimumNumStencilBits(8);
+
+  #ifdef Q_WS_X11
+  XInitThreads();
+  #endif
+
+  QApplication app(argc, argv);
+
+  osg::ref_ptr<osg::Group> root = new osg::Group();
+
+  //create ViewWidget and get views collection
+  osgEarth::QtGui::ViewerWidget* viewerWidget = new osgEarth::QtGui::ViewerWidget( root );
+  osgEarth::QtGui::ViewVector views;
+  viewerWidget->getViews( views );
+
+  osg::ref_ptr<osgViewer::View> mainView;
+  if (views.size() > 0)
+    mainView = views[0];
+
+  if (mainView.valid())
+  {
+    mainView->getCamera()->setNearFarRatio(0.00002);
+    mainView->addEventHandler( new osgGA::StateSetManipulator(mainView->getCamera()->getOrCreateStateSet()) );
+    mainView->addEventHandler( new osgViewer::StatsHandler() );
+
+    osgViewer::Viewer* viewer = dynamic_cast<osgViewer::Viewer*>(mainView.get());
+    if(viewer)
+      viewer->setThreadingModel(osgViewer::ViewerBase::SingleThreaded);
+
+  }
+
+  //create the SceneController, if no earth file is specified a blank
+  //globe will be loaded
+  osg::ArgumentParser args(&argc,argv);
+  std::string earthFile = findArgumentWithExtension(args, ".earth");
+  SceneController controller(root, mainView, earthFile);
+
+  //create the TMSExporter and main window
+  TMSExporter exporter;
+  PackageQtMainWindow appWin(viewerWidget, &controller, &exporter);
+  appWin.setGeometry(100, 100, 1280, 800);
+  appWin.show();
+
+  //return app.exec();
+  int ret = app.exec();
+
+  //TODO: move somewhere smarter
+  /*
+  if (log)
+  {
+      log->close();
+      delete log;
+  }
+  */
+
+  return ret;
+}
diff --git a/src/applications/osgearth_qt/CMakeLists.txt b/src/applications/osgearth_qt/CMakeLists.txt
index f4a735c..0e6cf54 100644
--- a/src/applications/osgearth_qt/CMakeLists.txt
+++ b/src/applications/osgearth_qt/CMakeLists.txt
@@ -1,6 +1,4 @@
-INCLUDE( ${QT_USE_FILE} )
-
-INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} ${QT_INCLUDES})
+INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} ${QT_INCLUDES} )
 
 SET(MOC_HDRS
     DemoMainWindow
@@ -11,9 +9,18 @@ set(LIB_QT_RCS
     images.qrc
 )
 
-QT4_ADD_RESOURCES( LIB_RC_SRCS ${LIB_QT_RCS} )
-
-QT4_WRAP_CPP( MOC_SRCS ${MOC_HDRS} OPTIONS "-f" )
+IF(Qt5Widgets_FOUND)
+    QT5_ADD_RESOURCES( LIB_RC_SRCS ${LIB_QT_RCS} )
+    IF(Qt5Widgets_VERSION VERSION_LESS 5.2.0)
+        QT5_WRAP_CPP( MOC_SRCS ${MOC_HDRS} OPTIONS "-f" )
+    ELSE()
+        QT5_WRAP_CPP( MOC_SRCS ${MOC_HDRS} )
+    ENDIF()
+ELSE()
+    INCLUDE( ${QT_USE_FILE} )
+    QT4_ADD_RESOURCES( LIB_RC_SRCS ${LIB_QT_RCS} )
+    QT4_WRAP_CPP( MOC_SRCS ${MOC_HDRS} OPTIONS "-f" )
+ENDIF()
 
 SET(TARGET_H
     DemoMainWindow
diff --git a/src/applications/osgearth_qt/DemoMainWindow b/src/applications/osgearth_qt/DemoMainWindow
index bccbf5e..5e3681a 100644
--- a/src/applications/osgearth_qt/DemoMainWindow
+++ b/src/applications/osgearth_qt/DemoMainWindow
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/applications/osgearth_qt/osgearth_qt.cpp b/src/applications/osgearth_qt/osgearth_qt.cpp
index 8a5115c..d8a70a2 100644
--- a/src/applications/osgearth_qt/osgearth_qt.cpp
+++ b/src/applications/osgearth_qt/osgearth_qt.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -34,15 +34,15 @@
 #include <osgEarthQt/TerrainProfileWidget>
 #include <osgEarthUtil/AnnotationEvents>
 #include <osgEarthUtil/AutoClipPlaneHandler>
-#include <osgEarthUtil/SkyNode>
 #include <osgEarthUtil/EarthManipulator>
-#include <osgEarthDrivers/ocean_surface/OceanSurface>
+#include <osgEarthUtil/Sky>
+#include <osgEarthUtil/Ocean>
 
 #include <QAction>
 #include <QDockWidget>
 #include <QMainWindow>
 #include <QToolBar>
-#include <QtGui/QApplication>
+#include <QApplication>
 
 #include "DemoMainWindow"
 
@@ -59,7 +59,7 @@ using namespace osgEarth::Util;
 
 static osg::ref_ptr<osg::Group> s_annoGroup;
 static osgEarth::Util::SkyNode* s_sky=0L;
-static osgEarth::Drivers::OceanSurfaceNode* s_ocean=0L;
+static osgEarth::Util::OceanNode* s_ocean=0L;
 
 //------------------------------------------------------------------
 
@@ -283,16 +283,19 @@ main(int argc, char** argv)
             Config skyConf = externals.child("sky");
 
             double hours = skyConf.value("hours", 12.0);
-            s_sky = new osgEarth::Util::SkyNode(mapNode->getMap());
+            s_sky = osgEarth::Util::SkyNode::create(mapNode);
             s_sky->setDateTime( DateTime(2011, 3, 6, hours) );
             for(osgEarth::QtGui::ViewVector::iterator i = views.begin(); i != views.end(); ++i )
-                s_sky->attach( *i );
+                s_sky->attach( *i, 0 );
             root->addChild(s_sky);
 
             // Ocean surface.
             if (externals.hasChild("ocean"))
             {
-                s_ocean = new osgEarth::Drivers::OceanSurfaceNode(mapNode.get(), externals.child("ocean"));
+                s_ocean = osgEarth::Util::OceanNode::create(
+                    osgEarth::Util::OceanOptions(externals.child("ocean")),
+                    mapNode.get());
+
                 if (s_ocean)
                     root->addChild(s_ocean);
             }
@@ -326,6 +329,9 @@ main(int argc, char** argv)
         viewer->addUpdateOperation(new TrackSimUpdate(trackSims));
     }
 
+    if(viewer.valid())
+      viewer->setThreadingModel(osgViewer::ViewerBase::SingleThreaded);
+
 
     // create catalog widget and add as a docked widget to the main window
     QDockWidget *catalogDock = new QDockWidget(QWidget::tr("Layers"));
diff --git a/src/applications/osgearth_qt_simple/CMakeLists.txt b/src/applications/osgearth_qt_simple/CMakeLists.txt
index a0ac75c..3b8249e 100644
--- a/src/applications/osgearth_qt_simple/CMakeLists.txt
+++ b/src/applications/osgearth_qt_simple/CMakeLists.txt
@@ -1,11 +1,23 @@
+IF(QT4_FOUND)
 INCLUDE( ${QT_USE_FILE} )
+ENDIF()
 
-INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} ${QT_INCLUDES})
+INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} ${QT_INCLUDES} )
 
 SET(MOC_HDRS
 )
 
-QT4_WRAP_CPP( MOC_SRCS ${MOC_HDRS} OPTIONS "-f" )
+IF(Qt5Widgets_FOUND)
+    IF(Qt5Widgets_VERSION VERSION_LESS 5.2.0)
+        QT5_WRAP_CPP( MOC_SRCS ${MOC_HDRS} OPTIONS "-f" )
+    ELSE()
+        QT5_WRAP_CPP( MOC_SRCS ${MOC_HDRS} )
+    ENDIF()
+ENDIF()
+
+IF(QT4_FOUND)
+    QT4_WRAP_CPP( MOC_SRCS ${MOC_HDRS} OPTIONS "-f" )
+ENDIF()
 
 SET(TARGET_H
     ${LIB_QT_RCS}
diff --git a/src/applications/osgearth_qt_simple/osgearth_qt_simple.cpp b/src/applications/osgearth_qt_simple/osgearth_qt_simple.cpp
index e855baf..49dc50f 100644
--- a/src/applications/osgearth_qt_simple/osgearth_qt_simple.cpp
+++ b/src/applications/osgearth_qt_simple/osgearth_qt_simple.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -22,11 +22,11 @@
 #include <osgEarthUtil/ExampleResources>
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthQt/ViewerWidget>
-#include <QtGui/QApplication>
-#include <QtGui/QMainWindow>
-#include <QtGui/QStatusBar>
-#include <QtGui/QMdiArea>
-#include <QtGui/QMdiSubWindow>
+#include <QApplication>
+#include <QMainWindow>
+#include <QStatusBar>
+#include <QMdiArea>
+#include <QMdiSubWindow>
 
 #ifdef Q_WS_X11
 #include <X11/Xlib.h>
diff --git a/src/applications/osgearth_qt_windows/CMakeLists.txt b/src/applications/osgearth_qt_windows/CMakeLists.txt
index 07aba8d..b466d36 100644
--- a/src/applications/osgearth_qt_windows/CMakeLists.txt
+++ b/src/applications/osgearth_qt_windows/CMakeLists.txt
@@ -1,11 +1,18 @@
-INCLUDE( ${QT_USE_FILE} )
-
-INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} ${QT_INCLUDES})
+INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} ${QT_INCLUDES} )
 
 SET(MOC_HDRS
 )
 
-QT4_WRAP_CPP( MOC_SRCS ${MOC_HDRS} OPTIONS "-f" )
+IF(Qt5Widgets_FOUND)
+    IF(Qt5Widgets_VERSION VERSION_LESS 5.2.0)
+        QT5_WRAP_CPP( MOC_SRCS ${MOC_HDRS} OPTIONS "-f" )
+    ELSE()
+        QT5_WRAP_CPP( MOC_SRCS ${MOC_HDRS} )
+    ENDIF()
+ELSE()
+    INCLUDE( ${QT_USE_FILE} )
+    QT4_WRAP_CPP( MOC_SRCS ${MOC_HDRS} OPTIONS "-f" )
+ENDIF()
 
 SET(TARGET_H
     ${LIB_QT_RCS}
diff --git a/src/applications/osgearth_qt_windows/osgearth_qt_windows.cpp b/src/applications/osgearth_qt_windows/osgearth_qt_windows.cpp
index 3f3f328..727e6ee 100644
--- a/src/applications/osgearth_qt_windows/osgearth_qt_windows.cpp
+++ b/src/applications/osgearth_qt_windows/osgearth_qt_windows.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -27,11 +27,12 @@
 #include <osgViewer/CompositeViewer>
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthQt/ViewWidget>
-#include <QtGui/QApplication>
-#include <QtGui/QDialog>
-#include <QtGui/QMainWindow>
-#include <QtGui/QPushButton>
-#include <QtGui/QLayout>
+#include <osgEarth/Random>
+#include <QApplication>
+#include <QDialog>
+#include <QMainWindow>
+#include <QPushButton>
+#include <QLayout>
 
 #ifdef Q_WS_X11
 #include <X11/Xlib.h>
diff --git a/src/applications/osgearth_seed/osgearth_seed.cpp b/src/applications/osgearth_seed/osgearth_seed.cpp
index b3f49b3..62a14a6 100644
--- a/src/applications/osgearth_seed/osgearth_seed.cpp
+++ b/src/applications/osgearth_seed/osgearth_seed.cpp
@@ -1,21 +1,21 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
 
 #include <osgDB/FileNameUtils>
 #include <osgDB/FileUtils>
@@ -23,14 +23,15 @@
 #include <osg/io_utils>
 
 #include <osgEarth/Common>
-#include <osgEarth/Map>
-#include <osgEarth/MapFrame>
 #include <osgEarth/Cache>
 #include <osgEarth/CacheEstimator>
 #include <osgEarth/CacheSeed>
 #include <osgEarth/MapNode>
 #include <osgEarth/Registry>
 #include <osgEarthDrivers/feature_ogr/OGRFeatureOptions>
+#include <osgEarth/FileUtils>
+
+#include <osgEarth/TileVisitor>
 
 #include <iostream>
 #include <sstream>
@@ -46,12 +47,10 @@ int seed( osg::ArgumentParser& args );
 int purge( osg::ArgumentParser& args );
 int usage( const std::string& msg );
 int message( const std::string& msg );
-std::string prettyPrintTime( double seconds );
-std::string prettyPrintSize( double mb );
 
 
 int
-main(int argc, char** argv)
+    main(int argc, char** argv)
 {
     osg::ArgumentParser args(&argc,argv);
 
@@ -60,13 +59,13 @@ main(int argc, char** argv)
     else if ( args.read( "--list" ) )
         return list( args );
     else if ( args.read( "--purge" ) )
-        return purge( args );
+        return purge( args );        
     else
-        return usage("");
+    return usage("");
 }
 
 int
-usage( const std::string& msg )
+    usage( const std::string& msg )
 {
     if ( !msg.empty() )
     {
@@ -77,7 +76,7 @@ usage( const std::string& msg )
         << std::endl
         << "USAGE: osgearth_cache" << std::endl
         << std::endl
-        << "    --list file.earth                   ; Lists info about the cache in a .earth file" << std::endl
+        << "    --list file.earth                   ; Lists info about the cache in a .earth file" << std::endl       
         << std::endl
         << "    --seed file.earth                   ; Seeds the cache in a .earth file"  << std::endl
         << "        [--estimate]                    ; Print out an estimation of the number of tiles, disk space and time it will take to perform this seed operation" << std::endl
@@ -85,9 +84,10 @@ usage( const std::string& msg )
         << "        [--max-level level]             ; Highest LOD level to seed (defaut=highest available)" << std::endl
         << "        [--bounds xmin ymin xmax ymax]* ; Geospatial bounding box to seed (in map coordinates; default=entire map)" << std::endl
         << "        [--index shapefile]             ; Use the feature extents in a shapefile to set the bounding boxes for seeding" << std::endl
-        << "        [--cache-path path]             ; Overrides the cache path in the .earth file" << std::endl
-        << "        [--cache-type type]             ; Overrides the cache type in the .earth file" << std::endl
-        << "        [--threads]                     ; The number of threads to use for the seed operation (default=1)" << std::endl
+        << "        [--mp]                          ; Use multiprocessing to process the tiles.  Useful for GDAL sources as this avoids the global GDAL lock" << std::endl
+        << "        [--mt]                          ; Use multithreading to process the tiles." << std::endl
+        << "        [--concurrency]                 ; The number of threads or proceses to use if --mp or --mt are provided." << std::endl
+        << "        [--verbose]                     ; Displays progress of the seed operation" << std::endl
         << std::endl
         << "    --purge file.earth                  ; Purges a layer cache in a .earth file (interactive)" << std::endl
         << std::endl;
@@ -105,21 +105,21 @@ int message( const std::string& msg )
 }
 
 int
-seed( osg::ArgumentParser& args )
+    seed( osg::ArgumentParser& args )
 {    
+    osgDB::Registry::instance()->getReaderWriterForExtension("png");
+    osgDB::Registry::instance()->getReaderWriterForExtension("jpg");
+    osgDB::Registry::instance()->getReaderWriterForExtension("tiff");
 
     //Read the min level
-    unsigned int minLevel = 0;
+    int minLevel = -1;
     while (args.read("--min-level", minLevel));
-    
+
     //Read the max level
-    unsigned int maxLevel = 5;
+    int maxLevel = -1;
     while (args.read("--max-level", maxLevel));
 
     bool estimate = args.read("--estimate");        
-
-    unsigned int threads = 1;
-    while (args.read("--threads", threads));
     
 
     std::vector< Bounds > bounds;
@@ -132,16 +132,26 @@ seed( osg::ArgumentParser& args )
         bounds.push_back( b );
     }    
 
-    //Read the cache override directory
-    std::string cachePath;
-    while (args.read("--cache-path", cachePath));
-
-    //Read the cache type
-    std::string cacheType;
-    while (args.read("--cache-type", cacheType));
+    std::string tileList;
+    while (args.read( "--tiles", tileList ) );
 
     bool verbose = args.read("--verbose");
 
+    unsigned int batchSize = 0;
+    args.read("--batchsize", batchSize);
+
+    // Read the concurrency level
+    unsigned int concurrency = 0;
+    args.read("-c", concurrency);
+    args.read("--concurrency", concurrency);
+
+    int imageLayerIndex = -1;
+    args.read("--image", imageLayerIndex);
+
+    int elevationLayerIndex = -1;
+    args.read("--elevation", elevationLayerIndex);
+
+
     //Read in the earth file.
     osg::ref_ptr<osg::Node> node = osgDB::readNodeFiles( args );
     if ( !node.valid() )
@@ -150,7 +160,7 @@ seed( osg::ArgumentParser& args )
     MapNode* mapNode = MapNode::findMapNode( node.get() );
     if ( !mapNode )
         return usage( "Input file was not a .earth file" );
-    
+
     // Read in an index shapefile
     std::string index;
     while (args.read("--index", index))
@@ -178,8 +188,10 @@ seed( osg::ArgumentParser& args )
     if (estimate)
     {        
         CacheEstimator est;
-        est.setMinLevel( minLevel );
-        est.setMaxLevel( maxLevel );
+        if ( minLevel >= 0 )
+            est.setMinLevel( minLevel );
+        if ( maxLevel >= 0 )
+            est.setMaxLevel( maxLevel );
         est.setProfile( mapNode->getMap()->getProfile() );
 
         for (unsigned int i = 0; i < bounds.size(); i++)
@@ -193,46 +205,183 @@ seed( osg::ArgumentParser& args )
         double size = est.getSizeInMB();
         double time = est.getTotalTimeInSeconds();
         std::cout << "Cache Estimation " << std::endl
-                  << "---------------- " << std::endl
-                  << "Total number of tiles: " << numTiles << std::endl
-                  << "Size on disk:          " << prettyPrintSize( size ) << std::endl
-                  << "Total time:            " << prettyPrintTime( time ) << std::endl;
+            << "---------------- " << std::endl
+            << "Total number of tiles: " << numTiles << std::endl
+            << "Size on disk:          " << osgEarth::prettyPrintSize( size ) << std::endl
+            << "Total time:            " << osgEarth::prettyPrintTime( time ) << std::endl;
 
         return 0;
     }
+    
+    osg::ref_ptr< TileVisitor > visitor;
 
-    CacheSeed seeder;
-    seeder.setMinLevel( minLevel );
-    seeder.setMaxLevel( maxLevel );
-    seeder.setNumThreads( threads );
 
+    // If we are given a task file, load it up and create a new TileKeyListVisitor
+    if (!tileList.empty())
+    {        
+        TaskList tasks( mapNode->getMap()->getProfile() );
+        tasks.load( tileList );
 
-    for (unsigned int i = 0; i < bounds.size(); i++)
+        TileKeyListVisitor* v = new TileKeyListVisitor();
+        v->setKeys( tasks.getKeys() );
+        visitor = v;        
+        OE_DEBUG << "Read task list with " << tasks.getKeys().size() << " tasks" << std::endl;
+    }
+  
+
+    // If we dont' have a visitor create one.
+    if (!visitor.valid())
     {
-        GeoExtent extent(mapNode->getMapSRS(), bounds[i]);
-        OE_DEBUG << "Adding extent " << extent.toString() << std::endl;
-        seeder.addExtent( extent );
-    }    
+        if (args.read("--mt"))
+        {
+            // Create a multithreaded visitor
+            MultithreadedTileVisitor* v = new MultithreadedTileVisitor();
+            if (concurrency > 0)
+            {
+                v->setNumThreads(concurrency);
+            }
+            visitor = v;            
+        }
+        else if (args.read("--mp"))
+        {
+            // Create a multiprocess visitor
+            MultiprocessTileVisitor* v = new MultiprocessTileVisitor();
+            if (concurrency > 0)
+            {
+                v->setNumProcesses(concurrency);
+            }
+
+            if (batchSize > 0)
+            {                
+                v->setBatchSize(batchSize);
+            }
+
+            // Try to find the earth file
+            std::string earthFile;
+            for(int pos=1;pos<args.argc();++pos)
+            {
+                if (!args.isOption(pos))
+                {
+                    earthFile  = args[ pos ];
+                    break;
+                }
+            }
+            v->setEarthFile( earthFile );            
+            visitor = v;            
+        }
+        else
+        {
+            // Create a single thread visitor
+            visitor = new TileVisitor();            
+        }        
+    }
 
+    osg::ref_ptr< ProgressCallback > progress = new ConsoleProgressCallback();
+    
     if (verbose)
     {
-        seeder.setProgressCallback(new ConsoleProgressCallback);
+        visitor->setProgressCallback( progress );
     }
 
+    if ( minLevel >= 0 )
+        visitor->setMinLevel( minLevel );
+    if ( maxLevel >= 0 )
+        visitor->setMaxLevel( maxLevel );        
 
-    osg::Timer_t start = osg::Timer::instance()->tick();
 
-    seeder.seed( mapNode->getMap() );
+    for (unsigned int i = 0; i < bounds.size(); i++)
+    {
+        GeoExtent extent(mapNode->getMapSRS(), bounds[i]);
+        OE_DEBUG << "Adding extent " << extent.toString() << std::endl;                
+        visitor->addExtent( extent );
+    }    
+    
 
-    osg::Timer_t end = osg::Timer::instance()->tick();
+    // Initialize the seeder
+    CacheSeed seeder;
+    seeder.setVisitor(visitor.get());
+
+    osgEarth::Map* map = mapNode->getMap();
 
-    OE_NOTICE << "Completed seeding in " << prettyPrintTime( osg::Timer::instance()->delta_s( start, end ) ) << std::endl;
+    // They want to seed an image layer
+    if (imageLayerIndex >= 0)
+    {
+        osg::ref_ptr< ImageLayer > layer = map->getImageLayerAt( imageLayerIndex );
+        if (layer)
+        {
+            OE_NOTICE << "Seeding single layer " << layer->getName() << std::endl;
+            osg::Timer_t start = osg::Timer::instance()->tick();        
+            seeder.run(layer, map);
+            osg::Timer_t end = osg::Timer::instance()->tick();
+            if (verbose)
+            {
+                OE_NOTICE << "Completed seeding layer " << layer->getName() << " in " << prettyPrintTime( osg::Timer::instance()->delta_s( start, end ) ) << std::endl;
+            }    
+        }
+        else
+        {
+            std::cout << "Failed to find an image layer at index " << imageLayerIndex << std::endl;
+            return 1;
+        }
+
+    }
+    // They want to seed an elevation layer
+    else if (elevationLayerIndex >= 0)
+    {
+        osg::ref_ptr< ElevationLayer > layer = map->getElevationLayerAt( elevationLayerIndex );
+        if (layer)
+        {
+            OE_NOTICE << "Seeding single layer " << layer->getName() << std::endl;
+            osg::Timer_t start = osg::Timer::instance()->tick();        
+            seeder.run(layer, map);
+            osg::Timer_t end = osg::Timer::instance()->tick();
+            if (verbose)
+            {
+                OE_NOTICE << "Completed seeding layer " << layer->getName() << " in " << prettyPrintTime( osg::Timer::instance()->delta_s( start, end ) ) << std::endl;
+            }    
+        }
+        else
+        {
+            std::cout << "Failed to find an elevation layer at index " << elevationLayerIndex << std::endl;
+            return 1;
+        }
+    }
+    // They want to seed the entire map
+    else
+    {                
+        // Seed all the map layers
+        for (unsigned int i = 0; i < map->getNumImageLayers(); ++i)
+        {            
+            osg::ref_ptr< ImageLayer > layer = map->getImageLayerAt(i);
+            OE_NOTICE << "Seeding layer" << layer->getName() << std::endl;            
+            osg::Timer_t start = osg::Timer::instance()->tick();
+            seeder.run(layer.get(), map);            
+            osg::Timer_t end = osg::Timer::instance()->tick();
+            if (verbose)
+            {
+                OE_NOTICE << "Completed seeding layer " << layer->getName() << " in " << prettyPrintTime( osg::Timer::instance()->delta_s( start, end ) ) << std::endl;
+            }                
+        }
+
+        for (unsigned int i = 0; i < map->getNumElevationLayers(); ++i)
+        {
+            osg::ref_ptr< ElevationLayer > layer = map->getElevationLayerAt(i);
+            OE_NOTICE << "Seeding layer" << layer->getName() << std::endl;
+            osg::Timer_t start = osg::Timer::instance()->tick();
+            seeder.run(layer.get(), map);            
+            osg::Timer_t end = osg::Timer::instance()->tick();
+            if (verbose)
+            {
+                OE_NOTICE << "Completed seeding layer " << layer->getName() << " in " << prettyPrintTime( osg::Timer::instance()->delta_s( start, end ) ) << std::endl;
+            }                
+        }        
+    }    
 
     return 0;
 }
 
 int
-list( osg::ArgumentParser& args )
+    list( osg::ArgumentParser& args )
 {
     osg::ref_ptr<osg::Node> node = osgDB::readNodeFiles( args );
     if ( !node.valid() )
@@ -295,10 +444,10 @@ struct Entry
 
 
 int
-purge( osg::ArgumentParser& args )
+    purge( osg::ArgumentParser& args )
 {
     //return usage( "Sorry, but purge is not yet implemented." );
-    
+
     osg::ref_ptr<osg::Node> node = osgDB::readNodeFiles( args );
     if ( !node.valid() )
         return usage( "Failed to read .earth file." );
@@ -420,42 +569,4 @@ purge( osg::ArgumentParser& args )
     }
 
     return 0;
-}
-
-/**
- * Gets the total number of seconds formatted as H:M:S
- */
-std::string prettyPrintTime( double seconds )
-{
-    int hours = (int)floor(seconds / (3600.0) );
-    seconds -= hours * 3600.0;
-
-    int minutes = (int)floor(seconds/60.0);
-    seconds -= minutes * 60.0;
-
-    std::stringstream buf;
-    buf << hours << ":" << minutes << ":" << seconds;
-    return buf.str();
-}
-
-/**
- * Gets a pretty printed version of the given size in MB.
- */
-std::string prettyPrintSize( double mb )
-{
-    std::stringstream buf;
-    // Convert to terabytes
-    if ( mb > 1024 * 1024 )
-    {
-        buf << (mb / (1024.0*1024.0)) << " TB";
-    }
-    else if (mb > 1024)
-    {
-        buf << (mb / 1024.0) << " GB";
-    }
-    else 
-    {
-        buf << mb << " MB";
-    }
-    return buf.str();
-}
+}
\ No newline at end of file
diff --git a/src/applications/osgearth_sequencecontrol/osgearth_sequencecontrol.cpp b/src/applications/osgearth_sequencecontrol/osgearth_sequencecontrol.cpp
index ee4bc15..c07d118 100644
--- a/src/applications/osgearth_sequencecontrol/osgearth_sequencecontrol.cpp
+++ b/src/applications/osgearth_sequencecontrol/osgearth_sequencecontrol.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -89,7 +89,7 @@ main(int argc, char** argv)
             label->setBackColor ( 0, 0, 0, 0.5 );
             label->setHorizAlign( ui::Control::ALIGN_CENTER );
             label->setVertAlign ( ui::Control::ALIGN_TOP );
-            ui::ControlCanvas::get( &viewer )->addControl( label );
+            ui::ControlCanvas::getOrCreate( &viewer )->addControl( label );
 
             // make sure the sequence is playing:
             sc->playSequence();
diff --git a/src/applications/osgearth_shadercomp/osgearth_shadercomp.cpp b/src/applications/osgearth_shadercomp/osgearth_shadercomp.cpp
index b32e289..74b3941 100644
--- a/src/applications/osgearth_shadercomp/osgearth_shadercomp.cpp
+++ b/src/applications/osgearth_shadercomp/osgearth_shadercomp.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -33,6 +33,7 @@
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarth/VirtualProgram>
 #include <osgEarth/Registry>
+#include <osgEarth/ShaderUtils>
 #include <osgEarthUtil/Controls>
 
 using namespace osgEarth;
@@ -44,14 +45,38 @@ int usage( const std::string& msg )
 {    
     OE_NOTICE
         << msg << "\n\n"
-        << "USAGE: osgearth_shadercomp <earthfile> \n"
+        << "USAGE: osgearth_shadercomp \n"
         << "           [--test1]    : Run the function injection test \n"
-        << "           [--test5]    : Run the Program state set text \n"
+        << "           [--test2]    : Run the accept callback test \n"
+        << "           [--test3]    : Run the shader LOD test \n"
+        << "           [--test4]    : Run the memory test \n"
+        << "           [--test5]    : Run the Program state set test \n"
         << std::endl;
 
     return -1;
 }
 
+//Utility:
+osg::Geode* makeGeom( float v )
+{
+    osg::Geode* geode = new osg::Geode();
+    osg::Geometry* geom = new osg::Geometry();
+    osg::Vec3Array* verts = new osg::Vec3Array();
+    verts->push_back( osg::Vec3(v-1, 0, 0) );
+    verts->push_back( osg::Vec3(v+1, 0, 0) );
+    verts->push_back( osg::Vec3(  0, 0, 2) );
+    geom->setVertexArray( verts );
+    geom->setUseDisplayList(false);
+    geom->setUseVertexBufferObjects(true);
+    osg::Vec4Array* colors = new osg::Vec4Array();
+    colors->push_back( osg::Vec4(0,0,1,1) );
+    geom->setColorArray(colors);
+    geom->setColorBinding(osg::Geometry::BIND_OVERALL);
+    geom->addPrimitiveSet(new osg::DrawArrays(GL_TRIANGLES,0,3));
+    geode->addDrawable(geom);
+    return geode;
+}
+
 //-------------------------------------------------------------------------
 
 // Simple function injection test -- turns the earth gray with a haze.
@@ -93,6 +118,145 @@ namespace TEST_1
 
 //-------------------------------------------------------------------------
 
+// Tests the VirtualProgram's ShaderComp::AcceptCallback
+namespace TEST_2
+{
+    const char* fragShader =
+        "#version 110\n"
+        "void make_it_red(inout vec4 color) {\n"
+        "    color.r = 1.0;\n"
+        "}\n";
+
+    struct Acceptor : public ShaderComp::AcceptCallback
+    {
+        // Return true to activate the shader function.
+        bool operator()(const osg::State& state)
+        {
+            osg::Camera* camera = *state.getGraphicsContext()->getCameras().begin();
+            if (!camera) return false;
+            osg::Viewport* viewport = camera->getViewport();
+            if (!viewport) return false;
+            return viewport->width() > 1000;
+        }
+    };
+
+    osg::Group* run(osg::Node* node)
+    {
+        VirtualProgram* vp = VirtualProgram::getOrCreate(node->getOrCreateStateSet());
+        vp->setFunction("make_it_red", fragShader, ShaderComp::LOCATION_FRAGMENT_LIGHTING, new Acceptor());
+        
+        osg::Group* g = new osg::Group();
+        g->addChild( node );
+        return g;
+    }
+}
+
+//-------------------------------------------------------------------------
+
+// Tests the VirtualProgram's min/max range for functions (shader LOD)
+namespace TEST_3
+{
+    const char* fragShader =
+        "#version 110\n"
+        "void make_it_red(inout vec4 color) {\n"
+        "    color.r = 1.0;\n"
+        "}\n";
+
+    osg::Group* run(osg::Node* node)
+    {
+        float radius = osgEarth::SpatialReference::get("wgs84")->getEllipsoid()->getRadiusEquator();
+
+        VirtualProgram* vp = VirtualProgram::getOrCreate(node->getOrCreateStateSet());
+
+        // Install the shader function:
+        vp->setFunction("make_it_red", fragShader, ShaderComp::LOCATION_FRAGMENT_LIGHTING);
+
+        // Set a maximum LOD range for the above function:
+        vp->setFunctionMinRange( "make_it_red", 500000 );
+        vp->setFunctionMaxRange( "make_it_red", 1000000 );
+
+        osg::Group* g = new osg::Group();
+
+        // Install a callback that will convey the LOD range to the shader LOD.
+        g->addCullCallback( new RangeUniformCullCallback() );
+
+        g->addChild( node );
+        return g;
+    }
+}
+//-------------------------------------------------------------------
+
+// Tests memory management by installing and uninstalling shader
+// functions every frame.
+
+namespace TEST_4
+{
+    const char* fragShader =
+        "#version 110\n"
+        "void make_it_red(inout vec4 color) {\n"
+        "    color.r *= 1.5;\n"
+        "}\n";
+
+    struct Acceptor : public ShaderComp::AcceptCallback
+    {
+        osg::ref_ptr<osg::Array> _a;
+
+        Acceptor()
+        {
+            _a = new osg::Vec4Array(1024000);
+        }
+
+        bool operator()(const osg::State& state)
+        {
+            return true;
+        }
+    };
+
+    struct MyGroup : public osg::Group
+    {
+        bool _toggle;
+
+        MyGroup() : _toggle(false)
+        {
+            this->setNumChildrenRequiringUpdateTraversal(1);
+        }
+
+        void traverse(osg::NodeVisitor& nv)
+        {
+            if (nv.getVisitorType() == nv.UPDATE_VISITOR)
+            {
+                if ( (nv.getFrameStamp()->getFrameNumber() % 2) == 0 )
+                {
+                    _toggle = !_toggle;
+
+                    VirtualProgram* vp = VirtualProgram::getOrCreate(this->getOrCreateStateSet());
+                    if ( _toggle )
+                    {
+                        vp->setFunction(
+                            "make_it_red", fragShader,
+                            osgEarth::ShaderComp::LOCATION_FRAGMENT_COLORING,
+                            new Acceptor() );
+                    }
+                    else
+                    {
+                        vp->removeShader("make_it_red");
+                    }
+                }
+            }
+            osg::Group::traverse(nv);
+        }
+    };
+
+    osg::Group* run(osg::Node* node)
+    {
+        MyGroup* g = new MyGroup();
+        g->addChild( node );
+        return g;
+    }
+}
+
+//-------------------------------------------------------------------------
+
 namespace TEST_5
 {
     char s_vert[] =
@@ -109,41 +273,14 @@ namespace TEST_5
         "#version " GLSL_VERSION_STR "\n"
         "void test( inout vec4 color ) { color = vec4(1.0,0.0,0.0,1.0); } \n";
 
-    osg::Geode* makeGeom( float v )
-    {
-        osg::Geode* geode = new osg::Geode();
-        osg::Geometry* geom = new osg::Geometry();
-        osg::Vec3Array* verts = new osg::Vec3Array();
-        verts->push_back( osg::Vec3(v-1, 0, 0) );
-        verts->push_back( osg::Vec3(v+1, 0, 0) );
-        verts->push_back( osg::Vec3(  0, 0, 2) );
-        geom->setVertexArray( verts );
-        geom->setUseDisplayList(false);
-        geom->setUseVertexBufferObjects(true);
-        osg::Vec4Array* colors = new osg::Vec4Array();
-        colors->push_back( osg::Vec4(0,0,1,1) );
-        geom->setColorArray(colors);
-        geom->setColorBinding(osg::Geometry::BIND_OVERALL);
-        geom->addPrimitiveSet(new osg::DrawArrays(GL_TRIANGLES,0,3));
-        geode->addDrawable(geom);
-        return geode;
-    }
-
     osg::Group* run()
     {
         osg::Node* n1 = makeGeom( -5 );
         osg::Node* n2 = makeGeom(  5 );
 
-#if 0
-        osg::Program* p = new osg::Program();
-        p->addShader( new osg::Shader(osg::Shader::VERTEX,   s_vert) );
-        p->addShader( new osg::Shader(osg::Shader::FRAGMENT, s_frag) );
-#else
-        VirtualProgram* p = new VirtualProgram();
-        p->setFunction("test", s_vp, ShaderComp::LOCATION_FRAGMENT_LIGHTING);
-#endif
-
-        n1->getOrCreateStateSet()->setAttributeAndModes( p, 1 );
+        VirtualProgram* vp = new VirtualProgram();
+        vp->setFunction("test", s_vp, ShaderComp::LOCATION_FRAGMENT_LIGHTING);
+        n1->getOrCreateStateSet()->setAttributeAndModes( vp, 1 );
 
         osg::Group* root = new osg::Group();
         root->getOrCreateStateSet()->setRenderBinDetails( 0, "TraversalOrderBin" );
@@ -165,37 +302,63 @@ int main(int argc, char** argv)
     osgViewer::Viewer viewer(arguments);
 
     bool test1 = arguments.read("--test1");
+    bool test2 = arguments.read("--test2");
+    bool test3 = arguments.read("--test3");
+    bool test4 = arguments.read("--test4");
     bool test5 = arguments.read("--test5");
-    bool ok    = test1 || test5; 
+    bool ok    = test1 || test2 || test3 || test4 || test5; 
 
     if ( !ok )
     {
         return usage("");
     }
 
+    osg::Group* root = new osg::Group();
+    viewer.setSceneData( root );
+
+    // add a canvas:
+    ControlCanvas* canvas = new ControlCanvas();
+    root->addChild( canvas );
+
+    // and a label:
+    LabelControl* label = new LabelControl();
+    canvas->addControl(label);
+
     if ( !test5 )
     {
-        osg::Node* earthNode = osgDB::readNodeFiles( arguments );
+        osg::Node* earthNode = osgDB::readNodeFile( "gdal_tiff.earth" );
         if (!earthNode)
         {
             return usage( "Unable to load earth model." );
         }
 
-        viewer.setCameraManipulator( new osgEarth::Util::EarthManipulator() );
-
-        LabelControl* label = new LabelControl();
-        if ( !test5 )
-            ControlCanvas::get(&viewer,true)->addControl(label);
-
         if ( test1 )
         {
-            viewer.setSceneData( TEST_1::run(earthNode) );
-            label->setText( "Function injection test: the map should appear hazy." );
+            root->addChild( TEST_1::run(earthNode) );
+            label->setText( "Function injection test: the map appears hazy at high altitude." );
         }
+        else if ( test2 )
+        {
+            root->addChild( TEST_2::run(earthNode) );
+            label->setText( "Accept callback test: the map turns red when viewport width > 1000" );
+        }
+        else if ( test3 )
+        {
+            root->addChild( TEST_3::run(earthNode) );
+            label->setText( "Shader LOD test: the map turns red between 500K and 1M meters altitude" );
+        }
+        else if ( test4 )
+        {
+            root->addChild( TEST_4::run(earthNode) );
+            label->setText("Memory management test; monitor memory for stability");
+        }
+        
+        viewer.setCameraManipulator( new osgEarth::Util::EarthManipulator() );
     }
     else // if ( test5 )
     {
-        viewer.setSceneData( TEST_5::run() );
+        root->addChild( TEST_5::run() );
+        label->setText( "Leakage test: red tri on the left, blue on the right." );
     }
 
     // add some stock OSG handlers:
diff --git a/src/applications/osgearth_shadergen/CMakeLists.txt b/src/applications/osgearth_shadergen/CMakeLists.txt
new file mode 100644
index 0000000..e2ea1c6
--- /dev/null
+++ b/src/applications/osgearth_shadergen/CMakeLists.txt
@@ -0,0 +1,7 @@
+INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} )
+SET(TARGET_LIBRARIES_VARS OSG_LIBRARY OSGDB_LIBRARY OSGUTIL_LIBRARY OSGVIEWER_LIBRARY OPENTHREADS_LIBRARY)
+
+SET(TARGET_SRC osgearth_shadergen.cpp )
+
+#### end var setup  ###
+SETUP_APPLICATION(osgearth_shadergen)
\ No newline at end of file
diff --git a/src/applications/osgearth_map/osgearth_map.cpp b/src/applications/osgearth_shadergen/osgearth_shadergen.cpp
similarity index 55%
copy from src/applications/osgearth_map/osgearth_map.cpp
copy to src/applications/osgearth_shadergen/osgearth_shadergen.cpp
index 89e9802..72426f6 100644
--- a/src/applications/osgearth_map/osgearth_map.cpp
+++ b/src/applications/osgearth_shadergen/osgearth_shadergen.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -17,55 +17,67 @@
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
 
-#include <osg/Notify>
-#include <osgGA/GUIEventHandler>
+#include <osgDB/ReadFile>
 #include <osgGA/StateSetManipulator>
 #include <osgViewer/Viewer>
 #include <osgViewer/ViewerEventHandlers>
-#include <osgEarth/MapNode>
-#include <osgEarthUtil/EarthManipulator>
-#include <osgEarthUtil/AutoClipPlaneHandler>
-#include <osgEarthUtil/Controls>
-#include <osgEarthSymbology/Color>
-#include <osgEarthDrivers/tms/TMSOptions>
+#include <osgUtil/Optimizer>
+
+#include <osgEarth/ShaderGenerator>
+#include <osgEarth/StringUtils>
+#include <osgEarth/Registry>
+#include <osgEarth/StateSetCache>
+
+#define LC "[shadergen] "
 
 using namespace osgEarth;
-using namespace osgEarth::Drivers;
-using namespace osgEarth::Util;
+
+int
+usage(const char* name)
+{
+    OE_NOTICE << "\nUsage: " << name << " file" << std::endl;
+    return 0;
+}
 
 int
 main(int argc, char** argv)
 {
     osg::ArgumentParser arguments(&argc,argv);
 
-    // create the map.
-    Map* map = new Map();
+    // help?
+    if ( arguments.read("--help") || argc < 2 )
+        return usage(argv[0]);
 
-    // add a TMS imager layer:
-    TMSOptions imagery;
-    imagery.url() = "http://readymaps.org/readymap/tiles/1.0.0/7/";
-    map->addImageLayer( new ImageLayer("Imagery", imagery) );
+    // create a viewer:
+    osgViewer::Viewer viewer(arguments);
 
-    // add a TMS elevation layer:
-    TMSOptions elevation;
-    elevation.url() = "http://readymap.org/readymap/tiles/1.0.0/9/";
-    map->addElevationLayer( new ElevationLayer("Elevation", elevation) );
+    StateSetCache* cache = Registry::stateSetCache();
+    cache->setMaxSize(INT_MAX);
 
-    // make the map scene graph:
-    MapNode* node = new MapNode( map );
+    // load the file
+    osg::Node* node = osgDB::readNodeFile(
+        Stringify() << argv[1] << ".osgearth_shadergen" );
 
-    // initialize a viewer:
-    osgViewer::Viewer viewer(arguments);
-    viewer.setCameraManipulator( new EarthManipulator );
-    viewer.setSceneData( node );
+    if ( !node )
+        return usage(argv[0]);
+
+    if ( cache )
+        cache->dumpStats();
 
-    // add some stock OSG handlers:
+#if 0
+    osgUtil::Optimizer o;
+    o.optimize( node,
+        osgUtil::Optimizer::INDEX_MESH |
+        osgUtil::Optimizer::VERTEX_PRETRANSFORM |
+        osgUtil::Optimizer::VERTEX_POSTTRANSFORM );
+#endif
+
+    viewer.setSceneData( node );
     viewer.addEventHandler(new osgViewer::StatsHandler());
     viewer.addEventHandler(new osgViewer::WindowSizeHandler());
     viewer.addEventHandler(new osgViewer::ThreadingHandler());
     viewer.addEventHandler(new osgViewer::LODScaleHandler());
     viewer.addEventHandler(new osgGA::StateSetManipulator(viewer.getCamera()->getOrCreateStateSet()));
-    viewer.addEventHandler(new osgViewer::HelpHandler(arguments.getApplicationUsage()));
-
-    return viewer.run();
+    viewer.run();
+    return 0;
 }
diff --git a/src/applications/osgearth_shadow/CMakeLists.txt b/src/applications/osgearth_shadow/CMakeLists.txt
deleted file mode 100644
index ab627b8..0000000
--- a/src/applications/osgearth_shadow/CMakeLists.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} )
-
-FIND_PATH(_HAS_VDSM osgShadow/ViewDependentShadowMap)
-
-if(_HAS_VDSM)
-ADD_DEFINITIONS(-DHAS_VDSM)
-endif(_HAS_VDSM)
-
-SET(TARGET_LIBRARIES_VARS OSG_LIBRARY OSGDB_LIBRARY OSGUTIL_LIBRARY OSGVIEWER_LIBRARY OSGSHADOW_LIBRARY OPENTHREADS_LIBRARY)
-
-SET(TARGET_SRC osgearth_shadow.cpp )
-
-#### end var setup  ###
-SETUP_APPLICATION(osgearth_shadow)
diff --git a/src/applications/osgearth_shadow/osgearth_shadow.cpp b/src/applications/osgearth_shadow/osgearth_shadow.cpp
deleted file mode 100644
index 6f4070a..0000000
--- a/src/applications/osgearth_shadow/osgearth_shadow.cpp
+++ /dev/null
@@ -1,171 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#include <osg/ArgumentParser>
-#include <osg/Texture2D>
-#include <osg/MatrixTransform>
-#include <osg/Geometry>
-
-#include <osgGA/AnimationPathManipulator>
-#include <osgGA/TerrainManipulator>
-#include <osgGA/AnimationPathManipulator>
-#include <osgGA/StateSetManipulator>
-
-#include <osgViewer/Viewer>
-#include <osgViewer/ViewerEventHandlers>
-
-#include <osgShadow/ShadowedScene>
-#include <osgShadow/ViewDependentShadowMap>
-
-#include <osgUtil/Optimizer>
-
-#include <osgDB/ReadFile>
-
-#include <osg/io_utils>
-#include <iostream>
-#include <sstream>
-
-#include <osgEarthUtil/EarthManipulator>
-#include <osgEarthUtil/ExampleResources>
-#include <osgEarthUtil/ShadowUtils>
-#include <osgEarth/NodeUtils>
-#include <osgEarth/MapNode>
-#include <osgEarth/TerrainEngineNode>
-#include <osgEarthDrivers/engine_osgterrain/OSGTerrainOptions>
-
-using namespace osgEarth;
-using namespace osgEarth::Util;
-using namespace osgEarth::Drivers;
-
-int main(int argc, char** argv)
-{
-    // use an ArgumentParser object to manage the program arguments.
-    osg::ArgumentParser arguments(&argc, argv);
-
-    // set up the usage document, in case we need to print out how to use this program.
-    arguments.getApplicationUsage()->setDescription(arguments.getApplicationName() + " demonstrates osgEarth working with osgShadow");
-    arguments.getApplicationUsage()->setCommandLineUsage(arguments.getApplicationName());
-    arguments.getApplicationUsage()->addCommandLineOption("-h or --help", "Display this information");     
-   
-    // construct the viewer.
-    osgViewer::Viewer viewer(arguments);
-
-    // if user request help write it out to cout.
-    if (arguments.read("-h") || arguments.read("--help"))
-    {
-        arguments.getApplicationUsage()->write(std::cout);
-        return 1;
-    }
-
-    float ambientBrightness = 0.4f;
-    arguments.read("--ambientBrightness", ambientBrightness);
-
-    // set up the camera manipulators.
-    viewer.setCameraManipulator(new EarthManipulator);
-
-    osg::ref_ptr<osgShadow::ShadowedScene> shadowedScene = new osgShadow::ShadowedScene();
-    
-    //Setup a vdsm shadow map
-    osgShadow::ShadowSettings* settings = new osgShadow::ShadowSettings;
-    settings->setShaderHint( osgShadow::ShadowSettings::NO_SHADERS );
-    settings->setUseOverrideForShadowMapTexture( true );
-    shadowedScene->setShadowSettings(settings);
-
-    if (arguments.read("--persp")) settings->setShadowMapProjectionHint(osgShadow::ShadowSettings::PERSPECTIVE_SHADOW_MAP);
-    if (arguments.read("--ortho")) settings->setShadowMapProjectionHint(osgShadow::ShadowSettings::ORTHOGRAPHIC_SHADOW_MAP);
-
-    double n=0.0;
-    if (arguments.read("-n",n)) settings->setMinimumShadowMapNearFarRatio(n);
-
-    unsigned int numShadowMaps;
-    if (arguments.read("--num-sm",numShadowMaps)) settings->setNumShadowMapsPerLight(numShadowMaps);
-
-    if (arguments.read("--parallel-split") || arguments.read("--ps") ) settings->setMultipleShadowMapHint(osgShadow::ShadowSettings::PARALLEL_SPLIT);
-    if (arguments.read("--cascaded")) settings->setMultipleShadowMapHint(osgShadow::ShadowSettings::CASCADED);
-
-    settings->setDebugDraw( arguments.read("--debug") );
-
-    int mapres = 1024;
-    while (arguments.read("--mapres", mapres))
-        settings->setTextureSize(osg::Vec2s(mapres,mapres));
-
-    // VDSM is really the only technique that osgEarth supports.
-    osg::ref_ptr<osgShadow::ViewDependentShadowMap> vdsm = new osgShadow::ViewDependentShadowMap;
-    shadowedScene->setShadowTechnique(vdsm.get());
-    
-    osg::ref_ptr<osg::Group> model = MapNodeHelper().load(arguments, &viewer);
-    if (!model.valid())
-    {
-        OE_NOTICE
-            << "\nUsage: " << argv[0] << " file.earth" << std::endl
-            << MapNodeHelper().usage() << std::endl;
-        exit(1);
-    }
-
-    MapNode* mapNode = osgEarth::findTopMostNodeOfType< MapNode > ( model.get() );
-
-    SkyNode* skyNode = osgEarth::findTopMostNodeOfType< SkyNode > ( model.get() );
-    if (!skyNode)
-    {
-        OE_NOTICE << "Please run with options --sky to enable the SkyNode" << std::endl;
-        //exit(1);
-    }
-
-    // Prevent terrain skirts (or other "secondary geometry") from casting shadows
-    const TerrainOptions& terrainOptions = mapNode->getTerrainEngine()->getTerrainOptions();
-    shadowedScene->setCastsShadowTraversalMask( ~terrainOptions.secondaryTraversalMask().value() );
-
-    // Enables shadowing on an osgEarth map node
-    ShadowUtils::setUpShadows(shadowedScene, mapNode);
-
-    // For shadowing to work, lighting MUST be enabled on the map node.
-    mapNode->getOrCreateStateSet()->setMode(
-        GL_LIGHTING,
-        osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE );
-
-    // Insert the ShadowedScene decorator just above the MapNode. We don't want other
-    // elements (Control canvas, annotations, etc) under the decorator.
-    osg::Group* root;
-
-    if ( mapNode->getNumParents() > 0 )
-    {
-        osg::Group* parent = mapNode->getParent(0);
-        parent->addChild( shadowedScene );
-        shadowedScene->addChild( mapNode );
-        parent->removeChild( mapNode );
-        root = model.get();
-    }
-    else
-    {
-        root = shadowedScene;
-        shadowedScene->addChild( model.get() );
-    }
-
-    // The skynode's ambient brightness will control the "darkness" of shadows.
-    if ( skyNode )
-    {
-        skyNode->setAmbientBrightness( ambientBrightness );
-    }
-
-    viewer.setSceneData( root );
-    viewer.realize();
-    viewer.addEventHandler(new osgViewer::ScreenCaptureHandler);
-    return viewer.run();    
-}
-
diff --git a/src/applications/osgearth_sharedlayer/osgearth_sharedlayer.cpp b/src/applications/osgearth_sharedlayer/osgearth_sharedlayer.cpp
index 522b698..c2df7ee 100644
--- a/src/applications/osgearth_sharedlayer/osgearth_sharedlayer.cpp
+++ b/src/applications/osgearth_sharedlayer/osgearth_sharedlayer.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -34,6 +34,7 @@
 #include <osgEarth/ImageLayer>
 #include <osgEarth/ColorFilter>
 #include <osgEarth/StringUtils>
+#include <osgEarth/MapNode>
 #include <osgEarthSymbology/StyleSheet>
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/ExampleResources>
diff --git a/src/applications/osgearth_terraineffects/osgearth_terraineffects.cpp b/src/applications/osgearth_terraineffects/osgearth_terraineffects.cpp
index 1aafd30..1b0a8ce 100644
--- a/src/applications/osgearth_terraineffects/osgearth_terraineffects.cpp
+++ b/src/applications/osgearth_terraineffects/osgearth_terraineffects.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -21,21 +21,26 @@
  * This sample tests all the various built-in TerrainEffect classes and
  * lets you toggle them and try them together.
  */
-#include <osg/Notify>
-#include <osgViewer/Viewer>
 #include <osgEarth/VirtualProgram>
 #include <osgEarth/Registry>
 #include <osgEarth/TerrainEngineNode>
+#include <osgEarth/MapNode>
+
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/ExampleResources>
 #include <osgEarthUtil/Controls>
 
 #include <osgEarthUtil/ContourMap>
-#include <osgEarthUtil/DetailTexture>
 #include <osgEarthUtil/LODBlending>
 #include <osgEarthUtil/NormalMap>
 #include <osgEarthUtil/VerticalScale>
 
+#include <osgEarthSymbology/Color>
+
+#include <osg/Notify>
+#include <osg/Fog>
+#include <osgViewer/Viewer>
+
 using namespace osgEarth;
 using namespace osgEarth::Util;
 using namespace osgEarth::Symbology;
@@ -55,7 +60,6 @@ struct App
     TerrainEngineNode*           engine;
 
     osg::ref_ptr<ContourMap>     contourMap;
-    osg::ref_ptr<DetailTexture>  detailTexture;
     osg::ref_ptr<LODBlending>    lodBlending;
     osg::ref_ptr<NormalMap>      normalMap;
     osg::ref_ptr<VerticalScale>  verticalScale;
@@ -63,10 +67,6 @@ struct App
     App()
     {
         contourMap = new ContourMap();
-
-        detailTexture = new DetailTexture();
-        detailTexture->setImage( osgDB::readImageFile("../data/noise3.png") );
-
         lodBlending = new LODBlending();
         normalMap = new NormalMap();
         verticalScale = new VerticalScale();
@@ -107,20 +107,6 @@ struct ContourMapController {
     }
 };
 
-struct DetailTextureController {
-    TOGGLE   ( detailTexture );
-    SET_FLOAT( detailTexture, setIntensity );
-    DetailTextureController(App& app, ui::Grid* grid) {
-        int r = grid->getNumRows();
-        grid->setControl(0, r, new ui::LabelControl("DetailTexture"));
-        grid->setControl(1, r, new ui::CheckBoxControl(false, new Toggle(app)));
-        ++r;
-        grid->setControl(0, r, new ui::LabelControl("   intensity:"));
-        grid->setControl(1, r, new ui::HSliderControl(0.0, 1.0, 0.5, new setIntensity(app)));
-        grid->setControl(2, r, new ui::LabelControl(grid->getControl(1, r)));
-    }
-};
-
 struct LODBlendingController {
     TOGGLE   ( lodBlending );
     SET_FLOAT( lodBlending, setDelay );
@@ -179,7 +165,6 @@ ui::Control* createUI( App& app )
     grid->getControl(1, 0)->setHorizFill( true, 250 );
 
     ContourMapController    (app, grid);
-    DetailTextureController (app, grid);
     LODBlendingController   (app, grid);
     NormalMapController     (app, grid);
     VerticalScaleController (app, grid);
@@ -211,6 +196,7 @@ int main(int argc, char** argv)
     if ( node )
     {
         MapNode* mapNode = MapNode::get(node);
+
         if ( !mapNode )
             return -1;
 
diff --git a/src/applications/osgearth_terrainprofile/osgearth_terrainprofile.cpp b/src/applications/osgearth_terrainprofile/osgearth_terrainprofile.cpp
index 11c2e52..c4abec0 100644
--- a/src/applications/osgearth_terrainprofile/osgearth_terrainprofile.cpp
+++ b/src/applications/osgearth_terrainprofile/osgearth_terrainprofile.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/applications/osgearth_tfs/osgearth_tfs.cpp b/src/applications/osgearth_tfs/osgearth_tfs.cpp
index 4367b25..b652646 100644
--- a/src/applications/osgearth_tfs/osgearth_tfs.cpp
+++ b/src/applications/osgearth_tfs/osgearth_tfs.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/applications/osgearth_tileindex/osgearth_tileindex.cpp b/src/applications/osgearth_tileindex/osgearth_tileindex.cpp
index 8a6a4f0..bba3e6a 100644
--- a/src/applications/osgearth_tileindex/osgearth_tileindex.cpp
+++ b/src/applications/osgearth_tileindex/osgearth_tileindex.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/applications/osgearth_tilesource/osgearth_tilesource.cpp b/src/applications/osgearth_tilesource/osgearth_tilesource.cpp
index 2f6adea..3413221 100644
--- a/src/applications/osgearth_tilesource/osgearth_tilesource.cpp
+++ b/src/applications/osgearth_tilesource/osgearth_tilesource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/applications/osgearth_toc/osgearth_toc.cpp b/src/applications/osgearth_toc/osgearth_toc.cpp
index 4ea6c7b..85864d2 100644
--- a/src/applications/osgearth_toc/osgearth_toc.cpp
+++ b/src/applications/osgearth_toc/osgearth_toc.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -221,7 +221,7 @@ struct MoveLayerHandler : public ControlEventHandler
 void
 createControlPanel( osgViewer::View* view )
 {
-    ControlCanvas* canvas = ControlCanvas::get( view );
+    ControlCanvas* canvas = ControlCanvas::getOrCreate( view );
 
     s_masterGrid = new Grid();
     s_masterGrid->setBackColor(0,0,0,0.5);
diff --git a/src/applications/osgearth_tracks/osgearth_tracks.cpp b/src/applications/osgearth_tracks/osgearth_tracks.cpp
index 8faf385..2c78fe8 100644
--- a/src/applications/osgearth_tracks/osgearth_tracks.cpp
+++ b/src/applications/osgearth_tracks/osgearth_tracks.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -143,7 +143,7 @@ createFieldSchema( TrackNodeFieldSchema& schema )
     nameSymbol->pixelOffset()->set( 0, 2+ICON_SIZE/2 );
     nameSymbol->alignment() = TextSymbol::ALIGN_CENTER_BOTTOM;
     nameSymbol->halo()->color() = Color::Black;
-    nameSymbol->size() = nameSymbol->size().value() + 2.0f;
+    nameSymbol->size() = nameSymbol->size()->eval() + 2.0f;
     schema[FIELD_NAME] = TrackNodeField(nameSymbol, false); // false => static label (won't change after set)
 
     // draw the track coordinates below the icon:
@@ -151,7 +151,7 @@ createFieldSchema( TrackNodeFieldSchema& schema )
     posSymbol->pixelOffset()->set( 0, -2-ICON_SIZE/2 );
     posSymbol->alignment() = TextSymbol::ALIGN_CENTER_TOP;
     posSymbol->fill()->color() = Color::Yellow;
-    posSymbol->size() = posSymbol->size().value() - 2.0f;
+    posSymbol->size() = posSymbol->size()->eval() - 2.0f;
     schema[FIELD_POSITION] = TrackNodeField(posSymbol, true); // true => may change at runtime
 
     // draw some other field to the left:
@@ -211,7 +211,7 @@ createTrackNodes( MapNode* mapNode, osg::Group* parent, const TrackNodeFieldSche
 void
 createControls( osgViewer::View* view )
 {
-    ControlCanvas* canvas = ControlCanvas::get(view, true);
+    ControlCanvas* canvas = ControlCanvas::getOrCreate(view);
     
     // title bar
     VBox* vbox = canvas->addControl(new VBox(Control::ALIGN_NONE, Control::ALIGN_BOTTOM, 2, 1 ));
diff --git a/src/applications/osgearth_transform/CMakeLists.txt b/src/applications/osgearth_transform/CMakeLists.txt
new file mode 100644
index 0000000..f4257a9
--- /dev/null
+++ b/src/applications/osgearth_transform/CMakeLists.txt
@@ -0,0 +1,7 @@
+INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} )
+SET(TARGET_LIBRARIES_VARS OSG_LIBRARY OSGDB_LIBRARY OSGUTIL_LIBRARY OSGVIEWER_LIBRARY OPENTHREADS_LIBRARY)
+
+SET(TARGET_SRC osgearth_transform.cpp )
+
+#### end var setup  ###
+SETUP_APPLICATION(osgearth_transform)
\ No newline at end of file
diff --git a/src/applications/osgearth_transform/osgearth_transform.cpp b/src/applications/osgearth_transform/osgearth_transform.cpp
new file mode 100644
index 0000000..3d3a282
--- /dev/null
+++ b/src/applications/osgearth_transform/osgearth_transform.cpp
@@ -0,0 +1,163 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * Demonstrates how to place an object and control its position and
+ * orientation by combining a GeoTransform and a PositionAttitudeTransform.
+ */
+
+#include <osg/PositionAttitudeTransform>
+#include <osgDB/ReadFile>
+#include <osgViewer/Viewer>
+#include <osgViewer/ViewerEventHandlers>
+#include <osgEarthUtil/EarthManipulator>
+#include <osgEarthUtil/ExampleResources>
+#include <osgEarthUtil/Controls>
+#include <osgEarth/GeoTransform>
+#include <osgEarth/MapNode>
+
+#define LC "[osgearth_transform] "
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+namespace ui = osgEarth::Util::Controls;
+
+int
+usage(const char* name)
+{
+    OE_NOTICE 
+        << "\nUsage: " << name << " file.earth" << std::endl;
+
+    return 0;
+}
+
+struct App
+{
+    const osgEarth::SpatialReference* srs;
+    osgEarth::GeoTransform*           geo;
+    osg::PositionAttitudeTransform*   pat;
+
+    ui::HSliderControl* uiLat;
+    ui::HSliderControl* uiLon;
+    ui::HSliderControl* uiAlt;
+    ui::HSliderControl* uiHeading;
+    ui::HSliderControl* uiPitch;
+    ui::HSliderControl* uiRoll;
+
+    void apply()
+    {
+        GeoPoint pos(
+            srs,
+            uiLon->getValue(), uiLat->getValue(), uiAlt->getValue(),
+            ALTMODE_ABSOLUTE);
+
+        geo->setPosition( pos );
+
+        osg::Quat ori =
+            osg::Quat(osg::DegreesToRadians(uiRoll->getValue()),    osg::Vec3(0,1,0)) *
+            osg::Quat(osg::DegreesToRadians(uiPitch->getValue()),   osg::Vec3(1,0,0)) *
+            osg::Quat(osg::DegreesToRadians(uiHeading->getValue()), osg::Vec3(0,0,-1));
+
+        pat->setAttitude( ori );
+    }
+};
+
+struct Apply : public ui::ControlEventHandler
+{
+    Apply(App& app) : _app(app) { }
+    void onValueChanged(ui::Control* control) {
+        _app.apply();
+    }
+    App& _app;
+};
+
+ui::Control* makeUI(App& app)
+{
+    ui::Grid* grid = new ui::Grid();
+
+    grid->setControl(0, 0, new ui::LabelControl("Lat:"));
+    grid->setControl(0, 1, new ui::LabelControl("Long:"));
+    grid->setControl(0, 2, new ui::LabelControl("Alt:"));
+    grid->setControl(0, 3, new ui::LabelControl("Heading:"));
+    grid->setControl(0, 4, new ui::LabelControl("Pitch:"));
+    grid->setControl(0, 5, new ui::LabelControl("Roll:"));
+
+    app.uiLat = grid->setControl(1, 0, new ui::HSliderControl(-80.0f, 80.0f, 42.0f, new Apply(app)));
+    app.uiLon = grid->setControl(1, 1, new ui::HSliderControl(-180.0f, 180.0f, 7.0f, new Apply(app)));
+    app.uiAlt = grid->setControl(1, 2, new ui::HSliderControl(50000.0f, 500000.0f, 250000.0f, new Apply(app)));
+    app.uiHeading = grid->setControl(1, 3, new ui::HSliderControl(-180.0f, 180.0f, 0.0f, new Apply(app)));
+    app.uiPitch   = grid->setControl(1, 4, new ui::HSliderControl(-90.0f, 90.0f, 0.0f, new Apply(app)));
+    app.uiRoll    = grid->setControl(1, 5, new ui::HSliderControl(-180.0f, 180.0f, 0.0f, new Apply(app)));
+
+    app.uiLat->setHorizFill(true, 300.0f);
+    return grid;
+}
+
+int
+main(int argc, char** argv)
+{
+    osg::ArgumentParser arguments(&argc,argv);
+
+    // help?
+    if ( arguments.read("--help") )
+        return usage(argv[0]);
+
+    osgViewer::Viewer viewer(arguments);
+    EarthManipulator* em = new EarthManipulator();
+    viewer.setCameraManipulator( em );
+
+    // load an earth file, and support all or our example command-line options
+    // and earth file <external> tags    
+    osg::Node* earth = MapNodeHelper().load( arguments, &viewer );
+    MapNode* mapNode = MapNode::get(earth);
+    if (!mapNode)
+        return usage(argv[0]);
+
+    // load the model file into the local coordinate frame, which will be
+    // +X=east, +Y=north, +Z=up.
+    osg::Node* model = osgDB::readNodeFile("cessna.osgt.1,-1,1.scale");
+    if ( !model )
+        return usage(argv[0]);
+
+    osg::Group* root = new osg::Group();
+    root->addChild( earth );
+    
+    App app;
+    app.srs = mapNode->getMapSRS();
+    app.geo = new GeoTransform();
+    app.geo->setTerrain( mapNode->getTerrain() );
+    app.pat = new osg::PositionAttitudeTransform();
+    app.pat->addChild( model );
+    app.geo->addChild( app.pat );
+
+    root->addChild( app.geo );
+    
+    viewer.setSceneData( root );
+    viewer.getCamera()->setNearFarRatio(0.00002);
+    viewer.getCamera()->setSmallFeatureCullingPixelSize(-1.0f);
+
+    ui::ControlCanvas::getOrCreate(&viewer)->addControl( makeUI(app) );
+    app.apply();
+
+    em->setTetherNode( app.geo );
+    em->setViewpoint(osgEarth::Viewpoint(0, 0, 0, -45, -20, model->getBound().radius()*10.0));
+
+    return viewer.run();
+}
diff --git a/src/applications/osgearth_version/osgearth_version.cpp b/src/applications/osgearth_version/osgearth_version.cpp
index ddd9bb2..9597105 100644
--- a/src/applications/osgearth_version/osgearth_version.cpp
+++ b/src/applications/osgearth_version/osgearth_version.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/applications/osgearth_viewer/osgearth_viewer.cpp b/src/applications/osgearth_viewer/osgearth_viewer.cpp
index de97bee..b8935cb 100644
--- a/src/applications/osgearth_viewer/osgearth_viewer.cpp
+++ b/src/applications/osgearth_viewer/osgearth_viewer.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -17,17 +17,15 @@
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
 
-#include <osg/Notify>
 #include <osgViewer/Viewer>
+#include <osgEarth/Notify>
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/ExampleResources>
-#include <osgEarthAnnotation/ModelNode>
 
 #define LC "[viewer] "
 
 using namespace osgEarth;
 using namespace osgEarth::Util;
-using namespace osgEarth::Annotation;
 
 int
 usage(const char* name)
@@ -54,7 +52,7 @@ main(int argc, char** argv)
     // create a viewer:
     osgViewer::Viewer viewer(arguments);
 
-    //Tell the database pager to not modify the unref settings
+    // Tell the database pager to not modify the unref settings
     viewer.getDatabasePager()->setUnrefImageDataAfterApplyPolicy( false, false );
 
     // install our default manipulator (do this before calling load)
@@ -67,15 +65,13 @@ main(int argc, char** argv)
     {
         viewer.setSceneData( node );
 
-        // configure the near/far so we don't clip things that are up close
         viewer.getCamera()->setNearFarRatio(0.00002);
         viewer.getCamera()->setSmallFeatureCullingPixelSize(-1.0f);
 
-        viewer.run();
+        return viewer.run();
     }
     else
     {
         return usage(argv[0]);
     }
-    return 0;
 }
diff --git a/src/osgEarth/AlphaEffect b/src/osgEarth/AlphaEffect
index dba3b41..07191c3 100644
--- a/src/osgEarth/AlphaEffect
+++ b/src/osgEarth/AlphaEffect
@@ -57,8 +57,9 @@ namespace osgEarth
 
         typedef std::list< osg::observer_ptr<osg::StateSet> > StateSetList;
 
-        StateSetList _statesets;
-        osg::ref_ptr<osg::Uniform>       _alphaUniform;
+        bool                       _active;
+        StateSetList               _statesets;
+        osg::ref_ptr<osg::Uniform> _alphaUniform;
 
         void init();
     };
diff --git a/src/osgEarth/AlphaEffect.cpp b/src/osgEarth/AlphaEffect.cpp
index 2a97b26..876b28f 100644
--- a/src/osgEarth/AlphaEffect.cpp
+++ b/src/osgEarth/AlphaEffect.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -18,9 +18,10 @@
 */
 
 #include <osgEarth/AlphaEffect>
-#include <osgEarth/Registry>
 #include <osgEarth/StringUtils>
 #include <osgEarth/VirtualProgram>
+#include <osgEarth/Registry>
+#include <osgEarth/Capabilities>
 
 using namespace osgEarth;
 
@@ -49,8 +50,12 @@ AlphaEffect::AlphaEffect(osg::StateSet* stateset)
 void
 AlphaEffect::init()
 {
-    _alphaUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_alphaeffect_alpha");
-    _alphaUniform->set( 1.0f );
+    _active = Registry::capabilities().supportsGLSL(110u);
+    if ( _active )
+    {
+        _alphaUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_alphaeffect_alpha");
+        _alphaUniform->set( 1.0f );
+    }
 }
 
 AlphaEffect::~AlphaEffect()
@@ -61,24 +66,27 @@ AlphaEffect::~AlphaEffect()
 void
 AlphaEffect::setAlpha(float value)
 {
-    _alphaUniform->set( value );
+    if ( _active )
+        _alphaUniform->set( value );
 }
 
 float
 AlphaEffect::getAlpha() const
 {
-    float value;
-    _alphaUniform->get(value);
+    float value = 1.0f;
+    if (_active)
+        _alphaUniform->get(value);
     return value;
 }
 
 void
 AlphaEffect::attach(osg::StateSet* stateset)
 {
-    if ( stateset )
+    if ( stateset && _active )
     {
         _statesets.push_back(stateset);
         VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
+        vp->setName( "osgEarth.AlphaEffect" );
         vp->setFunction( "oe_alphaeffect_fragment", fragment, ShaderComp::LOCATION_FRAGMENT_COLORING, 2 );
         stateset->addUniform( _alphaUniform.get() );
     }
@@ -87,6 +95,9 @@ AlphaEffect::attach(osg::StateSet* stateset)
 void
 AlphaEffect::detach()
 {
+    if (!_active)
+        return;
+
     for (StateSetList::iterator it = _statesets.begin(); it != _statesets.end(); ++it)
     {
         osg::ref_ptr<osg::StateSet> stateset;
@@ -103,7 +114,7 @@ AlphaEffect::detach()
 void
 AlphaEffect::detach(osg::StateSet* stateset)
 {
-    if ( stateset )
+    if ( stateset && _active )
     {
         stateset->removeUniform( _alphaUniform.get() );
         VirtualProgram* vp = VirtualProgram::get( stateset );
diff --git a/src/osgEarth/AutoScale b/src/osgEarth/AutoScale
index 9c9ae40..289daa6 100644
--- a/src/osgEarth/AutoScale
+++ b/src/osgEarth/AutoScale
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/AutoScale.cpp b/src/osgEarth/AutoScale.cpp
index 7e752e1..70c6495 100644
--- a/src/osgEarth/AutoScale.cpp
+++ b/src/osgEarth/AutoScale.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Bounds b/src/osgEarth/Bounds
index 160863b..a595369 100644
--- a/src/osgEarth/Bounds
+++ b/src/osgEarth/Bounds
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Bounds.cpp b/src/osgEarth/Bounds.cpp
index 7beea5f..bd83e21 100644
--- a/src/osgEarth/Bounds.cpp
+++ b/src/osgEarth/Bounds.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/CMakeLists.txt b/src/osgEarth/CMakeLists.txt
index 8ff7ad5..a2a89e8 100644
--- a/src/osgEarth/CMakeLists.txt
+++ b/src/osgEarth/CMakeLists.txt
@@ -58,6 +58,7 @@ SET(LIB_PUBLIC_HEADERS
     GeoData
     Geoid
     GeoMath
+	GeoTransform
     HeightFieldUtils
     HTTPClient
     ImageLayer
@@ -90,6 +91,7 @@ SET(LIB_PUBLIC_HEADERS
     optional
     OverlayDecorator
     OverlayNode
+	PhongLightingEffect
     Pickers
     PrimitiveIntersector
     Profile
@@ -100,9 +102,11 @@ SET(LIB_PUBLIC_HEADERS
     ShaderFactory
     ShaderGenerator
     ShaderUtils
+	SharedSARepo
     SparseTexture2DArray
     SpatialReference
     StateSetCache
+	StateSetLOD
     StringUtils
     TaskService
     Terrain
@@ -110,10 +114,11 @@ SET(LIB_PUBLIC_HEADERS
     TerrainLayer
     TerrainOptions
     TerrainEngineNode
+    Tessellator
     TextureCompositor
-    TextureCompositorMulti
-    TextureCompositorTexArray
     TileKey
+    TileHandler
+    TileVisitor
     TileSource
     TimeControl
     TraversalData
@@ -146,13 +151,20 @@ IF (NOT TINYXML_FOUND)
     )
 ENDIF (NOT TINYXML_FOUND)
 
-# auto-generate the VersionGit file to include the git SHA1 variable.
-configure_file(
-    "${CMAKE_CURRENT_SOURCE_DIR}/VersionGit.cpp.in"
-    "${CMAKE_CURRENT_BINARY_DIR}/VersionGit.cpp" 
-    @ONLY)
+
+SET(VERSION_GIT_SOURCE "")
+IF (OSGEARTH_EMBED_GIT_SHA)
+	ADD_DEFINITIONS(-DOSGEARTH_EMBED_GIT_SHA)
+	
+	# auto-generate the VersionGit file to include the git SHA1 variable.
+	configure_file(
+		"${CMAKE_CURRENT_SOURCE_DIR}/VersionGit.cpp.in"
+		"${CMAKE_CURRENT_BINARY_DIR}/VersionGit.cpp" 
+		@ONLY)
     
-set(VERSION_GIT_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/VersionGit.cpp")
+	set(VERSION_GIT_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/VersionGit.cpp")	
+ENDIF (OSGEARTH_EMBED_GIT_SHA)
+
 
 ADD_LIBRARY(${LIB_NAME} ${OSGEARTH_USER_DEFINED_DYNAMIC_OR_STATIC}
     ${LIB_PUBLIC_HEADERS}
@@ -190,6 +202,7 @@ ADD_LIBRARY(${LIB_NAME} ${OSGEARTH_USER_DEFINED_DYNAMIC_OR_STATIC}
     GeoData.cpp
     Geoid.cpp
     GeoMath.cpp
+	GeoTransform.cpp
     HeightFieldUtils.cpp
     HTTPClient.cpp
     ImageLayer.cpp
@@ -219,6 +232,7 @@ ADD_LIBRARY(${LIB_NAME} ${OSGEARTH_USER_DEFINED_DYNAMIC_OR_STATIC}
     Notify.cpp
     OverlayDecorator.cpp
     OverlayNode.cpp
+	PhongLightingEffect.cpp
     Pickers.cpp
     PrimitiveIntersector.cpp
     Profile.cpp
@@ -232,16 +246,18 @@ ADD_LIBRARY(${LIB_NAME} ${OSGEARTH_USER_DEFINED_DYNAMIC_OR_STATIC}
     SparseTexture2DArray.cpp
     SpatialReference.cpp
     StateSetCache.cpp
+	StateSetLOD.cpp
     StringUtils.cpp
     TaskService.cpp
     Terrain.cpp
     TerrainLayer.cpp
     TerrainOptions.cpp
     TerrainEngineNode.cpp
+    Tessellator.cpp
     TextureCompositor.cpp
-    TextureCompositorMulti.cpp
-    TextureCompositorTexArray.cpp
     TileKey.cpp
+    TileHandler.cpp
+    TileVisitor.cpp
     TileSource.cpp
     TimeControl.cpp
     TraversalData.cpp
diff --git a/src/osgEarth/Cache b/src/osgEarth/Cache
index 4cc63b9..a17f437 100644
--- a/src/osgEarth/Cache
+++ b/src/osgEarth/Cache
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -25,11 +25,19 @@
 #include <osgEarth/Config>
 #include <osgEarth/TileKey>
 #include <osgEarth/ThreadingUtils>
+#include <sys/types.h>
+
+// environment variables
+#define OSGEARTH_ENV_CACHE_DRIVER  "OSGEARTH_CACHE_DRIVER"
+#define OSGEARTH_ENV_CACHE_PATH    "OSGEARTH_CACHE_PATH"
+#define OSGEARTH_ENV_CACHE_ONLY    "OSGEARTH_CACHE_ONLY"
+#define OSGEARTH_ENV_NO_CACHE      "OSGEARTH_NO_CACHE"
+#define OSGEARTH_ENV_CACHE_MAX_AGE "OSGEARTH_CACHE_MAX_AGE"
 
 namespace osgEarth
 {
     /**
-     * Base class for Cache implementation options.
+     * Base class for Cache options.
      */
     class OSGEARTH_EXPORT CacheOptions : public DriverConfigOptions
     {
@@ -45,8 +53,10 @@ namespace osgEarth
 
     public:
         virtual Config getConfig() const {
-            return ConfigOptions::getConfig();
+            Config conf = ConfigOptions::getConfig();
+            return conf;
         }
+
         virtual void mergeConfig( const Config& conf ) {
             ConfigOptions::mergeConfig( conf );            
             fromConfig( conf );
@@ -54,7 +64,7 @@ namespace osgEarth
 
     private:
         void fromConfig( const Config& conf ) {
-            //nop
+            //future
         }
     };
 
@@ -115,6 +125,23 @@ namespace osgEarth
         const CacheOptions& getCacheOptions() const { return _options; }
 
         /**
+         * Gets the (approximate) size of the cache on disk, or zero if
+         * the size cannot be calculated
+         */
+        virtual off_t getApproximateSize() const { return 0; }
+
+        /**
+         * Compacts the cache (if applicable).
+         */
+        virtual bool compact() { return false; }
+
+        /**
+         * Removes all records in the cache (if possible). This could take
+         * some time to complete.
+         */
+        virtual bool clear() { return false; }
+
+        /**
          * Store this to an osgDB::Options
          */
         void apply( osgDB::Options* options ) {
diff --git a/src/osgEarth/Cache.cpp b/src/osgEarth/Cache.cpp
index 04ae37d..121b138 100644
--- a/src/osgEarth/Cache.cpp
+++ b/src/osgEarth/Cache.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -87,10 +87,6 @@ CacheFactory::create( const CacheOptions& options )
     {
         OE_WARN << LC << "Sorry, but TMS caching is no longer supported; try \"filesystem\" instead" << std::endl;
     }
-//    else if ( options.getDriver() == "tilecache" )
-//    {
-////        result = new DiskCache( options );
-//    }
     else // try to load from a plugin
     {
         osg::ref_ptr<osgDB::Options> rwopt = Registry::instance()->cloneOrCreateOptions();
diff --git a/src/osgEarth/CacheBin b/src/osgEarth/CacheBin
index d661807..b5751b1 100644
--- a/src/osgEarth/CacheBin
+++ b/src/osgEarth/CacheBin
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -47,7 +47,8 @@ namespace osgEarth
          * @param binID  Name of this caching bin (unique withing a Cache)
          * @param driver ReaderWriter that serializes data for this caching bin.
          */
-        CacheBin( const std::string& binID ) : _binID(binID) { }
+        CacheBin( const std::string& binID )
+            : _binID(binID), _hashKeys(false), _minTime(0) { }
 
         /** dtor */
         virtual ~CacheBin() { }
@@ -58,25 +59,29 @@ namespace osgEarth
         const std::string& getID() const { return _binID; }
 
         /**
+         * Whether the implemention should hash record keys instead of using
+         * them directly. Default = false.
+         */
+        void setHashKeys(bool value) { _hashKeys = value; }
+        bool getHashKeys() const { return _hashKeys; }
+
+        /**
          * Reads an object from the cache bin.
-         * @param key     Lookup key to read
-         * @param minTime Fail if the entry's timestamp is older than this
+         * @param key     Lookup key to read         
          */
-        virtual ReadResult readObject(const std::string& key, TimeStamp minTime) =0;
+        virtual ReadResult readObject(const std::string& key) =0;
 
         /**
          * Reads an image from the cache bin.
-         * @param key     Lookup key to read
-         * @param minTime Fail if the entry's timestamp is older than this
+         * @param key     Lookup key to read         
          */
-        virtual ReadResult readImage(const std::string& key, TimeStamp minTime) =0;
+        virtual ReadResult readImage(const std::string& key) =0;
 
         /**
          * Reads a string buffer from the cache bin.
          * @param key    Lookup key to read.
-         * @param minTime Fail if the entry's timestamp is older than this.
          */
-        virtual ReadResult readString(const std::string& key, TimeStamp minTime) =0;
+        virtual ReadResult readString(const std::string& key) =0;
 
         /**
          * Writes an object (or an image) to the cache bin.
@@ -91,17 +96,9 @@ namespace osgEarth
         /**
          * Gets the status of a key, i.e. not found, valid or expired.
          * Pass in a minTime = 0 to simply check whether the record exists.
-         * @param key     Lookup key to check for
-         * @param minTime Return EXPIRED if the key exists but is older than this
-         */
-        virtual RecordStatus getRecordStatus(const std::string& key, TimeStamp minTime) =0;
-
-        /**
-         * Checks whether a key exists in the cache.
-         * @deprecated - Please use getRecordStatus instead
+         * @param key     Lookup key to check for         
          */
-        //bool isCached(const std::string& key) { 
-        //    return getRecordStatus(key, 0) == STATUS_OK; }
+        virtual RecordStatus getRecordStatus(const std::string& key) =0;
 
         /**
          * Purge an entry from the cache bin
@@ -127,7 +124,18 @@ namespace osgEarth
         /**
          * Purges all entries in the cache bin.
          */
-        virtual bool purge() = 0;
+        virtual bool clear() { return false; }
+
+        /**
+         * Compacts the cache bin (where applicable)
+         */
+        virtual bool compact() { return false; }
+
+        /**
+         * Returns the approximate disk space being used by this cache,
+         * or 0 if unavailable.
+         */
+        virtual unsigned getStorageSize() { return 0u; }
 
         /**
          * Store this pointer in an options structure
@@ -143,8 +151,17 @@ namespace osgEarth
             return options ? const_cast<CacheBin*>(static_cast<const CacheBin*>(options->getPluginData("osgEarth::CacheBin"))) : 0L;
         }
 
+
+    public: //deprecated
+
+        /** @deprecated - use clear */
+        bool purge() { return clear(); } // backwards compatibility
+
+
     protected:
         std::string _binID;
+        bool        _hashKeys;
+        TimeStamp   _minTime;
     };
 }
 
diff --git a/src/osgEarth/CacheEstimator b/src/osgEarth/CacheEstimator
index 6e1b1de..b67a096 100644
--- a/src/osgEarth/CacheEstimator
+++ b/src/osgEarth/CacheEstimator
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/CacheEstimator.cpp b/src/osgEarth/CacheEstimator.cpp
index a1f6d24..e7d94a0 100644
--- a/src/osgEarth/CacheEstimator.cpp
+++ b/src/osgEarth/CacheEstimator.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/CachePolicy b/src/osgEarth/CachePolicy
index 2997773..b6764d2 100644
--- a/src/osgEarth/CachePolicy
+++ b/src/osgEarth/CachePolicy
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -69,9 +69,18 @@ namespace osgEarth
         /** Stores this cache policy in a DB Options. */
         void apply( osgDB::Options* options );
 
+        /** Merges any set properties in another CP into this one, override existing values. */
+        void mergeAndOverride(const CachePolicy& rhs);
+        void mergeAndOverride(const optional<CachePolicy>& rhs);
+
         /** Gets the oldest timestamp for which to accept a cache record */
         TimeStamp getMinAcceptTime() const;
 
+        /**
+         * Determine whether the given timestamp is considered to be expired based on this CachePolicy
+         */
+        bool isExpired(TimeStamp lastModified) const;
+
         /** dtor */
         virtual ~CachePolicy() { }
 
diff --git a/src/osgEarth/CachePolicy.cpp b/src/osgEarth/CachePolicy.cpp
index 455974d..d6dd270 100644
--- a/src/osgEarth/CachePolicy.cpp
+++ b/src/osgEarth/CachePolicy.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -24,17 +24,18 @@ using namespace osgEarth;
 //------------------------------------------------------------------------
 
 //statics
-CachePolicy CachePolicy::DEFAULT( CachePolicy::USAGE_READ_WRITE );
+CachePolicy CachePolicy::DEFAULT;
 CachePolicy CachePolicy::NO_CACHE( CachePolicy::USAGE_NO_CACHE );
 CachePolicy CachePolicy::CACHE_ONLY( CachePolicy::USAGE_CACHE_ONLY );
 
 //------------------------------------------------------------------------
 
 CachePolicy::CachePolicy() :
+_usage  ( USAGE_READ_WRITE ),
 _maxAge ( INT_MAX ),
 _minTime( 0 )
 {
-    _usage = USAGE_READ_WRITE;
+    //nop
 }
 
 CachePolicy::CachePolicy( const Usage& usage ) :
@@ -42,7 +43,7 @@ _usage  ( usage ),
 _maxAge ( INT_MAX ),
 _minTime( 0 )
 {
-    _usage = usage; // explicity init the optional<>
+    _usage = usage; // explicity set the optional<>
 }
 
 CachePolicy::CachePolicy( const Config& conf ) :
@@ -71,7 +72,8 @@ CachePolicy::fromOptions( const osgDB::Options* dbOptions, optional<CachePolicy>
         {
             Config conf;
             conf.fromJSON( jsonString );
-            out = CachePolicy( conf );
+            CachePolicy temp( conf );
+            out->mergeAndOverride( temp );
             return true;
         }
     }
@@ -88,6 +90,28 @@ CachePolicy::apply( osgDB::Options* dbOptions )
     }
 }
 
+void
+CachePolicy::mergeAndOverride(const CachePolicy& rhs)
+{
+    if ( rhs.usage().isSet() )
+        usage() = rhs.usage().get();
+
+    if ( rhs.minTime().isSet() )
+        minTime() = rhs.minTime().get();
+
+    if ( rhs.maxAge().isSet() )
+        maxAge() = rhs.maxAge().get();
+}
+
+void
+CachePolicy::mergeAndOverride(const optional<CachePolicy>& rhs)
+{
+    if ( rhs.isSet() )
+    {
+        mergeAndOverride( rhs.get() );
+    }
+}
+
 TimeStamp
 CachePolicy::getMinAcceptTime() const
 {
@@ -98,6 +122,12 @@ CachePolicy::getMinAcceptTime() const
 }
 
 bool
+CachePolicy::isExpired(TimeStamp lastModified) const
+{
+    return lastModified < getMinAcceptTime();    
+}
+
+bool
 CachePolicy::operator == (const CachePolicy& rhs) const
 {
     return 
diff --git a/src/osgEarth/CacheSeed b/src/osgEarth/CacheSeed
index ec43bf2..a68712f 100644
--- a/src/osgEarth/CacheSeed
+++ b/src/osgEarth/CacheSeed
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -23,29 +23,27 @@
 #include <osgEarth/Common>
 #include <osgEarth/Map>
 #include <osgEarth/TileKey>
-#include <osgEarth/Progress>
-#include <OpenThreads/Mutex>
-#include <OpenThreads/Atomic>
+#include <osgEarth/TileVisitor>
+
 
 namespace osgEarth
 {
-    class CacheSeed;
-
-    class CacheTileOperation : public osg::Operation
+    /**
+    * A TileHandler that caches tiles for the given layer.
+    */
+    class OSGEARTH_EXPORT CacheTileHandler : public TileHandler
     {
     public:
-        CacheTileOperation(const MapFrame& mapFrame, CacheSeed& cacheSeed, const TileKey& key);
-        virtual void operator()(osg::Object*);
+        CacheTileHandler( TerrainLayer* layer, Map* map );
+        virtual bool handleTile( const TileKey& key, const TileVisitor& tv );
+        virtual bool hasData( const TileKey& key ) const;
 
-        // The MapFrame to operate on
-        const MapFrame& _mapFrame;
+        virtual std::string getProcessString() const;
 
-        // The parent CacheSeed
-        CacheSeed& _cacheSeed;
-
-        // The TileKey to process
-        const TileKey _key;
-    };
+    protected:
+        osg::ref_ptr< TerrainLayer > _layer;
+        osg::ref_ptr< Map > _map;
+    };    
 
     /**
     * Utility class for seeding a cache
@@ -53,82 +51,27 @@ namespace osgEarth
     class OSGEARTH_EXPORT CacheSeed
     {
     public:
-        friend class CacheTileOperation;
-
         CacheSeed();
 
-		CacheSeed( const CacheSeed& rhs);
-
-        /** dtor */
-        virtual ~CacheSeed() { }
-
-        /**
-        * Sets the minimum level to seed to
-        */
-        void setMinLevel(const unsigned int& minLevel) {_minLevel = minLevel;}
-
-        /**
-        * Gets the minimum level to cache to.
-        */
-        const unsigned int getMinLevel() const {return _minLevel;}
-
         /**
-        * Sets the maximum level to seed to
+        * Gets the TileVisitor to use when seeding the cache.  Use this to set traversal options like the extents, levels, etc.
         */
-        void setMaxLevel(const unsigned int& maxLevel) {_maxLevel = maxLevel;}
+        TileVisitor* getVisitor() const;
 
         /**
-        * Gets the maximum level to cache to.
+        * Sets the TileVisitor to use when seeding the cache.  Must be configured BEFORE you call run
         */
-        const unsigned int getMaxLevel() const {return _maxLevel;}
-
-        /*
-         * The number of threads to use when seeding
-         */
-        unsigned int getNumThreads() const;
-        void setNumThreads( unsigned int numThreads );
-
-        std::vector< GeoExtent >& getExtents() { return _extents; }
-
-        /**
-        *Adds an extent to cache
-        */
-        void addExtent( const GeoExtent& value );
+        void setVisitor(TileVisitor* visitor);
 
         /**
-        * Set progress callback for reporting which tiles are seeded
+        * Seeds a TerrainLayer
         */
-        void setProgressCallback(osgEarth::ProgressCallback* progress) { _progress = progress? progress : new ProgressCallback; }
+        void run(TerrainLayer* layer, Map* map );
 
-        /**
-        * Performs the seed operation
-        */
-        void seed( Map* map );
 
     protected:
 
-        void incrementCompleted() const;
-
-        void reportProgress( const std::string& msg ) const;
-
-        unsigned int _minLevel;
-        unsigned int _maxLevel;
-
-        unsigned int _total;
-        OpenThreads::Atomic _completed;
-
-        unsigned int _numThreads;
-
-        osg::ref_ptr<ProgressCallback> _progress;
-
-        bool cacheTile( const MapFrame& mapf, const TileKey& key ) const;
-
-        std::vector< GeoExtent > _extents;
-
-        // The work queue to pass seed operations to
-        osg::ref_ptr< osg::OperationQueue > _queue;  
-
-        OpenThreads::Mutex _mutex;
+        osg::ref_ptr< TileVisitor > _visitor;
     };
 }
 
diff --git a/src/osgEarth/CacheSeed.cpp b/src/osgEarth/CacheSeed.cpp
index e1b7039..14929c4 100644
--- a/src/osgEarth/CacheSeed.cpp
+++ b/src/osgEarth/CacheSeed.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -28,329 +28,100 @@
 using namespace osgEarth;
 using namespace OpenThreads;
 
-
-
-
-
-/******************************************************************/
-CacheTileOperation::CacheTileOperation(const MapFrame& mapFrame, CacheSeed& cacheSeed, const TileKey& key):
-_mapFrame( mapFrame ),
-_cacheSeed( cacheSeed ),
-_key( key )
+CacheTileHandler::CacheTileHandler( TerrainLayer* layer, Map* map ):
+_layer( layer ),
+_map( map )
 {
 }
 
-void CacheTileOperation::operator()(osg::Object*)
-{
-    unsigned int x, y, lod;
-    _key.getTileXY(x, y);
-    lod = _key.getLevelOfDetail();
-
-    bool gotData = true;
+bool CacheTileHandler::handleTile(const TileKey& key, const TileVisitor& tv)
+{        
+    ImageLayer* imageLayer = dynamic_cast< ImageLayer* >( _layer.get() );
+    ElevationLayer* elevationLayer = dynamic_cast< ElevationLayer* >( _layer.get() );    
 
-    if ( _cacheSeed.getMinLevel() <= lod && _cacheSeed.getMaxLevel() >= lod )
-    {
-        gotData = _cacheSeed.cacheTile( _mapFrame, _key );
-        if (gotData)
+    // Just call createImage or createHeightField on the layer and the it will be cached!
+    if (imageLayer)
+    {                
+        GeoImage image = imageLayer->createImage( key );
+        if (image.valid())
         {                
-            _cacheSeed.incrementCompleted();
-            _cacheSeed.reportProgress( std::string("Cached tile: ") + _key.str() );
-        }       
+            return true;
+        }            
     }
-
-    if ( gotData && lod <= _cacheSeed.getMaxLevel() )
+    else if (elevationLayer )
     {
-        TileKey k0 = _key.createChildKey(0);
-        TileKey k1 = _key.createChildKey(1);
-        TileKey k2 = _key.createChildKey(2);
-        TileKey k3 = _key.createChildKey(3); 
-
-        bool intersectsKey = false;
-        if ( _cacheSeed.getExtents().empty()) intersectsKey = true;
-        else
-        {
-            for (unsigned int i = 0; i < _cacheSeed.getExtents().size(); ++i)
-            {
-                const GeoExtent& extent = _cacheSeed.getExtents()[i];
-                if (extent.intersects( k0.getExtent() ) ||
-                    extent.intersects( k1.getExtent() ) ||
-                    extent.intersects( k2.getExtent() ) ||
-                    extent.intersects( k3.getExtent() ))
-                {
-                    intersectsKey = true;
-                }
-
-            }
-        }
-
-        //Check to see if the bounds intersects ANY of the tile's children.  If it does, then process all of the children
-        //for this level
-        if (intersectsKey)
-        {
-            // Queue the task up for the children
-            _cacheSeed._queue.get()->add( new CacheTileOperation( _mapFrame, _cacheSeed, k0) );
-            _cacheSeed._queue.get()->add( new CacheTileOperation( _mapFrame, _cacheSeed, k1) );
-            _cacheSeed._queue.get()->add( new CacheTileOperation( _mapFrame, _cacheSeed, k2) );
-            _cacheSeed._queue.get()->add( new CacheTileOperation( _mapFrame, _cacheSeed, k3) );                
-        }
+        GeoHeightField hf = elevationLayer->createHeightField( key );
+        if (hf.valid())
+        {                
+            return true;
+        }            
     }
-}
-
-/******************************************************************/
-
-
-
-
-
-CacheSeed::CacheSeed():
-_minLevel (0),
-_maxLevel (12),
-_total    (0),
-_completed(0),
-_numThreads(1)
-{
-}
+    return false;        
+}   
 
-CacheSeed::CacheSeed( const CacheSeed& rhs):
-_minLevel( rhs._minLevel),
-_maxLevel( rhs._maxLevel),
-_numThreads( rhs._numThreads )
+bool CacheTileHandler::hasData( const TileKey& key ) const
 {
-}
-
-void CacheSeed::seed( Map* map )
-{
-    // We must do this to avoid an error message in OpenSceneGraph b/c the findWrapper method doesn't appear to be threadsafe.
-    // This really isn't a big deal b/c this only effects data that is already cached.
-    osgDB::ObjectWrapper* wrapper = osgDB::Registry::instance()->getObjectWrapperManager()->findWrapper( "osg::Image" );
-
-    osg::Timer_t startTime = osg::Timer::instance()->tick();
-    if ( !map->getCache() )
-    {
-        OE_WARN << LC << "Warning: No cache defined; aborting." << std::endl;
-        return;
-    }
-
-    std::vector<TileKey> keys;
-    map->getProfile()->getRootKeys(keys);
-
-    //Add the map's entire extent if we don't have one specified.
-    if (_extents.empty())
+    TileSource* ts = _layer->getTileSource();
+    if (ts)
     {
-        addExtent( map->getProfile()->getExtent() );
-    }
-
-    bool hasCaches = false;
-    int src_min_level = INT_MAX;
-    unsigned int src_max_level = 0;
-
-    MapFrame mapf( map, Map::TERRAIN_LAYERS, "CacheSeed::seed" );
-
-    //Assumes the the TileSource will perform the caching for us when we call createImage
-    for( ImageLayerVector::const_iterator i = mapf.imageLayers().begin(); i != mapf.imageLayers().end(); i++ )
-    {
-        ImageLayer* layer = i->get();
-        TileSource* src   = layer->getTileSource();
-
-        const ImageLayerOptions& opt = layer->getImageLayerOptions();
-
-        if ( layer->isCacheOnly() )
-        {
-            OE_WARN << LC << "Warning: Layer \"" << layer->getName() << "\" is set to cache-only; skipping." << std::endl;
-        }
-        else if ( !src )
-        {
-            OE_WARN << "Warning: Layer \"" << layer->getName() << "\" could not create TileSource; skipping." << std::endl;
-        }
-        //else if ( src->getCachePolicyHint(0L) == CachePolicy::NO_CACHE )
-        //{
-        //    OE_WARN << LC << "Warning: Layer \"" << layer->getName() << "\" does not support seeding; skipping." << std::endl;
-        //}
-        else if ( !layer->getCache() )
-        {
-            OE_WARN << LC << "Notice: Layer \"" << layer->getName() << "\" has no cache defined; skipping." << std::endl;
-        }
-        else
-        {
-            hasCaches = true;
-
-            if (opt.minLevel().isSet() && (int)opt.minLevel().get() < src_min_level)
-                src_min_level = opt.minLevel().get();
-            if (opt.maxLevel().isSet() && opt.maxLevel().get() > src_max_level)
-                src_max_level = opt.maxLevel().get();
-        }
+        return ts->hasData(key);
     }
+    return true;
+}
 
-    for( ElevationLayerVector::const_iterator i = mapf.elevationLayers().begin(); i != mapf.elevationLayers().end(); i++ )
-    {
-        ElevationLayer* layer = i->get();
-        TileSource*     src   = layer->getTileSource();
-        const ElevationLayerOptions& opt = layer->getElevationLayerOptions();
+std::string CacheTileHandler::getProcessString() const
+{
+    ImageLayer* imageLayer = dynamic_cast< ImageLayer* >( _layer.get() );
+    ElevationLayer* elevationLayer = dynamic_cast< ElevationLayer* >( _layer.get() );    
 
-        if ( layer->isCacheOnly() )
-        {
-            OE_WARN << LC << "Warning: Layer \"" << layer->getName() << "\" is set to cache-only; skipping." << std::endl;
-        }
-        else if (!src)
-        {
-            OE_WARN << "Warning: Layer \"" << layer->getName() << "\" could not create TileSource; skipping." << std::endl;
-        }
-        //else if ( src->getCachePolicyHint(0L) == CachePolicy::NO_CACHE )
-        //{
-        //    OE_WARN << LC << "Warning: Layer \"" << layer->getName() << "\" does not support seeding; skipping." << std::endl;
-        //}
-        else if ( !layer->getCache() )
-        {
-            OE_WARN << LC << "Notice: Layer \"" << layer->getName() << "\" has no cache defined; skipping." << std::endl;
-        }
-        else
+    std::stringstream buf;
+    buf << "osgearth_cache --seed ";
+    if (imageLayer)
+    {        
+        for (int i = 0; i < _map->getNumImageLayers(); i++)
         {
-            hasCaches = true;
-
-            if (opt.minLevel().isSet() && (int)opt.minLevel().get() < src_min_level)
-                src_min_level = opt.minLevel().get();
-            if (opt.maxLevel().isSet() && opt.maxLevel().get() > src_max_level)
-                src_max_level = opt.maxLevel().get();
+            if (imageLayer == _map->getImageLayerAt(i))
+            {
+                buf << " --image " << i << " ";
+                break;
+            }
         }
     }
-
-    if ( !hasCaches )
-    {
-        OE_WARN << LC << "There are either no caches defined in the map, or no sources to cache; aborting." << std::endl;
-        return;
-    }
-
-    if ( src_max_level > 0 && src_max_level < _maxLevel )
-    {
-        _maxLevel = src_max_level;
-    }
-
-    OE_NOTICE << LC << "Maximum cache level will be " << _maxLevel << std::endl;
-
-    //Estimate the number of tiles
-    _total = 0;    
-    CacheEstimator est;
-    est.setMinLevel( _minLevel );
-    est.setMaxLevel( _maxLevel );
-    est.setProfile( map->getProfile() ); 
-    for (unsigned int i = 0; i < _extents.size(); i++)
-    {                
-        est.addExtent( _extents[ i ] );
-    } 
-    _total = est.getNumTiles();
-
-    OE_INFO << "Processing ~" << _total << " tiles" << std::endl;
-
-
-    // Initialize the operations queue
-    _queue = new osg::OperationQueue;
-
-    osg::Timer_t endTime = osg::Timer::instance()->tick();
-
-    // Start the threads
-    std::vector< osg::ref_ptr< osg::OperationsThread > > threads;
-    for (unsigned int i = 0; i < _numThreads; i++)
-    {        
-        osg::OperationsThread* thread = new osg::OperationsThread();
-        thread->setOperationQueue(_queue.get());
-        thread->start();
-        threads.push_back( thread );
-    }
-
-    OE_NOTICE << "Startup time " << osg::Timer::instance()->delta_s( startTime, endTime ) << std::endl;
-
-    
-    // Add the root keys to the queue
-    for (unsigned int i = 0; i < keys.size(); ++i)
+    else if (elevationLayer)
     {
-        //processKey( mapf, keys[i] );
-        _queue.get()->add( new CacheTileOperation( mapf, *this, keys[i]) );
-    }    
-
-    bool done = false;
-    while (!done)
-    {
-        OpenThreads::Thread::microSleep(500000); // sleep for half a second
-        done = true;
-        if (_queue->getNumOperationsInQueue() > 0)
-        {
-            done = false;
-            continue;
-        }
-        else
+        for (int i = 0; i < _map->getNumElevationLayers(); i++)
         {
-            // Make sure no threads are currently working on an operation, which actually might add MORE operations since we are doing a quadtree traversal
-            for (unsigned int i = 0; i < threads.size(); i++)
+            if (elevationLayer == _map->getElevationLayerAt(i))
             {
-                if (threads[i]->getCurrentOperation())
-                {
-                    done = false;
-                    continue;
-                }
+                buf << " --elevation " << i << " ";
+                break;
             }
         }
-    }    
+    }
+    return buf.str();
+}
 
-    _total = _completed;
 
-    if ( _progress.valid()) _progress->reportProgress(_completed, _total, 0, 1, "Finished");
-}
 
-unsigned int CacheSeed::getNumThreads() const
-{
-    return _numThreads;
-}
+/***************************************************************************************/
 
-void CacheSeed::setNumThreads( unsigned int numThreads )
+CacheSeed::CacheSeed():
+_visitor(new TileVisitor())
 {
-    _numThreads = numThreads;
 }
 
-void CacheSeed::incrementCompleted( ) const
-{            
-    CacheSeed* nonconst_this = const_cast<CacheSeed*>(this);    
-    ++nonconst_this->_completed;    
-}
-
-void CacheSeed::reportProgress( const std::string& message ) const
+TileVisitor* CacheSeed::getVisitor() const
 {
-    if ( _progress.valid() )
-    {
-        CacheSeed* nonconst_this = const_cast<CacheSeed*>(this);    
-        OpenThreads::ScopedLock< OpenThreads::Mutex > lock( nonconst_this->_mutex );
-        _progress->reportProgress(_completed, _total, message );
-    }
+    return _visitor;
 }
 
-bool
-CacheSeed::cacheTile(const MapFrame& mapf, const TileKey& key ) const
+void CacheSeed::setVisitor(TileVisitor* visitor)
 {
-    bool gotData = false;
-
-    for( ImageLayerVector::const_iterator i = mapf.imageLayers().begin(); i != mapf.imageLayers().end(); i++ )
-    {
-        ImageLayer* layer = i->get();
-        if ( layer->isKeyValid( key ) )
-        {
-            GeoImage image = layer->createImage( key );
-            if ( image.valid() )
-                gotData = true;
-        }
-    }
-
-    if ( mapf.elevationLayers().size() > 0 )
-    {
-        osg::ref_ptr<osg::HeightField> hf;
-        mapf.getHeightField( key, false, hf );
-        if ( hf.valid() )
-            gotData = true;
-    }
-
-    return gotData;
+    _visitor = visitor;
 }
 
-void
-CacheSeed::addExtent( const GeoExtent& value)
+void CacheSeed::run( TerrainLayer* layer, Map* map )
 {
-    _extents.push_back( value );
-}
+    _visitor->setTileHandler( new CacheTileHandler( layer, map ) );
+    _visitor->run( map->getProfile() );
+}
\ No newline at end of file
diff --git a/src/osgEarth/Capabilities b/src/osgEarth/Capabilities
index 2e2861a..387b1b3 100644
--- a/src/osgEarth/Capabilities
+++ b/src/osgEarth/Capabilities
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -22,6 +22,7 @@
 
 #include <osgEarth/Common>
 #include <osg/Uniform>
+#include <osg/Texture>
 
 namespace osgEarth
 {
@@ -59,13 +60,27 @@ namespace osgEarth
         /** bits in depth buffer */
         int getDepthBufferBits() const { return _depthBits; }
 
-        /** whether the GPU supports shaders */
-        bool supportsGLSL(float minimumVersion =1.0f) const { 
+        /** whether the GPU supports shaders. */
+        bool supportsGLSL() const { 
+            return _supportsGLSL; }
+        
+        /** whether the GPU supports a minimum GLSL version */
+        bool supportsGLSL(float minimumVersion) const { 
             return _supportsGLSL && _GLSLversion >= minimumVersion; }
+        
+        /** whether the GPU supports a minimum GLSL version (as an int; e.g. 1.2 => "120") */
+        bool supportsGLSL(unsigned minVersionInt) const {
+            return _supportsGLSL && ((unsigned)(_GLSLversion*100.0f)) >= minVersionInt; }
 
         /** the GLSL version */
         float getGLSLVersion() const { return _GLSLversion; }
 
+        /** GLSL version as an integer x 100. I.e.: GLSL 1.4 => 140. */
+        unsigned getGLSLVersionInt() const { return (unsigned)(_GLSLversion*100.0f); }
+
+        /** Are we running OpenGLES? */
+        bool isGLES() const { return _isGLES; }
+
         /** the GPU vendor */
         const std::string& getVendor() const { return _vendor;}
 
@@ -122,6 +137,9 @@ namespace osgEarth
 
         /** whether the GPU supports writing to the depth fragment */
         bool supportsFragDepthWrite() const { return _supportsFragDepthWrite; }
+        
+        /** whether the GPU supports a texture compression scheme */
+        bool supportsTextureCompression(const osg::Texture::InternalFormatMode& mode) const;
 
     protected:
         Capabilities();
@@ -159,6 +177,12 @@ namespace osgEarth
         std::string _vendor;
         std::string _renderer;
         std::string _version;
+        bool _supportsS3TC;
+        bool _supportsPVRTC;
+        bool _supportsARBTC;
+        bool _supportsETC;
+        bool _supportsRGTC;
+        bool _isGLES;
 
     public:
         friend class Registry;
diff --git a/src/osgEarth/Capabilities.cpp b/src/osgEarth/Capabilities.cpp
index f7a3a08..f8f2f12 100644
--- a/src/osgEarth/Capabilities.cpp
+++ b/src/osgEarth/Capabilities.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -38,7 +38,15 @@ struct MyGraphicsContext
 {
     MyGraphicsContext()
     {
-        osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
+
+    	osg::GraphicsContext::ScreenIdentifier si;
+	    si.readDISPLAY();
+	    si.setUndefinedScreenDetailsToDefaultScreen();
+
+        osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;  
+    	traits->hostName = si.hostName;
+	    traits->displayNum = si.displayNum;
+	    traits->screenNum = si.screenNum;
         traits->x = 0;
         traits->y = 0;
         traits->width = 1;
@@ -122,7 +130,12 @@ _supportsNonPowerOfTwoTextures( false ),
 _maxUniformBlockSize    ( 0 ),
 _preferDLforStaticGeom  ( true ),
 _numProcessors          ( 1 ),
-_supportsFragDepthWrite ( false )
+_supportsFragDepthWrite ( false ),
+_supportsS3TC           ( false ),
+_supportsPVRTC          ( false ),
+_supportsARBTC          ( false ),
+_supportsETC            ( false ),
+_supportsRGTC           ( false )
 {
     // little hack to force the osgViewer library to link so we can create a graphics context
     osgViewerGetVersion();
@@ -135,6 +148,13 @@ _supportsFragDepthWrite ( false )
     // logical CPUs (cores)
     _numProcessors = OpenThreads::GetNumberOfProcessors();
 
+    // GLES compile?
+#if (defined(OSG_GLES1_AVAILABLE) || defined(OSG_GLES2_AVAILABLE))
+    _isGLES = true;
+#else
+    _isGLES = false;
+#endif
+
     // create a graphics context so we can query OpenGL support:
     MyGraphicsContext mgc;
 
@@ -143,6 +163,16 @@ _supportsFragDepthWrite ( false )
         osg::GraphicsContext* gc = mgc._gc.get();
         unsigned int id = gc->getState()->getContextID();
         const osg::GL2Extensions* GL2 = osg::GL2Extensions::Get( id, true );
+        
+        if ( ::getenv("OSGEARTH_NO_GLSL") )
+        {
+            _supportsGLSL = false;
+            OE_INFO << LC << "Note: GLSL expressly disabled (OSGEARTH_NO_GLSL)" << std::endl;
+        }
+        else
+        {
+            _supportsGLSL = GL2->isGlslSupported();
+        }
 
         OE_INFO << LC << "Detected hardware capabilities:" << std::endl;
 
@@ -196,22 +226,17 @@ _supportsFragDepthWrite ( false )
 #endif
         OE_INFO << LC << "  Max lights = " << _maxLights << std::endl;
 
-        
-        if ( ::getenv("OSGEARTH_NO_GLSL") )
-            _supportsGLSL = false;
-        else
-            _supportsGLSL = GL2->isGlslSupported();
         OE_INFO << LC << "  GLSL = " << SAYBOOL(_supportsGLSL) << std::endl;
 
         if ( _supportsGLSL )
         {
             _GLSLversion = GL2->getLanguageVersion();
-            OE_INFO << LC << "  GLSL Version = " << _GLSLversion << std::endl;
+            OE_INFO << LC << "  GLSL Version = " << getGLSLVersionInt() << std::endl;
         }
 
         _supportsTextureArrays = 
             _supportsGLSL &&
-            osg::getGLVersionNumber() >= 2.0 && // hopefully this will detect Intel cards
+            osg::getGLVersionNumber() >= 2.0f && // hopefully this will detect Intel cards
             osg::isGLExtensionSupported( id, "GL_EXT_texture_array" );
         OE_INFO << LC << "  Texture arrays = " << SAYBOOL(_supportsTextureArrays) << std::endl;
 
@@ -219,7 +244,7 @@ _supportsFragDepthWrite ( false )
         OE_INFO << LC << "  3D textures = " << SAYBOOL(_supportsTexture3D) << std::endl;
 
         _supportsMultiTexture = 
-            osg::getGLVersionNumber() >= 1.3 ||
+            osg::getGLVersionNumber() >= 1.3f ||
             osg::isGLExtensionSupported( id, "GL_ARB_multitexture") ||
             osg::isGLExtensionSupported( id, "GL_EXT_multitexture" );
         OE_INFO << LC << "  Multitexturing = " << SAYBOOL(_supportsMultiTexture) << std::endl;
@@ -296,13 +321,13 @@ _supportsFragDepthWrite ( false )
         }
 #endif
 
-        OE_INFO << LC << "  prefer DL for static geom = " << SAYBOOL(_preferDLforStaticGeom) << std::endl;
+        //OE_INFO << LC << "  prefer DL for static geom = " << SAYBOOL(_preferDLforStaticGeom) << std::endl;
 
         // ATI workarounds:
         bool isATI = _vendor.find("ATI ") == 0;
 
         _supportsMipmappedTextureUpdates = isATI && enableATIworkarounds ? false : true;
-        OE_INFO << LC << "  Mipmapped texture updates = " << SAYBOOL(_supportsMipmappedTextureUpdates) << std::endl;
+        //OE_INFO << LC << "  Mipmapped texture updates = " << SAYBOOL(_supportsMipmappedTextureUpdates) << std::endl;
 
 #if 0
         // Intel workarounds:
@@ -314,7 +339,63 @@ _supportsFragDepthWrite ( false )
 
         _maxFastTextureSize = _maxTextureSize;
 
-        OE_INFO << LC << "  Max Fast Texture Size = " << _maxFastTextureSize << std::endl;
+        //OE_INFO << LC << "  Max Fast Texture Size = " << _maxFastTextureSize << std::endl;
+
+        // tetxure compression
+        OE_INFO << LC << "  Compression = ";
+        _supportsARBTC = osg::isGLExtensionSupported( id, "GL_ARB_texture_compression" );
+        if (_supportsARBTC) OE_INFO_CONTINUE << "ARB ";
+
+        _supportsS3TC = osg::isGLExtensionSupported( id, "GL_EXT_texture_compression_s3tc" );
+        if ( _supportsS3TC ) OE_INFO_CONTINUE << "S3 ";
+
+        _supportsPVRTC = osg::isGLExtensionSupported( id, "GL_IMG_texture_compression_pvrtc" );
+        if ( _supportsPVRTC ) OE_INFO_CONTINUE << "PVR ";
+
+        _supportsETC = osg::isGLExtensionSupported( id, "GL_OES_compressed_ETC1_RGB8_texture" );
+        if ( _supportsETC ) OE_INFO_CONTINUE << "ETC1 ";
+
+        _supportsRGTC = osg::isGLExtensionSupported( id, "GL_EXT_texture_compression_rgtc" );
+        if ( _supportsRGTC ) OE_INFO_CONTINUE << "RG";
+
+        OE_INFO_CONTINUE << std::endl;
     }
 }
 
+bool
+Capabilities::supportsTextureCompression(const osg::Texture::InternalFormatMode& mode) const
+{
+    switch( mode )
+    {
+    case osg::Texture::USE_ARB_COMPRESSION:
+        return _supportsARBTC;
+        break;
+
+    case osg::Texture::USE_S3TC_DXT1a_COMPRESSION:
+    case osg::Texture::USE_S3TC_DXT1c_COMPRESSION:
+    case osg::Texture::USE_S3TC_DXT1_COMPRESSION:
+    case osg::Texture::USE_S3TC_DXT3_COMPRESSION:
+    case osg::Texture::USE_S3TC_DXT5_COMPRESSION:
+        return _supportsS3TC;
+        break;
+
+    case osg::Texture::USE_PVRTC_2BPP_COMPRESSION:
+    case osg::Texture::USE_PVRTC_4BPP_COMPRESSION:
+        return _supportsPVRTC;
+        break;
+
+    case osg::Texture::USE_ETC_COMPRESSION:
+        return _supportsETC;
+        break;
+
+    case osg::Texture::USE_RGTC1_COMPRESSION:
+    case osg::Texture::USE_RGTC2_COMPRESSION:
+        return _supportsRGTC;
+        break;
+
+    default:
+        return false;
+    }
+
+    return false;
+}
diff --git a/src/osgEarth/ClampableNode.cpp b/src/osgEarth/ClampableNode.cpp
index 406ac10..d45a0e4 100644
--- a/src/osgEarth/ClampableNode.cpp
+++ b/src/osgEarth/ClampableNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/ClampingTechnique b/src/osgEarth/ClampingTechnique
index d6ca973..dca03a1 100644
--- a/src/osgEarth/ClampingTechnique
+++ b/src/osgEarth/ClampingTechnique
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/ClampingTechnique.cpp b/src/osgEarth/ClampingTechnique.cpp
index c742cb8..c9de48d 100644
--- a/src/osgEarth/ClampingTechnique.cpp
+++ b/src/osgEarth/ClampingTechnique.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -277,7 +277,7 @@ static osgEarthRegisterRenderBinProxy<ClampingRenderBin> s_regbin(OSGEARTH_CLAMP
 //---------------------------------------------------------------------------
 
 ClampingTechnique::ClampingTechnique() :
-_textureSize( 4096 )
+_textureSize( 1024 )
 {
     // disable if GLSL is not supported
     _supported = Registry::capabilities().supportsGLSL();
@@ -329,7 +329,7 @@ ClampingTechnique::setUpCamera(OverlayDecorator::TechRTTParams& params)
     // set up the RTT camera:
     params._rttCamera = new osg::Camera();
     params._rttCamera->setReferenceFrame( osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT );
-    params._rttCamera->setClearColor( osg::Vec4f(0,0,0,0) );
+    params._rttCamera->setClearDepth( 1.0 );
     params._rttCamera->setClearMask( GL_DEPTH_BUFFER_BIT );
     params._rttCamera->setComputeNearFarMode( osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR );
     params._rttCamera->setViewport( 0, 0, *_textureSize, *_textureSize );
diff --git a/src/osgEarth/ColorFilter b/src/osgEarth/ColorFilter
index 77f3fea..62cbbb5 100644
--- a/src/osgEarth/ColorFilter
+++ b/src/osgEarth/ColorFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/ColorFilter.cpp b/src/osgEarth/ColorFilter.cpp
index 30e2540..6d6e218 100644
--- a/src/osgEarth/ColorFilter.cpp
+++ b/src/osgEarth/ColorFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Common b/src/osgEarth/Common
index bf6d6e9..d50e859 100644
--- a/src/osgEarth/Common
+++ b/src/osgEarth/Common
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/CompositeTileSource b/src/osgEarth/CompositeTileSource
index 062c807..365e7a7 100644
--- a/src/osgEarth/CompositeTileSource
+++ b/src/osgEarth/CompositeTileSource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -22,6 +22,7 @@
 #include <osgEarth/Common>
 #include <osgEarth/TileSource>
 #include <osgEarth/ImageLayer>
+#include <osgEarth/ElevationLayer>
 
 namespace osgEarth
 {
@@ -39,6 +40,11 @@ namespace osgEarth
         /** Add a component described by an image layer configuration. */
         void add( const ImageLayerOptions& options );
 
+        /**
+         * Adds a component described by an elevation layer configuration
+         */
+        void add( const ElevationLayerOptions& options );
+
     public:
         virtual Config getConfig() const;
 
@@ -50,13 +56,14 @@ namespace osgEarth
 
         struct Component {
             optional<ImageLayerOptions> _imageLayerOptions;
-            osg::ref_ptr<TileSource>    _tileSourceInstance;
+            optional<ElevationLayerOptions> _elevationLayerOptions;
+            osg::ref_ptr<TerrainLayer>    _layer;
         };
 
         typedef std::vector<Component> ComponentVector;
         ComponentVector _components;
 
-        friend class CompositeTileSource;
+        friend class CompositeTileSource;        
     };
 
     //--------------------------------------------------------------------
@@ -75,23 +82,24 @@ namespace osgEarth
         virtual ~CompositeTileSource() { }
 
         /**
-         * Adds a pre-existing tile source instance to the composite. 
+         * Adds a pre-existing ImageLayer to the composite layer.
          * Note: You can only add tile sources BEFORE the tile source is initialized,
          * i.e., before you add it to the map.
          * Returns true upon success; false if the composite tile source is already
          * initialized.
          */
-        bool add( TileSource* tileSource );
-        
+        bool add( ImageLayer* layer );
+
+
         /**
-         * Adds a pre-existing tile source instance to the composite along with
-         * image layer options.
+         * Adds a pre-existing ElevationLayer to the composite layer.
          * Note: You can only add tile sources BEFORE the tile source is initialized,
          * i.e., before you add it to the map.
          * Returns true upon success; false if the composite tile source is already
          * initialized.
          */
-        bool add( TileSource* tileSource, const ImageLayerOptions& options );
+        bool add( ElevationLayer* layer );
+
 
     public: // TileSource overrides
         
@@ -100,6 +108,11 @@ namespace osgEarth
             const TileKey&        key,
             ProgressCallback*     progress =0 );
 
+        /** Creates a heightfield for the given key */
+        virtual osg::HeightField* createHeightField(
+            const TileKey&        key,
+            ProgressCallback*     progress );
+
         /** Whether one of the underlying tile source's is dynamic */
         virtual bool isDynamic() const { return _dynamic; }
         
@@ -110,10 +123,10 @@ namespace osgEarth
         CompositeTileSourceOptions         _options;
         bool                               _initialized;
         bool                               _dynamic;
-        osg::ref_ptr<const osgDB::Options> _dbOptions;
-       
+        osg::ref_ptr<const osgDB::Options> _dbOptions;              
 
-        CompositeTileSourceOptions::ComponentVector _components;
+        ElevationLayerVector _elevationLayers;    
+        ImageLayerVector _imageLayers;
     };
 }
 
diff --git a/src/osgEarth/CompositeTileSource.cpp b/src/osgEarth/CompositeTileSource.cpp
index f362510..d854555 100644
--- a/src/osgEarth/CompositeTileSource.cpp
+++ b/src/osgEarth/CompositeTileSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,6 +21,7 @@
 #include <osgEarth/StringUtils>
 #include <osgEarth/Registry>
 #include <osgEarth/Progress>
+#include <osgEarth/HeightFieldUtils>
 #include <osgDB/FileNameUtils>
 
 #define LC "[CompositeTileSource] "
@@ -44,9 +45,17 @@ CompositeTileSourceOptions::add( const ImageLayerOptions& options )
     _components.push_back( c );
 }
 
+void
+CompositeTileSourceOptions::add( const ElevationLayerOptions& options )
+{
+    Component c;
+    c._elevationLayerOptions = options;
+    _components.push_back( c );
+}
+
 Config 
 CompositeTileSourceOptions::getConfig() const
-{
+{    
     Config conf = TileSourceOptions::newConfig();
 
     for( ComponentVector::const_iterator i = _components.begin(); i != _components.end(); ++i )
@@ -67,17 +76,28 @@ CompositeTileSourceOptions::mergeConfig( const Config& conf )
 
 void 
 CompositeTileSourceOptions::fromConfig( const Config& conf )
-{
-    const ConfigSet& children = conf.children("image");
-    for( ConfigSet::const_iterator i = children.begin(); i != children.end(); ++i )
+{    
+    const ConfigSet& images = conf.children("image");
+    for( ConfigSet::const_iterator i = images.begin(); i != images.end(); ++i )
     {
         add( ImageLayerOptions( *i ) );
     }
 
-    if (conf.children("elevation").size() > 0 || conf.children("heightfield").size() > 0 ||
-        conf.children("model").size() > 0 || conf.children("overlay").size() > 0 )
+    const ConfigSet& elevations = conf.children("elevation");
+    for( ConfigSet::const_iterator i = elevations.begin(); i != elevations.end(); ++i )
     {
-        OE_WARN << LC << "Illegal - composite driver only supports image layers" << std::endl;
+        add( ElevationLayerOptions( *i ) );
+    }
+
+    const ConfigSet& heightfields = conf.children("heightfield");
+    for( ConfigSet::const_iterator i = heightfields.begin(); i != heightfields.end(); ++i )
+    {
+        add( ElevationLayerOptions( *i ) );
+    }
+
+    if (conf.children("model").size() > 0 || conf.children("overlay").size() > 0 )
+    {
+        OE_WARN << LC << "Illegal - composite driver only supports image and elevation layers" << std::endl;
     }
 }
 
@@ -107,152 +127,48 @@ namespace
     };
 
     // some helper types.    
-    typedef std::vector<ImageInfo> ImageMixVector;
-
-    // same op that occurs in ImageLayer.cpp ... maybe consilidate
-    struct ImageLayerPreCacheOperation : public TileSource::ImageOperation
-    {
-        void operator()( osg::ref_ptr<osg::Image>& image )
-        {
-            _processor.process( image );
-        }
-
-        ImageLayerTileProcessor _processor;
-    };
+    typedef std::vector<ImageInfo> ImageMixVector;   
 }
 
 //-----------------------------------------------------------------------
 
 CompositeTileSource::CompositeTileSource( const TileSourceOptions& options ) :
+TileSource  ( options ),
 _options    ( options ),
 _initialized( false ),
 _dynamic    ( false )
 {
-#if 0
-    for(CompositeTileSourceOptions::ComponentVector::iterator i = _options._components.begin(); 
-        i != _options._components.end(); )
-
-    {
-        //if ( i->_imageLayerOptions.isSet() )
-        //{
-        //    if ( i->_imageLayerOptions->driver().isSet() )
-        //        i->_tileSourceOptions = i->_imageLayerOptions->driver().value();
-        //}
-
-        if ( i->_tileSourceOptions.isSet() )
-        {
-            if ( !i->_tileSourceInstance->valid() )
-                i->_tileSourceInstance = TileSourceFactory::create( i->_tileSourceOptions.value() );
-            
-            if ( !i->_tileSourceInstance->valid() )
-                OE_WARN << LC << "Could not find a TileSource for driver [" << i->_tileSourceOptions->getDriver() << "]" << std::endl;
-        }
-
-        if ( !i->_tileSourceInstance->valid() )
-        {
-            OE_WARN << LC << "A component has no valid TileSource ... removing." << std::endl;
-            i = _options._components.erase( i );
-        }
-        else
-        {
-            ++i;
-        }
-    }
-#endif
+    //nop
 }
 
 osg::Image*
 CompositeTileSource::createImage(const TileKey&    key,
                                  ProgressCallback* progress )
-{
+{    
     ImageMixVector images;
-    images.reserve( _options._components.size() );
+    images.reserve(_imageLayers.size());
 
-    for(CompositeTileSourceOptions::ComponentVector::const_iterator i = _options._components.begin();
-        i != _options._components.end();
-        ++i )
+    // Try to get an image from each of the layers for the given key.
+    for (ImageLayerVector::const_iterator itr = _imageLayers.begin(); itr != _imageLayers.end(); ++itr)
     {
-        if ( progress && progress->isCanceled() )
-            return 0L;
-
+        ImageLayer* layer = itr->get();
         ImageInfo imageInfo;
-        imageInfo.dataInExtents = false;
-
+        imageInfo.dataInExtents = layer->getTileSource()->hasDataInExtent( key.getExtent() );
+        imageInfo.opacity = layer->getOpacity();
 
-
-        TileSource* source = i->_tileSourceInstance.get();
-        if ( source )
+        if (imageInfo.dataInExtents)
         {
-            //TODO:  This duplicates code in ImageLayer::isKeyValid.  Maybe should move that to TileSource::isKeyValid instead
-            int minLevel = 0;
-            int maxLevel = INT_MAX;
-            if (i->_imageLayerOptions->minLevel().isSet())
-            {
-                minLevel = i->_imageLayerOptions->minLevel().value();
-            }
-            else if (i->_imageLayerOptions->minResolution().isSet())
-            {
-                minLevel = source->getProfile()->getLevelOfDetailForHorizResolution( 
-                    i->_imageLayerOptions->minResolution().value(), 
-                    source->getPixelsPerTile());
-            }
-
-            if (i->_imageLayerOptions->maxLevel().isSet())
-            {
-                maxLevel = i->_imageLayerOptions->maxLevel().value();
-            }
-            else if (i->_imageLayerOptions->maxResolution().isSet())
-            {
-                maxLevel = source->getProfile()->getLevelOfDetailForHorizResolution( 
-                    i->_imageLayerOptions->maxResolution().value(), 
-                    source->getPixelsPerTile());
-            }
-
-            // check that this source is within the level bounds:
-            if (minLevel > (int)key.getLevelOfDetail() ||
-                maxLevel < (int)key.getLevelOfDetail() )
+            GeoImage image = layer->createImage(key, progress);
+            if (image.valid())
             {
-                continue;
+                imageInfo.image = image.getImage();
             }
-            
-                //Only try to get data if the source actually has data                
-                if (source->hasDataInExtent( key.getExtent() ) )
-                {
-                    //We have data within these extents
-                    imageInfo.dataInExtents = true;
-
-                    if ( !source->getBlacklist()->contains( key.getTileId() ) )
-                    {                        
-                        osg::ref_ptr< ImageLayerPreCacheOperation > preCacheOp;
-                        if ( i->_imageLayerOptions.isSet() )
-                        {
-                            preCacheOp = new ImageLayerPreCacheOperation();
-                            preCacheOp->_processor.init( i->_imageLayerOptions.value(), _dbOptions.get(), true );                        
-                        }
-
-                        imageInfo.image = source->createImage( key, preCacheOp.get(), progress );
-                        imageInfo.opacity = 1.0f;
-
-                        //If the image is not valid and the progress was not cancelled, blacklist
-                        if (!imageInfo.image.valid() && (!progress || !progress->isCanceled()))
-                        {
-                            //Add the tile to the blacklist
-                            OE_DEBUG << LC << "Adding tile " << key.str() << " to the blacklist" << std::endl;
-                            source->getBlacklist()->add( key.getTileId() );
-                        }
-                        imageInfo.opacity = i->_imageLayerOptions.isSet() ? i->_imageLayerOptions->opacity().value() : 1.0f;
-                    }
-                }
-                else
-                {
-                    OE_DEBUG << LC << "Source has no data at " << key.str() << std::endl;
-                }
         }
 
-        //Add the ImageInfo to the list
-        images.push_back( imageInfo );
+        images.push_back(imageInfo);
     }
 
+    // Determine the output texture size to use based on the image that were creatd.
     unsigned numValidImages = 0;
     osg::Vec2s textureSize;
     for (unsigned int i = 0; i < images.size(); i++)
@@ -266,60 +182,48 @@ CompositeTileSource::createImage(const TileKey&    key,
             }
             numValidImages++;        
         }
-    }
+    } 
 
-    //Try to fallback on any empty images if we have some valid images but not valid images for ALL layers
+    // Create fallback images if we have some valid data but not for all the layers
     if (numValidImages > 0 && numValidImages < images.size())
-    {        
+    {
         for (unsigned int i = 0; i < images.size(); i++)
         {
             ImageInfo& info = images[i];
+            ImageLayer* layer = _imageLayers[i].get();
             if (!info.image.valid() && info.dataInExtents)
-            {                                
+            {                      
                 TileKey parentKey = key.createParentKey();
 
-                TileSource* source = _options._components[i]._tileSourceInstance;
-                if (source)
-                {                 
-                    osg::ref_ptr< ImageLayerPreCacheOperation > preCacheOp;
-                    if ( _options._components[i]._imageLayerOptions.isSet() )
-                    {
-                        preCacheOp = new ImageLayerPreCacheOperation();
-                        preCacheOp->_processor.init( _options._components[i]._imageLayerOptions.value(), _dbOptions.get(), true );                        
-                    }                
-
-                    osg::ref_ptr< osg::Image > image;
-                    while (!image.valid() && parentKey.valid())
-                    {                        
-                        image = source->createImage( parentKey, preCacheOp.get(), progress );
-                        if (image.valid())
-                        {                     
-                            break;
-                        }
-                        parentKey = parentKey.createParentKey();
-                    }     
-
+                GeoImage image;
+                while (!image.valid() && parentKey.valid())
+                {
+                    image = layer->createImage(parentKey, progress);
                     if (image.valid())
                     {
-                        //We got an image, but now we need to crop it to match the incoming key's extents
-                        GeoImage geoImage( image.get(), parentKey.getExtent());
-                        GeoImage cropped = geoImage.crop( key.getExtent(), true, textureSize.x(), textureSize.y(), *source->_options.bilinearReprojection());
-                        image = cropped.getImage();
+                        break;
                     }
-
-                    info.image = image.get();
+                    parentKey = parentKey.createParentKey();
                 }
+
+                if (image.valid())
+                {                                        
+                    // TODO:  Bilinear options?
+                    GeoImage cropped = image.crop( key.getExtent(), true, textureSize.x(), textureSize.y(), true);
+                    info.image = cropped.getImage();
+                }                    
             }
         }
     }
 
+    // Now finally create the output image.
     //Recompute the number of valid images
     numValidImages = 0;
     for (unsigned int i = 0; i < images.size(); i++)
     {
         ImageInfo& info = images[i];
         if (info.image.valid()) numValidImages++;        
-    }
+    }    
 
     if ( progress && progress->isCanceled() )
     {
@@ -363,121 +267,152 @@ CompositeTileSource::createImage(const TileKey&    key,
         }        
         return result;
     }
+
+
+
+}
+
+osg::HeightField* CompositeTileSource::createHeightField(
+            const TileKey&        key,
+            ProgressCallback*     progress )
+{    
+    unsigned int size = *getOptions().tileSize();    
+    bool hae = false;
+    osg::ref_ptr< osg::HeightField > heightField = new osg::HeightField();
+    heightField->allocate(size, size);
+
+    // Initialize the heightfield to nodata
+    for (unsigned int i = 0; i < heightField->getFloatArray()->size(); i++)
+    {
+        heightField->getFloatArray()->at( i ) = NO_DATA_VALUE;
+    }  
+
+    // Populate the heightfield and return it if it's valid
+    if (_elevationLayers.populateHeightField(heightField.get(), key, 0, INTERP_AVERAGE, progress))
+    {                
+        return heightField.release();
+    }
+    else
+    {        
+        return NULL;
+    }
 }
 
 bool
-CompositeTileSource::add( TileSource* ts )
+CompositeTileSource::add( ImageLayer* layer )
 {
     if ( _initialized )
     {
-        OE_WARN << LC << "Illegal: cannot add a tile source after initialization" << std::endl;
+        OE_WARN << LC << "Illegal: cannot modify TileSource after initialization" << std::endl;
         return false;
     }
 
-    if ( !ts )
+    if ( !layer )
     {
-        OE_WARN << LC << "Illegal: tried to add a NULL tile source" << std::endl;
+        OE_WARN << LC << "Illegal: tried to add a NULL layer" << std::endl;
         return false;
     }
 
+    _imageLayers.push_back( layer );
     CompositeTileSourceOptions::Component comp;
-    comp._tileSourceInstance = ts;
-    _options._components.push_back( comp );
+    comp._layer = layer;
+    comp._imageLayerOptions = layer->getImageLayerOptions();
+    _options._components.push_back( comp );    
 
     return true;
 }
 
 bool
-CompositeTileSource::add( TileSource* ts, const ImageLayerOptions& options )
+CompositeTileSource::add( ElevationLayer* layer )
 {
-    if ( add(ts) )
+    if ( _initialized )
     {
-        _options._components.back()._imageLayerOptions = options;
-        return true;
+        OE_WARN << LC << "Illegal: cannot modify TileSource after initialization" << std::endl;
+        return false;
     }
-    else
+
+    if ( !layer )
     {
+        OE_WARN << LC << "Illegal: tried to add a NULL layer" << std::endl;
         return false;
     }
+
+    _elevationLayers.push_back( layer );
+    CompositeTileSourceOptions::Component comp;
+    comp._layer = layer;
+    comp._elevationLayerOptions = layer->getElevationLayerOptions();
+    _options._components.push_back( comp );    
+
+    return true;
 }
 
+
 TileSource::Status
 CompositeTileSource::initialize(const osgDB::Options* dbOptions)
 {
     _dbOptions = Registry::instance()->cloneOrCreateOptions(dbOptions);
 
-    osg::ref_ptr<const Profile> profile = getProfile();
+    osg::ref_ptr<const Profile> profile = getProfile();    
 
     for(CompositeTileSourceOptions::ComponentVector::iterator i = _options._components.begin();
         i != _options._components.end(); )
-    {
+    {        
         if ( i->_imageLayerOptions.isSet() )
         {
-            if ( !i->_tileSourceInstance.valid() )
+            osg::ref_ptr< ImageLayer > layer = new ImageLayer(*i->_imageLayerOptions);
+            if (!layer->getTileSource())
             {
-                i->_tileSourceInstance = TileSourceFactory::create( i->_imageLayerOptions->driver().value() );
-            
-                if ( !i->_tileSourceInstance.valid() )
-                {
-                    OE_WARN << LC << "Could not find a TileSource for driver [" << i->_imageLayerOptions->driver()->getDriver() << "]" << std::endl;
-                }
+                OE_WARN << LC << "Could not find a TileSource for driver [" << i->_imageLayerOptions->driver()->getDriver() << "]" << std::endl;
+            }
+            else
+            {
+                i->_layer = layer;
+                _imageLayers.push_back( layer );
+            }            
+        }
+        else if (i->_elevationLayerOptions.isSet())
+        {
+            osg::ref_ptr< ElevationLayer > layer = new ElevationLayer(*i->_elevationLayerOptions);            
+            if (!layer->getTileSource())
+            {
+                   OE_WARN << LC << "Could not find a TileSource for driver [" << i->_elevationLayerOptions->driver()->getDriver() << "]" << std::endl;
+            }
+            else
+            {
+                i->_layer = layer;
+                _elevationLayers.push_back( layer.get() );                
             }
         }
 
-        if ( !i->_tileSourceInstance.valid() )
+        if ( !i->_layer.valid() )
         {
-            OE_WARN << LC << "A component has no valid TileSource ... removing." << std::endl;
+            OE_WARN << LC << "A component has no valid TerrainLayer ... removing." << std::endl;
             i = _options._components.erase( i );
         }
         else
-        {
-            TileSource* source = i->_tileSourceInstance.get();
-            if ( source )
-            {
-                osg::ref_ptr<const Profile> localOverrideProfile = profile.get();
-
-                const TileSourceOptions& opt = source->getOptions();
-                if ( opt.profile().isSet() )
-                {
-                    localOverrideProfile = Profile::create( opt.profile().value() );
-                    source->setProfile( localOverrideProfile.get() );
-                }
-
-                // initialize the component tile source:
-                TileSource::Status compStatus = source->startup( _dbOptions.get() );
+        {            
+            TileSource* source = i->_layer->getTileSource();
 
-                if ( compStatus == TileSource::STATUS_OK )
-                {
-                    if ( !profile.valid() )
-                    {
-                        // assume the profile of the first source to be the overall profile.
-                        profile = source->getProfile();
-                    }
-                    else if ( !profile->isEquivalentTo( source->getProfile() ) )
-                    {
-                        // if sub-sources have different profiles, print a warning because this is
-                        // not supported!
-                        OE_WARN << LC << "Components with differing profiles are not supported. " 
-                            << "Visual anomalies may result." << std::endl;
-                    }
-                
-                    _dynamic = _dynamic || source->isDynamic();
-
-                    // gather extents
-                    const DataExtentList& extents = source->getDataExtents();
-                    for( DataExtentList::const_iterator j = extents.begin(); j != extents.end(); ++j )
-                    {
-                        getDataExtents().push_back( *j );
-                    }
-                }
-
-                else
-                {
-                    // if even one of the components fails to initialize, the entire
-                    // composite tile source is invalid.
-                    return Status::Error("At least one component is invalid");
-                }
+            // If no profile is specified assume they want to use the profile of the first layer in the list.
+            if (!profile.valid())
+            {
+                profile = source->getProfile();
             }
+
+            _dynamic = _dynamic || source->isDynamic();
+            
+            // gather extents                        
+            const DataExtentList& extents = source->getDataExtents();            
+            for( DataExtentList::const_iterator j = extents.begin(); j != extents.end(); ++j )
+            {                
+                // Convert the data extent to the profile that is actually used by this TileSource
+                DataExtent dataExtent = *j;                
+                GeoExtent ext = dataExtent.transform(profile->getSRS());
+                unsigned int minLevel = 0;
+                unsigned int maxLevel = profile->getEquivalentLOD( source->getProfile(), *dataExtent.maxLevel() );                                        
+                dataExtent = DataExtent(ext, minLevel, maxLevel);                                
+                getDataExtents().push_back( dataExtent );
+            }          
         }
 
         ++i;
diff --git a/src/osgEarth/Config b/src/osgEarth/Config
index f8fa86b..04433d8 100644
--- a/src/osgEarth/Config
+++ b/src/osgEarth/Config
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Config.cpp b/src/osgEarth/Config.cpp
index 6be55d2..370e779 100644
--- a/src/osgEarth/Config.cpp
+++ b/src/osgEarth/Config.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -164,7 +164,7 @@ namespace
     // Converts a Config to JSON. The "nicer" flag formats the data in a more 
     // readable way than nicer=false. Nicer=true attempts to create JSON "objects",
     // whereas nicer=false makes "$key" and "$children" members.
-    Json::Value conf2json( const Config& conf, bool nicer )
+    Json::Value conf2json(const Config& conf, bool nicer, int depth)
     {
         Json::Value value( Json::objectValue );
 
@@ -207,7 +207,7 @@ namespace
                             if ( c.isSimple() )
                                 value[i->first] = c.value();
                             else
-                                value[i->first] = conf2json(c, nicer);
+                                value[i->first] = conf2json(c, nicer, depth+1);
                         }
                         else
                         {
@@ -215,35 +215,11 @@ namespace
                             Json::Value array_value( Json::arrayValue );
                             for( std::vector<Config>::iterator j = i->second.begin(); j != i->second.end(); ++j )
                             {
-                                array_value.append( conf2json(*j, nicer) );
+                                array_value.append( conf2json(*j, nicer, depth+1) );
                             }
                             value[array_key] = array_value;
                         }
                     }
-
-#if 0
-                    bool hasdupes = false;
-                    std::set<std::string> dupes;
-                    for( ConfigSet::const_iterator c = conf.children().begin(); c != conf.children().end(); ++c ) {
-                        if ( dupes.find( c->key() ) != dupes.end() ) {
-                            hasdupes = true;
-                            break;
-                        }
-                        else {
-                            dupes.insert(c->key());
-                        }
-                    }
-
-                    for( ConfigSet::const_iterator c = conf.children().begin(); c != conf.children().end(); ++c )
-                    {
-                        if ( hasdupes )
-                            children[i++] = conf2json(*c, nicer);
-                        else if ( c->isSimple() )
-                            value[c->key()] = c->value();
-                        else
-                            value[c->key()] = conf2json(*c, nicer);
-                    }
-#endif
                 }
                 else
                 {
@@ -255,7 +231,7 @@ namespace
                         if ( c->isSimple() )
                             value[c->key()] = c->value();
                         else
-                            children[i++] = conf2json(*c, nicer);
+                            children[i++] = conf2json(*c, nicer, depth+1);
                     }
 
                     if ( !children.empty() )
@@ -264,37 +240,42 @@ namespace
                     }
                 }
             }
+
+            // At the root, embed the Config in a single JSON object.
+            if ( depth == 0 )
+            {
+                Json::Value root;
+                root[conf.key()] = value;
+                value = root;
+            }
         }
 
         return value;
     }
 
-    void json2conf( const Json::Value& json, Config& conf )
+    void json2conf(const Json::Value& json, Config& conf, int depth)
     {
         if ( json.type() == Json::objectValue )
         {
             Json::Value::Members members = json.getMemberNames();
 
-            if ( members.size() == 1 )
-            {
-                const Json::Value& value = json[members[0]];
-                if ( value.type() != Json::nullValue && value.type() != Json::objectValue && value.type() != Json::arrayValue )
-                {
-                    conf.key() = members[0];
-                    conf.value() = value.asString();
-                    return;
-                }
-            }
-
             for( Json::Value::Members::const_iterator i = members.begin(); i != members.end(); ++i )
             {
                 const Json::Value& value = json[*i];
 
                 if ( value.isObject() )
                 {
-                    Config element( *i );
-                    json2conf( value, element );
-                    conf.add( element );
+                    if (depth == 0 && members.size() == 1)
+                    {
+                        conf.key() = *i;
+                        json2conf(value, conf, depth+1);
+                    }
+                    else
+                    {
+                        Config element( *i );
+                        json2conf( value, element, depth+1 );
+                        conf.add( element );
+                    }
                 }
                 else if ( value.isArray() && endsWith(*i, "_$set") )
                 {
@@ -302,7 +283,7 @@ namespace
                     for( Json::Value::const_iterator j = value.begin(); j != value.end(); ++j )
                     {
                         Config child( key );
-                        json2conf( *j, child );
+                        json2conf( *j, child, depth+1 );
                         conf.add( child );
                     }
                 }
@@ -316,12 +297,12 @@ namespace
                 }
                 else if ( (*i) == "$children" && value.isArray() )
                 {
-                    json2conf( value, conf );
+                    json2conf( value, conf, depth+1 );
                 }
                 else if ( value.isArray() )
                 {
                     Config element( *i );
-                    json2conf( value, element );
+                    json2conf( value, element, depth+1 );
                     conf.add( element );
                 }
                 else
@@ -335,7 +316,7 @@ namespace
             for( Json::Value::const_iterator j = json.begin(); j != json.end(); ++j )
             {
                 Config child;
-                json2conf( *j, child );
+                json2conf( *j, child, depth+1 );
                 if ( !child.empty() )
                     conf.add( child );
             }
@@ -350,7 +331,7 @@ namespace
 std::string
 Config::toJSON( bool pretty ) const
 {
-    Json::Value root = conf2json( *this, pretty );
+    Json::Value root = conf2json( *this, true, 0 );
     if ( pretty )
         return Json::StyledWriter().write( root );
     else
@@ -364,7 +345,7 @@ Config::fromJSON( const std::string& input )
     Json::Value root( Json::objectValue );
     if ( reader.parse( input, root ) )
     {
-        json2conf( root, *this );
+        json2conf( root, *this, 0 );
         return true;
     }
     return false;
diff --git a/src/osgEarth/Containers b/src/osgEarth/Containers
index e4bec1d..c05e4d7 100644
--- a/src/osgEarth/Containers
+++ b/src/osgEarth/Containers
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -21,6 +21,8 @@
 
 #include <osgEarth/Common>
 #include <osgEarth/ThreadingUtils>
+#include <osg/observer_ptr>
+#include <osg/State>
 #include <list>
 #include <vector>
 
@@ -84,6 +86,12 @@ namespace osgEarth
         iterator end() { return _data.end(); }
 
         bool empty() const { return _data.empty(); }
+
+        void clear() { _data.clear(); }
+        
+        iterator erase(iterator& i) { return _data.erase( i ); }
+
+        int size() const { return _data.size(); }
     };
 
     //------------------------------------------------------------------------
@@ -123,7 +131,7 @@ namespace osgEarth
         struct Record {
             Record() : _valid(false) { }
             Record(const T& value) : _value(value), _valid(true) { }
-            const bool valid() const { return _valid; }
+            bool valid() const { return _valid; }
             const T& value() const { return _value; }
         private:
             bool _valid;
@@ -456,7 +464,6 @@ namespace osgEarth
     private:
         vector_type _impl;
     };
-
 }
 
 #endif // OSGEARTH_CONTAINERS_H
diff --git a/src/osgEarth/Cube b/src/osgEarth/Cube
index 0d7aad0..87e2466 100644
--- a/src/osgEarth/Cube
+++ b/src/osgEarth/Cube
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -131,8 +131,8 @@ namespace osgEarth
 
         // This SRS uses a WGS84 lat/long SRS under the hood for reprojection. So we need the
         // pre/post transforms to move from cube to latlong and back.
-        virtual bool preTransform ( std::vector<osg::Vec3d>& points ) const;
-        virtual bool postTransform( std::vector<osg::Vec3d>& points ) const;
+        virtual const SpatialReference* preTransform ( std::vector<osg::Vec3d>& points ) const;
+        virtual const SpatialReference* postTransform( std::vector<osg::Vec3d>& points ) const;
 
         virtual bool transformExtentToMBR(
             const SpatialReference* to_srs,
@@ -186,10 +186,13 @@ namespace osgEarth
 
     public: // Profile
 
-        virtual void getIntersectingTiles(
+        void getIntersectingTiles(
             const GeoExtent& extent,
+            unsigned localLOD,
             std::vector< TileKey >& out_intersectingKeys ) const;
 
+        unsigned getEquivalentLOD(const Profile* rhsProfile, unsigned rhsLOD) const;
+
     private:
 
         GeoExtent _faceExtent_gcs[6];
diff --git a/src/osgEarth/Cube.cpp b/src/osgEarth/Cube.cpp
index c0c449d..d53b37c 100644
--- a/src/osgEarth/Cube.cpp
+++ b/src/osgEarth/Cube.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -318,7 +318,8 @@ CubeFaceLocator::convertLocalToModel( const osg::Vec3d& local, osg::Vec3d& world
         osg::Vec3d faceCoord = local * _transform;
 
         double lat_deg, lon_deg;
-        CubeUtils::faceCoordsToLatLon( faceCoord.x(), faceCoord.y(), _face, lat_deg, lon_deg );
+        if ( !CubeUtils::faceCoordsToLatLon( faceCoord.x(), faceCoord.y(), _face, lat_deg, lon_deg ))
+            return false;
 
         //OE_NOTICE << "LatLon=" << latLon <<  std::endl;
 
@@ -361,11 +362,12 @@ CubeFaceLocator::convertModelToLocal(const osg::Vec3d& world, osg::Vec3d& local)
 
             if (!success)
             {
-                OE_NOTICE << LC << "Couldn't convert to face coords " << std::endl;
+                OE_WARN << LC << "Couldn't convert to face coords " << std::endl;
+                return false;
             }
             if (face != _face)
             {
-                OE_NOTICE << LC
+                OE_WARN << LC
                     << "Face should be " << _face << " but is " << face
                     << ", lat = " << lat_deg
                     << ", lon = " << lon_deg
@@ -394,9 +396,9 @@ CubeFaceLocator::convertModelToLocal(const osg::Vec3d& world, osg::Vec3d& local)
 CubeSpatialReference::CubeSpatialReference( void* handle ) :
 SpatialReference( handle, "OSGEARTH" )
 {
-    //nop
-    _key.first = "unified-cube";
-    _name      = "Unified Cube";
+    _key.horiz      = "unified-cube";
+    _key.horizLower = "unified-cube";
+    _name           = "Unified Cube";
 }
 
 CubeSpatialReference::~CubeSpatialReference()
@@ -410,10 +412,16 @@ CubeSpatialReference::_init()
 
     _is_user_defined = true;
     _is_cube         = true;
-    _is_contiguous  = false;
-    _is_geographic  = false;
-    _key.first      = "unified-cube";
-    _name           = "Unified Cube";
+    _is_contiguous   = false;
+    _is_geographic   = false;
+    _key.horiz       = "unified-cube";
+    _key.horizLower  = "unified-cube";
+    _name            = "Unified Cube";
+
+    // Custom units. The big number there roughly converts [0..1] to meters
+    // on a spheroid with WGS84-ish radius. Not perfect but close enough for
+    // the purposes of this class
+    _units = Units("Cube face", "cube", Units::TYPE_LINEAR, 42949672.96/4.0);
 }
 
 GeoLocator*
@@ -436,7 +444,7 @@ CubeSpatialReference::createLocator(double xmin, double ymin, double xmax, doubl
     return result;
 }
 
-bool
+const SpatialReference*
 CubeSpatialReference::preTransform( std::vector<osg::Vec3d>& points ) const
 {
     for( unsigned i=0; i<points.size(); ++i )
@@ -448,7 +456,7 @@ CubeSpatialReference::preTransform( std::vector<osg::Vec3d>& points ) const
         if ( !CubeUtils::cubeToFace( p.x(), p.y(), face ) )
         {
             OE_WARN << LC << "Failed to convert (" << p.x() << "," << p.y() << ") into face coordinates." << std::endl;
-            return false;
+            return 0L;
         }
 
         double lat_deg, lon_deg;
@@ -460,15 +468,15 @@ CubeSpatialReference::preTransform( std::vector<osg::Vec3d>& points ) const
                 << "Could not transform face coordinates ["
                 << p.x() << ", " << p.y() << ", " << face << "] to lat lon"
                 << std::endl;
-            return false;
+            return 0L;
         }
         p.x() = lon_deg;
         p.y() = lat_deg;
     }
-    return true;
+    return getGeodeticSRS();
 }
 
-bool
+const SpatialReference*
 CubeSpatialReference::postTransform( std::vector<osg::Vec3d>& points) const
 {
     for( unsigned i=0; i<points.size(); ++i )
@@ -488,7 +496,7 @@ CubeSpatialReference::postTransform( std::vector<osg::Vec3d>& points) const
                 << "Could not transform lat long ["
                 << p.y() << ", " << p.x() << "] coordinates to face" 
                 << std::endl;
-            return false;
+            return 0L;
         }
 
         //TODO: what to do about boundary points?
@@ -496,13 +504,13 @@ CubeSpatialReference::postTransform( std::vector<osg::Vec3d>& points) const
         if ( !CubeUtils::faceToCube( out_x, out_y, face ) )
         {
             OE_WARN << LC << "fromFace(" << out_x << "," << out_y << "," << face << ") failed" << std::endl;
-            return false;
+            return 0L;
         }
         
         p.x() = out_x;
         p.y() = out_y;
     }
-    return true;
+    return getGeodeticSRS();
 }
 
 #define LL 0
@@ -692,13 +700,13 @@ UnifiedCubeProfile::transformGcsExtentOnFace( const GeoExtent& gcsExtent, int fa
 }
 
 void
-UnifiedCubeProfile::getIntersectingTiles(
-    const GeoExtent& remoteExtent,
-    std::vector<TileKey>& out_intersectingKeys ) const
+UnifiedCubeProfile::getIntersectingTiles(const GeoExtent&      remoteExtent,
+                                         unsigned              localLOD,
+                                         std::vector<TileKey>& out_intersectingKeys ) const
 {
-    if ( getSRS()->isEquivalentTo( remoteExtent.getSRS() ) )
+    if ( getSRS()->isHorizEquivalentTo( remoteExtent.getSRS() ) )
     {
-        addIntersectingTiles( remoteExtent, out_intersectingKeys );
+        addIntersectingTiles( remoteExtent, localLOD, out_intersectingKeys );
     }
     else
     {
@@ -718,12 +726,19 @@ UnifiedCubeProfile::getIntersectingTiles(
             if ( partExtent_gcs.isValid() )
             {
                 GeoExtent partExtent = transformGcsExtentOnFace( partExtent_gcs, face );
-                addIntersectingTiles( partExtent, out_intersectingKeys );
+                addIntersectingTiles( partExtent, localLOD, out_intersectingKeys );
             }
-        }
+        }        
     }
 }
 
+unsigned
+UnifiedCubeProfile::getEquivalentLOD(const Profile* rhsProfile, unsigned rhsLOD) const
+{    
+    return rhsLOD;
+}
+
+
 UnifiedCubeProfile::~UnifiedCubeProfile()
 {
 }
diff --git a/src/osgEarth/CullingUtils b/src/osgEarth/CullingUtils
index a1b252c..6dce750 100644
--- a/src/osgEarth/CullingUtils
+++ b/src/osgEarth/CullingUtils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -29,6 +29,7 @@
 #include <osg/MatrixTransform>
 #include <osg/Vec3d>
 #include <osg/Vec3>
+#include <osg/ClipPlane>
 #include <osgUtil/CullVisitor>
 
 namespace osgEarth
@@ -133,6 +134,13 @@ namespace osgEarth
         osg::BoundingSphere computeBound(const osg::Node&) const { return _bs; }
     };
 
+    // a cull callback that prevents objects from being included in the near/fear clip
+    // plane calculates that OSG does.
+    struct OSGEARTH_EXPORT DoNotComputeNearFarCullCallback : public osg::NodeCallback
+    {
+        void operator()(osg::Node* node, osg::NodeVisitor* nv);
+    };
+
     /**
      * Simple occlusion culling callback that does a ray interseciton between the eyepoint
      * and a world point and doesn't draw if there are intersections with the node.
@@ -263,6 +271,24 @@ namespace osgEarth
     private:
         float _scaleFactor;
     };
+
+
+    
+    // Cull callback that figures out where the visible horizon is
+    // based on the eyepoint.
+    class OSGEARTH_EXPORT ClipToGeocentricHorizon : public osg::NodeCallback
+    {
+    public:
+        ClipToGeocentricHorizon(
+            const osgEarth::SpatialReference* srs,
+            osg::ClipPlane*                   clipPlane);
+        
+        void operator()(osg::Node* node, osg::NodeVisitor* nv);
+
+    protected:
+        double _radius;
+        osg::observer_ptr<osg::ClipPlane> _clipPlane;
+    };
 }
 
 #endif // OSGEARTH_CULLING_UTILS_H
diff --git a/src/osgEarth/CullingUtils.cpp b/src/osgEarth/CullingUtils.cpp
index dc6110f..4a426dd 100644
--- a/src/osgEarth/CullingUtils.cpp
+++ b/src/osgEarth/CullingUtils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,6 +21,7 @@
 #include <osgEarth/VirtualProgram>
 #include <osgEarth/DPLineSegmentIntersector>
 #include <osgEarth/GeoData>
+#include <osgEarth/Utils>
 #include <osg/ClusterCullingCallback>
 #include <osg/PrimitiveSet>
 #include <osg/Geode>
@@ -309,6 +310,25 @@ Culling::asCullVisitor(osg::NodeVisitor* nv)
     return 0L;
 }
 
+//------------------------------------------------------------------------
+
+void
+DoNotComputeNearFarCullCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
+{
+    osgUtil::CullVisitor* cv = static_cast< osgUtil::CullVisitor*>( nv );
+    osg::CullSettings::ComputeNearFarMode oldMode;
+    if( cv )
+    {
+        oldMode = cv->getComputeNearFarMode();
+        cv->setComputeNearFarMode( osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR );
+    }
+    traverse(node, nv);
+    if( cv )
+    {
+        cv->setComputeNearFarMode(oldMode);
+    }
+}
+
 
 //----------------------------------------------------------------------------
 
@@ -334,6 +354,11 @@ SuperClusterCullingCallback::cull(osg::NodeVisitor* nv, osg::Drawable* , osg::St
     if (radius < _radius)
         return false;
 
+#if 0 // underwater test.
+    if (radius-_radius < 1000000)
+        return false;
+#endif
+
     // handle perspective and orthographic projections differently.
     const osg::Matrixd& proj = *cv->getProjectionMatrix();
     bool isOrtho = ( proj(3,3) == 1. ) && ( proj(2,3) == 0. ) && ( proj(1,3) == 0. ) && ( proj(0,3) == 0.);
@@ -816,9 +841,15 @@ ProxyCullVisitor::distance(const osg::Vec3& coord,const osg::Matrix& matrix)
 void 
 ProxyCullVisitor::handle_cull_callbacks_and_traverse(osg::Node& node)
 {
+#if OSG_VERSION_GREATER_THAN(3,3,1)
+    osg::Callback* callback = node.getCullCallback();
+    if (callback) callback->run(&node, this);
+    else traverse(node);
+#else
     osg::NodeCallback* callback = node.getCullCallback();
     if (callback) (*callback)(&node,this);
     else traverse(node);
+#endif
 }
 
 void 
@@ -902,12 +933,17 @@ ProxyCullVisitor::apply(osg::Geode& node)
     for(unsigned int i=0;i<node.getNumDrawables();++i)
     {
         osg::Drawable* drawable = node.getDrawable(i);
-        const osg::BoundingBox& bb =drawable->getBound();
+        const osg::BoundingBox& bb = Utils::getBoundingBox(drawable);
 
         if( drawable->getCullCallback() )
         {
+#if OSG_VERSION_GREATER_THAN(3,3,1)
+            if( drawable->getCullCallback()->run(drawable, _cv) == true )
+                continue;
+#else
             if( drawable->getCullCallback()->cull( _cv, drawable, &_cv->getRenderInfo() ) == true )
                 continue;
+#endif
         }
 
         //else
@@ -1066,3 +1102,31 @@ LODScaleGroup::traverse(osg::NodeVisitor& nv)
 
     osg::Group::traverse( nv );
 }
+
+//------------------------------------------------------------------
+
+ClipToGeocentricHorizon::ClipToGeocentricHorizon(const osgEarth::SpatialReference* srs,
+                                                 osg::ClipPlane*                   clipPlane)
+{
+    _radius = std::min(
+        srs->getEllipsoid()->getRadiusPolar(),
+        srs->getEllipsoid()->getRadiusEquator() );
+
+    _clipPlane = clipPlane;
+}
+
+void
+ClipToGeocentricHorizon::operator()(osg::Node* node, osg::NodeVisitor* nv)
+{
+    osg::ref_ptr<osg::ClipPlane> clipPlane;
+    if ( _clipPlane.lock(clipPlane) )
+    {
+        osg::Vec3 eye = nv->getEyePoint();
+        double d = eye.length();
+        double a = acos(_radius/d);
+        double horizonRadius = _radius*cos(a);
+        eye.normalize();
+        clipPlane->setClipPlane(osg::Plane(eye, eye*horizonRadius));
+    }
+    traverse(node, nv);
+}
diff --git a/src/osgEarth/DPLineSegmentIntersector b/src/osgEarth/DPLineSegmentIntersector
index fe21186..393db81 100644
--- a/src/osgEarth/DPLineSegmentIntersector
+++ b/src/osgEarth/DPLineSegmentIntersector
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/DPLineSegmentIntersector.cpp b/src/osgEarth/DPLineSegmentIntersector.cpp
index 225e244..d180fd3 100644
--- a/src/osgEarth/DPLineSegmentIntersector.cpp
+++ b/src/osgEarth/DPLineSegmentIntersector.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -17,6 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarth/DPLineSegmentIntersector>
+#include <osgEarth/Utils>
 #include <osg/KdTree>
 #include <osg/TriangleFunctor>
 
@@ -259,7 +260,7 @@ DPLineSegmentIntersector::intersect(osgUtil::IntersectionVisitor& iv, osg::Drawa
     if (reachedLimit()) return;
 
     osg::Vec3d s(_start), e(_end);
-    if ( !intersectAndClip( s, e, drawable->getBound() ) ) return;
+    if ( !intersectAndClip( s, e, Utils::getBoundingBox(drawable))) return;
 
     if (iv.getDoDummyTraversal()) return;
 
diff --git a/src/osgEarth/DateTime b/src/osgEarth/DateTime
index 32b42a5..7291dcf 100644
--- a/src/osgEarth/DateTime
+++ b/src/osgEarth/DateTime
@@ -33,6 +33,7 @@ namespace osgEarth
 
     /**
      * General-purpose UTC date/time object.
+     * One second resolution, GMT time zone.
      */
     class OSGEARTH_EXPORT DateTime
     {
@@ -43,7 +44,7 @@ namespace osgEarth
         /** DateTime copy */
         DateTime(const DateTime& rhs);
 
-        /** DateTime from a tm */
+        /** DateTime from a tm (in the local time zone) */
         DateTime(const ::tm& tm);
 
         /** DateTime from UTC seconds since the epoch */
@@ -52,9 +53,19 @@ namespace osgEarth
         /** DateTime from year, month, date, hours */
         DateTime(int year, int month, int day, double hours);
 
-        /** As a date/time string in RFC 1123 format */
+        /** DateTime from an ISO 8601 string */
+        DateTime(const std::string& iso8601);
+
+        /** As a date/time string in RFC 1123 format (e.g., HTTP) */
         const std::string asRFC1123() const;
 
+        /** As a date/time string in ISO 8601 format (lexigraphic order). */
+        const std::string asISO8601() const;
+
+        /** As a date/time string in compact ISO 8601 format (lexigraphic
+          * order with no delimiters). */
+        const std::string asCompactISO8601() const;
+
     public:
         int    year()  const;
         int    month() const;
@@ -62,11 +73,14 @@ namespace osgEarth
         double hours() const;
 
         TimeStamp   asTimeStamp() const { return _time_t; }
-        const ::tm& as_tm()       const { return _tm; }
 
     protected:
         ::tm     _tm;
         ::time_t _time_t;
+
+    private:
+        // since timegm is not cross-platform
+        ::time_t timegm(const ::tm* tm) const;
     };
 
 } // namespace osgEarth
diff --git a/src/osgEarth/DateTime.cpp b/src/osgEarth/DateTime.cpp
index 40f7c40..83a34f8 100644
--- a/src/osgEarth/DateTime.cpp
+++ b/src/osgEarth/DateTime.cpp
@@ -20,6 +20,7 @@
 #include <osgEarth/StringUtils>
 #include <math.h>
 #include <iomanip>
+#include <stdio.h>
 
 using namespace osgEarth;
 
@@ -61,10 +62,11 @@ DateTime::DateTime(TimeStamp utc)
 DateTime::DateTime(const ::tm& in_tm)
 {
     tm temptm = in_tm;
-    _time_t = ::mktime( &temptm );
+    _time_t = ::mktime( &temptm ); // assumes in_tm is in local time
     tm* temp = ::gmtime( &_time_t );
     if ( temp ) _tm = *temp;
     else memset( &_tm, 0, sizeof(tm) );
+    _time_t = this->timegm(&_tm); // back to UTC
 }
 
 DateTime::DateTime(int year, int month, int day, double hour)
@@ -82,12 +84,69 @@ DateTime::DateTime(int year, int month, int day, double hour)
     _tm.tm_sec = (int)(frac*60.0);
 
     // now go to time_t, and back to tm, to populate the rest of the fields.
-    _time_t =  ::mktime( &_tm );
+    _time_t =  this->timegm( &_tm );
+    //_time_t =  ::mktime( &_tm );
     tm* temp = ::gmtime( &_time_t );
     if ( temp ) _tm = *temp;
     else memset( &_tm, 0, sizeof(tm) );
 }
 
+DateTime::DateTime(const std::string& input)
+{
+    bool ok = false;
+    int year, month, day, hour, min, sec;
+
+    if (sscanf(input.c_str(), "%4d-%2d-%2dT%2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec) == 6)
+    {
+        _tm.tm_year = year - 1900;
+        _tm.tm_mon  = month - 1;
+        _tm.tm_mday = day;
+        _tm.tm_hour = hour;
+        _tm.tm_min  = min;
+        _tm.tm_sec  = sec;
+        ok = true;
+    }
+    else if (sscanf(input.c_str(), "%4d-%2d-%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec) == 6)
+    {
+        _tm.tm_year = year - 1900;
+        _tm.tm_mon  = month - 1;
+        _tm.tm_mday = day;
+        _tm.tm_hour = hour;
+        _tm.tm_min  = min;
+        _tm.tm_sec  = sec;
+        ok = true;
+    }
+    else if (sscanf(input.c_str(), "%4d%2d%2dT%2d%2d%2d", &year, &month, &day, &hour, &min, &sec) == 6)
+    {
+        _tm.tm_year = year - 1900;
+        _tm.tm_mon  = month - 1;
+        _tm.tm_mday = day;
+        _tm.tm_hour = hour;
+        _tm.tm_min  = min;
+        _tm.tm_sec  = sec;
+        ok = true;
+    }
+    else if (sscanf(input.c_str(), "%4d%2d%2d%2d%2d%2d", &year, &month, &day, &hour, &min, &sec) == 6)
+    {
+        _tm.tm_year = year - 1900;
+        _tm.tm_mon  = month - 1;
+        _tm.tm_mday = day;
+        _tm.tm_hour = hour;
+        _tm.tm_min  = min;
+        _tm.tm_sec  = sec;
+        ok = true;
+    }
+
+    if ( ok )
+    {
+        // now go to time_t, and back to tm, to populate the rest of the fields.
+        _time_t =  this->timegm( &_tm );
+        tm* temp = ::gmtime( &_time_t );
+        if ( temp ) _tm = *temp;
+        else memset( &_tm, 0, sizeof(tm) );
+    }
+}
+
 DateTime::DateTime(const DateTime& rhs) :
 _tm    ( rhs._tm ),
 _time_t( rhs._time_t )
@@ -132,3 +191,112 @@ DateTime::asRFC1123() const
         << std::setw(2) << _tm.tm_sec << ' '
         << "GMT";
 }
+
+const std::string
+DateTime::asISO8601() const
+{
+    return Stringify()
+        << std::setw(4) << (_tm.tm_year + 1900) << '-'
+        << std::setfill('0') << std::setw(2) << (_tm.tm_mon + 1) << '-'
+        << std::setfill('0') << std::setw(2) << (_tm.tm_mday)
+        << 'T'
+        << std::setfill('0') << std::setw(2) << _tm.tm_hour << ':'
+        << std::setfill('0') << std::setw(2) << _tm.tm_min << ':'
+        << std::setfill('0') << std::setw(2) << _tm.tm_sec
+        << 'Z';
+}
+
+const std::string
+DateTime::asCompactISO8601() const
+{
+    return Stringify()
+        << std::setw(4) << (_tm.tm_year + 1900)
+        << std::setfill('0') << std::setw(2) << (_tm.tm_mon + 1)
+        << std::setfill('0') << std::setw(2) << (_tm.tm_mday)
+        << 'T'
+        << std::setfill('0') << std::setw(2) << _tm.tm_hour
+        << std::setfill('0') << std::setw(2) << _tm.tm_min
+        << std::setfill('0') << std::setw(2) << _tm.tm_sec
+        << 'Z';
+}
+
+//------------------------------------------------------------------------
+
+/*
+ * Copyright (c) 2001-2006, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace
+{
+    /* Number of days per month (except for February in leap years). */
+    static const int monoff[] = {
+        0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
+    };
+
+    static int is_leap_year(int year)
+    {
+        return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
+    }
+
+    static int leap_days(int y1, int y2)
+    {
+        --y1;
+        --y2;
+        return (y2/4 - y1/4) - (y2/100 - y1/100) + (y2/400 - y1/400);
+    }
+}
+
+/*
+* Code adapted from Python 2.4.1 sources (Lib/calendar.py).
+*/
+::time_t DateTime::timegm(const struct tm* tm) const
+{
+    int year;
+    time_t days;
+    time_t hours;
+    time_t minutes;
+    time_t seconds;
+
+    year = 1900 + tm->tm_year;
+    days = 365 * (year - 1970) + leap_days(1970, year);
+    days += monoff[tm->tm_mon];
+
+    if (tm->tm_mon > 1 && is_leap_year(year))
+        ++days;
+    days += tm->tm_mday - 1;
+
+    hours = days * 24 + tm->tm_hour;
+    minutes = hours * 60 + tm->tm_min;
+    seconds = minutes * 60 + tm->tm_sec;
+
+    return seconds;
+}
\ No newline at end of file
diff --git a/src/osgEarth/Decluttering b/src/osgEarth/Decluttering
index a59a744..9c6ed64 100644
--- a/src/osgEarth/Decluttering
+++ b/src/osgEarth/Decluttering
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Decluttering.cpp b/src/osgEarth/Decluttering.cpp
index 7a5f13c..935f0ee 100644
--- a/src/osgEarth/Decluttering.cpp
+++ b/src/osgEarth/Decluttering.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -242,7 +242,8 @@ struct /*internal*/ DeclutterSort : public osgUtil::RenderBin::SortCallback
             const osg::Node*     drawableParent = drawable->getParent(0);
 
             // transform the bounding box of the drawable into window-space.
-            osg::BoundingBox box = drawable->getBound();
+            osg::BoundingBox box = Utils::getBoundingBox(drawable);
+
             static osg::Vec4d s_zero_w(0,0,0,1);
             osg::Vec4d clip = s_zero_w * (*leaf->_modelview.get()) * (*leaf->_projection.get());
             osg::Vec3d clip_ndc( clip.x()/clip.w(), clip.y()/clip.w(), clip.z()/clip.w() );
diff --git a/src/osgEarth/DepthOffset b/src/osgEarth/DepthOffset
index b91b01c..ab025cf 100644
--- a/src/osgEarth/DepthOffset
+++ b/src/osgEarth/DepthOffset
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -106,6 +106,8 @@ namespace osgEarth
     public:
         virtual void setDepthOffsetOptions( const DepthOffsetOptions& options ) =0;
         virtual const DepthOffsetOptions& getDepthOffsetOptions() const =0;
+
+        virtual ~DepthOffsetInterface() { }
     };
 
 
@@ -117,12 +119,17 @@ namespace osgEarth
     public:
         DepthOffsetAdapter();
         DepthOffsetAdapter(osg::Node* graph);
+        
+        virtual ~DepthOffsetAdapter() { }
 
         void setGraph(osg::Node* graph );
         void recalculate();
 
         bool isDirty() const { return _dirty; }
 
+        /** whether depth offsetting is supported. */
+        bool supported() const { return _supported; }
+
     public: // DepthOffsetInterface
 
         void setDepthOffsetOptions( const DepthOffsetOptions& options );
@@ -135,8 +142,10 @@ namespace osgEarth
         bool                         _supported;
         bool                         _dirty;
         osg::observer_ptr<osg::Node> _graph;
-        osg::ref_ptr<osg::Uniform>   _biasUniform;
-        osg::ref_ptr<osg::Uniform>   _rangeUniform;
+        //osg::ref_ptr<osg::Uniform>   _biasUniform;
+        //osg::ref_ptr<osg::Uniform>   _rangeUniform;
+        osg::ref_ptr<osg::Uniform>   _minBiasUniform, _maxBiasUniform;
+        osg::ref_ptr<osg::Uniform>   _minRangeUniform, _maxRangeUniform;
         DepthOffsetOptions           _options;
     };
 
diff --git a/src/osgEarth/DepthOffset.cpp b/src/osgEarth/DepthOffset.cpp
index ca21d9b..a52c25a 100644
--- a/src/osgEarth/DepthOffset.cpp
+++ b/src/osgEarth/DepthOffset.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -35,10 +35,9 @@
 
 using namespace osgEarth;
 
-// undef this if you want to adjust in the normal direction (of a geocentric point) instead
-#define ADJUST_TOWARDS_EYE 1
-
-
+// vertex-only method - just pull the actual vertex. test for a while
+// and accept if it works consistently.
+#define VERTEX_ONLY_METHOD 1
 
 //------------------------------------------------------------------------
 
@@ -94,13 +93,47 @@ namespace
     //...............................
     // Shader code:
 
+#ifdef VERTEX_ONLY_METHOD
+
     const char* s_vertex =
         "#version " GLSL_VERSION_STR "\n"
         GLSL_DEFAULT_PRECISION_FLOAT "\n"
 
-        // uniforms from ClampableNode:
-        "uniform vec2 oe_doff_bias; \n"
-        "uniform vec2 oe_doff_range; \n"
+        "uniform float oe_doff_min_bias; \n"
+        "uniform float oe_doff_max_bias; \n"
+        "uniform float oe_doff_min_range; \n"
+        "uniform float oe_doff_max_range; \n"
+
+        "void oe_doff_vertex(inout vec4 VertexVIEW) \n"
+        "{ \n"
+        //   calculate range to target:
+        "    vec3 vert3 = VertexVIEW.xyz/VertexVIEW.w; \n"
+        "    float range = length(vert3); \n"
+
+        //   calculate the depth offset bias for this range:
+        "    float ratio = (clamp(range, oe_doff_min_range, oe_doff_max_range)-oe_doff_min_range)/(oe_doff_max_range-oe_doff_min_range);\n"
+        "    float bias = oe_doff_min_bias + ratio * (oe_doff_max_bias-oe_doff_min_bias);\n"
+
+        //   clamp the bias to 1/2 of the range of the vertex. We don't want to 
+        //   pull the vertex TOO close to the camera and certainly not behind it.
+        "    bias = min(bias, range*0.5); \n"
+
+        //   pull the vertex towards the camera.
+        "    vec3 pullVec = normalize(vert3); \n"
+        "    vec3 simVert3 = vert3 - pullVec*bias; \n"
+        "    VertexVIEW = vec4( simVert3 * VertexVIEW.w, VertexVIEW.w ); \n"
+        "} \n";
+
+#else
+
+    const char* s_vertex =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+        
+        "uniform float oe_doff_min_bias; \n"
+        "uniform float oe_doff_max_bias; \n"
+        "uniform float oe_doff_min_range; \n"
+        "uniform float oe_doff_max_range; \n"
 
         // values to pass to fragment shader:
         "varying vec4 oe_doff_vert; \n"
@@ -113,8 +146,8 @@ namespace
         "    float range = length(vert3); \n"
 
         //   calculate the depth offset bias for this range:
-        "    float ratio = (clamp(range, oe_doff_range[0], oe_doff_range[1])-oe_doff_range[0])/(oe_doff_range[1]-oe_doff_range[0]);\n"
-        "    float bias = oe_doff_bias[0] + ratio * (oe_doff_bias[1]-oe_doff_bias[0]);\n"
+        "    float ratio = (clamp(range, oe_doff_min_range, oe_doff_max_range)-oe_doff_min_range)/(oe_doff_max_range-oe_doff_min_range);\n"
+        "    float bias = oe_doff_min_bias + ratio * (oe_doff_max_bias-oe_doff_min_bias);\n"
 
         //   calculate the "simulated" vertex, pulled toward the camera:
         "    vec3 pullVec = normalize(vert3); \n"
@@ -144,6 +177,8 @@ namespace
         "    if ( sim_depth > 1.0 && oe_doff_vertRange < 0.0 ) { sim_depth = 0.0; } \n"
         "    gl_FragDepth = max(0.0, sim_depth); \n"
         "} \n";
+
+#endif // !VERTEX_ONLY_METHOD
 }
 
 //------------------------------------------------------------------------
@@ -198,11 +233,13 @@ _dirty( false )
 void
 DepthOffsetAdapter::init()
 {
-    _supported = Registry::capabilities().supportsFragDepthWrite();
+    _supported = Registry::capabilities().supportsGLSL();
     if ( _supported )
     {
-        _biasUniform  = new osg::Uniform(osg::Uniform::FLOAT_VEC2, "oe_doff_bias");
-        _rangeUniform = new osg::Uniform(osg::Uniform::FLOAT_VEC2, "oe_doff_range");
+        _minBiasUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_doff_min_bias");
+        _maxBiasUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_doff_max_bias");
+        _minRangeUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_doff_min_range");
+        _maxRangeUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_doff_max_range");
         updateUniforms();
     }
 }
@@ -229,13 +266,19 @@ DepthOffsetAdapter::setGraph(osg::Node* graph)
 
         // uninstall uniforms and shaders.
         osg::StateSet* s = _graph->getStateSet();
-        s->removeUniform( _biasUniform.get() );
-        s->removeUniform( _rangeUniform.get() );
+        s->removeUniform( _minBiasUniform.get() );
+        s->removeUniform( _maxBiasUniform.get() );
+        s->removeUniform( _minRangeUniform.get() );
+        s->removeUniform( _maxRangeUniform.get() );
+
         VirtualProgram* vp = VirtualProgram::get( s );
         if ( vp )
         {
             vp->removeShader( "oe_doff_vertex" );
+
+#ifndef VERTEX_ONLY_METHOD
             vp->removeShader( "oe_doff_fragment" );
+#endif
         }
     }
 
@@ -245,13 +288,18 @@ DepthOffsetAdapter::setGraph(osg::Node* graph)
 
         // install uniforms and shaders.
         osg::StateSet* s = graph->getOrCreateStateSet();
-        s->addUniform( _biasUniform.get() );
-        s->addUniform( _rangeUniform.get() );
+        s->addUniform( _minBiasUniform.get() );
+        s->addUniform( _maxBiasUniform.get() );
+        s->addUniform( _minRangeUniform.get() );
+        s->addUniform( _maxRangeUniform.get() );
 
         VirtualProgram* vp = VirtualProgram::getOrCreate( s );
+
         vp->setFunction( "oe_doff_vertex", s_vertex, ShaderComp::LOCATION_VERTEX_VIEW );
+
+#ifndef VERTEX_ONLY_METHOD
         vp->setFunction( "oe_doff_fragment", s_fragment, ShaderComp::LOCATION_FRAGMENT_COLORING );
-        s->setAttributeAndModes( vp, osg::StateAttribute::ON );
+#endif
     }
 
     if ( graphChanging )
@@ -269,13 +317,16 @@ DepthOffsetAdapter::updateUniforms()
 {
     if ( !_supported ) return;
 
-    _biasUniform->set( osg::Vec2f(*_options.minBias(), *_options.maxBias()) );
-    _rangeUniform->set( osg::Vec2f(*_options.minRange(), *_options.maxRange()) );
+    _minBiasUniform->set( *_options.minBias() );
+    _maxBiasUniform->set( *_options.maxBias() );
+    _minRangeUniform->set( *_options.minRange() );
+    _maxRangeUniform->set( *_options.maxRange() );
 
     if ( _options.enabled() == true )
     {
-        OE_TEST << LC << "bias=[" << *_options.minBias() << ", " << *_options.maxBias() << "] ... "
-                << "range=[" << *_options.minRange() << ", " << *_options.maxRange() << "]" << std::endl;
+        OE_TEST << LC 
+            << "bias=[" << *_options.minBias() << ", " << *_options.maxBias() << "] ... "
+            << "range=[" << *_options.minRange() << ", " << *_options.maxRange() << "]" << std::endl;
     }
 }
 
@@ -322,17 +373,20 @@ DepthOffsetAdapter::recalculate()
 DepthOffsetGroup::DepthOffsetGroup() :
 _updatePending( false )
 {
-    _adapter.setGraph( this );
+    if ( _adapter.supported() )
+    {
+        _adapter.setGraph( this );
 
-    if ( _adapter.isDirty() )
-        _adapter.recalculate();
+        if ( _adapter.isDirty() )
+            _adapter.recalculate();
+    }
 }
 
 void
 DepthOffsetGroup::setDepthOffsetOptions(const DepthOffsetOptions& options)
 {
     _adapter.setDepthOffsetOptions(options);
-    if ( _adapter.isDirty() && !_updatePending )
+    if ( _adapter.supported() && _adapter.isDirty() && !_updatePending )
         scheduleUpdate();
 }
 
@@ -345,17 +399,22 @@ DepthOffsetGroup::getDepthOffsetOptions() const
 void
 DepthOffsetGroup::scheduleUpdate()
 {
-    ADJUST_UPDATE_TRAV_COUNT(this, 1);
-    _updatePending = true;
+    if ( _adapter.supported() )
+    {
+        ADJUST_UPDATE_TRAV_COUNT(this, 1);
+        _updatePending = true;
+    }
 }
 
 osg::BoundingSphere
 DepthOffsetGroup::computeBound() const
 {
-    static Threading::Mutex s_mutex;
+    if ( _adapter.supported() )
     {
-        Threading::ScopedMutexLock lock(s_mutex);
+        static Threading::Mutex s_mutex;
+        s_mutex.lock();
         const_cast<DepthOffsetGroup*>(this)->scheduleUpdate();
+        s_mutex.unlock();
     }
     return osg::Group::computeBound();
 }
diff --git a/src/osgEarth/Draggers b/src/osgEarth/Draggers
index aaf930b..ee47c6f 100644
--- a/src/osgEarth/Draggers
+++ b/src/osgEarth/Draggers
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Draggers.cpp b/src/osgEarth/Draggers.cpp
index eaa4862..53f4c82 100644
--- a/src/osgEarth/Draggers.cpp
+++ b/src/osgEarth/Draggers.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -177,8 +177,9 @@ void Dragger::traverse(osg::NodeVisitor& nv)
             itr != ev->getEvents().end();
             ++itr)
         {
-            osgGA::GUIEventAdapter* ea = itr->get();
-            if (handle(*ea, *(ev->getActionAdapter()))) ea->setHandled(true);
+            osgGA::GUIEventAdapter* ea = dynamic_cast<osgGA::GUIEventAdapter*>(itr->get());
+            if ( ea && handle(*ea, *(ev->getActionAdapter())))
+                ea->setHandled(true);
         }
     }
     osg::MatrixTransform::traverse(nv);
diff --git a/src/osgEarth/DrapeableNode b/src/osgEarth/DrapeableNode
index bb51f13..8de53d6 100644
--- a/src/osgEarth/DrapeableNode
+++ b/src/osgEarth/DrapeableNode
@@ -43,13 +43,20 @@ namespace osgEarth
          */
         DrapeableNode( MapNode* mapNode, bool active =true );
 
+        /** Sets the rendering order of the draped geometry */
+        void setRenderOrder(int order);
+        const optional<int>& renderOrder() const { return _renderOrder; }
+
         /** Backwards compatibility */
         void setDraped( bool value ) { setActive(value); }
         bool getDraped() const { return getActive(); }
 
+
     protected:
         /** dtor */
         virtual ~DrapeableNode() { }
+
+        optional<int> _renderOrder;
     };
 
 } // namespace osgEarth
diff --git a/src/osgEarth/DrapeableNode.cpp b/src/osgEarth/DrapeableNode.cpp
index b456446..1ccaa33 100644
--- a/src/osgEarth/DrapeableNode.cpp
+++ b/src/osgEarth/DrapeableNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -44,3 +44,10 @@ OverlayNode( mapNode, draped, &getTechniqueGroup )
     //nop
 }
 
+void
+DrapeableNode::setRenderOrder(int order)
+{
+    _renderOrder = order;
+    osg::StateSet* s = _overlayProxyContainer->getOrCreateStateSet();
+    s->setRenderBinDetails(order, "RenderBin");
+}
diff --git a/src/osgEarth/DrapingTechnique b/src/osgEarth/DrapingTechnique
index 5d35162..06de95d 100644
--- a/src/osgEarth/DrapingTechnique
+++ b/src/osgEarth/DrapingTechnique
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/DrapingTechnique.cpp b/src/osgEarth/DrapingTechnique.cpp
index 4a3d9cb..bcc66f0 100644
--- a/src/osgEarth/DrapingTechnique.cpp
+++ b/src/osgEarth/DrapingTechnique.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -40,7 +40,7 @@ namespace
     // Additional per-view data stored by the draping technique.
     struct LocalPerViewData : public osg::Referenced
     {
-        osg::ref_ptr<osg::Uniform> _texGenUniform;  // when shady
+        osg::ref_ptr<osg::Uniform> _texGenUniform;
     };
 }
 
@@ -125,9 +125,13 @@ namespace
             if ( eyeClip.y() >= -1.0 && eyeClip.y() <= 1.0 )
                 return;
 
+            // sanity check. 6 faces requires since we need near and far
+            if ( params._visibleFrustumPH._faces.size() < 6 )
+                return;
+
             // discover the max near-plane width.
             double halfWidthNear = 0.0;
-            osgShadow::ConvexPolyhedron::Faces::iterator f = params._frustumPH._faces.begin();
+            osgShadow::ConvexPolyhedron::Faces::iterator f = params._visibleFrustumPH._faces.begin();
             f++; f++; f++; f++; // the near plane Face
             // f->vertices.size() should always be 4, I would think.. but it's not..
             for(unsigned i=0; i<f->vertices.size(); ++i)
@@ -291,8 +295,10 @@ _textureSize     ( 1024 ),
 _mipmapping      ( false ),
 _rttBlending     ( true ),
 _attachStencil   ( false ),
-_maxFarNearRatio ( 3.0 )
+_maxFarNearRatio ( 5.0 )
 {
+    _supported = Registry::capabilities().supportsGLSL();
+
     // try newer version
     const char* nfr2 = ::getenv("OSGEARTH_OVERLAY_RESOLUTION_RATIO");
     if ( nfr2 )
@@ -413,7 +419,7 @@ DrapingTechnique::setUpCamera(OverlayDecorator::TechRTTParams& params)
         //Setup a separate blend function for the alpha components and the RGB components.  
         //Because the destination alpha is initialized to 0 instead of 1
         osg::BlendFunc* blendFunc = 0;        
-        if (Registry::capabilities().supportsGLSL(1.4f))
+        if (Registry::capabilities().supportsGLSL(140u))
         {
             //Blend Func Separate is only available on OpenGL 1.4 and above
             blendFunc = new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
@@ -626,7 +632,7 @@ DrapingTechnique::onInstall( TerrainEngineNode* engine )
     if ( !_textureSize.isSet() )
     {
         unsigned maxSize = Registry::capabilities().getMaxFastTextureSize();
-        _textureSize.init( osg::minimum( 4096u, maxSize ) );
+        _textureSize.init( osg::minimum( 2048u, maxSize ) );
     }
     OE_INFO << LC << "Using texture size = " << *_textureSize << std::endl;
 }
diff --git a/src/osgEarth/DrawInstanced b/src/osgEarth/DrawInstanced
index 39e28ec..5db2179 100644
--- a/src/osgEarth/DrawInstanced
+++ b/src/osgEarth/DrawInstanced
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,6 +21,7 @@
 #define OSGEARTH_DRAW_INSTANCED_H 1
 
 #include <osgEarth/Common>
+#include <osgEarth/Containers>
 #include <osgEarth/VirtualProgram>
 #include <osg/NodeVisitor>
 #include <osg/Geode>
@@ -33,6 +34,20 @@ namespace osgEarth
     namespace DrawInstanced
     {
         /**
+         * Referenced-counted vector of instance matrices. These are stored
+         * in the node that's been instanced so the app can recover the 
+         * original positioning data.
+         */
+        class MatrixRefVector : public osgEarth::MixinVector<osg::Matrixf,osg::Object>
+        {
+        public:
+            META_Object(osgEarth,MatrixRefVector);
+            MatrixRefVector() : osgEarth::MixinVector<osg::Matrixf,osg::Object>() { }
+        protected:
+            MatrixRefVector(const MatrixRefVector& rhs, const osg::CopyOp& op) { }
+        };
+
+        /**
          * Visitor that converts all the primitive sets in a graph to use
          * instanced draw calls.
          * Called by convertGraphToUseDrawInstanced().
@@ -50,11 +65,13 @@ namespace osgEarth
                 bool                    optimize );
 
             void apply(osg::Geode&);
+            void apply(osg::LOD&);
 
         protected:
             unsigned _numInstances;
             bool     _optimize;
             osg::ref_ptr<osg::Drawable::ComputeBoundingBoxCallback> _staticBBoxCallback;
+            std::list<osg::PrimitiveSet*> _primitiveSets;
         };
 
 
@@ -75,6 +92,13 @@ namespace osgEarth
          */
         extern OSGEARTH_EXPORT void convertGraphToUseDrawInstanced( 
             osg::Group* graph );
+
+        /**
+         * Gets the vector of instance matrices attached to a node,
+         * or NULL if not found.
+         */
+        extern OSGEARTH_EXPORT const MatrixRefVector* getMatrixVector(
+            osg::Node* node );
     }
 }
 
diff --git a/src/osgEarth/DrawInstanced.cpp b/src/osgEarth/DrawInstanced.cpp
index cc0b212..166d29f 100644
--- a/src/osgEarth/DrawInstanced.cpp
+++ b/src/osgEarth/DrawInstanced.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -23,9 +23,12 @@
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
 #include <osgEarth/ImageUtils>
+#include <osgEarth/Utils>
 
 #include <osg/ComputeBoundsVisitor>
 #include <osg/MatrixTransform>
+#include <osg/UserDataContainer>
+#include <osg/LOD>
 #include <osgUtil/MeshOptimizers>
 
 #define LC "[DrawInstanced] "
@@ -38,10 +41,82 @@ using namespace osgEarth::DrawInstanced;
 #define POSTEX_TEXTURE_UNIT 5
 #define POSTEX_MAX_TEXTURE_SIZE 256
 
+#define TAG_MATRIX_VECTOR "osgEarth::DrawInstanced::MatrixRefVector"
+
+//Uncomment to experiment with instance count adjustment
+//#define USE_INSTANCE_LODS
+
 //----------------------------------------------------------------------
 
 namespace
 {
+#ifdef USE_INSTANCE_LODS
+
+    struct LODCallback : public osg::Drawable::DrawCallback
+    {
+        LODCallback() : _first(true), _maxInstances(0) { }
+
+        void drawImplementation(osg::RenderInfo& ri, const osg::Drawable* drawable) const
+        {
+            const osg::Geometry* geom = drawable->asGeometry();
+
+            if ( _first && geom->getNumPrimitiveSets() > 0 )
+            {
+                _maxInstances = geom->getPrimitiveSet(0)->getNumInstances();
+                _first = false;
+            }
+
+            const osg::BoundingBox bbox = Utils::getBoundingBox(geom);
+            float radius = bbox.radius();
+
+            osg::Vec3d centerView = bbox.center() * ri.getState()->getModelViewMatrix();
+            float rangeToBS = (float)-centerView.z() - radius;
+
+#if OSG_MIN_VERSION_REQUIRED(3,3,0)
+            // check for inherit mode (3.3.0+ only)
+            osg::Camera* cam = ri.getCurrentCamera();
+
+            // Problem: the camera stack is *always* size=1. So no access to the ref cam.
+            if (cam->getReferenceFrame() == cam->ABSOLUTE_RF_INHERIT_VIEWPOINT &&
+                ri.getCameraStack().size() > 1)
+            {
+                osg::Camera* refCam = *(ri.getCameraStack().end()-2);
+                if ( refCam )
+                {
+                    osg::Vec3d centerWorld = centerView * cam->getInverseViewMatrix();
+                    osg::Vec3d centerRefView = centerWorld * refCam->getViewMatrix();
+                    rangeToBS = (float)(-centerRefView.z() - radius);
+                }
+            }
+#endif
+
+            // these should obviously be programmable
+            const float maxDistance = 2000.0f;
+            const float minDistance = 100.0f;
+
+            float ratio = (rangeToBS-minDistance)/(maxDistance-minDistance);
+            ratio = 1.0 - osg::clampBetween(ratio, 0.0f, 1.0f);
+            // 1 = closest, 0 = farthest
+
+            unsigned instances = (unsigned)(ratio*(float)_maxInstances);
+
+            if ( instances > 0 )
+            {
+                for(unsigned i=0; i<geom->getNumPrimitiveSets(); ++i)
+                {
+                    const osg::PrimitiveSet* ps = geom->getPrimitiveSet(i);
+                    const_cast<osg::PrimitiveSet*>(ps)->setNumInstances(instances);
+                }
+
+                drawable->drawImplementation(ri);
+            }
+        }
+
+        mutable bool     _first;
+        mutable unsigned _maxInstances;
+    };
+#endif // USE_INSTANCE_LODS
+
     typedef std::map< osg::ref_ptr<osg::Node>, std::vector<osg::Matrix> > ModelNodeMatrices;
     
     /**
@@ -123,9 +198,6 @@ ConvertToDrawInstanced::apply( osg::Geode& geode )
         {
             if ( _optimize )
             {
-                osgUtil::IndexMeshVisitor imv;
-                imv.makeMesh( *geom );
-
                 // activate VBOs
                 geom->setUseDisplayList( false );
                 geom->setUseVertexBufferObjects( true );
@@ -137,8 +209,14 @@ ConvertToDrawInstanced::apply( osg::Geode& geode )
             // convert to use DrawInstanced
             for( unsigned p=0; p<geom->getNumPrimitiveSets(); ++p )
             {
-                geom->getPrimitiveSet(p)->setNumInstances( _numInstances );
+                osg::PrimitiveSet* ps = geom->getPrimitiveSet(p);
+                ps->setNumInstances( _numInstances );
+                _primitiveSets.push_back( ps );
             }
+
+#ifdef USE_INSTANCE_LODS
+            geom->setDrawCallback( new LODCallback() );
+#endif
         }
     }
 
@@ -147,6 +225,32 @@ ConvertToDrawInstanced::apply( osg::Geode& geode )
 
 
 void
+ConvertToDrawInstanced::apply(osg::LOD& lod)
+{
+    // find the highest LOD:
+    int   minIndex = 0;
+    float minRange = FLT_MAX;
+    for(unsigned i=0; i<lod.getNumRanges(); ++i)
+    {
+        if ( lod.getRangeList()[i].first < minRange )
+        {
+            minRange = lod.getRangeList()[i].first;
+            minIndex = i;
+        }
+    }
+
+    // remove all but the highest:
+    osg::ref_ptr<osg::Node> highestLOD = lod.getChild( minIndex );
+    lod.removeChildren( 0, lod.getNumChildren() );
+
+    // add it back with a full range.
+    lod.addChild( highestLOD.get(), 0.0f, FLT_MAX );
+
+    traverse(lod);
+}
+
+
+void
 DrawInstanced::install(osg::StateSet* stateset)
 {
     if ( !stateset )
@@ -154,24 +258,24 @@ DrawInstanced::install(osg::StateSet* stateset)
 
     // simple vertex program to position a vertex based on its instance
     // matrix, which is stored in a texture.
-    std::string src_vert = Stringify()
-        << "#version 120 \n"
-        << "#extension GL_EXT_gpu_shader4 : enable \n"
-        << "#extension GL_ARB_draw_instanced: enable \n"
-        << "uniform sampler2D oe_di_postex; \n"
-        << "uniform float oe_di_postex_size; \n"
-        << "void oe_di_setInstancePosition(inout vec4 VertexMODEL) \n"
-        << "{ \n"
-        << "    float index = float(4 * gl_InstanceID) / oe_di_postex_size; \n"
-        << "    float s = fract(index); \n"
-        << "    float t = floor(index)/oe_di_postex_size; \n"
-        << "    float step = 1.0 / oe_di_postex_size; \n"  // step from one vec4 to the next
-        << "    vec4 m0 = texture2D(oe_di_postex, vec2(s, t)); \n"
-        << "    vec4 m1 = texture2D(oe_di_postex, vec2(s+step, t)); \n"
-        << "    vec4 m2 = texture2D(oe_di_postex, vec2(s+step+step, t)); \n"
-        << "    vec4 m3 = texture2D(oe_di_postex, vec2(s+step+step+step, t)); \n"
-        << "    VertexMODEL = VertexMODEL * mat4(m0, m1, m2, m3); \n" // why???
-        << "} \n";
+     std::string src_vert = Stringify() 
+        << "#version 120 \n" 
+        << "#extension GL_EXT_gpu_shader4 : enable \n" 
+        << "#extension GL_ARB_draw_instanced: enable \n" 
+        << "uniform sampler2D oe_di_postex; \n" 
+        << "uniform vec2 oe_di_postex_size; \n" 
+        << "void oe_di_setInstancePosition(inout vec4 VertexMODEL) \n" 
+        << "{ \n" 
+        << "    float index = float(4 * gl_InstanceID) / oe_di_postex_size.x; \n" 
+        << "    float s = fract(index); \n" 
+        << "    float t = floor(index)/oe_di_postex_size.y; \n" 
+        << "    float step = 1.0 / oe_di_postex_size.x; \n"  // step from one vec4 to the next 
+        << "    vec4 m0 = texture2D(oe_di_postex, vec2(s, t)); \n" 
+        << "    vec4 m1 = texture2D(oe_di_postex, vec2(s+step, t)); \n" 
+        << "    vec4 m2 = texture2D(oe_di_postex, vec2(s+step+step, t)); \n" 
+        << "    vec4 m3 = texture2D(oe_di_postex, vec2(s+step+step+step, t)); \n" 
+        << "    VertexMODEL = VertexMODEL * mat4(m0, m1, m2, m3); \n" // why??? 
+        << "} \n"; 
 
     VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
 
@@ -220,6 +324,8 @@ DrawInstanced::convertGraphToUseDrawInstanced( osg::Group* parent )
         osg::MatrixTransform* mt = dynamic_cast<osg::MatrixTransform*>( parent->getChild(i) );
         if ( mt )
         {
+            // we have to deep-copy the primitives because we're going to convert them
+            // to use draw-instancing.
             osg::Node* n = mt->getChild(0);
             models[n].push_back( mt->getMatrix() );
         }
@@ -286,6 +392,23 @@ DrawInstanced::convertGraphToUseDrawInstanced( osg::Group* parent )
             lastNode->accept( cdi );
         }
 
+        // Assign matrix vectors to the nodes, so the application can easily retrieve
+        // the original position data if necessary.
+        MatrixRefVector* nodeMats = new MatrixRefVector();
+        nodeMats->setName(TAG_MATRIX_VECTOR);
+        nodeMats->reserve(lastNode != node ? sliceSize*(numSlices-1) : sliceSize*numSlices);
+        node->getOrCreateUserDataContainer()->addUserObject(nodeMats);
+
+        // ...and a separate one for lastNode if necessary
+        MatrixRefVector* lastNodeMats = 0L;
+        if (lastNode != node)
+        {
+            lastNodeMats = new MatrixRefVector();
+            lastNodeMats->setName(TAG_MATRIX_VECTOR);
+            lastNodeMats->reserve(lastSliceSize);
+            lastNode->getOrCreateUserDataContainer()->addUserObject(lastNodeMats);
+        }
+
         // Next, break the rendering down into "slices". GLSL will only support a limited
         // amount of pre-instance uniform data, so we have to portion the graph out into
         // slices of no more than this chunk size.
@@ -304,6 +427,7 @@ DrawInstanced::convertGraphToUseDrawInstanced( osg::Group* parent )
 
             // sampler that will hold the instance matrices:
             osg::Image* image = new osg::Image();
+            image->setName("osgearth.drawinstanced.postex");
             image->allocateImage( (int)texSize.x(), (int)texSize.y(), 1, GL_RGBA, GL_FLOAT );
 
             osg::Texture2D* postex = new osg::Texture2D( image );
@@ -316,9 +440,12 @@ DrawInstanced::convertGraphToUseDrawInstanced( osg::Group* parent )
             if ( !ImageUtils::isPowerOfTwo(image) )
                 postex->setResizeNonPowerOfTwoHint( false );
 
+            // Tell the SG to skip the positioning texture.
+            ShaderGenerator::setIgnoreHint(postex, true);
+
             osg::StateSet* stateset = sliceGroup->getOrCreateStateSet();
             stateset->setTextureAttributeAndModes(POSTEX_TEXTURE_UNIT, postex, 1);
-            stateset->getOrCreateUniform("oe_di_postex_size", osg::Uniform::FLOAT)->set((float)texSize.x());
+            stateset->getOrCreateUniform("oe_di_postex_size", osg::Uniform::FLOAT_VEC2)->set(texSize);
 
             // could use PixelWriter but we know the format.
             GLfloat* ptr = reinterpret_cast<GLfloat*>( image->data() );
@@ -328,6 +455,12 @@ DrawInstanced::convertGraphToUseDrawInstanced( osg::Group* parent )
                 for(int col=0; col<4; ++col)
                     for(int row=0; row<4; ++row)
                         *ptr++ = mat(row,col);
+
+                // store them int the metadata as well
+                if (currentNode == node)
+                    nodeMats->push_back(mat);
+                else
+                    lastNodeMats->push_back(mat);
             }
 
             // add the node as a child:
@@ -337,3 +470,22 @@ DrawInstanced::convertGraphToUseDrawInstanced( osg::Group* parent )
         }
     }
 }
+
+
+const DrawInstanced::MatrixRefVector*
+DrawInstanced::getMatrixVector(osg::Node* node)
+{
+    if ( !node )
+        return 0L;
+
+    osg::UserDataContainer* udc = node->getUserDataContainer();
+    if ( !udc )
+        return 0L;
+
+    osg::Object* obj = udc->getUserObject(TAG_MATRIX_VECTOR);
+    if ( !obj )
+        return 0L;
+
+    // cast is safe because of our unique tag
+    return static_cast<const MatrixRefVector*>( obj );
+}
diff --git a/src/osgEarth/ECEF b/src/osgEarth/ECEF
index fc5813c..e2a224e 100644
--- a/src/osgEarth/ECEF
+++ b/src/osgEarth/ECEF
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/ECEF.cpp b/src/osgEarth/ECEF.cpp
index 6c74a6c..548947d 100644
--- a/src/osgEarth/ECEF.cpp
+++ b/src/osgEarth/ECEF.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/ElevationLOD b/src/osgEarth/ElevationLOD
index d79b7dc..df4761f 100644
--- a/src/osgEarth/ElevationLOD
+++ b/src/osgEarth/ElevationLOD
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/ElevationLOD.cpp b/src/osgEarth/ElevationLOD.cpp
index c679c82..d7ee6c7 100644
--- a/src/osgEarth/ElevationLOD.cpp
+++ b/src/osgEarth/ElevationLOD.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/ElevationLayer b/src/osgEarth/ElevationLayer
index 3aa04e2..730d722 100644
--- a/src/osgEarth/ElevationLayer
+++ b/src/osgEarth/ElevationLayer
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -41,7 +41,11 @@ namespace osgEarth
         virtual ~ElevationLayerOptions() { }
 
         optional<bool>& offset() { return _offset; }
-        const optional<bool>& offset() const { return _offset; }  
+        const optional<bool>& offset() const { return _offset; }
+
+        /** Policy for dealing with NO_DATA values in elevation */
+        optional<ElevationNoDataPolicy>& noDataPolicy() { return _noDataPolicy; }
+        const optional<ElevationNoDataPolicy>& noDataPolicy() const { return _noDataPolicy; }
 
     public:
         virtual Config getConfig() const { return getConfig(false); }
@@ -52,7 +56,8 @@ namespace osgEarth
         void fromConfig( const Config& conf );
         void setDefaults();
 
-        optional< bool > _offset;
+        optional<bool>                  _offset;
+        optional<ElevationNoDataPolicy> _noDataPolicy;
     };
 
     //--------------------------------------------------------------------
@@ -121,9 +126,11 @@ namespace osgEarth
             ProgressCallback* progress =0L );
 
         /**
-         * Whether the given key is valid for this layer
+         * Whether this layer contains offsets instead of absolute heights
          */
-        virtual bool isKeyValid(const TileKey& key) const;
+        bool isOffset() const {
+            return _runtimeOptions.offset() == true;
+        }
 
     protected:
         
@@ -162,17 +169,15 @@ namespace osgEarth
     {
     public:
         /**
-         * Creates a heightfield object by sampling this elevation layer vector.
+         * Populates an existing height field (hf must already exist) with height
+         * values from the elevation layers.
          */
-        bool createHeightField(
-            const TileKey&                  key,
-            bool                            fallback,
-            const Profile*                  haeProfile,
-            ElevationInterpolation          interpolation,
-            ElevationSamplePolicy           samplePolicy,
-            osg::ref_ptr<osg::HeightField>& out_result,
-            bool*                           out_isFallback,
-            ProgressCallback*               progress ) const;
+        bool populateHeightField(
+            osg::HeightField*      hf,
+            const TileKey&         key,
+            const Profile*         haeProfile,
+            ElevationInterpolation interpolation,
+            ProgressCallback*      progress ) const;
 
     public:
         /** Default ctor */
diff --git a/src/osgEarth/ElevationLayer.cpp b/src/osgEarth/ElevationLayer.cpp
index c33c774..cd94bd9 100644
--- a/src/osgEarth/ElevationLayer.cpp
+++ b/src/osgEarth/ElevationLayer.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -20,7 +20,9 @@
 #include <osgEarth/VerticalDatum>
 #include <osgEarth/HeightFieldUtils>
 #include <osgEarth/Progress>
+#include <osgEarth/MemCache>
 #include <osg/Version>
+#include <iterator>
 
 using namespace osgEarth;
 using namespace OpenThreads;
@@ -46,7 +48,8 @@ TerrainLayerOptions( name, driverOptions )
 void
 ElevationLayerOptions::setDefaults()
 {
-    _offset = false;
+    _offset.init( false );
+    _noDataPolicy.init( NODATA_INTERPOLATE );
 }
 
 Config
@@ -54,13 +57,20 @@ ElevationLayerOptions::getConfig( bool isolate ) const
 {
     Config conf = TerrainLayerOptions::getConfig( isolate );
     conf.updateIfSet("offset", _offset);
+    conf.updateIfSet("nodata_policy", "default",     _noDataPolicy, NODATA_INTERPOLATE );
+    conf.updateIfSet("nodata_policy", "interpolate", _noDataPolicy, NODATA_INTERPOLATE );
+    conf.updateIfSet("nodata_policy", "msl",         _noDataPolicy, NODATA_MSL );
+
     return conf;
 }
 
 void
 ElevationLayerOptions::fromConfig( const Config& conf )
 {
-    conf.getIfSet( "offset", _offset );
+    conf.getIfSet("offset", _offset );
+    conf.getIfSet("nodata_policy", "default",     _noDataPolicy, NODATA_INTERPOLATE );
+    conf.getIfSet("nodata_policy", "interpolate", _noDataPolicy, NODATA_INTERPOLATE );
+    conf.getIfSet("nodata_policy", "msl",         _noDataPolicy, NODATA_MSL );
 }
 
 void
@@ -76,13 +86,13 @@ namespace
 {
     struct ElevationLayerPreCacheOperation : public TileSource::HeightFieldOperation
     {
-        osg::ref_ptr<CompositeValidValueOperator> ops;
+        osg::ref_ptr<CompositeValidValueOperator> _ops;
 
         ElevationLayerPreCacheOperation( TileSource* source )
         {
-            ops = new CompositeValidValueOperator;
-            ops->getOperators().push_back(new osgTerrain::NoDataValue(source->getNoDataValue()));
-            ops->getOperators().push_back(new osgTerrain::ValidRange(source->getNoDataMinValue(), source->getNoDataMaxValue()));
+            _ops = new CompositeValidValueOperator;
+            _ops->getOperators().push_back(new osgTerrain::NoDataValue(source->getNoDataValue()));
+            _ops->getOperators().push_back(new osgTerrain::ValidRange(source->getNoDataMinValue(), source->getNoDataMaxValue()));
         }
 
         void operator()( osg::ref_ptr<osg::HeightField>& hf )
@@ -90,7 +100,7 @@ namespace
             //Modify the heightfield data so that is contains a standard value for NO_DATA
             ReplaceInvalidDataOperator op;
             op.setReplaceWith(NO_DATA_VALUE);
-            op.setValidDataOperator(ops.get());
+            op.setValidDataOperator(_ops.get());
             op( hf.get() );
         }
     };
@@ -110,7 +120,7 @@ namespace
             return false;
         
         return true;
-    }
+    }    
 }
 
 //------------------------------------------------------------------------
@@ -139,20 +149,13 @@ _runtimeOptions( options )
 void
 ElevationLayer::init()
 {
-    _tileSize = 15;
-    //_tileSize = 32;
+    //nop
 }
 
 std::string
 ElevationLayer::suggestCacheFormat() const
 {
-#if OSG_MIN_VERSION_REQUIRED(2,8,0)
-        //OSG 2.8 onwards should use TIF for heightfields
-        return "tif";
-#else
-        //OSG 2.8 and below should use DDS
-        return "dds";
-#endif
+    return "tif";
 }
 
 void
@@ -196,7 +199,9 @@ ElevationLayer::initTileSource()
     TerrainLayer::initTileSource();
 
     if ( _tileSource.valid() )
-        _preCacheOp = new ElevationLayerPreCacheOperation( _tileSource.get() );
+    {
+        _preCacheOp = new ElevationLayerPreCacheOperation( _tileSource.get() );        
+    }
 }
 
 
@@ -211,7 +216,7 @@ ElevationLayer::createHeightFieldFromTileSource(const TileKey&    key,
         return 0L;
 
     // If the key is blacklisted, fail.
-    if ( source->getBlacklist()->contains( key.getTileId() ) )
+    if ( source->getBlacklist()->contains( key ))
     {
         OE_DEBUG << LC << "Tile " << key.str() << " is blacklisted " << std::endl;
         return 0L;
@@ -248,7 +253,7 @@ ElevationLayer::createHeightFieldFromTileSource(const TileKey&    key,
         // Blacklist the tile if it is the same projection as the source and we can't get it and it wasn't cancelled
         if ( !result && (!progress || !progress->isCanceled()))
         {
-            source->getBlacklist()->add( key.getTileId() );
+            source->getBlacklist()->add( key );
         }
     }
 
@@ -276,10 +281,6 @@ ElevationLayer::assembleHeightFieldFromTileSource(const TileKey&    key,
     std::vector< TileKey > intersectingTiles;
     getProfile()->getIntersectingTiles( key, intersectingTiles );
 
-
-    //Maintain a list of heightfield tiles that have been added to the list already.
-    std::set< osgTerrain::TileID > existingTiles; 
-
     // collect heightfield for each intersecting key. Note, we're hitting the
     // underlying tile source here, so there's no vetical datum shifts happening yet.
     // we will do that later.
@@ -289,40 +290,13 @@ ElevationLayer::assembleHeightFieldFromTileSource(const TileKey&    key,
         {
             const TileKey& layerKey = intersectingTiles[i];
 
-            if ( isKeyValid(layerKey) )
+            if ( isKeyInRange(layerKey) )
             {
                 osg::HeightField* hf = createHeightFieldFromTileSource( layerKey, progress );
                 if ( hf )
                 {
                     heightFields.push_back( GeoHeightField(hf, layerKey.getExtent()) );
                 }
-                else
-                { 
-                    // We couldn't get a heightfield at the given key so fall back on parent tiles
-                    TileKey parentKey = layerKey.createParentKey();
-                    while (!hf && parentKey.valid())
-                    {
-                        // Make sure we haven't already added this heightfield to the list.
-                        // This could happen if you have multiple high resolution tiles that dont' have data.
-                        // So if you have four level 5 tiles with no data, they will fall back on the same level 4 tile.
-                        // This existingTiles check makes sure we don't process and add the same tile multiple times
-                        if (existingTiles.find(parentKey.getTileId()) == existingTiles.end()) 
-                        {
-                            hf = createHeightFieldFromTileSource( parentKey, progress );
-                            if (hf)
-                            {
-                                heightFields.push_back( GeoHeightField(hf, parentKey.getExtent()) );                                
-                                existingTiles.insert(parentKey.getTileId());
-                                break;
-                            }                        
-                            parentKey = parentKey.createParentKey();
-                        }                        
-                        else
-                        {                            
-                            break;
-                        }                        
-                    }                    
-                }
             }
         }
     }
@@ -384,124 +358,175 @@ ElevationLayer::assembleHeightFieldFromTileSource(const TileKey&    key,
 
 
 GeoHeightField
-ElevationLayer::createHeightField(const TileKey&    key, 
+ElevationLayer::createHeightField(const TileKey&    key,
                                   ProgressCallback* progress )
 {
-    osg::ref_ptr<osg::HeightField> result;
+    GeoHeightField result;
+    osg::ref_ptr<osg::HeightField> hf;
 
     // If the layer is disabled, bail out.
-    if ( _runtimeOptions.enabled().isSetTo( false ) )
-    {
-        return GeoHeightField::INVALID;
-    }
-
-    // Check the max data level, which limits the LOD of available data.
-    if ( _runtimeOptions.maxDataLevel().isSet() && key.getLOD() > _runtimeOptions.maxDataLevel().value() )
-    {
-        return GeoHeightField::INVALID;
-    }
-
-    CacheBin* cacheBin = getCacheBin( key.getProfile() );
-
-    // validate that we have either a valid tile source, or we're cache-only.
-    if ( ! (getTileSource() || (isCacheOnly() && cacheBin) ) )
-    {
-        OE_WARN << LC << "Error: layer does not have a valid TileSource, cannot create heightfield" << std::endl;
-        _runtimeOptions.enabled() = false;
-        return GeoHeightField::INVALID;
-    }
-
-    // validate the existance of a valid layer profile.
-    if ( !isCacheOnly() && !getProfile() )
+    if ( getEnabled() == false )
     {
-        OE_WARN << LC << "Could not establish a valid profile" << std::endl;
-        _runtimeOptions.enabled() = false;
         return GeoHeightField::INVALID;
     }
 
-    // First, attempt to read from the cache. Since the cached data is stored in the
-    // map profile, we can try this first.
-    bool fromCache = false;
-    if ( cacheBin && getCachePolicy().isCacheReadable() )
+    // Check the memory cache first
+    if ( _memCache.valid() )
     {
-        ReadResult r = cacheBin->readObject( key.str(), getCachePolicy().getMinAcceptTime() );
-        if ( r.succeeded() )
+        CacheBin* bin = _memCache->getOrCreateBin( key.getProfile()->getFullSignature() );        
+        ReadResult cacheResult = bin->readObject(key.str() );
+        if ( cacheResult.succeeded() )
         {
-            osg::HeightField* cachedHF = r.get<osg::HeightField>();
-            if ( cachedHF && validateHeightField(cachedHF) )
-            {
-                result = cachedHF;
-                fromCache = true;
-            }
+            result = GeoHeightField(
+                static_cast<osg::HeightField*>(cacheResult.releaseObject()),
+                key.getExtent());
         }
-    }
-
-    // if we're cache-only, but didn't get data from the cache, fail silently.
-    if ( !result.valid() && isCacheOnly() )
-    {
-        return GeoHeightField::INVALID;
+        //_memCache->dumpStats(key.getProfile()->getFullSignature());
     }
 
     if ( !result.valid() )
     {
-        // bad tilesource? fail
-        if ( !getTileSource() || !getTileSource()->isOK() )
+        // See if there's a persistent cache.
+        CacheBin* cacheBin = getCacheBin( key.getProfile() );
+
+        // validate that we have either a valid tile source, or we're cache-only.
+        if ( ! (getTileSource() || (isCacheOnly() && cacheBin) ) )
+        {
+            OE_WARN << LC << "Error: layer does not have a valid TileSource, cannot create heightfield" << std::endl;
+            _runtimeOptions.enabled() = false;
             return GeoHeightField::INVALID;
+        }
 
-        if ( !isKeyValid(key) )
+        // validate the existance of a valid layer profile.
+        if ( !isCacheOnly() && !getProfile() )
+        {
+            OE_WARN << LC << "Could not establish a valid profile" << std::endl;
+            _runtimeOptions.enabled() = false;
             return GeoHeightField::INVALID;
+        }
+
+        // Now attempt to read from the cache. Since the cached data is stored in the
+        // map profile, we can try this first.
+        bool fromCache = false;
 
-        // build a HF from the TileSource.
-        result = createHeightFieldFromTileSource( key, progress );
+        osg::ref_ptr< osg::HeightField > cachedHF;
 
-        // validate it to make sure it's legal.
-        if ( result.valid() && !validateHeightField(result.get()) )
+        if ( cacheBin && getCachePolicy().isCacheReadable() )
         {
-            OE_WARN << LC << "Driver " << getTileSource()->getName() << " returned an illegal heightfield" << std::endl;
-            result = 0L;
+            ReadResult r = cacheBin->readObject( key.str() );
+            if ( r.succeeded() )
+            {            
+                bool expired = getCachePolicy().isExpired(r.lastModifiedTime());
+                cachedHF = r.get<osg::HeightField>();
+                if ( cachedHF && validateHeightField(cachedHF) )
+                {
+                    if (!expired)
+                    {
+                        hf = cachedHF;
+                        fromCache = true;
+                    }
+                }
+            }
         }
-    }
 
-    // cache if necessary
-    if ( result        && 
-         cacheBin      && 
-         !fromCache    &&
-         getCachePolicy().isCacheWriteable() )
-    {
-        cacheBin->write( key.str(), result );
-    }
+        // if we're cache-only, but didn't get data from the cache, fail silently.
+        if ( !hf.valid() && isCacheOnly() )
+        {
+            return GeoHeightField::INVALID;
+        }
 
-    if ( result )
-    {
-        // Set up the heightfield so we don't have to worry about it later
-        double minx, miny, maxx, maxy;
-        key.getExtent().getBounds(minx, miny, maxx, maxy);
-        result->setOrigin( osg::Vec3d( minx, miny, 0.0 ) );
-        double dx = (maxx - minx)/(double)(result->getNumColumns()-1);
-        double dy = (maxy - miny)/(double)(result->getNumRows()-1);
-        result->setXInterval( dx );
-        result->setYInterval( dy );
-        result->setBorderWidth( 0 );
-    }
+        if ( !hf.valid() )
+        {
+            // bad tilesource? fail
+            if ( !getTileSource() || !getTileSource()->isOK() )
+                return GeoHeightField::INVALID;
 
-    return result ?
-        GeoHeightField( result, key.getExtent() ) :
-        GeoHeightField::INVALID;
-}
+            if ( !isKeyInRange(key) )
+                return GeoHeightField::INVALID;
 
+            // build a HF from the TileSource.
+            hf = createHeightFieldFromTileSource( key, progress );
 
-bool
-ElevationLayer::isKeyValid(const TileKey& key) const
-{
-    if (!key.valid())
-        return false;
+            // validate it to make sure it's legal.
+            if ( hf.valid() && !validateHeightField(hf.get()) )
+            {
+                OE_WARN << LC << "Driver " << getTileSource()->getName() << " returned an illegal heightfield" << std::endl;
+                hf = 0L; // to fall back on cached data if possible.
+            }
+
+            // memory cache first:
+            if ( hf && _memCache.valid() )
+            {
+                CacheBin* bin = _memCache->getOrCreateBin( key.getProfile()->getFullSignature() ); 
+                bin->write(key.str(), hf.get());
+            }
+
+            // cache if necessary
+            if ( hf            && 
+                 cacheBin      && 
+                 !fromCache    &&
+                 getCachePolicy().isCacheWriteable() )
+            {
+                cacheBin->write( key.str(), hf );
+            }
+
+            // We have an expired heightfield from the cache and no new data from the TileSource.  So just return the cached data.
+            if (!hf.valid() && cachedHF.valid())
+            {
+                OE_DEBUG << LC << "Using cached but expired heightfield for " << key.str() << std::endl;
+                hf = cachedHF;
+            }
+
+            if ( !hf.valid() )
+            {
+                return GeoHeightField::INVALID;
+            }
 
-    if ( _runtimeOptions.minLevel().isSet() && key.getLOD() < _runtimeOptions.minLevel().value() ) 
+            // Set up the heightfield so we don't have to worry about it later
+            double minx, miny, maxx, maxy;
+            key.getExtent().getBounds(minx, miny, maxx, maxy);
+            hf->setOrigin( osg::Vec3d( minx, miny, 0.0 ) );
+            double dx = (maxx - minx)/(double)(hf->getNumColumns()-1);
+            double dy = (maxy - miny)/(double)(hf->getNumRows()-1);
+            hf->setXInterval( dx );
+            hf->setYInterval( dy );
+            hf->setBorderWidth( 0 );
+        }
+
+        if ( hf.valid() )
+        {
+            result = GeoHeightField( hf.get(), key.getExtent() );
+        }
+    }
+
+    // post-processing:
+    if ( result.valid() )
     {
-        return false;
+        if ( _runtimeOptions.noDataPolicy() == NODATA_MSL )
+        {
+            // requested VDatum:
+            const VerticalDatum* outputVDatum = key.getExtent().getSRS()->getVerticalDatum();
+            const Geoid* geoid = 0L;
+
+            // if there's an output vdatum, just set all invalid's to zero MSL.
+            if ( outputVDatum == 0L )
+            {
+                // if the output is geodetic (HAE), but the input has a geoid, 
+                // use that geoid to populate the invalid data at sea level.
+                const VerticalDatum* profileDatum  = getProfile()->getSRS()->getVerticalDatum();
+                if ( profileDatum )
+                    geoid = profileDatum->getGeoid();
+            }
+
+            HeightFieldUtils::resolveInvalidHeights(
+                result.getHeightField(),
+                result.getExtent(),
+                NO_DATA_VALUE,
+                geoid );
+        }
     }
 
-    return TerrainLayer::isKeyValid(key);
+    return result;
 }
 
 
@@ -532,31 +557,15 @@ ElevationLayerVector::setExpressTileSize(unsigned tileSize)
 
 
 bool
-ElevationLayerVector::createHeightField(const TileKey&                  key,
-                                        bool                            fallback,
-                                        const Profile*                  haeProfile,
-                                        ElevationInterpolation          interpolation,
-                                        ElevationSamplePolicy           samplePolicy,
-                                        osg::ref_ptr<osg::HeightField>& out_result,
-                                        bool*                           out_isFallback,
-                                        ProgressCallback*               progress )  const
+ElevationLayerVector::populateHeightField(osg::HeightField*      hf,
+                                          const TileKey&         key,
+                                          const Profile*         haeProfile,
+                                          ElevationInterpolation interpolation,
+                                          ProgressCallback*      progress ) const
 {
-    unsigned lowestLOD = key.getLevelOfDetail();
-    bool hfInitialized = false;
-
-    //Get a HeightField for each of the enabled layers
-    GeoHeightFieldVector heightFields;
-
-    GeoHeightFieldVector offsetHeightFields;
-
-    //The number of fallback heightfields we have
-    int numFallbacks = 0;
-
-    //Default to being fallback data.
-    if ( out_isFallback )
-    {
-        *out_isFallback = true;
-    }
+    // heightfield must already exist.
+    if ( !hf )
+        return false;
 
     // if the caller provided an "HAE map profile", he wants an HAE elevation grid even if
     // the map profile has a vertical datum. This is the usual case when building the 3D
@@ -565,282 +574,133 @@ ElevationLayerVector::createHeightField(const TileKey&                  key,
     TileKey keyToUse = key;
     if ( haeProfile )
     {
-        keyToUse = TileKey(key.getLevelOfDetail(), key.getTileX(), key.getTileY(), haeProfile );
+        keyToUse = TileKey(key.getLOD(), key.getTileX(), key.getTileY(), haeProfile );
     }
-
-    // Generate a heightfield for each elevation layer.
-
-    for( ElevationLayerVector::const_iterator i = this->begin(); i != this->end(); i++ )
+    
+    // Collect the valid layers for this tile.
+    ElevationLayerVector contenders;
+    ElevationLayerVector offsets;
+    for(ElevationLayerVector::const_reverse_iterator i = this->rbegin(); i != this->rend(); ++i)
     {
         ElevationLayer* layer = i->get();
 
         if ( layer->getEnabled() && layer->getVisible() )
         {
-            GeoHeightField geoHF;
-            if ( layer->isKeyValid(keyToUse) )
-            {
-                geoHF = layer->createHeightField( keyToUse, progress );
-            }
-
-            // if "fallback" is set, try to fall back on lower LODs.
-            if ( !geoHF.valid() && fallback )
+            // calculate the resolution-mapped key (adjusted for tile resolution differential).            
+            TileKey mappedKey = 
+                keyToUse.mapResolution(hf->getNumColumns(), layer->getTileSize());
+
+            // Note: isKeyInRange tests the key, but haData tests the mapped key.
+            // I think that's right!
+            if ((layer->getTileSource() == 0L) || 
+                (layer->isKeyInRange(key) && layer->getTileSource()->hasData(mappedKey)))
             {
-                TileKey hf_key = keyToUse.createParentKey();
-
-                while ( hf_key.valid() && !geoHF.valid() )
-                {
-                    geoHF = layer->createHeightField( hf_key, progress );
-                    if ( !geoHF.valid() )
-                        hf_key = hf_key.createParentKey();
-                }
-
-                if ( geoHF.valid() )
-                {
-                    if ( hf_key.getLevelOfDetail() < lowestLOD )
-                    {
-                        lowestLOD = hf_key.getLevelOfDetail();
-                    }
-
-                    //This HeightField is fallback data, so increment the count.
-                    numFallbacks++;
-                }
-            }
-
-            if ( geoHF.valid() )
-            {
-                //If the layer is offset, add it to the list of offset heightfields
-                if (*layer->getElevationLayerOptions().offset())
-                {                    
-                    offsetHeightFields.push_back( geoHF );
-                }
-                //Otherwise add it to the list of regular heightfields
+                if (layer->isOffset())
+                    offsets.push_back(layer);
                 else
-                {
-                    heightFields.push_back( geoHF );
-                }
+                    contenders.push_back(layer);
             }
         }
     }
 
-    //If any of the layers produced valid data then it's not considered a fallback
-    if ( out_isFallback )
-    {
-        *out_isFallback = (numFallbacks == heightFields.size());
-        //OE_NOTICE << "Num fallbacks=" << numFallbacks << " numHeightFields=" << heightFields.size() << " is fallback " << *out_isFallback << std::endl;
-    }   
-
-    if ( heightFields.size() == 0 )
-    {
-        //If we got no heightfields but were requested to fallback, create an empty heightfield.
-        if ( fallback )
-        {
-            unsigned defaultSize = _expressTileSize.getOrUse( 7 );
-
-            out_result = HeightFieldUtils::createReferenceHeightField( 
-                keyToUse.getExtent(), 
-                defaultSize, 
-                defaultSize );
-
-            if ( offsetHeightFields.size() == 0 )
-            return true;
-        }
-        else
-        {
-            //We weren't requested to fallback so just return.
-            return false;
-        }
-    }
-
-    else if (heightFields.size() == 1)
+    // nothing? bail out.
+    if ( contenders.empty() && offsets.empty() )
     {
-        if ( lowestLOD == key.getLevelOfDetail() )
-        {
-            // If we only have on heightfield, just return it.
-            out_result = heightFields[0].takeHeightField();
-        }
-        else
-        {
-            GeoHeightField geoHF = heightFields[0].createSubSample( key.getExtent(), interpolation);
-            out_result = geoHF.takeHeightField();
-            hfInitialized = true;
-        }
-
-        // resample if necessary:
-        if ( _expressTileSize.isSet() )
-        {
-            out_result = HeightFieldUtils::resampleHeightField(
-                out_result.get(),
-                key.getExtent(),
-                *_expressTileSize,
-                *_expressTileSize,
-                interpolation );
-        }
+        return false;
     }
 
-    else
+    
+    // Sample the layers into our target.
+    unsigned numColumns = hf->getNumColumns();
+    unsigned numRows    = hf->getNumRows();    
+    double   xmin       = key.getExtent().xMin();
+    double   ymin       = key.getExtent().yMin();
+    double   dx         = key.getExtent().width() / (double)(numColumns-1);
+    double   dy         = key.getExtent().height() / (double)(numRows-1);
+    
+    // We will load the actual heightfields on demand. We might not need them all.
+    GeoHeightFieldVector heightFields(contenders.size());
+    GeoHeightFieldVector offsetFields(offsets.size());
+    std::vector<bool>    heightFailed (contenders.size(), false);
+    std::vector<bool>    offsetFailed(offsets.size(), false);
+
+    const SpatialReference* keySRS = keyToUse.getProfile()->getSRS();
+
+    bool realData = false;
+
+    for (unsigned c = 0; c < numColumns; ++c)
     {
-        // If we have multiple heightfields, we need to composite them together.
-        unsigned int width = 0;
-        unsigned int height = 0;
-
-        if ( _expressTileSize.isSet() )
-        {
-            // user set a tile size; use it.
-            width  = *_expressTileSize;
-            height = *_expressTileSize;
-        }
-        else
+        double x = xmin + (dx * (double)c);
+        for (unsigned r = 0; r < numRows; ++r)
         {
-            // user did not ask for a tile size; find the biggest among the layers.
-            for (GeoHeightFieldVector::const_iterator i = heightFields.begin(); i < heightFields.end(); ++i)
-            {
-                if (i->getHeightField()->getNumColumns() > width) 
-                    width = i->getHeightField()->getNumColumns();
-                if (i->getHeightField()->getNumRows() > height) 
-                    height = i->getHeightField()->getNumRows();
-            }
-        }
-
-        // make the new heightfield.
-        out_result = new osg::HeightField();
-        out_result->allocate( width, height );
-
-        // calculate the post spacings.
-        double minx, miny, maxx, maxy;
-        key.getExtent().getBounds(minx, miny, maxx, maxy);
-        double dx = (maxx - minx)/(double)(out_result->getNumColumns()-1);
-        double dy = (maxy - miny)/(double)(out_result->getNumRows()-1);
+            double y = ymin + (dy * (double)r);
 
-        const SpatialReference* keySRS = keyToUse.getProfile()->getSRS();
+            // Collect elevations from each layer as necessary.
+            bool resolved = false;
 
-        // Create the new heightfield by sampling all layer heightfields.
-        for (unsigned int c = 0; c < width; ++c)
-        {
-            double x = minx + (dx * (double)c);
-            for (unsigned r = 0; r < height; ++r)
+            for(int i=0; i<contenders.size() && !resolved; ++i)
             {
-                double y = miny + (dy * (double)r);
+                if ( heightFailed[i] )
+                    continue;
 
-                //Collect elevations from all of the layers. Iterate BACKWARDS because the last layer
-                // is the highest priority.
-                std::vector<float> elevations;
-                for( GeoHeightFieldVector::reverse_iterator itr = heightFields.rbegin(); itr != heightFields.rend(); ++itr )
+                GeoHeightField& layerHF = heightFields[i];
+                if ( !layerHF.valid() )
                 {
-                    const GeoHeightField& geoHF = *itr;
+                    TileKey mappedKey = 
+                        keyToUse.mapResolution(hf->getNumColumns(), contenders[i]->getTileSize());
 
-                    float elevation = 0.0f;
-                    if ( geoHF.getElevation(keySRS, x, y, interpolation, keySRS, elevation) )
+                    layerHF = contenders[i]->createHeightField(mappedKey, progress);
+                    if ( !layerHF.valid() )
                     {
-                        if (elevation != NO_DATA_VALUE)
-                        {
-                            elevations.push_back(elevation);
-                        }
+                        heightFailed[i] = true;
+                        continue;
                     }
                 }
 
-                float elevation = NO_DATA_VALUE;
+                // If we actually got a layer then we have real data
+                realData = true;
 
-                //The list of elevations only contains valid values
-                if (elevations.size() > 0)
+                float elevation;
+                if (layerHF.getElevation(keySRS, x, y, interpolation, keySRS, elevation) &&
+                    elevation != NO_DATA_VALUE)
                 {
-                    if (samplePolicy == SAMPLE_FIRST_VALID)
-                    {
-                        elevation = elevations[0];
-                    }
-                    else if (samplePolicy == SAMPLE_HIGHEST)
-                    {
-                        elevation = -FLT_MAX;
-                        for (unsigned int i = 0; i < elevations.size(); ++i)
-                        {
-                            if (elevation < elevations[i]) elevation = elevations[i];
-                        }
-                    }
-                    else if (samplePolicy == SAMPLE_LOWEST)
-                    {
-                        elevation = FLT_MAX;
-                        for (unsigned i = 0; i < elevations.size(); ++i)
-                        {
-                            if (elevation > elevations[i]) elevation = elevations[i];
-                        }
-                    }
-                    else if (samplePolicy == SAMPLE_AVERAGE)
-                    {
-                        elevation = 0.0;
-                        for (unsigned i = 0; i < elevations.size(); ++i)
-                        {
-                            elevation += elevations[i];
-                        }
-                        elevation /= (float)elevations.size();
-                    }
+                    resolved = true;                    
+                    hf->setHeight(c, r, elevation);
                 }
-                out_result->setHeight(c, r, elevation);
             }
-        }
-    }
-
-    // Replace any NoData areas with the reference value. This is zero for HAE datums,
-    // and some geoid height for orthometric datums.
-    if (out_result.valid())
-    {
-        const Geoid*         geoid = 0L;
-        const VerticalDatum* vdatum = key.getProfile()->getSRS()->getVerticalDatum();
-
-        if ( haeProfile && vdatum )
-        {
-            geoid = vdatum->getGeoid();
-        }
 
-        HeightFieldUtils::resolveInvalidHeights(
-            out_result.get(),
-            key.getExtent(),
-            NO_DATA_VALUE,
-            geoid );
-    }
+            for(int i=offsets.size()-1; i>=0; --i)
+            {
+                if ( offsetFailed[i] )
+                    continue;
 
-    // Initialize the HF values
-    if (out_result.valid() && !hfInitialized )
-    {   
-        //Go ahead and set up the heightfield so we don't have to worry about it later
-        double minx, miny, maxx, maxy;
-        key.getExtent().getBounds(minx, miny, maxx, maxy);
-        out_result->setOrigin( osg::Vec3d( minx, miny, 0.0 ) );
-        double dx = (maxx - minx)/(double)(out_result->getNumColumns()-1);
-        double dy = (maxy - miny)/(double)(out_result->getNumRows()-1);
-        out_result->setXInterval( dx );
-        out_result->setYInterval( dy );
-        out_result->setBorderWidth( 0 );
-    }
+                GeoHeightField& layerHF = offsetFields[i];
+                if ( !layerHF.valid() )
+                {
+                    TileKey mappedKey = 
+                        keyToUse.mapResolution(hf->getNumColumns(), offsets[i]->getTileSize());
 
-    // Add any "offset" elevation layers to the resulting heightfield
-    if (out_result.valid() && offsetHeightFields.size() )
-    {        
-        // calculate the post spacings.
-        double minx, miny, maxx, maxy;
-        key.getExtent().getBounds(minx, miny, maxx, maxy);
-        double dx = (maxx - minx)/(double)(out_result->getNumColumns()-1);
-        double dy = (maxy - miny)/(double)(out_result->getNumRows()-1);
+                    layerHF = offsets[i]->createHeightField(mappedKey, progress);
+                    if ( !layerHF.valid() )
+                    {
+                        offsetFailed[i] = true;
+                        continue;
+                    }
+                }
 
-        const SpatialReference* keySRS = keyToUse.getProfile()->getSRS();
+                // If we actually got a layer then we have real data
+                realData = true;
 
-        for( GeoHeightFieldVector::iterator itr = offsetHeightFields.begin(); itr != offsetHeightFields.end(); ++itr )
-        {
-            for (unsigned int c = 0; c < out_result->getNumColumns(); c++)
-            {
-                double x = minx + (dx * (double)c);
-                for (unsigned int r = 0; r < out_result->getNumRows(); r++)
-                {                         
-                    double y = miny + (dy * (double)r);
-                    float elevation = 0.0;                    
-                    if (itr->getElevation(keySRS, x, y, interpolation, keySRS, elevation))
-                    {                    
-                        double h = out_result->getHeight( c, r );                        
-                        h += elevation;                                     
-                        out_result->setHeight( c, r, h );
-                    }                                
+                float elevation = 0.0f;
+                if (layerHF.getElevation(keySRS, x, y, interpolation, keySRS, elevation) &&
+                    elevation != NO_DATA_VALUE)
+                {                    
+                    hf->getHeight(c, r) += elevation;
                 }
             }
         }
-    }
+    }   
 
-    return out_result.valid();
+    // Return whether or not we actually read any real data
+    return realData;
 }
diff --git a/src/osgEarth/ElevationQuery b/src/osgEarth/ElevationQuery
index 4329d74..b254faf 100644
--- a/src/osgEarth/ElevationQuery
+++ b/src/osgEarth/ElevationQuery
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,6 +21,7 @@
 
 #include <osgEarth/MapFrame>
 #include <osgEarth/Containers>
+#include <osgEarth/DPLineSegmentIntersector>
 
 namespace osgEarth
 {
@@ -35,6 +36,9 @@ namespace osgEarth
      * NOTE: EQ does NOT take into account rendering properties like vertical scale or
      * skirts. If you need a vertical scale, for example, simply scale the resulting
      * elevation value.
+     *
+     * ElevationQuery is not thread-safe. So not use the same instance of ElevationQuery
+     * from multiple threads without mutexing.
      */
     class OSGEARTH_EXPORT ElevationQuery
     {
@@ -77,7 +81,7 @@ namespace osgEarth
             const GeoPoint& point,
             double&         out_elevation,
             double          desiredResolution    =0.0,
-            double*         out_actualResolution =0L );
+            double*         out_actualResolution =0L );      
 
         /** 
          * Gets elevations for a whole array of points, storing the result in the
@@ -134,22 +138,24 @@ namespace osgEarth
         unsigned int getMaxLevel(double x, double y, const SpatialReference* srs, const Profile* profile ) const;
 
     private:
-        MapFrame  _mapf;
-        unsigned  _maxCacheSize;
-        int       _tileSize;        
-        int       _maxLevelOverride;
-
-        typedef LRUCache< TileKey, osg::ref_ptr<osg::HeightField> > TileCache;
-        TileCache _tileCache;
+        MapFrame     _mapf;
+        unsigned     _maxCacheSize;
+        int          _tileSize;        
+        int          _maxLevelOverride;        
 
+        typedef LRUCache< TileKey, GeoHeightField > TileCache;
+        TileCache _cache;
         double _queries;
         double _totalTime;
+        std::vector<ModelLayer*> _patchLayers;
+        osg::ref_ptr<DPLineSegmentIntersector> _patchLayersLSI;
 
     private:
         void postCTOR();
         void sync();
+        void gatherPatchLayers();
 
-        bool getElevationImpl(
+        bool getElevationImpl(            
             const GeoPoint& point,
             double&         out_elevation,
             double          desiredResolution,
diff --git a/src/osgEarth/ElevationQuery.cpp b/src/osgEarth/ElevationQuery.cpp
index 0fcb470..54d2106 100644
--- a/src/osgEarth/ElevationQuery.cpp
+++ b/src/osgEarth/ElevationQuery.cpp
@@ -1,21 +1,52 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
 #include <osgEarth/ElevationQuery>
 #include <osgEarth/Locators>
 #include <osgEarth/HeightFieldUtils>
+#include <osgEarth/DPLineSegmentIntersector>
 #include <osgUtil/IntersectionVisitor>
-#include <osgUtil/LineSegmentIntersector>
 
 #define LC "[ElevationQuery] "
 
 using namespace osgEarth;
 using namespace OpenThreads;
 
-ElevationQuery::ElevationQuery( const Map* map ) :
-_mapf( map, Map::TERRAIN_LAYERS )
+namespace
+{
+    int nextPowerOf2(int x) {
+        --x;
+        x |= x >> 1;
+        x |= x >> 2;
+        x |= x >> 4;
+        x |= x >> 8;
+        x |= x >> 16;
+        return x+1;
+    }
+}
+
+ElevationQuery::ElevationQuery(const Map* map) :
+_mapf( map, (Map::ModelParts)(Map::TERRAIN_LAYERS | Map::MODEL_LAYERS) )
 {
     postCTOR();
 }
 
-ElevationQuery::ElevationQuery( const MapFrame& mapFrame ) :
+ElevationQuery::ElevationQuery(const MapFrame& mapFrame) :
 _mapf( mapFrame )
 {
     postCTOR();
@@ -24,46 +55,58 @@ _mapf( mapFrame )
 void
 ElevationQuery::postCTOR()
 {
-    _tileSize         = 0;
+    // defaults:
     _maxLevelOverride = -1;
     _queries          = 0.0;
-    _totalTime        = 0.0;
+    _totalTime        = 0.0;  
+    _cache.setMaxSize( 500 );
 
-    // Limit the size of the cache we'll use to cache heightfields. This is an
-    // LRU cache.
-    _tileCache.setMaxSize( 50 );
+    // find terrain patch layers.
+    gatherPatchLayers();
 }
 
 void
 ElevationQuery::sync()
 {
-    if ( _mapf.sync() || _tileSize == 0  )
+    if ( _mapf.needsSync() )
     {
-        _tileSize = 0;        
+        _mapf.sync();
+        _cache.clear();
+        gatherPatchLayers();
+    }
+}
 
-        for( ElevationLayerVector::const_iterator i = _mapf.elevationLayers().begin(); i != _mapf.elevationLayers().end(); ++i )
-        {
-            // we need the maximum tile size
-            int layerTileSize = i->get()->getTileSize();
-            if ( layerTileSize > _tileSize )
-                _tileSize = layerTileSize;
-        }
+void
+ElevationQuery::gatherPatchLayers()
+{
+    // cache a vector of terrain patch models.
+    _patchLayers.clear();
+    for(ModelLayerVector::const_iterator i = _mapf.modelLayers().begin();
+        i != _mapf.modelLayers().end();
+        ++i)
+    {
+        if ( i->get()->isTerrainPatch() )
+            _patchLayers.push_back( i->get() );
     }
 }
 
-unsigned int
+unsigned
 ElevationQuery::getMaxLevel( double x, double y, const SpatialReference* srs, const Profile* profile ) const
 {
-    unsigned int maxLevel = 0;
+    int targetTileSizePOT = nextPowerOf2((int)_mapf.getMapOptions().elevationTileSize().get());
+
+    int maxLevel = 0;
     for( ElevationLayerVector::const_iterator i = _mapf.elevationLayers().begin(); i != _mapf.elevationLayers().end(); ++i )
     {
+        const ElevationLayer* layer = i->get();
+
         // skip disabled layers
-        if ( !i->get()->getEnabled() )
+        if ( !layer->getEnabled() || !layer->getVisible() )
             continue;
 
-        unsigned int layerMax = 0;
+        int layerMaxLevel = 0;
 
-        osgEarth::TileSource* ts = i->get()->getTileSource();
+        osgEarth::TileSource* ts = layer->getTileSource();
         if ( ts )
         {
             // TileSource is good; check for optional data extents:
@@ -79,87 +122,49 @@ ElevationQuery::getMaxLevel( double x, double y, const SpatialReference* srs, co
                 
                 for (osgEarth::DataExtentList::iterator j = ts->getDataExtents().begin(); j != ts->getDataExtents().end(); j++)
                 {
-                    if (j->maxLevel().isSet() && j->maxLevel() > layerMax && j->contains( tsCoord.x(), tsCoord.y(), tsSRS ))
+                    if (j->maxLevel().isSet() && j->maxLevel() > layerMaxLevel && j->contains( tsCoord.x(), tsCoord.y(), tsSRS ))
                     {
-                        layerMax = j->maxLevel().value();
+                        layerMaxLevel = j->maxLevel().value();
                     }
-                }
-
-                //Need to convert the layer max of this TileSource to that of the actual profile
-                layerMax = profile->getEquivalentLOD( ts->getProfile(), layerMax );            
+                }            
+            }
+            else
+            {
+                // Just use the default max level.  Without any data extents we don't know the actual max
+                layerMaxLevel = (int)(*layer->getTerrainLayerRuntimeOptions().maxLevel());
             }
 
             // cap the max to the layer's express max level (if set).
-            if ( i->get()->getTerrainLayerRuntimeOptions().maxLevel().isSet() )
+            if ( layer->getTerrainLayerRuntimeOptions().maxLevel().isSet() )
             {
-                layerMax = std::min( layerMax, *i->get()->getTerrainLayerRuntimeOptions().maxLevel() );
+                layerMaxLevel = std::min( layerMaxLevel, (int)(*layer->getTerrainLayerRuntimeOptions().maxLevel()) );
             }
+
+            // Need to convert the layer max of this TileSource to that of the actual profile
+            layerMaxLevel = profile->getEquivalentLOD( ts->getProfile(), layerMaxLevel );
         }
         else
         {
             // no TileSource? probably in cache-only mode. Use the layer max (or its default).
-            layerMax = i->get()->getTerrainLayerRuntimeOptions().maxLevel().value();
+            layerMaxLevel = (int)(layer->getTerrainLayerRuntimeOptions().maxLevel().value());
         }
 
-        if (layerMax > maxLevel) maxLevel = layerMax;
-    }    
-
-    // need to check the image layers too, because if image layers go deeper than elevation layers,
-    // upsampling occurs that can change the formation of the terrain skin.
-    // NOTE: this doesn't happen in "triangulation" interpolation mode.
-    if ( _mapf.getMapInfo().getElevationInterpolation() != osgEarth::INTERP_TRIANGULATE )
-    {
-        for( ImageLayerVector::const_iterator i = _mapf.imageLayers().begin(); i != _mapf.imageLayers().end(); ++i )
+        // Adjust for the tile size resolution differential, if supported by the layer.
+        int layerTileSize = layer->getTileSize();
+        if (layerTileSize > targetTileSizePOT)
         {
-            // skip disabled layers
-            if ( !i->get()->getEnabled() )
-                continue;
-
-            unsigned int layerMax = 0;
-            osgEarth::TileSource* ts = i->get()->getTileSource();
-            if ( ts )
-            {
-                // TileSource is good; check for optional data extents:
-                if ( ts->getDataExtents().size() > 0 )
-                {
-                    osg::Vec3d tsCoord(x, y, 0);
-                    const SpatialReference* tsSRS = ts->getProfile() ? ts->getProfile()->getSRS() : 0L;
-                    if ( srs && tsSRS )
-                        srs->transform(tsCoord, tsSRS, tsCoord);
-                    else
-                        tsSRS = srs;
-                    
-                    for (osgEarth::DataExtentList::iterator j = ts->getDataExtents().begin(); j != ts->getDataExtents().end(); j++)
-                    {
-                        if (j->maxLevel().isSet()  && j->maxLevel() > layerMax && j->contains( tsCoord.x(), tsCoord.y(), tsSRS ))
-                        {
-                            layerMax = j->maxLevel().value();
-                        }
-                    }
-
-                    // Need to convert the layer max of this TileSource to that of the actual profile
-                    layerMax = profile->getEquivalentLOD( ts->getProfile(), layerMax );            
-                }        
-                
-                if ( i->get()->getTerrainLayerRuntimeOptions().maxLevel().isSet() )
-                {
-                    layerMax = std::min( layerMax, *i->get()->getTerrainLayerRuntimeOptions().maxLevel() );
-                }
+            int oldMaxLevel = layerMaxLevel;
+            int temp = std::max(targetTileSizePOT, 2);
+            while(temp < layerTileSize) {
+                temp *= 2;
+                ++layerMaxLevel;
             }
-            else
-            {
-                // no TileSource? probably in cache-only mode. Use the layer max (or its default).
-                layerMax = i->get()->getTerrainLayerRuntimeOptions().maxLevel().value();
-            }
-
-            if (layerMax > maxLevel)
-                maxLevel = layerMax;
         }
-    }
 
-    if (maxLevel == 0) 
-    {
-        //This means we had no data extents on any of our layers and no max levels are set
+        if (layerMaxLevel > maxLevel)
+        {
+            maxLevel = layerMaxLevel;
+        }
     }
 
     return maxLevel;
@@ -168,13 +173,13 @@ ElevationQuery::getMaxLevel( double x, double y, const SpatialReference* srs, co
 void
 ElevationQuery::setMaxTilesToCache( int value )
 {
-    _tileCache.setMaxSize( value );
+    _cache.setMaxSize( value );   
 }
 
 int
 ElevationQuery::getMaxTilesToCache() const
 {
-    return _tileCache.getMaxSize();
+    return _cache.getMaxSize();    
 }
         
 void
@@ -196,9 +201,18 @@ ElevationQuery::getElevation(const GeoPoint&         point,
                              double*                 out_actualResolution)
 {
     sync();
-    return getElevationImpl( point, out_elevation, desiredResolution, out_actualResolution );
+    if ( point.altitudeMode() == ALTMODE_ABSOLUTE )
+    {
+        return getElevationImpl( point, out_elevation, desiredResolution, out_actualResolution );
+    }
+    else
+    {
+        GeoPoint point_abs( point.getSRS(), point.x(), point.y(), 0.0, ALTMODE_ABSOLUTE );
+        return getElevationImpl( point_abs, out_elevation, desiredResolution, out_actualResolution );
+    }
 }
 
+
 bool
 ElevationQuery::getElevations(std::vector<osg::Vec3d>& points,
                               const SpatialReference*  pointsSRS,
@@ -244,34 +258,108 @@ ElevationQuery::getElevations(const std::vector<osg::Vec3d>& points,
 }
 
 bool
-ElevationQuery::getElevationImpl(const GeoPoint& point,
+ElevationQuery::getElevationImpl(const GeoPoint& point, /* abs */
                                  double&         out_elevation,
                                  double          desiredResolution,
                                  double*         out_actualResolution)
 {
-    osg::Timer_t start = osg::Timer::instance()->tick();
-    
+    // assertion.
+    if ( !point.isAbsolute() )
+    {
+        OE_WARN << LC << "Assertion failure; input must be absolute" << std::endl;
+        return false;
+    }
+
+    osg::Timer_t begin = osg::Timer::instance()->tick();
+
+    // first try the terrain patches.
+    if ( _patchLayers.size() > 0 )
+    {
+        osgUtil::IntersectionVisitor iv;
+
+        for(std::vector<ModelLayer*>::iterator i = _patchLayers.begin(); i != _patchLayers.end(); ++i)
+        {
+            // find the scene graph for this layer:
+            osg::Node* node = (*i)->getSceneGraph( _mapf.getUID() );
+            if ( node )
+            {
+                // configure for intersection:
+                osg::Vec3d surface;
+                point.toWorld( surface );
+
+                // trivial bounds check:
+                if ( node->getBound().contains(surface) )
+                {
+                    osg::Vec3d nvector;
+                    point.createWorldUpVector(nvector);
+
+                    osg::Vec3d start( surface + nvector*5e5 );
+                    osg::Vec3d end  ( surface - nvector*5e5 );
+                
+                    // first time through, set up the intersector on demand
+                    if ( !_patchLayersLSI.valid() )
+                    {
+                        _patchLayersLSI = new DPLineSegmentIntersector(start, end);
+                        _patchLayersLSI->setIntersectionLimit( _patchLayersLSI->LIMIT_NEAREST );
+                    }
+                    else
+                    {
+                        _patchLayersLSI->reset();
+                        _patchLayersLSI->setStart( start );
+                        _patchLayersLSI->setEnd  ( end );
+                    }
+
+                    // try it.
+                    iv.setIntersector( _patchLayersLSI.get() );
+                    node->accept( iv );
+
+                    // check for a result!!
+                    if ( _patchLayersLSI->containsIntersections() )
+                    {
+                        osg::Vec3d isect = _patchLayersLSI->getIntersections().begin()->getWorldIntersectPoint();
+
+                        // transform back to input SRS:
+                        GeoPoint output;
+                        output.fromWorld( point.getSRS(), isect );
+                        out_elevation = output.z();
+                        if ( out_actualResolution )
+                            *out_actualResolution = 0.0;
+
+                        return true;
+                    }
+                }
+                else
+                {
+                    //OE_INFO << LC << "Trivial rejection (bounds check)" << std::endl;
+                }
+            }
+        }
+    }
+
     if ( _mapf.elevationLayers().empty() )
     {
         // this means there are no heightfields.
         out_elevation = 0.0;
-        return true;
+        return true;        
     }
-    
+
+    // tile size (resolution of elevation tiles)
+    unsigned tileSize = std::max(_mapf.getMapOptions().elevationTileSize().get(), 2u);
+
     //This is the max resolution that we actually have data at this point
     unsigned int bestAvailLevel = getMaxLevel( point.x(), point.y(), point.getSRS(), _mapf.getProfile());
 
     if (desiredResolution > 0.0)
     {
-        unsigned int desiredLevel = _mapf.getProfile()->getLevelOfDetailForHorizResolution( desiredResolution, _tileSize );
+        unsigned int desiredLevel = _mapf.getProfile()->getLevelOfDetailForHorizResolution( desiredResolution, tileSize );
         if (desiredLevel < bestAvailLevel) bestAvailLevel = desiredLevel;
     }
 
-    OE_DEBUG << "Best available data level " << point.x() << ", " << point.y() << " = "  << bestAvailLevel << std::endl;
+    OE_DEBUG << LC << "Best available data level " << point.x() << ", " << point.y() << " = "  << bestAvailLevel << std::endl;
 
     // transform the input coords to map coords:
     GeoPoint mapPoint = point;
-    if ( point.isValid() && !point.getSRS()->isEquivalentTo( _mapf.getProfile()->getSRS() ) )
+    if ( point.isValid() && !point.getSRS()->isHorizEquivalentTo( _mapf.getProfile()->getSRS() ) )
     {
         mapPoint = point.transform(_mapf.getProfile()->getSRS());
         if ( !mapPoint.isValid() )
@@ -279,9 +367,7 @@ ElevationQuery::getElevationImpl(const GeoPoint& point,
             OE_WARN << LC << "Fail: coord transform failed" << std::endl;
             return false;
         }
-    }
-
-    osg::ref_ptr<osg::HeightField> tile;
+    }    
 
     // get the tilekey corresponding to the tile we need:
     TileKey key = _mapf.getProfile()->createTileKey( mapPoint.x(), mapPoint.y(), bestAvailLevel );
@@ -290,57 +376,69 @@ ElevationQuery::getElevationImpl(const GeoPoint& point,
         OE_WARN << LC << "Fail: coords fall outside map" << std::endl;
         return false;
     }
+        
+    bool result = false;      
+    while (!result)
+    {      
+        GeoHeightField geoHF;
+        TileCache::Record record;
+        // Try to get the hf from the cache
+        if ( _cache.get( key, record ) )
+        {                        
+            geoHF = record.value();
+        }
+        else
+        {
+            // Create it            
+            osg::ref_ptr<osg::HeightField> hf = new osg::HeightField();
+            hf->allocate( tileSize, tileSize );
 
-    // Check the tile cache. Note that the TileSource already likely has a MemCache
-    // attached to it. We employ a secondary cache here because: since the call to
-    // getHeightField can fallback on a lower resolution, this cache will hold the
-    // final resolution heightfield instead of trying to fetch the higher resolution
-    // one each item.
-
-    TileCache::Record record;
-    if ( _tileCache.get(key, record) )
-    {
-        tile = record.value().get();
-    }
+            // Initialize the heightfield to nodata
+            for (unsigned int i = 0; i < hf->getFloatArray()->size(); i++)
+            {
+                hf->getFloatArray()->at( i ) = NO_DATA_VALUE;
+            }   
 
-    // if we didn't find it, build it.
-    if ( !tile.valid() )
-    {
-        // generate the heightfield corresponding to the tile key, automatically falling back
-        // on lower resolution if necessary:
-        _mapf.getHeightField( key, true, tile, 0L );
+            if (_mapf.populateHeightField(hf, key, false))
+            {                
+                geoHF = GeoHeightField( hf.get(), key.getExtent() );
+                _cache.insert( key, geoHF );
+            }
+        }
 
-        // bail out if we could not make a heightfield a all.
-        if ( !tile.valid() )
-        {
-            OE_WARN << LC << "Unable to create heightfield for key " << key.str() << std::endl;
-            return false;
+        if (geoHF.valid())
+        {            
+            float elevation = 0.0f;                 
+            result = geoHF.getElevation( mapPoint.getSRS(), mapPoint.x(), mapPoint.y(), _mapf.getMapInfo().getElevationInterpolation(), mapPoint.getSRS(), elevation);                              
+            if (result && elevation != NO_DATA_VALUE)
+            {                        
+                // see what the actual resolution of the heightfield is.
+                if ( out_actualResolution )
+                    *out_actualResolution = geoHF.getXInterval(); 
+                out_elevation = (double)elevation;                
+                break;
+            }
+            else
+            {                               
+                result = false;
+            }
         }
 
-        _tileCache.insert(key, tile.get());
+        if (!result)
+        {
+            key = key.createParentKey();                        
+            if (!key.valid())
+            {
+                break;
+            }
+        }         
     }
 
-    OE_DEBUG << LC << "LRU Cache, hit ratio = " << _tileCache.getStats()._hitRatio << std::endl;
-
-    // see what the actual resolution of the heightfield is.
-    if ( out_actualResolution )
-        *out_actualResolution = (double)tile->getXInterval();
-
-    bool result = true;
-
-    const GeoExtent& extent = key.getExtent();
-    double xInterval = extent.width()  / (double)(tile->getNumColumns()-1);
-    double yInterval = extent.height() / (double)(tile->getNumRows()-1);
-    
-    out_elevation = (double) HeightFieldUtils::getHeightAtLocation( 
-        tile.get(), 
-        mapPoint.x(), mapPoint.y(), 
-        extent.xMin(), extent.yMin(), 
-        xInterval, yInterval, _mapf.getMapInfo().getElevationInterpolation() );
+         
 
     osg::Timer_t end = osg::Timer::instance()->tick();
     _queries++;
-    _totalTime += osg::Timer::instance()->delta_s( start, end );
+    _totalTime += osg::Timer::instance()->delta_s( begin, end );
 
     return result;
 }
diff --git a/src/osgEarth/Export b/src/osgEarth/Export
index fb40055..b786208 100644
--- a/src/osgEarth/Export
+++ b/src/osgEarth/Export
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/FadeEffect b/src/osgEarth/FadeEffect
index 1ddc137..a639c39 100644
--- a/src/osgEarth/FadeEffect
+++ b/src/osgEarth/FadeEffect
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/FadeEffect.cpp b/src/osgEarth/FadeEffect.cpp
index b5902af..ca85a1c 100644
--- a/src/osgEarth/FadeEffect.cpp
+++ b/src/osgEarth/FadeEffect.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/FileUtils b/src/osgEarth/FileUtils
index 9f872be..a88fdbd 100644
--- a/src/osgEarth/FileUtils
+++ b/src/osgEarth/FileUtils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -53,6 +53,13 @@ namespace osgEarth
     extern OSGEARTH_EXPORT bool isArchive(const std::string& path);
 
     /**
+     * whether the given path refers to a file contained inside an
+     * archive.
+     */
+    extern OSGEARTH_EXPORT bool isPathToArchivedFile(const std::string& path);
+
+    /**
+     * @deprecated Please use isPathToArchivedFile instead
      * Gets whether or not the given path contains a zip file within the path
      */
     extern OSGEARTH_EXPORT bool isZipPath(const std::string& path);
@@ -81,6 +88,16 @@ namespace osgEarth
      */
      extern OSGEARTH_EXPORT std::string getTempName(const std::string& prefix="", const std::string& suffix="");
 
+     /** Make a new directory.  Returns true if directory exists or was created.
+      * Note:  This is adapted from osg's makeDirectory function to ensure that it works with concurrent create attempts.
+     */
+     extern OSGEARTH_EXPORT bool makeDirectory( const std::string &directoryPath );
+
+     /** Make a new directory for a given file.
+      * Note:  This is adapted from osg's makeDirectoryForFile to call our custom makeDirectory function.
+     */
+     extern OSGEARTH_EXPORT bool makeDirectoryForFile( const std::string &filePath );
+
      /**
       * Utility class that processes files and directories recursively.
       */
diff --git a/src/osgEarth/FileUtils.cpp b/src/osgEarth/FileUtils.cpp
index 85967ea..6ac281a 100644
--- a/src/osgEarth/FileUtils.cpp
+++ b/src/osgEarth/FileUtils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -24,6 +24,8 @@
 #include <osgDB/FileNameUtils>
 #include <osgDB/Registry>
 #include <osg/Notify>
+#include <stack>
+#include <errno.h>
 
 #ifdef WIN32
 #  include <windows.h>
@@ -46,6 +48,83 @@
 #include <list>
 #include <sstream>
 
+// currently this impl is for _all_ platforms, except as defined.
+// the mac version will change soon to reflect the path scheme under osx, but
+// for now, the above include is commented out, and the below code takes precedence.
+
+#if defined(WIN32) && !defined(__CYGWIN__)
+    #include <io.h>
+    #define WINBASE_DECLARE_GET_MODULE_HANDLE_EX
+    #include <windows.h>
+    #include <winbase.h>
+    #include <sys/types.h>
+    #include <sys/stat.h>
+    #include <direct.h> // for _mkdir
+
+    #define mkdir(x,y) _mkdir((x))
+    #define stat64 _stati64
+
+    // set up for windows so acts just like unix access().
+#ifndef F_OK
+    #define F_OK 4
+#endif
+
+#else // unix
+
+#if defined( __APPLE__ )
+    // I'm not sure how we would handle this in raw Darwin
+    // without the AvailablilityMacros.
+    #include <AvailabilityMacros.h>
+
+    //>OSG_IOS
+    //IOS includes
+    #include "TargetConditionals.h"
+
+    #if (TARGET_OS_IPHONE)
+        #include <Availability.h>
+        // workaround a bug which appears when compiling for SDK < 4.0 and for the simulator
+        #if defined(__IPHONE_4_0) && (__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_4_0)
+            #define stat64 stat
+        #else
+            #if !TARGET_IPHONE_SIMULATOR
+                #define stat64 stat
+            #endif
+        #endif
+    #endif
+    //<OSG_IPHONE
+
+    // 10.5 defines stat64 so we can't use this #define
+    // By default, MAC_OS_X_VERSION_MAX_ALLOWED is set to the latest
+    // system the headers know about. So I will use this as the control
+    // variable. (MIN_ALLOWED is set low by default so it is
+    // unhelpful in this case.)
+    // Unfortunately, we can't use the label MAC_OS_X_VERSION_10_4
+    // for older OS's like Jaguar, Panther since they are not defined,
+    // so I am going to hardcode the number.
+    #if (MAC_OS_X_VERSION_MAX_ALLOWED <= 1040)
+        #define stat64 stat
+    #endif
+#elif defined(__CYGWIN__) || defined(__FreeBSD__) || (defined(__hpux) && !defined(_LARGEFILE64_SOURCE))
+    #define stat64 stat
+#endif
+
+    #include <stdlib.h>
+    #include <unistd.h>
+    #include <sys/types.h>
+    #include <sys/stat.h>
+#endif
+
+    // set up _S_ISDIR()
+#if !defined(S_ISDIR)
+#  if defined( _S_IFDIR) && !defined( __S_IFDIR)
+#    define __S_IFDIR _S_IFDIR
+#  endif
+#  define S_ISDIR(mode)    (mode&__S_IFDIR)
+#endif
+
+
+#define LC "[FileUtils] "
+
 
 using namespace osgEarth;
 
@@ -143,8 +222,24 @@ osgEarth::isArchive(const std::string& path)
     return false;
 }
 
+bool
+osgEarth::isPathToArchivedFile(const std::string& path)
+{
+    osgDB::Registry::ArchiveExtensionList list = osgDB::Registry::instance()->getArchiveExtensions();
+    for( osgDB::Registry::ArchiveExtensionList::const_iterator i = list.begin(); i != list.end(); ++i )
+    {
+        if (path.find("."+*i+"/")  != std::string::npos ||
+            path.find("."+*i+"\\") != std::string::npos)
+        {
+            return true;
+        }
+    }
+    return false;
+}
+
 bool osgEarth::isZipPath(const std::string &path)
 {
+    OE_WARN << LC << "FileUtils::isZipPath is deprecated; use isPathToArchivedFile instead" << std::endl;
     return (path.find(".zip") != std::string::npos);
 }
 
@@ -185,6 +280,97 @@ std::string osgEarth::getTempName(const std::string& prefix, const std::string&
     return "";
 }
 
+bool osgEarth::makeDirectory( const std::string &path )
+{    
+    if (path.empty())
+    {
+        OSG_DEBUG << "osgDB::makeDirectory(): cannot create an empty directory" << std::endl;
+        return false;
+    }
+
+    struct stat64 stbuf;
+#ifdef OSG_USE_UTF8_FILENAME
+    if( _wstat64( OSGDB_STRING_TO_FILENAME(path).c_str(), &stbuf ) == 0 )
+#else
+    if( stat64( path.c_str(), &stbuf ) == 0 )
+#endif
+    {
+        if( S_ISDIR(stbuf.st_mode))
+            return true;
+        else
+        {
+            OSG_DEBUG << "osgDB::makeDirectory(): "  <<
+                    path << " already exists and is not a directory!" << std::endl;
+            return false;
+        }
+    }
+
+    std::string dir = path;
+    std::stack<std::string> paths;
+    while( true )
+    {
+        if( dir.empty() )
+            break;
+
+#ifdef OSG_USE_UTF8_FILENAME
+        if( _wstat64( OSGDB_STRING_TO_FILENAME(dir).c_str(), &stbuf ) < 0 )
+#else
+        if( stat64( dir.c_str(), &stbuf ) < 0 )
+#endif
+        {
+            switch( errno )
+            {
+                case ENOENT:
+                case ENOTDIR:                    
+                    paths.push( dir );
+                    break;
+
+                default:
+                    OSG_DEBUG << "osgDB::makeDirectory(): "  << strerror(errno) << std::endl;
+                    return false;
+            }
+        }
+        dir = osgDB::getFilePath(std::string(dir));
+    }
+
+    while( !paths.empty() )
+    {
+        std::string dir = paths.top();
+
+        #if defined(WIN32)
+            //catch drive name
+            if (dir.size() == 2 && dir.c_str()[1] == ':') {
+                paths.pop();
+                continue;
+            }
+        #endif
+
+#ifdef OSG_USE_UTF8_FILENAME
+        if ( _wmkdir(OSGDB_STRING_TO_FILENAME(dir).c_str())< 0 )
+#else
+        if( mkdir( dir.c_str(), 0755 )< 0 )
+#endif
+        {            
+            if (osgDB::fileExists(dir))
+            {
+                OE_DEBUG << "Attempt to create directory that already exists " << dir << std::endl;
+            }
+            else            
+            {
+                OSG_DEBUG << "osgDB::makeDirectory(): "  << strerror(errno) << std::endl;
+                return false;
+            }
+        }
+        paths.pop();
+    }
+    return true;
+}
+
+bool osgEarth::makeDirectoryForFile( const std::string &path )
+{
+    return osgEarth::makeDirectory( osgDB::getFilePath( path ));
+}
+
 
 bool
 osgEarth::touchFile(const std::string& path)
@@ -253,4 +439,5 @@ CollectFilesVisitor::CollectFilesVisitor()
 void CollectFilesVisitor::handleFile( const std::string& filename )
 {
 	filenames.push_back( filename );        
-}        
\ No newline at end of file
+}
+
diff --git a/src/osgEarth/GeoCommon b/src/osgEarth/GeoCommon
index 31e0c8e..52642cf 100644
--- a/src/osgEarth/GeoCommon
+++ b/src/osgEarth/GeoCommon
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,6 +21,7 @@
 
 namespace osgEarth
 {
+    // Default normalized no-data value for elevation
 #define NO_DATA_VALUE -FLT_MAX
 
     /**
@@ -55,6 +56,15 @@ namespace osgEarth
     };
 
     /**
+     * Elevation NO_DATA treatment policy
+     */
+    enum ElevationNoDataPolicy
+    {
+        NODATA_INTERPOLATE,     // interpolate across NO_DATA samples
+        NODATA_MSL              // rewrite NO_DATA samples as MSL
+    };
+
+    /**
      * Indicates how to interpret a Z coordinate.
      */
     enum AltitudeMode
diff --git a/src/osgEarth/GeoData b/src/osgEarth/GeoData
index cb05a9f..5898b37 100644
--- a/src/osgEarth/GeoData
+++ b/src/osgEarth/GeoData
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -31,7 +31,7 @@
 
 namespace osgEarth
 {
-    class TerrainHeightProvider;
+    class TerrainResolver;
     class GeoExtent;
 
     /**
@@ -52,6 +52,15 @@ namespace osgEarth
             const AltitudeMode& mode );
 
         /**
+         * Constructs a GeoPoint with an absolute Z.
+         */
+        GeoPoint(
+            const SpatialReference* srs,
+            double x,
+            double y,
+            double z );
+
+        /**
          * Constructs a GeoPoint with X and Y coordinates. The Z defaults
          * to zero with an ALTMODE_RELATIVE altitude mode (i.e., 0 meters
          * above the terrain).
@@ -70,6 +79,13 @@ namespace osgEarth
             const AltitudeMode&     mode );
 
         /**
+         * Constructs a GeoPoint from a vec3 with absolute Z.
+         */
+        GeoPoint(
+            const SpatialReference* srs,
+            const osg::Vec3d&       xyz);
+
+        /**
          * Constructs a new GeoPoint by transforming an existing GeoPoint into
          * the specified spatial reference.
          */
@@ -129,8 +145,16 @@ namespace osgEarth
 
         const SpatialReference* getSRS() const { return _srs.get(); }
 
+        /**
+         * AltitudeMode reflects whether the Z coordinate is absolute with
+         * respect to MSL or relative to the terrain elevation at that
+         * point. When using relative points, GeoPoint usually requires
+         * access to a TerrainProvider in order to resolve the altitude.
+         */
         AltitudeMode& altitudeMode() { return _altMode; }
         const AltitudeMode& altitudeMode() const { return _altMode; }
+        bool isRelative() const { return _altMode == ALTMODE_RELATIVE; }
+        bool isAbsolute() const { return _altMode == ALTMODE_ABSOLUTE; }
 
         /**
          * Returns a copy of this geopoint transformed into another SRS.
@@ -145,22 +169,22 @@ namespace osgEarth
         /**
          * Transforms this geopoint's Z coordinate (in place)
          */
-        bool transformZ(const AltitudeMode& altMode, const TerrainHeightProvider* t );
+        bool transformZ(const AltitudeMode& altMode, const TerrainResolver* t );
 
         /**
          * Transforms and returns the geopoints Z coordinate.
          */
-        bool transformZ(const AltitudeMode& altMode, const TerrainHeightProvider* t, double& out_z) const;
+        bool transformZ(const AltitudeMode& altMode, const TerrainResolver* t, double& out_z) const;
 
         /**
          * Transforms this geopoint's Z to be absolute w.r.t. the vertical datum
          */
-        bool makeAbsolute(const TerrainHeightProvider* t) { return transformZ(ALTMODE_ABSOLUTE, t); }
+        bool makeAbsolute(const TerrainResolver* t) { return transformZ(ALTMODE_ABSOLUTE, t); }
 
         /**
          * Transforms this geopoint's Z to be terrain-relative.
          */
-        bool makeRelative(const TerrainHeightProvider* t) { return transformZ(ALTMODE_RELATIVE, t); }
+        bool makeRelative(const TerrainResolver* t) { return transformZ(ALTMODE_RELATIVE, t); }
 
         /**
          * Transforms this GeoPoint to geographic (lat/long) coords in place.
@@ -180,7 +204,7 @@ namespace osgEarth
          * object that will be used if the point needs to be converted to absolute
          * altitude
          */
-        bool toWorld( osg::Vec3d& out_world, const TerrainHeightProvider* terrain ) const;
+        bool toWorld( osg::Vec3d& out_world, const TerrainResolver* terrain ) const;
 
         /**
          * Converts world coordinates into a geopoint
@@ -602,6 +626,11 @@ namespace osgEarth
             bool topBorder=true);
 
         /**
+         * Sets alpha to Zero for any pixels that do NOT intersect the masking extent.
+         */
+        void applyAlphaMask(const GeoExtent& maskingExtent);
+
+        /**
          * Returns the underlying OSG image and releases the reference pointer.
          */
         osg::Image* takeImage();
diff --git a/src/osgEarth/GeoData.cpp b/src/osgEarth/GeoData.cpp
index fed0880..2fff23e 100644
--- a/src/osgEarth/GeoData.cpp
+++ b/src/osgEarth/GeoData.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -135,6 +135,17 @@ _altMode( altMode )
 }
 
 GeoPoint::GeoPoint(const SpatialReference* srs,
+                   double x,
+                   double y,
+                   double z) :
+_srs    ( srs ),
+_p      ( x, y, z ),
+_altMode( ALTMODE_ABSOLUTE )
+{
+    //nop
+}
+
+GeoPoint::GeoPoint(const SpatialReference* srs,
                    const osg::Vec3d&       xyz,
                    const AltitudeMode&     altMode) :
 _srs(srs),
@@ -145,6 +156,15 @@ _altMode( altMode )
 }
 
 GeoPoint::GeoPoint(const SpatialReference* srs,
+                   const osg::Vec3d&       xyz) :
+_srs(srs),
+_p  (xyz),
+_altMode( ALTMODE_ABSOLUTE )
+{
+    //nop
+}
+
+GeoPoint::GeoPoint(const SpatialReference* srs,
                    const GeoPoint&         rhs)
 {
      rhs.transform(srs, *this);
@@ -299,7 +319,7 @@ GeoPoint::transform(const SpatialReference* outSRS) const
 }
 
 bool
-GeoPoint::transformZ(const AltitudeMode& altMode, const TerrainHeightProvider* terrain ) 
+GeoPoint::transformZ(const AltitudeMode& altMode, const TerrainResolver* terrain ) 
 {
     double z;
     if ( transformZ(altMode, terrain, z) )
@@ -312,7 +332,7 @@ GeoPoint::transformZ(const AltitudeMode& altMode, const TerrainHeightProvider* t
 }
 
 bool
-GeoPoint::transformZ(const AltitudeMode& altMode, const TerrainHeightProvider* terrain, double& out_z ) const
+GeoPoint::transformZ(const AltitudeMode& altMode, const TerrainResolver* terrain, double& out_z ) const
 {
     if ( !isValid() )
         return false;
@@ -365,7 +385,11 @@ GeoPoint::transform(const SpatialReference* outSRS, GeoPoint& output) const
 bool
 GeoPoint::toWorld( osg::Vec3d& out_world ) const
 {
-    if ( !isValid() ) return false;
+    if ( !isValid() )
+    {
+        OE_WARN << LC << "Called toWorld() on an invalid point" << std::endl;
+        return false;
+    }
     if ( _altMode != ALTMODE_ABSOLUTE )
     {
         OE_WARN << LC << "ILLEGAL: called GeoPoint::toWorld with AltitudeMode = RELATIVE_TO_TERRAIN" << std::endl;
@@ -375,9 +399,13 @@ GeoPoint::toWorld( osg::Vec3d& out_world ) const
 }
 
 bool
-GeoPoint::toWorld( osg::Vec3d& out_world, const TerrainHeightProvider* terrain ) const
+GeoPoint::toWorld( osg::Vec3d& out_world, const TerrainResolver* terrain ) const
 {
-    if ( !isValid() ) return false;
+    if ( !isValid() )
+    {
+        OE_WARN << LC << "Called toWorld() on an invalid point" << std::endl;
+        return false;
+    }
     if ( _altMode == ALTMODE_ABSOLUTE )
     {
         return _srs->transformToWorld( _p, out_world );
@@ -420,7 +448,7 @@ GeoPoint::createLocalToWorld( osg::Matrixd& out_l2w ) const
     if ( !isValid() ) return false;
     if ( _altMode != ALTMODE_ABSOLUTE )
     {
-        OE_WARN << LC << "ILLEGAL: called GeoPoint::createLocal2World with AltitudeMode = RELATIVE_TO_TERRAIN" << std::endl;
+        OE_WARN << LC << "ILLEGAL: called GeoPoint::createLocalToorld with AltitudeMode = RELATIVE_TO_TERRAIN" << std::endl;
         return false;
     }
     return _srs->createLocalToWorld( _p, out_l2w );
@@ -432,7 +460,7 @@ GeoPoint::createWorldToLocal( osg::Matrixd& out_w2l ) const
     if ( !isValid() ) return false;
     if ( _altMode != ALTMODE_ABSOLUTE )
     {
-        OE_WARN << LC << "ILLEGAL: called GeoPoint::createLocal2World with AltitudeMode = RELATIVE_TO_TERRAIN" << std::endl;
+        OE_WARN << LC << "ILLEGAL: called GeoPoint::createWorldToLocal with AltitudeMode = RELATIVE_TO_TERRAIN" << std::endl;
         return false;
     }
     return _srs->createWorldToLocal( _p, out_w2l );
@@ -774,6 +802,14 @@ GeoExtent::transform( const SpatialReference* to_srs ) const
     return GeoExtent::INVALID;
 }
 
+
+bool
+GeoExtent::transform( const SpatialReference* srs, GeoExtent& output ) const
+{
+    output = transform(srs);
+    return output.isValid();
+}
+
 void
 GeoExtent::getBounds(double &xmin, double &ymin, double &xmax, double &ymax) const
 {
@@ -1728,6 +1764,45 @@ GeoImage::reproject(const SpatialReference* to_srs, const GeoExtent* to_extent,
     return GeoImage(resultImage, destExtent);
 }
 
+void
+GeoImage::applyAlphaMask(const GeoExtent& maskingExtent)
+{
+    if ( !valid() )
+        return;
+
+    GeoExtent maskingExtentLocal = maskingExtent.transform(_extent.getSRS());
+
+    // if the image is completely contains by the mask, no work to do.
+    if ( maskingExtentLocal.contains(getExtent()))
+        return;
+
+    // TODO: find a more performant way about this 
+    ImageUtils::PixelReader read (_image.get());
+    ImageUtils::PixelWriter write(_image.get());
+
+    double sInterval = _extent.width()/(double)_image->s();
+    double tInterval = _extent.height()/(double)_image->t();
+
+    for( int t=0; t<_image->t(); ++t )
+    {
+        double y = _extent.south() + tInterval*(double)t;
+
+        for( int s=0; s<_image->s(); ++s )
+        {
+            double x = _extent.west() + sInterval*(double)s;
+
+            for( int r=0; r<_image->r(); ++r )
+            {
+                if ( !maskingExtentLocal.contains(x, y) )
+                {
+                    osg::Vec4f pixel = read(s,t,r);
+                    write(osg::Vec4f(pixel.r(), pixel.g(), pixel.b(), 0.0f), s, t, r);
+                }
+            }
+        }
+    }
+}
+
 osg::Image*
 GeoImage::takeImage()
 {
diff --git a/src/osgEarth/GeoMath b/src/osgEarth/GeoMath
index a4f68b6..27d53ef 100644
--- a/src/osgEarth/GeoMath
+++ b/src/osgEarth/GeoMath
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -24,6 +24,7 @@
 #include <osgEarth/GeoCommon>
 #include <osgEarth/SpatialReference>
 #include <osg/CoordinateSystemNode>
+#include <osg/Plane>
 
 namespace osgEarth
 {
@@ -203,6 +204,33 @@ namespace osgEarth
                                        double bearing, double distance,
                                        double &out_latRad, double &out_lonRad,
                                        double radius = osg::WGS_84_RADIUS_EQUATOR);
+
+
+        /**
+         * Computes the intersection(s) of a line with a sphere.
+         * Returns the number of intersections: 0, 1, or 2.
+         */
+        static unsigned interesectLineWithSphere(const osg::Vec3d& p0,
+                                                 const osg::Vec3d& p1,
+                                                 double            radius,
+                                                 osg::Vec3d&       out_i0,
+                                                 osg::Vec3d&       out_i1);
+        /**
+         * Computes the intersection(s) of a line with a plane.
+         * Returns the number of intersections: 0 or 1.
+         */
+        static unsigned intersectLineWithPlane(const osg::Vec3d& p0,
+                                               const osg::Vec3d& p1,
+                                               const osg::Plane& plane,
+                                               osg::Vec3d&       out_i0);
+
+        /**
+         * Whether the target point is visible from the eye point in a 
+         * spherical earth approximation with the given Radius.
+         */
+        static bool isPointVisible(const osg::Vec3d& eye,
+                                   const osg::Vec3d& target,
+                                   double radius = osg::WGS_84_RADIUS_EQUATOR);
     };
 };
 
diff --git a/src/osgEarth/GeoMath.cpp b/src/osgEarth/GeoMath.cpp
index b06dc76..de5376b 100644
--- a/src/osgEarth/GeoMath.cpp
+++ b/src/osgEarth/GeoMath.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -254,3 +254,140 @@ GeoMath::rhumbDestination(double lat1Rad, double lon1Rad,
   out_lonRad = lon2Rad;
 }
 
+unsigned
+GeoMath::interesectLineWithSphere(const osg::Vec3d& p0,
+                                  const osg::Vec3d& p1,
+                                  double            R,
+                                  osg::Vec3d&       out_i0,
+                                  osg::Vec3d&       out_i1)
+{
+    unsigned hits = 0;
+
+    // http://stackoverflow.com/questions/6533856/ray-sphere-intersection
+
+    osg::Vec3d d = p1-p0;
+
+    double A = d * d;
+    double B = 2.0 * (d * p0);
+    double C = (p0 * p0) - R*R;
+
+    // now solve the quadratic A + B*t + C*t^2 = 0.
+    double D = B*B - 4.0*A*C;
+    if ( D >= 0 )
+    {
+        if ( osg::equivalent(D, 0.0) )
+        {
+            // one root (line is tangent to sphere)
+            double t = -B/(2.0*A);
+            //if (t >= 0.0 && t <= 1.0)
+            {
+                out_i0 = p0 + d*t;
+                ++hits;
+            }
+        }
+        else
+        {
+            // two roots (line passes through sphere twice)
+            double sqrtD = sqrt(D);
+            double t0 = (-B + sqrtD)/(2.0*A);
+            double t1 = (-B - sqrtD)/(2.0*A);
+            
+            //if ( t0 >= 0.0 && t0 <= 1.0 )
+            {
+                out_i0 = p0 + d*t0;
+                ++hits;
+            }
+
+            //if ( t1 >= 0.0 && t1 <= 1.0 )
+            {
+                if (hits == 0)
+                    out_i0 = p0 + d*t1;
+                else
+                    out_i1 = p0 + d*t1;
+                ++hits;
+            }
+        }
+    }
+
+    return hits;
+}
+
+unsigned
+GeoMath::intersectLineWithPlane(const osg::Vec3d& p0,
+                                const osg::Vec3d& p1,
+                                const osg::Plane& plane,
+                                osg::Vec3d&       out_p)
+{
+    osg::Vec3d V = p1-p0;
+    V.normalize();
+    double denom = plane.dotProductNormal(V);
+
+    // if N*V == 0, line is parallel to the plane
+    if ( osg::equivalent(denom, 0.0) )
+    {
+        // if p0 lies on the plane, line is coincident with plane
+        // and intersections are infinite
+        if ( osg::equivalent(plane.distance(p0), 0.0) )
+        {
+            out_p = p0;
+            return 2;
+        }
+        else
+        {
+            // line does not intersect plane.
+            return 0;
+        }
+    }
+    else
+    {
+        // one intersection:
+        double t = -(plane.dotProductNormal(p0) + plane[3])/denom;
+        out_p = p0 + V*t;
+        return 1;
+    }
+}
+
+bool
+GeoMath::isPointVisible(const osg::Vec3d& eye,
+                        const osg::Vec3d& target,
+                        double            R)
+{
+    double r2 = R*R;
+
+    // same quadrant:
+    if ( eye * target >= 0.0 )
+    {
+        double d2 = eye.length2();
+        double horiz2 = d2 - r2;
+        double dist2 = (target-eye).length2();
+        if ( dist2 < horiz2 )
+        {
+            return true;
+        }
+    }
+
+    // different quadrants:
+    else
+    {
+        // there's a horizon between them; now see if the thing is visible.
+        // find the triangle formed by the viewpoint, the target point, and 
+        // the center of the earth.
+        double a = (target-eye).length();
+        double b = target.length();
+        double c = eye.length();
+
+        // Heron's formula for triangle area:
+        double s = 0.5*(a+b+c);
+        double area = 0.25*sqrt( s*(s-a)*(s-b)*(s-c) );
+
+        // Get the triangle's height:
+        double h = (2*area)/a;
+
+        if ( h >= R )
+        {
+            return true;
+        }
+    }
+
+    return false;
+}
\ No newline at end of file
diff --git a/src/osgEarth/GeoTransform b/src/osgEarth/GeoTransform
new file mode 100644
index 0000000..8a1b9d2
--- /dev/null
+++ b/src/osgEarth/GeoTransform
@@ -0,0 +1,95 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_GEO_TRANSFORM
+#define OSGEARTH_GEO_TRANSFORM
+
+#include <osgEarth/Common>
+#include <osgEarth/Config>
+#include <osgEarth/GeoData>
+#include <osgEarth/Terrain>
+#include <osg/MatrixTransform>
+
+namespace osgEarth
+{
+    class TileKey;
+
+    /**
+     * Transform node that accepts geospatial coordinates.
+     */
+    class OSGEARTH_EXPORT GeoTransform : public osg::MatrixTransform
+    {
+    public:
+        META_Node(osgEarth, GeoTransform);
+
+        /** Constructor */
+        GeoTransform();
+
+        /** Copy constructor */
+        GeoTransform(const GeoTransform& rhs, const osg::CopyOp& op);
+
+        /**
+         * Sets the geospatial position. Returns false upon error.
+         * This method will only handle a GeoPoint with ALTMODE_ABSOLUTE.
+         */
+        bool setPosition(const GeoPoint& p);
+
+        /**
+         * Gets the last known geospatial position.
+         */
+        const GeoPoint& getPosition() const;
+
+        /**
+         * Sets a reference terrain for this transform. Setting this
+         * is required if you want to transform positions into the
+         * terrain's SRS or if you want support for ALTMODE_RELATIVE
+         * positions.
+         */
+        void setTerrain(Terrain* terrain);
+
+        /**
+         * Enabling this will cause the object to automatically 
+         * recompute the matrix for an ALTMODE_RELATIVE position if
+         * the terrain under that position changes. This is disabled
+         * by default. This functionality requires that you set
+         * a reference terrain (see setTerrain).
+         */
+        void setAutoRecomputeHeights(bool value);
+
+
+    public: // TerrainCallback interface
+
+        // called when new data pages in and autoRecompute is true
+        void onTileAdded(const TileKey&          key,
+                         osg::Node*              node,
+                         TerrainCallbackContext& context);
+
+    protected:
+        virtual ~GeoTransform() { }
+
+        GeoPoint                   _position;
+        osg::observer_ptr<Terrain> _terrain;
+        bool                       _autoRecompute;
+        bool                       _autoRecomputeReady;
+
+        void configureAutoRecompute(Terrain* terrain);
+    };
+
+} // namespace osgEarth
+
+#endif // OSGEARTH_GEO_TRANSFORM
diff --git a/src/osgEarth/GeoTransform.cpp b/src/osgEarth/GeoTransform.cpp
new file mode 100644
index 0000000..52910f9
--- /dev/null
+++ b/src/osgEarth/GeoTransform.cpp
@@ -0,0 +1,128 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osgEarth/GeoTransform>
+#include <osgEarth/Terrain>
+
+using namespace osgEarth;
+
+GeoTransform::GeoTransform() :
+_autoRecompute     ( false ),
+_autoRecomputeReady( false )
+{
+   //nop
+}
+
+GeoTransform::GeoTransform(const GeoTransform& rhs,
+                           const osg::CopyOp&  op) :
+osg::MatrixTransform(rhs, op)
+{
+    _position           = rhs._position;
+    _terrain            = rhs._terrain.get();
+    _autoRecompute      = rhs._autoRecompute;
+    _autoRecomputeReady = false;
+}
+
+void
+GeoTransform::setTerrain(Terrain* terrain)
+{
+    _terrain = terrain;
+}
+
+void
+GeoTransform::setAutoRecomputeHeights(bool value)
+{
+    if (value != _autoRecompute)
+    {
+        _autoRecompute = value;
+    }
+}
+
+const GeoPoint&
+GeoTransform::getPosition() const
+{
+    return _position;
+}
+
+bool
+GeoTransform::setPosition(const GeoPoint& position)
+{
+    if ( !position.isValid() )
+        return false;
+
+    // relative Z or reprojection require a terrain:
+    osg::ref_ptr<Terrain> terrain;
+    _terrain.lock(terrain);
+
+    // relative Z requires a terrain:
+    if (position.altitudeMode() == ALTMODE_RELATIVE && !terrain.valid())
+        return false;
+
+    GeoPoint p;
+
+    // transform into terrain SRS if neccesary:
+    if (terrain.valid() && !terrain->getSRS()->isEquivalentTo(position.getSRS()))
+        p = position.transform(terrain->getSRS());
+    else
+        p = position;
+
+    // bail if the transformation failed:
+    if ( !p.isValid() )
+        return false;
+
+    // convert to absolute height:
+    if ( !p.makeAbsolute(_terrain.get()) )
+        return false;
+
+    // assemble the matrix:
+    osg::Matrixd local2world;
+    p.createLocalToWorld( local2world );
+    this->setMatrix( local2world );
+
+    // save the last know position
+    _position = position;
+
+    // install auto-recompute?
+    if (_autoRecompute &&
+        _position.altitudeMode() == ALTMODE_RELATIVE &&
+        !_autoRecomputeReady)
+    {
+        // by using the adapter, there's no need to remove
+        // the callback then this object destructs.
+        terrain->addTerrainCallback(
+           new TerrainCallbackAdapter<GeoTransform>(this) );
+
+        _autoRecomputeReady = true;
+    }
+
+    return true;
+}
+
+void
+GeoTransform::onTileAdded(const TileKey&          key,
+                          osg::Node*              node,
+                          TerrainCallbackContext& context)
+{
+   if (!_position.isValid() || _position.altitudeMode() != ALTMODE_RELATIVE)
+       return;
+
+   if (!key.getExtent().contains(_position))
+       return;
+
+   setPosition(_position);
+}
diff --git a/src/osgEarth/Geoid b/src/osgEarth/Geoid
index 24dd49e..1944d29 100644
--- a/src/osgEarth/Geoid
+++ b/src/osgEarth/Geoid
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Geoid.cpp b/src/osgEarth/Geoid.cpp
index 9da21fb..3ee4f75 100644
--- a/src/osgEarth/Geoid.cpp
+++ b/src/osgEarth/Geoid.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -46,8 +46,8 @@ Geoid::setHeightField( osg::HeightField* hf )
     _bounds = Bounds(
         _hf->getOrigin().x(),
         _hf->getOrigin().y(),
-        _hf->getOrigin().x() + _hf->getXInterval() * double(_hf->getNumColumns()),
-        _hf->getOrigin().y() + _hf->getYInterval() * double(_hf->getNumRows()) );
+        _hf->getOrigin().x() + _hf->getXInterval() * double(_hf->getNumColumns()-1),
+        _hf->getOrigin().y() + _hf->getYInterval() * double(_hf->getNumRows()-1) );
     validate();
 }
 
diff --git a/src/osgEarth/HTTPClient b/src/osgEarth/HTTPClient
index a194f68..cad2027 100644
--- a/src/osgEarth/HTTPClient
+++ b/src/osgEarth/HTTPClient
@@ -1,375 +1,405 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#ifndef OSGEARTH_HTTP_CLIENT_H
-#define OSGEARTH_HTTP_CLIENT_H 1
-
-#include <osgEarth/Common>
-#include <osgEarth/IOTypes>
-#include <osg/ref_ptr>
-#include <osg/Referenced>
-#include <osgDB/ReaderWriter>
-#include <sstream>
-#include <iostream>
-#include <string>
-#include <map>
-#include <vector>
-
-namespace osgEarth
-{
-    class ProgressCallback;
-
-    /**
-     * Proxy server configuration.
-     */
-    class OSGEARTH_EXPORT ProxySettings
-    {
-    public:
-        ProxySettings( const Config& conf =Config() );
-        ProxySettings( const std::string& host, int port );
-
-        virtual ~ProxySettings() { }
-
-        std::string& hostName() { return _hostName; }
-        const std::string& hostName() const { return _hostName; }
-
-        int& port() { return _port; }
-        const int& port() const { return _port; }
-
-        std::string& userName() { return _userName; }
-        const std::string& userName() const { return _userName; }
-
-        std::string& password() { return _password; }
-        const std::string& password() const { return _password; }
-
-        void apply(osgDB::Options* dbOptions) const;
-        static bool fromOptions( const osgDB::Options* dbOptions, optional<ProxySettings>& out );
-
-    public:
-        virtual Config getConfig() const;
-        virtual void mergeConfig( const Config& conf );
-
-    protected:
-        std::string _hostName;
-        int _port;
-        std::string _userName;
-        std::string _password;
-    };
-
-
-    /**
-     * An HTTP request for use with the HTTPClient class.
-     */
-    class OSGEARTH_EXPORT HTTPRequest
-    {
-    public:
-        /** Constructs a new HTTP request that will acces the specified base URL. */
-        HTTPRequest( const std::string& url );
-
-        /** copy constructor. */
-        HTTPRequest( const HTTPRequest& rhs );
-
-        /** dtor */
-        virtual ~HTTPRequest() { }
-
-        /** Adds an HTTP parameter to the request query string. */
-        void addParameter( const std::string& name, const std::string& value );
-        void addParameter( const std::string& name, int value );
-        void addParameter( const std::string& name, double value );
-        
-        typedef std::map<std::string,std::string> Parameters;
-
-        /** Ready-only access to the parameter list (as built with addParameter) */
-        const Parameters& getParameters() const;
-
-        /** Gets a copy of the complete URL (base URL + query string) for this request */
-        std::string getURL() const;
-        
-    private:
-        Parameters _parameters;
-        std::string _url;
-    };
-
-    /**
-     * An HTTP response object for use with the HTTPClient class - supports
-     * multi-part mime responses.
-     */
-    class OSGEARTH_EXPORT HTTPResponse
-    {
-    public:
-        enum Code {
-            NONE         = 0,
-            OK           = 200,
-            BAD_REQUEST  = 400,
-            NOT_FOUND    = 404,
-            CONFLICT     = 409,
-            SERVER_ERROR = 500
-        };
-
-    public:
-        /** Constructs a response with the specified HTTP response code */
-        HTTPResponse( long code =0L );
-
-        /** Copy constructor */
-        HTTPResponse( const HTTPResponse& rhs );
-
-        /** dtor */
-        virtual ~HTTPResponse() { }
-
-        /** Gets the HTTP response code (Code) in this response */
-        unsigned getCode() const;
-
-        /** True is the HTTP response code is OK (200) */
-        bool isOK() const;
-
-        /** True if the request associated with this response was cancelled before it completed */
-        bool isCancelled() const;
-
-        /** Gets the number of parts in a (possibly multipart mime) response */
-        unsigned int getNumParts() const;
-
-        /** Gets the input stream for the nth part in the response */
-        std::istream& getPartStream( unsigned int n ) const;
-
-        /** Gets the nth response part as a string */
-        std::string getPartAsString( unsigned int n ) const;
-
-        /** Gets the length of the nth response part */
-        unsigned int getPartSize( unsigned int n ) const;
-        
-        /** Gets the HTTP header associated with the nth multipart/mime response part */
-        const std::string& getPartHeader( unsigned int n, const std::string& name ) const;
-
-        /** Gets the master mime-type returned by the request */
-        const std::string& getMimeType() const;
-
-    private:
-        struct Part : public osg::Referenced
-        {
-            Part() : _size(0) { }
-            typedef std::map<std::string,std::string> Headers;
-            Headers _headers;
-            unsigned int _size;
-            std::stringstream _stream;
-        };
-        typedef std::vector< osg::ref_ptr<Part> > Parts;
-        Parts       _parts;
-        long        _response_code;
-        std::string _mimeType;
-        bool        _cancelled;
-
-        Config getHeadersAsConfig() const;
-
-        friend class HTTPClient;
-    };
-
-    /**
-     * Object that lets you modify and incoming URL before it's passed to the server
-     */
-    struct OSGEARTH_EXPORT URLRewriter : public osg::Referenced
-    {    
-        virtual std::string rewrite( const std::string& url ) = 0;
-    };
-
-    /**
-     * Utility class for making HTTP requests.
-     *
-     * TODO: This class will actually read data from disk as well, and therefore should
-     * probably be renamed. It analyzes the URI and decides whether to make an  HTTP request
-     * or to read from disk.
-     */
-    class OSGEARTH_EXPORT HTTPClient // : public osg::Referenced
-    {
-    public:
-        /**
-         * Returns true is the result code represents a recoverable situation,
-         * i.e. one in which retrying might work.
-         */
-        static bool isRecoverable( ReadResult::Code code )
-        {
-            return
-                code == ReadResult::RESULT_OK ||
-                code == ReadResult::RESULT_SERVER_ERROR ||
-                code == ReadResult::RESULT_TIMEOUT ||
-                code == ReadResult::RESULT_CANCELED;
-        }
-
-        /** Gest the user-agent string that all HTTP requests will use.
-            TODO: This should probably move into the Registry */
-        static const std::string& getUserAgent();
-
-        /** Sets a user-agent string to use in all HTTP requests.
-            TODO: This should probably move into the Registry */
-        static void setUserAgent(const std::string& userAgent);
-
-        /** Sets up proxy info to use in all HTTP requests.
-            TODO: This should probably move into the Registry */
-        static void setProxySettings( const ProxySettings &proxySettings );
-
-        /**
-           Gets the timeout in seconds to use for HTTP requests.*/
-        static long getTimeout();
-
-        /**
-           Sets the timeout in seconds to use for HTTP requests.
-           Setting to 0 (default) is infinite timeout */
-        static void setTimeout( long timeout );
-
-        /**
-           Gets the timeout in seconds to use for HTTP connect requests.*/
-        static long getConnectTimeout();
-
-        /**
-           Sets the timeout in seconds to use for HTTP connect requests.
-           Setting to 0 (default) is infinite timeout */
-        static void setConnectTimeout( long timeout );
-
-        /**
-         * Gets the URLRewriter that is used to modify urls before sending them to the server
-         */
-        static URLRewriter* getURLRewriter();
-
-        /**
-         * Sets the URLRewriter that is used to modify urls before sending them to the server         
-         */
-        static void setURLRewriter( URLRewriter* rewriter );
-
-        /**
-         * One time thread safe initialization. In osgEarth, you don't need
-         * to call this directly; osgEarth::Registry will call it at
-         * startup.
-         */
-        static void globalInit();
-
-
-    public:
-        /**
-         * Reads an image.
-         */
-        static ReadResult readImage(
-            const std::string&    location,
-            const osgDB::Options* dbOptions =0L,
-            ProgressCallback*     progress  =0L );
-
-        /**
-         * Reads an osg::Node.
-         */
-        static ReadResult readNode(
-            const std::string&    location,
-            const osgDB::Options* dbOptions =0L,
-            ProgressCallback*     progress  =0L );
-
-        /**
-         * Reads an object.
-         */
-        static ReadResult readObject(
-            const std::string&    location,
-            const osgDB::Options* dbOptions =0L,
-            ProgressCallback*     progress  =0L );
-
-        /**
-         * Reads a string.
-         */
-        static ReadResult readString(
-            const std::string&    location,
-            const osgDB::Options* dbOptions =0L,
-            ProgressCallback*     progress  =0L );
-
-        /**
-         * Downloads a file directly to disk.
-         */
-        static bool download(
-            const std::string& uri,
-            const std::string& localPath );
-
-    public:
-
-        /**
-         * Performs an HTTP "GET".
-         */
-        static HTTPResponse get( const HTTPRequest&    request,
-                                 const osgDB::Options* dbOptions =0L,
-                                 ProgressCallback*     progress  =0L );
-
-        static HTTPResponse get( const std::string&    url,
-                                 const osgDB::Options* options  =0L,
-                                 ProgressCallback*     progress =0L );
-
-    public:
-        HTTPClient();
-        virtual ~HTTPClient();
-
-    private:
-
-        void readOptions( const osgDB::ReaderWriter::Options* options, std::string &proxy_host, std::string &proxy_port ) const;
-
-        HTTPResponse doGet( const HTTPRequest&    request,
-                            const osgDB::Options* options  =0L,
-                            ProgressCallback*     callback =0L ) const;
-
-        HTTPResponse doGet( const std::string&    url,
-                            const osgDB::Options* options  =0L,
-                            ProgressCallback*     callback =0L ) const;
-
-        ReadResult doReadObject(
-            const std::string&    location,
-            const osgDB::Options* dbOptions,
-            ProgressCallback*     progress );
-
-        ReadResult doReadImage(
-            const std::string&    location,
-            const osgDB::Options* dbOptions,
-            ProgressCallback*     progress );
-
-        ReadResult doReadNode(
-            const std::string&    location,
-            const osgDB::Options* dbOptions,
-            ProgressCallback*     progress );
-
-        ReadResult doReadString(
-            const std::string&    location,
-            const osgDB::Options* dbOptions,
-            ProgressCallback*     progress );
-
-        /**
-         * Convenience method for downloading a URL directly to a file
-         */
-        bool doDownload(const std::string& url, const std::string& filename);
-
-    private:
-        void*       _curl_handle;
-        std::string _previousPassword;
-        long        _previousHttpAuthentication;
-        bool        _initialized;
-        long        _simResponseCode;
-
-        void initialize() const;
-        void initializeImpl();
-
-
-        static HTTPClient& getClient();
-
-    private:
-        void decodeMultipartStream(
-            const std::string&   boundary,
-            HTTPResponse::Part*  input,
-            HTTPResponse::Parts& output) const;
-    };
-}
-
-#endif // OSGEARTH_HTTP_CLIENT_H
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_HTTP_CLIENT_H
+#define OSGEARTH_HTTP_CLIENT_H 1
+
+#include <osgEarth/Common>
+#include <osgEarth/IOTypes>
+#include <osg/ref_ptr>
+#include <osg/Referenced>
+#include <osgDB/ReaderWriter>
+#include <sstream>
+#include <iostream>
+#include <string>
+#include <map>
+#include <vector>
+
+namespace osgEarth
+{
+    class ProgressCallback;
+
+    /**
+     * Proxy server configuration.
+     */
+    class OSGEARTH_EXPORT ProxySettings
+    {
+    public:
+        ProxySettings( const Config& conf =Config() );
+        ProxySettings( const std::string& host, int port );
+
+        virtual ~ProxySettings() { }
+
+        std::string& hostName() { return _hostName; }
+        const std::string& hostName() const { return _hostName; }
+
+        int& port() { return _port; }
+        const int& port() const { return _port; }
+
+        std::string& userName() { return _userName; }
+        const std::string& userName() const { return _userName; }
+
+        std::string& password() { return _password; }
+        const std::string& password() const { return _password; }
+
+        void apply(osgDB::Options* dbOptions) const;
+        static bool fromOptions( const osgDB::Options* dbOptions, optional<ProxySettings>& out );
+
+    public:
+        virtual Config getConfig() const;
+        virtual void mergeConfig( const Config& conf );
+
+    protected:
+        std::string _hostName;
+        int _port;
+        std::string _userName;
+        std::string _password;
+    };
+
+    typedef std::map<std::string,std::string> Headers;
+
+
+    /**
+     * An HTTP request for use with the HTTPClient class.
+     */
+    class OSGEARTH_EXPORT HTTPRequest
+    {
+    public:
+        /** Constructs a new HTTP request that will acces the specified base URL. */
+        HTTPRequest( const std::string& url );
+
+        /** copy constructor. */
+        HTTPRequest( const HTTPRequest& rhs );
+
+        /** dtor */
+        virtual ~HTTPRequest() { }
+
+        /** Adds an HTTP parameter to the request query string. */
+        void addParameter( const std::string& name, const std::string& value );
+        void addParameter( const std::string& name, int value );
+        void addParameter( const std::string& name, double value );        
+        
+        typedef std::map<std::string,std::string> Parameters;
+
+        /** Ready-only access to the parameter list (as built with addParameter) */
+        const Parameters& getParameters() const;        
+
+        void addHeader( const std::string& name, const std::string& value );
+
+        const Headers& getHeaders() const;
+
+        /**
+         * Sets the last modified date of any locally cached data for this request.  This will 
+         * automatically add a If-Modified-Since header to the request
+         */
+        void setLastModified( const DateTime &lastModified );
+
+        /** Gets a copy of the complete URL (base URL + query string) for this request */
+        std::string getURL() const;
+        
+    private:
+        Parameters _parameters;
+        Headers _headers;
+        std::string _url;
+    };
+
+    /**
+     * An HTTP response object for use with the HTTPClient class - supports
+     * multi-part mime responses.
+     */
+    class OSGEARTH_EXPORT HTTPResponse
+    {
+    public:
+        enum Code {
+            NONE         = 0,
+            OK           = 200,
+            NOT_MODIFIED = 304,
+            BAD_REQUEST  = 400,
+            NOT_FOUND    = 404,
+            CONFLICT     = 409,
+            SERVER_ERROR = 500
+        };
+
+    public:
+        /** Constructs a response with the specified HTTP response code */
+        HTTPResponse( long code =0L );
+
+        /** Copy constructor */
+        HTTPResponse( const HTTPResponse& rhs );
+
+        /** dtor */
+        virtual ~HTTPResponse() { }
+
+        /** Gets the HTTP response code (Code) in this response */
+        unsigned getCode() const;
+
+        /** True is the HTTP response code is OK (200) */
+        bool isOK() const;
+
+        /** True if the request associated with this response was cancelled before it completed */
+        bool isCancelled() const;
+
+        /** Gets the number of parts in a (possibly multipart mime) response */
+        unsigned int getNumParts() const;
+
+        /** Gets the input stream for the nth part in the response */
+        std::istream& getPartStream( unsigned int n ) const;
+
+        /** Gets the nth response part as a string */
+        std::string getPartAsString( unsigned int n ) const;
+
+        /** Gets the length of the nth response part */
+        unsigned int getPartSize( unsigned int n ) const;
+        
+        /** Gets the HTTP header associated with the nth multipart/mime response part */
+        const std::string& getPartHeader( unsigned int n, const std::string& name ) const;
+
+        /** Gets the master mime-type returned by the request */
+        const std::string& getMimeType() const;
+
+        /** How long did it take to fetch this response (in seconds) */
+        double getDuration() const { return _duration_s; }        
+
+    private:
+        struct Part : public osg::Referenced
+        {
+            Part() : _size(0) { }            
+            Headers _headers;
+            unsigned int _size;
+            std::stringstream _stream;
+        };
+        typedef std::vector< osg::ref_ptr<Part> > Parts;
+        Parts       _parts;
+        long        _response_code;
+        std::string _mimeType;
+        bool        _cancelled;
+        double      _duration_s;
+
+        Config getHeadersAsConfig() const;
+
+        friend class HTTPClient;
+    };
+
+    /**
+     * Object that lets you modify and incoming URL before it's passed to the server
+     */
+    struct OSGEARTH_EXPORT URLRewriter : public osg::Referenced
+    {    
+        virtual std::string rewrite( const std::string& url ) = 0;
+    };
+
+	/**
+	 *
+	 * A CURL configuration handler to apply CURL settings. It can be used for setting client certificates
+	 */
+	struct OSGEARTH_EXPORT CurlConfigHandler : public osg::Referenced
+	{
+		virtual void onInitialize(void* curl_handle) = 0;
+		virtual void onGet(void* curl_handle) = 0;
+	};
+	
+	/**
+     * Utility class for making HTTP requests.
+     *
+     * TODO: This class will actually read data from disk as well, and therefore should
+     * probably be renamed. It analyzes the URI and decides whether to make an  HTTP request
+     * or to read from disk.
+     */
+    class OSGEARTH_EXPORT HTTPClient
+    {
+    public:
+        /**
+         * Returns true is the result code represents a recoverable situation,
+         * i.e. one in which retrying might work.
+         */
+        static bool isRecoverable( ReadResult::Code code )
+        {
+            return
+                code == ReadResult::RESULT_OK ||                
+                code == ReadResult::RESULT_SERVER_ERROR ||
+                code == ReadResult::RESULT_TIMEOUT ||
+                code == ReadResult::RESULT_CANCELED;
+        }
+
+        /** Gest the user-agent string that all HTTP requests will use.
+            TODO: This should probably move into the Registry */
+        static const std::string& getUserAgent();
+
+        /** Sets a user-agent string to use in all HTTP requests.
+            TODO: This should probably move into the Registry */
+        static void setUserAgent(const std::string& userAgent);
+
+        /** Sets up proxy info to use in all HTTP requests.
+            TODO: This should probably move into the Registry */
+        static void setProxySettings( const ProxySettings &proxySettings );
+
+        /**
+           Gets the timeout in seconds to use for HTTP requests.*/
+        static long getTimeout();
+
+        /**
+           Sets the timeout in seconds to use for HTTP requests.
+           Setting to 0 (default) is infinite timeout */
+        static void setTimeout( long timeout );
+
+        /**
+           Gets the timeout in seconds to use for HTTP connect requests.*/
+        static long getConnectTimeout();
+
+        /**
+           Sets the timeout in seconds to use for HTTP connect requests.
+           Setting to 0 (default) is infinite timeout */
+        static void setConnectTimeout( long timeout );
+
+        /**
+         * Gets the URLRewriter that is used to modify urls before sending them to the server
+         */
+        static URLRewriter* getURLRewriter();
+
+        /**
+         * Sets the URLRewriter that is used to modify urls before sending them to the server         
+         */
+        static void setURLRewriter( URLRewriter* rewriter );
+
+		static CurlConfigHandler* getCurlConfigHandler();
+
+		/**
+		* Sets the CurlConfigHandler to configurate the CURL library. It can be used for apply client certificates
+		*/
+		static void setCurlConfighandler(CurlConfigHandler* handler);
+		
+		/**
+         * One time thread safe initialization. In osgEarth, you don't need
+         * to call this directly; osgEarth::Registry will call it at
+         * startup.
+         */
+        static void globalInit();
+
+
+    public:
+        /**
+         * Reads an image.
+         */
+        static ReadResult readImage(
+            const HTTPRequest&    request,
+            const osgDB::Options* dbOptions =0L,
+            ProgressCallback*     progress  =0L );
+
+        /**
+         * Reads an osg::Node.
+         */
+        static ReadResult readNode(
+            const HTTPRequest&    request,
+            const osgDB::Options* dbOptions =0L,
+            ProgressCallback*     progress  =0L );
+
+        /**
+         * Reads an object.
+         */
+        static ReadResult readObject(
+            const HTTPRequest&    request,
+            const osgDB::Options* dbOptions =0L,
+            ProgressCallback*     progress  =0L );
+
+        /**
+         * Reads a string.
+         */
+        static ReadResult readString(
+            const HTTPRequest&    request,
+            const osgDB::Options* dbOptions =0L,
+            ProgressCallback*     progress  =0L );
+
+        /**
+         * Downloads a file directly to disk.
+         */
+        static bool download(
+            const std::string& uri,
+            const std::string& localPath );
+
+    public:
+
+        /**
+         * Performs an HTTP "GET".
+         */
+        static HTTPResponse get( const HTTPRequest&    request,
+                                 const osgDB::Options* dbOptions =0L,
+                                 ProgressCallback*     progress  =0L );
+
+        static HTTPResponse get( const std::string&    url,
+                                 const osgDB::Options* options  =0L,
+                                 ProgressCallback*     progress =0L );
+
+    public:
+        HTTPClient();
+        virtual ~HTTPClient();
+
+    private:
+
+        void readOptions( const osgDB::ReaderWriter::Options* options, std::string &proxy_host, std::string &proxy_port ) const;
+
+        HTTPResponse doGet( const HTTPRequest&    request,
+                            const osgDB::Options* options  =0L,
+                            ProgressCallback*     callback =0L ) const;
+        
+        ReadResult doReadObject(
+            const HTTPRequest&    request,
+            const osgDB::Options* dbOptions,
+            ProgressCallback*     progress );
+
+        ReadResult doReadImage(
+            const HTTPRequest&    request,
+            const osgDB::Options* dbOptions,
+            ProgressCallback*     progress );
+
+        ReadResult doReadNode(
+            const HTTPRequest&    request,
+            const osgDB::Options* dbOptions,
+            ProgressCallback*     progress );
+
+        ReadResult doReadString(
+            const HTTPRequest&    request,
+            const osgDB::Options* dbOptions,
+            ProgressCallback*     progress );
+
+        /**
+         * Convenience method for downloading a URL directly to a file
+         */
+        bool doDownload(const std::string& url, const std::string& filename);
+
+    private:
+        void*       _curl_handle;
+        std::string _previousPassword;
+        long        _previousHttpAuthentication;
+        bool        _initialized;
+        long        _simResponseCode;
+
+        void initialize() const;
+        void initializeImpl();
+
+
+        static HTTPClient& getClient();
+
+    private:
+        void decodeMultipartStream(
+            const std::string&   boundary,
+            HTTPResponse::Part*  input,
+            HTTPResponse::Parts& output) const;
+    };
+}
+
+#endif // OSGEARTH_HTTP_CLIENT_H
diff --git a/src/osgEarth/HTTPClient.cpp b/src/osgEarth/HTTPClient.cpp
index 22897b7..3eb8562 100644
--- a/src/osgEarth/HTTPClient.cpp
+++ b/src/osgEarth/HTTPClient.cpp
@@ -1,1225 +1,1344 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#include <osgEarth/HTTPClient>
-#include <osgEarth/Registry>
-#include <osgEarth/Version>
-#include <osgEarth/Progress>
-#include <osgDB/ReadFile>
-#include <osgDB/Registry>
-#include <osgDB/FileNameUtils>
-#include <osg/Notify>
-#include <string.h>
-#include <sstream>
-#include <fstream>
-#include <iterator>
-#include <iostream>
-#include <algorithm>
-#include <curl/curl.h>
-
-#define LC "[HTTPClient] "
-
-//#define OE_TEST OE_NOTICE
-#define OE_TEST OE_NULL
-
-using namespace osgEarth;
-
-//----------------------------------------------------------------------------
-
-ProxySettings::ProxySettings( const Config& conf )
-{
-    mergeConfig( conf );
-}
-
-ProxySettings::ProxySettings( const std::string& host, int port ) :
-_hostName(host),
-_port(port)
-{
-    //nop
-}
-
-void
-ProxySettings::mergeConfig( const Config& conf )
-{
-    _hostName = conf.value<std::string>( "host", "" );
-    _port = conf.value<int>( "port", 8080 );
-    _userName = conf.value<std::string>( "username", "" );
-    _password = conf.value<std::string>( "password", "" );
-}
-
-Config
-ProxySettings::getConfig() const
-{
-    Config conf( "proxy" );
-    conf.add( "host", _hostName );
-    conf.add( "port", toString(_port) );
-    conf.add( "username", _userName);
-    conf.add( "password", _password);
-
-    return conf;
-}
-
-bool
-ProxySettings::fromOptions( const osgDB::Options* dbOptions, optional<ProxySettings>& out )
-{
-    if ( dbOptions )
-    {
-        std::string jsonString = dbOptions->getPluginStringData( "osgEarth::ProxySettings" );
-        if ( !jsonString.empty() )
-        {
-            Config conf;
-            conf.fromJSON( jsonString );
-            out = ProxySettings( conf );
-            return true;
-        }
-    }
-    return false;
-}
-
-void
-ProxySettings::apply( osgDB::Options* dbOptions ) const
-{
-    if ( dbOptions )
-    {
-        Config conf = getConfig();
-        dbOptions->setPluginStringData( "osgEarth::ProxySettings", conf.toJSON() );
-    }
-}
-
-/****************************************************************************/
-   
-namespace osgEarth
-{
-    struct StreamObject
-    {
-        StreamObject(std::ostream* stream) : _stream(stream) { }
-
-        void write(const char* ptr, size_t realsize)
-        {
-            if (_stream) _stream->write(ptr, realsize);
-        }
-
-        std::ostream* _stream;
-        std::string     _resultMimeType;
-    };
-
-    static size_t
-    StreamObjectReadCallback(void* ptr, size_t size, size_t nmemb, void* data)
-    {
-        size_t realsize = size* nmemb;
-        StreamObject* sp = (StreamObject*)data;
-        sp->write((const char*)ptr, realsize);
-        return realsize;
-    }
-}
-
-static int CurlProgressCallback(void *clientp,double dltotal,double dlnow,double ultotal,double ulnow)
-{
-    ProgressCallback* callback = (ProgressCallback*)clientp;
-    bool cancelled = false;
-    if (callback)
-    {
-        cancelled = callback->isCanceled() || callback->reportProgress(dlnow, dltotal);
-    }
-    return cancelled;
-}
-
-/****************************************************************************/
-
-HTTPRequest::HTTPRequest( const std::string& url )
-: _url( url )
-{
-    //NOP
-}
-
-HTTPRequest::HTTPRequest( const HTTPRequest& rhs ) :
-_parameters( rhs._parameters ),
-_url( rhs._url )
-{
-    //nop
-}
-
-void
-HTTPRequest::addParameter( const std::string& name, const std::string& value )
-{
-    _parameters[name] = value;
-}
-
-void
-HTTPRequest::addParameter( const std::string& name, int value )
-{
-    std::stringstream buf;
-    buf << value;
-     std::string bufStr;
-    bufStr = buf.str();
-    _parameters[name] = bufStr;
-}
-
-void
-HTTPRequest::addParameter( const std::string& name, double value )
-{
-    std::stringstream buf;
-    buf << value;
-     std::string bufStr;
-    bufStr = buf.str();
-    _parameters[name] = bufStr;
-}
-
-const HTTPRequest::Parameters&
-HTTPRequest::getParameters() const
-{
-    return _parameters; 
-}
-
-std::string
-HTTPRequest::getURL() const
-{
-    if ( _parameters.size() == 0 )
-    {
-        return _url;
-    }
-    else
-    {
-        std::stringstream buf;
-        buf << _url;
-        for( Parameters::const_iterator i = _parameters.begin(); i != _parameters.end(); i++ )
-        {
-            buf << ( i == _parameters.begin() && _url.find( "?" ) == std::string::npos? "?" : "&" );
-            buf << i->first << "=" << i->second;
-        }
-         std::string bufStr;
-         bufStr = buf.str();
-        return bufStr;
-    }
-}
-
-/****************************************************************************/
-
-HTTPResponse::HTTPResponse( long _code )
-: _response_code( _code ),
-  _cancelled(false)
-{
-    _parts.reserve(1);
-}
-
-HTTPResponse::HTTPResponse( const HTTPResponse& rhs ) :
-_response_code( rhs._response_code ),
-_parts( rhs._parts ),
-_mimeType( rhs._mimeType ),
-_cancelled( rhs._cancelled )
-{
-    //nop
-}
-
-unsigned
-HTTPResponse::getCode() const {
-    return _response_code;
-}
-
-bool
-HTTPResponse::isOK() const {
-    return _response_code == 200L && !isCancelled();
-}
-
-bool
-HTTPResponse::isCancelled() const {
-    return _cancelled;
-}
-
-unsigned int
-HTTPResponse::getNumParts() const {
-    return _parts.size();
-}
-
-unsigned int
-HTTPResponse::getPartSize( unsigned int n ) const {
-    return _parts[n]->_size;
-}
-
-const std::string&
-HTTPResponse::getPartHeader( unsigned int n, const std::string& name ) const {
-    return _parts[n]->_headers[name];
-}
-
-std::istream&
-HTTPResponse::getPartStream( unsigned int n ) const {
-    return _parts[n]->_stream;
-}
-
-std::string
-HTTPResponse::getPartAsString( unsigned int n ) const {
-     std::string streamStr;
-     streamStr = _parts[n]->_stream.str();
-    return streamStr;
-}
-
-const std::string&
-HTTPResponse::getMimeType() const {
-    return _mimeType;
-}
-
-Config
-HTTPResponse::getHeadersAsConfig() const
-{
-    Config conf;
-    if ( _parts.size() > 0 )
-    {
-        for( Part::Headers::const_iterator i = _parts[0]->_headers.begin(); i != _parts[0]->_headers.end(); ++i )
-        {
-            conf.set(i->first, i->second);
-        }
-    }
-    return conf;
-}
-
-/****************************************************************************/
-
-#define QUOTE_(X) #X
-#define QUOTE(X) QUOTE_(X)
-#define USER_AGENT "osgearth" QUOTE(OSGEARTH_MAJOR_VERSION) "." QUOTE(OSGEARTH_MINOR_VERSION)
-
-
-namespace
-{
-    // TODO: consider moving this stuff into the osgEarth::Registry;
-    // don't like it here in the global scope
-    // per-thread client map (must be global scope)
-    static Threading::PerThread<HTTPClient> s_clientPerThread;
-
-    static optional<ProxySettings>     s_proxySettings;
-
-    static std::string                 s_userAgent = USER_AGENT;
-
-    static long                        s_timeout = 0;
-    static long                        s_connectTimeout = 0;
-
-    // HTTP debugging.
-    static bool                        s_HTTP_DEBUG = false;
-
-    static osg::ref_ptr< URLRewriter > s_rewriter;
-}
-
-HTTPClient&
-HTTPClient::getClient()
-{
-    return s_clientPerThread.get();
-}
-
-HTTPClient::HTTPClient() :
-_initialized    ( false ),
-_curl_handle    ( 0L ),
-_simResponseCode( -1L )
-{
-    //nop
-    //do no CURL calls here.
-}
-
-void
-HTTPClient::initialize() const
-{
-    if ( !_initialized )
-    {
-        const_cast<HTTPClient*>(this)->initializeImpl();
-    }
-}
-
-void
-HTTPClient::initializeImpl()
-{
-    _previousHttpAuthentication = 0;
-    _curl_handle = curl_easy_init();
-
-    //Get the user agent
-    std::string userAgent = s_userAgent;
-    const char* userAgentEnv = getenv("OSGEARTH_USERAGENT");
-    if (userAgentEnv)
-    {
-        userAgent = std::string(userAgentEnv);
-    }
-
-    //Check for a response-code simulation (for testing)
-    const char* simCode = getenv("OSGEARTH_SIMULATE_HTTP_RESPONSE_CODE");
-    if ( simCode )
-    {
-        _simResponseCode = osgEarth::as<long>(std::string(simCode), 404L);
-        OE_WARN << LC << "Simulating a network error with Response Code = " << _simResponseCode << std::endl;
-    }
-
-    // Dumps out HTTP request/response info
-    if ( ::getenv("OSGEARTH_HTTP_DEBUG") )
-    {
-        s_HTTP_DEBUG = true;
-        OE_WARN << LC << "HTTP debugging enabled" << std::endl;
-    }
-
-    OE_DEBUG << LC << "HTTPClient setting userAgent=" << userAgent << std::endl;
-
-    curl_easy_setopt( _curl_handle, CURLOPT_USERAGENT, userAgent.c_str() );
-    curl_easy_setopt( _curl_handle, CURLOPT_WRITEFUNCTION, osgEarth::StreamObjectReadCallback );
-    curl_easy_setopt( _curl_handle, CURLOPT_FOLLOWLOCATION, (void*)1 );
-    curl_easy_setopt( _curl_handle, CURLOPT_MAXREDIRS, (void*)5 );
-    curl_easy_setopt( _curl_handle, CURLOPT_PROGRESSFUNCTION, &CurlProgressCallback);
-    curl_easy_setopt( _curl_handle, CURLOPT_NOPROGRESS, (void*)0 ); //FALSE);
-    curl_easy_setopt( _curl_handle, CURLOPT_FILETIME, true );
-
-    long timeout = s_timeout;
-    const char* timeoutEnv = getenv("OSGEARTH_HTTP_TIMEOUT");
-    if (timeoutEnv)
-    {
-        timeout = osgEarth::as<long>(std::string(timeoutEnv), 0);
-    }
-    OE_DEBUG << LC << "Setting timeout to " << timeout << std::endl;
-    curl_easy_setopt( _curl_handle, CURLOPT_TIMEOUT, timeout );
-    long connectTimeout = s_connectTimeout;
-    const char* connectTimeoutEnv = getenv("OSGEARTH_HTTP_CONNECTTIMEOUT");
-    if (connectTimeoutEnv)
-    {
-        connectTimeout = osgEarth::as<long>(std::string(connectTimeoutEnv), 0);
-    }
-    OE_DEBUG << LC << "Setting connect timeout to " << connectTimeout << std::endl;
-    curl_easy_setopt( _curl_handle, CURLOPT_CONNECTTIMEOUT, connectTimeout );
-
-    _initialized = true;
-}
-
-HTTPClient::~HTTPClient()
-{
-    if (_curl_handle) curl_easy_cleanup( _curl_handle );
-    _curl_handle = 0;
-}
-
-void
-HTTPClient::setProxySettings( const ProxySettings& proxySettings )
-{
-    s_proxySettings = proxySettings;
-}
-
-const std::string& HTTPClient::getUserAgent()
-{
-    return s_userAgent;
-}
-
-void  HTTPClient::setUserAgent(const std::string& userAgent)
-{
-    s_userAgent = userAgent;
-}
-
-long HTTPClient::getTimeout()
-{
-    return s_timeout;
-}
-
-void HTTPClient::setTimeout( long timeout )
-{
-    s_timeout = timeout;
-}
-
-long HTTPClient::getConnectTimeout()
-{
-    return s_connectTimeout;
-}
-
-void HTTPClient::setConnectTimeout( long timeout )
-{
-    s_connectTimeout = timeout;
-}
-URLRewriter* HTTPClient::getURLRewriter()
-{
-    return s_rewriter.get();
-}
-
-void HTTPClient::setURLRewriter( URLRewriter* rewriter )
-{
-    s_rewriter = rewriter;
-}
-
-void
-HTTPClient::globalInit()
-{
-    curl_global_init(CURL_GLOBAL_ALL);
-}
-
-void
-HTTPClient::readOptions(const osgDB::Options* options, std::string& proxy_host, std::string& proxy_port) const
-{
-    // try to set proxy host/port by reading the CURL proxy options
-    if ( options )
-    {
-        std::istringstream iss( options->getOptionString() );
-        std::string opt;
-        while( iss >> opt )
-        {
-            int index = opt.find( "=" );
-            if( opt.substr( 0, index ) == "OSG_CURL_PROXY" )
-            {
-                proxy_host = opt.substr( index+1 );
-            }
-            else if ( opt.substr( 0, index ) == "OSG_CURL_PROXYPORT" )
-            {
-                proxy_port = opt.substr( index+1 );
-            }
-        }
-    }
-}
-
-namespace
-{
-    // from: http://www.rosettacode.org/wiki/Tokenizing_A_String#C.2B.2B
-    std::vector<std::string> 
-    tokenize_str(const std::string & str, const std::string & delims=", \t")
-    {
-      using namespace std;
-      // Skip delims at beginning, find start of first token
-      string::size_type lastPos = str.find_first_not_of(delims, 0);
-      // Find next delimiter @ end of token
-      string::size_type pos     = str.find_first_of(delims, lastPos);
-
-      // output vector
-      vector<string> tokens;
-
-      while (string::npos != pos || string::npos != lastPos)
-        {
-          // Found a token, add it to the vector.
-          tokens.push_back(str.substr(lastPos, pos - lastPos));
-          // Skip delims.  Note the "not_of". this is beginning of token
-          lastPos = str.find_first_not_of(delims, pos);
-          // Find next delimiter at end of token.
-          pos     = str.find_first_of(delims, lastPos);
-        }
-
-      return tokens;
-    }
-}
-
-void
-HTTPClient::decodeMultipartStream(const std::string&   boundary,
-                                  HTTPResponse::Part*  input,
-                                  HTTPResponse::Parts& output) const
-{
-    std::string bstr = std::string("--") + boundary;
-    std::string line;
-    char tempbuf[256];
-
-    // first thing in the stream should be the boundary.
-    input->_stream.read( tempbuf, bstr.length() );
-    tempbuf[bstr.length()] = 0;
-    line = tempbuf;
-    if ( line != bstr )
-    {
-        OE_WARN << LC 
-            << "decodeMultipartStream: protocol violation; "
-            << "expecting boundary; instead got: \"" 
-            << line
-            << "\"" << std::endl;
-        return;
-    }
-
-    for( bool done=false; !done; )
-    {
-        osg::ref_ptr<HTTPResponse::Part> next_part = new HTTPResponse::Part();
-
-        // first finish off the boundary.
-        std::getline( input->_stream, line );
-        if ( line == "--" )
-        {
-            done = true;
-        }
-        else
-        {
-            // read all headers. this ends with a blank line.
-            line = " ";
-            while( line.length() > 0 && !done )
-            {
-                std::getline( input->_stream, line );
-
-                // check for EOS:
-                if ( line == "--" )
-                {
-                    done = true;
-                }
-                else
-                {
-                    std::vector<std::string> tized = tokenize_str( line, ":" );
-                    if ( tized.size() >= 2 )
-                        next_part->_headers[tized[0]] = tized[1];
-                }
-            }
-        }
-
-        if ( !done )
-        {
-            // read data until we reach the boundary
-            unsigned int bstr_ptr = 0;
-            std::string temp;
-            //unsigned int c = 0;
-            while( bstr_ptr < bstr.length() )
-            {
-                char b;
-                input->_stream.read( &b, 1 );
-                if ( b == bstr[bstr_ptr] )
-                {
-                    bstr_ptr++;
-                }
-                else
-                {
-                    for( unsigned int i=0; i<bstr_ptr; i++ )
-                    {
-                        next_part->_stream << bstr[i];
-                    }
-                    next_part->_stream << b;
-                    next_part->_size += bstr_ptr + 1;
-                    bstr_ptr = 0;
-                }
-            }
-            output.push_back( next_part.get() );
-        }
-    }
-}
-
-HTTPResponse
-HTTPClient::get( const HTTPRequest&    request,
-                 const osgDB::Options* options,
-                 ProgressCallback*     callback)
-{
-    return getClient().doGet( request, options, callback );
-}
-
-HTTPResponse 
-HTTPClient::get( const std::string&    url,
-                 const osgDB::Options* options,
-                 ProgressCallback*     callback)
-{
-    return getClient().doGet( url, options, callback);
-}
-
-ReadResult
-HTTPClient::readImage(const std::string&    location,
-                      const osgDB::Options* options,
-                      ProgressCallback*     callback)
-{
-    return getClient().doReadImage( location, options, callback );
-}
-
-ReadResult
-HTTPClient::readNode(const std::string&    location,
-                     const osgDB::Options* options,
-                     ProgressCallback*     callback)
-{
-    return getClient().doReadNode( location, options, callback );
-}
-
-ReadResult
-HTTPClient::readObject(const std::string&    location,
-                       const osgDB::Options* options,
-                       ProgressCallback*     callback)
-{
-    return getClient().doReadObject( location, options, callback );
-}
-
-ReadResult
-HTTPClient::readString(const std::string&    location,
-                       const osgDB::Options* options,
-                       ProgressCallback*     callback)
-{
-    return getClient().doReadString( location, options, callback );
-}
-
-bool
-HTTPClient::download(const std::string& uri,
-                     const std::string& localPath)
-{
-    return getClient().doDownload( uri, localPath );
-}
-
-HTTPResponse
-HTTPClient::doGet( const HTTPRequest& request, const osgDB::Options* options, ProgressCallback* callback) const
-{
-    initialize();
-
-    const osgDB::AuthenticationMap* authenticationMap = (options && options->getAuthenticationMap()) ? 
-            options->getAuthenticationMap() :
-            osgDB::Registry::instance()->getAuthenticationMap();
-
-    std::string proxy_host;
-    std::string proxy_port = "8080";
-
-    std::string proxy_auth;
-
-    //TODO: don't do all this proxy setup on every GET. Just do it once per client, or only when 
-    // the proxy information changes.
-
-    //Try to get the proxy settings from the global settings
-    if (s_proxySettings.isSet())
-    {
-        proxy_host = s_proxySettings.get().hostName();
-        std::stringstream buf;
-        buf << s_proxySettings.get().port();
-        proxy_port = buf.str();
-
-        std::string proxy_username = s_proxySettings.get().userName();
-        std::string proxy_password = s_proxySettings.get().password();
-        if (!proxy_username.empty() && !proxy_password.empty())
-        {
-            proxy_auth = proxy_username + std::string(":") + proxy_password;
-        }
-    }
-
-    //Try to get the proxy settings from the local options that are passed in.
-    readOptions( options, proxy_host, proxy_port );
-
-    optional< ProxySettings > proxySettings;
-    ProxySettings::fromOptions( options, proxySettings );
-    if (proxySettings.isSet())
-    {       
-        proxy_host = proxySettings.get().hostName();
-        proxy_port = toString<int>(proxySettings.get().port());
-        OE_DEBUG << "Read proxy settings from options " << proxy_host << " " << proxy_port << std::endl;
-    }
-
-    //Try to get the proxy settings from the environment variable
-    const char* proxyEnvAddress = getenv("OSG_CURL_PROXY");
-    if (proxyEnvAddress) //Env Proxy Settings
-    {
-        proxy_host = std::string(proxyEnvAddress);
-
-        const char* proxyEnvPort = getenv("OSG_CURL_PROXYPORT"); //Searching Proxy Port on Env
-        if (proxyEnvPort)
-        {
-            proxy_port = std::string( proxyEnvPort );
-        }
-    }
-
-    const char* proxyEnvAuth = getenv("OSGEARTH_CURL_PROXYAUTH");
-    if (proxyEnvAuth)
-    {
-        proxy_auth = std::string(proxyEnvAuth);
-    }
-
-    // Set up proxy server:
-    std::string proxy_addr;
-    if ( !proxy_host.empty() )
-    {
-        std::stringstream buf;
-        buf << proxy_host << ":" << proxy_port;
-        std::string bufStr;
-        bufStr = buf.str();
-        proxy_addr = bufStr;
-    
-        if ( s_HTTP_DEBUG )
-            OE_NOTICE << LC << "Using proxy: " << proxy_addr << std::endl;
-
-        //curl_easy_setopt( _curl_handle, CURLOPT_HTTPPROXYTUNNEL, 1 ); 
-        curl_easy_setopt( _curl_handle, CURLOPT_PROXY, proxy_addr.c_str() );
-
-        //Setup the proxy authentication if setup
-        if (!proxy_auth.empty())
-        {
-            if ( s_HTTP_DEBUG )
-                OE_NOTICE << LC << "Using proxy authentication " << proxy_auth << std::endl;
-
-            curl_easy_setopt( _curl_handle, CURLOPT_PROXYUSERPWD, proxy_auth.c_str());
-        }
-    }
-    else
-    {
-        OE_DEBUG << "Removing proxy settings" << std::endl;
-        curl_easy_setopt( _curl_handle, CURLOPT_PROXY, 0 );
-    }
-
-    std::string url = request.getURL();
-    // Rewrite the url if the url rewriter is available  
-    osg::ref_ptr< URLRewriter > rewriter = getURLRewriter();
-    if ( rewriter.valid() )
-    {
-        std::string oldURL = url;
-        url = rewriter->rewrite( oldURL );
-        OE_INFO << "Rewrote URL " << oldURL << " to " << url << std::endl;
-    }
-
-    const osgDB::AuthenticationDetails* details = authenticationMap ?
-        authenticationMap->getAuthenticationDetails( url ) :
-        0;
-
-    if (details)
-    {
-        const std::string colon(":");
-        std::string password(details->username + colon + details->password);
-        curl_easy_setopt(_curl_handle, CURLOPT_USERPWD, password.c_str());
-        const_cast<HTTPClient*>(this)->_previousPassword = password;
-
-        // use for https.
-        // curl_easy_setopt(_curl, CURLOPT_KEYPASSWD, password.c_str());
-
-#if LIBCURL_VERSION_NUM >= 0x070a07
-        if (details->httpAuthentication != _previousHttpAuthentication)
-        { 
-            curl_easy_setopt(_curl_handle, CURLOPT_HTTPAUTH, details->httpAuthentication); 
-            const_cast<HTTPClient*>(this)->_previousHttpAuthentication = details->httpAuthentication;
-        }
-#endif
-    }
-    else
-    {
-        if (!_previousPassword.empty())
-        {
-            curl_easy_setopt(_curl_handle, CURLOPT_USERPWD, 0);
-            const_cast<HTTPClient*>(this)->_previousPassword.clear();
-        }
-
-#if LIBCURL_VERSION_NUM >= 0x070a07
-        // need to reset if previously set.
-        if (_previousHttpAuthentication!=0)
-        {
-            curl_easy_setopt(_curl_handle, CURLOPT_HTTPAUTH, 0); 
-            const_cast<HTTPClient*>(this)->_previousHttpAuthentication = 0;
-        }
-#endif
-    }
-
-    osg::ref_ptr<HTTPResponse::Part> part = new HTTPResponse::Part();
-    StreamObject sp( &part->_stream );
-
-    //Take a temporary ref to the callback
-    osg::ref_ptr<ProgressCallback> progressCallback = callback;
-    curl_easy_setopt( _curl_handle, CURLOPT_URL, url.c_str() );
-    if (callback)
-    {
-        curl_easy_setopt(_curl_handle, CURLOPT_PROGRESSDATA, progressCallback.get());
-    }
-
-    CURLcode res;
-    long response_code = 0L;
-
-    if ( _simResponseCode < 0 )
-    {
-        char errorBuf[CURL_ERROR_SIZE];
-        errorBuf[0] = 0;
-        curl_easy_setopt( _curl_handle, CURLOPT_ERRORBUFFER, (void*)errorBuf );
-
-        curl_easy_setopt( _curl_handle, CURLOPT_WRITEDATA, (void*)&sp);
-        res = curl_easy_perform( _curl_handle );
-        curl_easy_setopt( _curl_handle, CURLOPT_WRITEDATA, (void*)0 );
-        curl_easy_setopt( _curl_handle, CURLOPT_PROGRESSDATA, (void*)0);
-
-        //Disable peer certificate verification to allow us to access in https servers where the peer certificate cannot be verified.
-        curl_easy_setopt( _curl_handle, CURLOPT_SSL_VERIFYPEER, (void*)0 );
-
-        if (!proxy_addr.empty())
-        {
-            long connect_code = 0L;
-            CURLcode r = curl_easy_getinfo(_curl_handle, CURLINFO_HTTP_CONNECTCODE, &connect_code);
-            if ( r != CURLE_OK )
-            {
-                OE_WARN << LC << "Proxy connect error: " << curl_easy_strerror(r) << std::endl;
-                return HTTPResponse(0);
-            }
-        }
-
-        curl_easy_getinfo( _curl_handle, CURLINFO_RESPONSE_CODE, &response_code );        
-    }
-    else
-    {
-        // simulate failure with a custom response code
-        response_code = _simResponseCode;
-        res = response_code == 408 ? CURLE_OPERATION_TIMEDOUT : CURLE_COULDNT_CONNECT;
-    }    
-
-    HTTPResponse response( response_code );
-    
-    // read the response content type:
-    char* content_type_cp;
-
-    curl_easy_getinfo( _curl_handle, CURLINFO_CONTENT_TYPE, &content_type_cp );    
-
-    if ( content_type_cp != NULL )
-    {
-        response._mimeType = content_type_cp;    
-    }            
-
-    if ( s_HTTP_DEBUG )
-    {
-        TimeStamp filetime = 0;
-        if (CURLE_OK != curl_easy_getinfo(_curl_handle, CURLINFO_FILETIME, &filetime))
-            filetime = 0;
-
-        OE_NOTICE << LC 
-            << "GET(" << response_code << ", " << response._mimeType << ") : \"" 
-            << url << "\" (" << DateTime(filetime).asRFC1123() << ")"<< std::endl;
-    }
-
-    // upon success, parse the data:
-    if ( res != CURLE_ABORTED_BY_CALLBACK && res != CURLE_OPERATION_TIMEDOUT )
-    {        
-        // check for multipart content
-        if (response._mimeType.length() > 9 && 
-            ::strstr( response._mimeType.c_str(), "multipart" ) == response._mimeType.c_str() )
-        {
-            OE_DEBUG << LC << "detected multipart data; decoding..." << std::endl;
-
-            //TODO: parse out the "wcs" -- this is WCS-specific
-            decodeMultipartStream( "wcs", part.get(), response._parts );
-        }
-        else
-        {
-            // store headers that we care about
-            part->_headers[IOMetadata::CONTENT_TYPE] = response._mimeType;
-            response._parts.push_back( part.get() );
-        }
-    }
-    else  /*if (res == CURLE_ABORTED_BY_CALLBACK || res == CURLE_OPERATION_TIMEDOUT) */
-    {        
-        //If we were aborted by a callback, then it was cancelled by a user
-        response._cancelled = true;
-    }
-
-    return response;
-}
-
-
-HTTPResponse
-HTTPClient::doGet( const std::string& url, const osgDB::Options* options, ProgressCallback* callback) const
-{
-    return doGet( HTTPRequest(url), options, callback );
-}
-
-bool
-HTTPClient::doDownload(const std::string& url, const std::string& filename)
-{
-    initialize();
-
-    // download the data
-    HTTPResponse response = this->doGet( HTTPRequest(url) );
-
-    if ( response.isOK() )
-    {
-        unsigned int part_num = response.getNumParts() > 1? 1 : 0;
-        std::istream& input_stream = response.getPartStream( part_num );
-
-        std::ofstream fout;
-        fout.open(filename.c_str(), std::ios::out | std::ios::binary);
-
-        input_stream.seekg (0, std::ios::end);
-        int length = input_stream.tellg();
-        input_stream.seekg (0, std::ios::beg);
-
-        char *buffer = new char[length];
-        input_stream.read(buffer, length);
-        fout.write(buffer, length);
-        delete[] buffer;
-        fout.close();
-        return true;
-    }
-    else
-    {
-        OE_WARN << LC << "Error downloading file " << filename
-            << " (" << response.getCode() << ")" << std::endl;
-        return false;
-    } 
-}
-
-namespace
-{
-    osgDB::ReaderWriter*
-    getReader( const std::string& url, const HTTPResponse& response )
-    {
-        osgDB::ReaderWriter* reader = 0L;
-
-        // try extension first:
-        std::string ext = osgDB::getFileExtension( url );
-        if ( !ext.empty() )
-        {
-            reader = osgDB::Registry::instance()->getReaderWriterForExtension( ext );
-        }
-
-        if ( !reader )
-        {
-            // try to look up a reader by mime-type first:
-            std::string mimeType = response.getMimeType();
-            if ( !mimeType.empty() )
-            {
-                reader = osgDB::Registry::instance()->getReaderWriterForMimeType(mimeType);
-            }
-        }
-
-        if ( !reader )
-        {
-            OE_WARN << LC << "Cannot find an OSG plugin to read response data (ext="
-                << ext << "; mime-type=" << response.getMimeType()
-                << ")" << std::endl;
-        }
-
-        return reader;
-    }
-}
-
-ReadResult
-HTTPClient::doReadImage(const std::string&    location,
-                        const osgDB::Options* options,
-                        ProgressCallback*     callback)
-{
-    initialize();
-
-    ReadResult result;
-
-    HTTPResponse response = this->doGet(location, options, callback);
-
-    if (response.isOK())
-    {
-        osgDB::ReaderWriter* reader = getReader(location, response);
-        if (!reader)
-        {            
-            result = ReadResult(ReadResult::RESULT_NO_READER);
-        }
-
-        else 
-        {
-            osgDB::ReaderWriter::ReadResult rr = reader->readImage(response.getPartStream(0), options);
-            if ( rr.validImage() )
-            {
-                result = ReadResult(rr.takeImage(), response.getHeadersAsConfig() );
-            }
-            else 
-            {
-                if ( !rr.message().empty() )
-                {
-                    OE_WARN << LC << "HTTP error: " << rr.message() << std::endl;
-                }
-                OE_WARN << LC << reader->className() << " failed to read image from " << location << std::endl;
-                result = ReadResult(ReadResult::RESULT_READER_ERROR);
-            }
-        }
-        
-        // last-modified (file time)
-        TimeStamp filetime = 0;
-        if ( CURLE_OK == curl_easy_getinfo(_curl_handle, CURLINFO_FILETIME, &filetime) )
-        {
-            result.setLastModifiedTime( filetime );
-        }
-    }
-    else
-    {
-        result = ReadResult(
-            response.isCancelled() ? ReadResult::RESULT_CANCELED :
-            response.getCode() == HTTPResponse::NOT_FOUND ? ReadResult::RESULT_NOT_FOUND :
-            response.getCode() == HTTPResponse::SERVER_ERROR ? ReadResult::RESULT_SERVER_ERROR :
-            ReadResult::RESULT_UNKNOWN_ERROR );
-
-        //If we have an error but it's recoverable, like a server error or timeout then set the callback to retry.
-        if (HTTPClient::isRecoverable( result.code() ) )
-        {            
-            if (callback)
-            {
-                OE_DEBUG << "Error in HTTPClient for " << location << " but it's recoverable" << std::endl;
-                callback->setNeedsRetry( true );
-            }
-        }        
-    }
-
-    // set the source name
-    if ( result.getImage() )
-        result.getImage()->setName( location );
-
-    return result;
-}
-
-ReadResult
-HTTPClient::doReadNode(const std::string&    location,
-                       const osgDB::Options* options,
-                       ProgressCallback*     callback)
-{
-    initialize();
-
-    ReadResult result;
-
-    HTTPResponse response = this->doGet(location, options, callback);
-
-    if (response.isOK())
-    {
-        osgDB::ReaderWriter* reader = getReader(location, response);
-        if (!reader)
-        {
-            result = ReadResult(ReadResult::RESULT_NO_READER);
-        }
-
-        else 
-        {
-            osgDB::ReaderWriter::ReadResult rr = reader->readNode(response.getPartStream(0), options);
-            if ( rr.validNode() )
-            {
-                result = ReadResult(rr.takeNode(), response.getHeadersAsConfig());
-            }
-            else 
-            {
-                if ( !rr.message().empty() )
-                {
-                    OE_WARN << LC << "HTTP error: " << rr.message() << std::endl;
-                }
-                OE_WARN << LC << reader->className() << " failed to read node from " << location << std::endl;
-                result = ReadResult(ReadResult::RESULT_READER_ERROR);
-            }
-        }
-        
-        // last-modified (file time)
-        TimeStamp filetime = 0;
-        if ( CURLE_OK == curl_easy_getinfo(_curl_handle, CURLINFO_FILETIME, &filetime) )
-        {
-            result.setLastModifiedTime( filetime );
-        }
-    }
-    else
-    {
-        result = ReadResult(
-            response.isCancelled() ? ReadResult::RESULT_CANCELED :
-            response.getCode() == HTTPResponse::NOT_FOUND ? ReadResult::RESULT_NOT_FOUND :
-            response.getCode() == HTTPResponse::SERVER_ERROR ? ReadResult::RESULT_SERVER_ERROR :
-            ReadResult::RESULT_UNKNOWN_ERROR );
-
-        //If we have an error but it's recoverable, like a server error or timeout then set the callback to retry.
-        if (HTTPClient::isRecoverable( result.code() ) )
-        {
-            if (callback)
-            {
-                OE_DEBUG << "Error in HTTPClient for " << location << " but it's recoverable" << std::endl;
-                callback->setNeedsRetry( true );
-            }
-        }
-    }
-
-    return result;
-}
-
-ReadResult
-HTTPClient::doReadObject(const std::string&    location,
-                         const osgDB::Options* options,
-                         ProgressCallback*     callback)
-{
-    initialize();
-
-    ReadResult result;
-
-    HTTPResponse response = this->doGet(location, options, callback);
-
-    if (response.isOK())
-    {
-        osgDB::ReaderWriter* reader = getReader(location, response);
-        if (!reader)
-        {
-            result = ReadResult(ReadResult::RESULT_NO_READER);
-        }
-
-        else 
-        {
-            osgDB::ReaderWriter::ReadResult rr = reader->readObject(response.getPartStream(0), options);
-            if ( rr.validObject() )
-            {
-                result = ReadResult(rr.takeObject(), response.getHeadersAsConfig());
-            }
-            else 
-            {
-                if ( !rr.message().empty() )
-                {
-                    OE_WARN << LC << "HTTP error: " << rr.message() << std::endl;
-                }
-                OE_WARN << LC << reader->className() << " failed to read object from " << location << std::endl;
-                result = ReadResult(ReadResult::RESULT_READER_ERROR);
-            }
-        }
-        
-        // last-modified (file time)
-        TimeStamp filetime = 0;
-        if ( CURLE_OK == curl_easy_getinfo(_curl_handle, CURLINFO_FILETIME, &filetime) )
-        {
-            result.setLastModifiedTime( filetime );
-        }
-    }
-    else
-    {
-        result = ReadResult(
-            response.isCancelled() ? ReadResult::RESULT_CANCELED :
-            response.getCode() == HTTPResponse::NOT_FOUND ? ReadResult::RESULT_NOT_FOUND :
-            response.getCode() == HTTPResponse::SERVER_ERROR ? ReadResult::RESULT_SERVER_ERROR :
-            ReadResult::RESULT_UNKNOWN_ERROR );
-
-        //If we have an error but it's recoverable, like a server error or timeout then set the callback to retry.
-        if (HTTPClient::isRecoverable( result.code() ) )
-        {
-            if (callback)
-            {
-                OE_DEBUG << "Error in HTTPClient for " << location << " but it's recoverable" << std::endl;
-                callback->setNeedsRetry( true );
-            }
-        }
-    }
-
-    return result;
-}
-
-
-ReadResult
-HTTPClient::doReadString(const std::string&    location,
-                         const osgDB::Options* options,
-                         ProgressCallback*     callback )
-{
-    initialize();
-
-    ReadResult result;
-
-    HTTPResponse response = this->doGet( location, options, callback );
-    if ( response.isOK() )
-    {
-        result = ReadResult( new StringObject(response.getPartAsString(0)), response.getHeadersAsConfig());
-    }
-
-    else if ( response.getCode() >= 400 && response.getCode() < 500 && response.getCode() != 404 )
-    {
-        // for request errors, return an error result with the part data intact
-        // so the user can parse it as needed. We only do this for readString.
-        result = ReadResult( 
-            ReadResult::RESULT_SERVER_ERROR,
-            new StringObject(response.getPartAsString(0)), 
-            response.getHeadersAsConfig() );
-    }
-
-    else
-    {
-        result = ReadResult(
-            response.isCancelled() ? ReadResult::RESULT_CANCELED :
-            response.getCode() == HTTPResponse::NOT_FOUND ? ReadResult::RESULT_NOT_FOUND :
-            response.getCode() == HTTPResponse::SERVER_ERROR ? ReadResult::RESULT_SERVER_ERROR :
-            ReadResult::RESULT_UNKNOWN_ERROR );
-
-        //If we have an error but it's recoverable, like a server error or timeout then set the callback to retry.
-        if (HTTPClient::isRecoverable( result.code() ) )
-        {            
-            if (callback)
-            {
-                OE_DEBUG << "Error in HTTPClient for " << location << " but it's recoverable" << std::endl;
-                callback->setNeedsRetry( true );
-            }
-        }
-    }
-
-    // last-modified (file time)
-    TimeStamp filetime = 0;
-    if ( CURLE_OK == curl_easy_getinfo(_curl_handle, CURLINFO_FILETIME, &filetime) )
-    {
-        result.setLastModifiedTime( filetime );
-    }
-
-    return result;
-}
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osgEarth/HTTPClient>
+#include <osgEarth/Registry>
+#include <osgEarth/Version>
+#include <osgEarth/Progress>
+#include <osgEarth/StringUtils>
+#include <osgDB/ReadFile>
+#include <osgDB/Registry>
+#include <osgDB/FileNameUtils>
+#include <osg/Notify>
+#include <osg/Timer>
+#include <string.h>
+#include <sstream>
+#include <fstream>
+#include <iterator>
+#include <iostream>
+#include <algorithm>
+#include <curl/curl.h>
+
+#define LC "[HTTPClient] "
+
+//#define OE_TEST OE_NOTICE
+#define OE_TEST OE_NULL
+
+using namespace osgEarth;
+
+//----------------------------------------------------------------------------
+
+ProxySettings::ProxySettings( const Config& conf )
+{
+    mergeConfig( conf );
+}
+
+ProxySettings::ProxySettings( const std::string& host, int port ) :
+_hostName(host),
+_port(port)
+{
+    //nop
+}
+
+void
+ProxySettings::mergeConfig( const Config& conf )
+{
+    _hostName = conf.value<std::string>( "host", "" );
+    _port = conf.value<int>( "port", 8080 );
+    _userName = conf.value<std::string>( "username", "" );
+    _password = conf.value<std::string>( "password", "" );
+}
+
+Config
+ProxySettings::getConfig() const
+{
+    Config conf( "proxy" );
+    conf.add( "host", _hostName );
+    conf.add( "port", toString(_port) );
+    conf.add( "username", _userName);
+    conf.add( "password", _password);
+
+    return conf;
+}
+
+bool
+ProxySettings::fromOptions( const osgDB::Options* dbOptions, optional<ProxySettings>& out )
+{
+    if ( dbOptions )
+    {
+        std::string jsonString = dbOptions->getPluginStringData( "osgEarth::ProxySettings" );
+        if ( !jsonString.empty() )
+        {
+            Config conf;
+            conf.fromJSON( jsonString );
+            out = ProxySettings( conf );
+            return true;
+        }
+    }
+    return false;
+}
+
+void
+ProxySettings::apply( osgDB::Options* dbOptions ) const
+{
+    if ( dbOptions )
+    {
+        Config conf = getConfig();
+        dbOptions->setPluginStringData( "osgEarth::ProxySettings", conf.toJSON() );
+    }
+}
+
+/****************************************************************************/
+   
+namespace osgEarth
+{
+    struct StreamObject
+    {
+        StreamObject(std::ostream* stream) : _stream(stream) { }
+
+        void write(const char* ptr, size_t realsize)
+        {
+            if (_stream) _stream->write(ptr, realsize);
+        }
+
+        void writeHeader(const char* ptr, size_t realsize)
+        {            
+            std::string header(ptr);            
+            StringTokenizer tok(":");
+            StringVector tized;
+            tok.tokenize(header, tized);            
+            if ( tized.size() >= 2 )
+                _headers[tized[0]] = tized[1];                
+        }
+
+        std::ostream* _stream;
+        Headers _headers;
+        std::string     _resultMimeType;
+    };
+
+    static size_t
+    StreamObjectReadCallback(void* ptr, size_t size, size_t nmemb, void* data)
+    {
+        size_t realsize = size* nmemb;
+        StreamObject* sp = (StreamObject*)data;
+        sp->write((const char*)ptr, realsize);
+        return realsize;
+    }
+
+    static size_t
+    StreamObjectHeaderCallback(void* ptr, size_t size, size_t nmemb, void* data)
+    {
+        size_t realsize = size* nmemb;
+        StreamObject* sp = (StreamObject*)data;                
+        sp->writeHeader((const char*)ptr, realsize);        
+        return realsize;
+    }
+
+    TimeStamp
+    getCurlFileTime(void* curl)
+    {
+        long filetime;
+        if (CURLE_OK != curl_easy_getinfo(curl, CURLINFO_FILETIME, &filetime))
+            return TimeStamp(0);
+        else if (filetime < 0)
+            return TimeStamp(0);
+        else
+            return TimeStamp(filetime);
+    }
+}
+
+static int CurlProgressCallback(void *clientp,double dltotal,double dlnow,double ultotal,double ulnow)
+{
+    ProgressCallback* callback = (ProgressCallback*)clientp;
+    bool cancelled = false;
+    if (callback)
+    {
+        cancelled = callback->isCanceled() || callback->reportProgress(dlnow, dltotal);
+    }
+    return cancelled;
+}
+
+/****************************************************************************/
+
+HTTPRequest::HTTPRequest( const std::string& url )
+: _url( url )
+{
+    //NOP
+}
+
+HTTPRequest::HTTPRequest( const HTTPRequest& rhs ) :
+_parameters( rhs._parameters ),
+_headers(rhs._headers),
+_url( rhs._url )
+{
+    //nop
+}
+
+void
+HTTPRequest::addParameter( const std::string& name, const std::string& value )
+{
+    _parameters[name] = value;
+}
+
+void
+HTTPRequest::addParameter( const std::string& name, int value )
+{
+    std::stringstream buf;
+    buf << value;
+     std::string bufStr;
+    bufStr = buf.str();
+    _parameters[name] = bufStr;
+}
+
+void
+HTTPRequest::addParameter( const std::string& name, double value )
+{
+    std::stringstream buf;
+    buf << value;
+     std::string bufStr;
+    bufStr = buf.str();
+    _parameters[name] = bufStr;
+}
+
+const HTTPRequest::Parameters&
+HTTPRequest::getParameters() const
+{
+    return _parameters; 
+}
+
+void
+HTTPRequest::addHeader( const std::string& name, const std::string& value )
+{
+    _headers[name] = value;
+}
+
+const Headers&
+HTTPRequest::getHeaders() const
+{
+    return _headers; 
+}
+
+void HTTPRequest::setLastModified( const DateTime &lastModified)
+{    
+    addHeader("If-Modified-Since", lastModified.asRFC1123());
+}
+
+
+std::string
+HTTPRequest::getURL() const
+{
+    if ( _parameters.size() == 0 )
+    {
+        return _url;
+    }
+    else
+    {
+        std::stringstream buf;
+        buf << _url;
+        for( Parameters::const_iterator i = _parameters.begin(); i != _parameters.end(); i++ )
+        {
+            buf << ( i == _parameters.begin() && _url.find( "?" ) == std::string::npos? "?" : "&" );
+            buf << i->first << "=" << i->second;
+        }
+         std::string bufStr;
+         bufStr = buf.str();
+        return bufStr;
+    }
+}
+
+/****************************************************************************/
+
+HTTPResponse::HTTPResponse( long _code )
+: _response_code( _code ),
+  _cancelled(false)
+{
+    _parts.reserve(1);
+}
+
+HTTPResponse::HTTPResponse( const HTTPResponse& rhs ) :
+_response_code( rhs._response_code ),
+_parts( rhs._parts ),
+_mimeType( rhs._mimeType ),
+_cancelled( rhs._cancelled )
+{
+    //nop
+}
+
+unsigned
+HTTPResponse::getCode() const {
+    return _response_code;
+}
+
+bool
+HTTPResponse::isOK() const {
+    return _response_code == 200L && !isCancelled();
+}
+
+bool
+HTTPResponse::isCancelled() const {
+    return _cancelled;
+}
+
+unsigned int
+HTTPResponse::getNumParts() const {
+    return _parts.size();
+}
+
+unsigned int
+HTTPResponse::getPartSize( unsigned int n ) const {
+    return _parts[n]->_size;
+}
+
+const std::string&
+HTTPResponse::getPartHeader( unsigned int n, const std::string& name ) const {
+    return _parts[n]->_headers[name];
+}
+
+std::istream&
+HTTPResponse::getPartStream( unsigned int n ) const {
+    return _parts[n]->_stream;
+}
+
+std::string
+HTTPResponse::getPartAsString( unsigned int n ) const {
+     std::string streamStr;
+     streamStr = _parts[n]->_stream.str();
+    return streamStr;
+}
+
+const std::string&
+HTTPResponse::getMimeType() const {
+    return _mimeType;
+}
+
+Config
+HTTPResponse::getHeadersAsConfig() const
+{
+    Config conf;
+    if ( _parts.size() > 0 )
+    {
+        for( Headers::const_iterator i = _parts[0]->_headers.begin(); i != _parts[0]->_headers.end(); ++i )
+        {
+            conf.set(i->first, i->second);
+        }
+    }
+    return conf;
+}
+
+/****************************************************************************/
+
+#define QUOTE_(X) #X
+#define QUOTE(X) QUOTE_(X)
+#define USER_AGENT "osgearth" QUOTE(OSGEARTH_MAJOR_VERSION) "." QUOTE(OSGEARTH_MINOR_VERSION)
+
+
+namespace
+{
+    // TODO: consider moving this stuff into the osgEarth::Registry;
+    // don't like it here in the global scope
+    // per-thread client map (must be global scope)
+    static Threading::PerThread<HTTPClient> s_clientPerThread;
+
+    static optional<ProxySettings>     s_proxySettings;
+
+    static std::string                 s_userAgent = USER_AGENT;
+
+    static long                        s_timeout = 0;
+    static long                        s_connectTimeout = 0;
+
+    // HTTP debugging.
+    static bool                        s_HTTP_DEBUG = false;
+    static Threading::Mutex            s_HTTP_DEBUG_mutex;
+    static int                         s_HTTP_DEBUG_request_count;
+    static double                      s_HTTP_DEBUG_total_duration;
+
+    static osg::ref_ptr< URLRewriter > s_rewriter;
+
+    static osg::ref_ptr< CurlConfigHandler > s_curlConfigHandler;
+}
+
+HTTPClient&
+HTTPClient::getClient()
+{
+    return s_clientPerThread.get();
+}
+
+HTTPClient::HTTPClient() :
+_initialized    ( false ),
+_curl_handle    ( 0L ),
+_simResponseCode( -1L )
+{
+    //nop
+    //do no CURL calls here.
+}
+
+void
+HTTPClient::initialize() const
+{
+    if ( !_initialized )
+    {
+        const_cast<HTTPClient*>(this)->initializeImpl();
+    }
+}
+
+void
+HTTPClient::initializeImpl()
+{
+    _previousHttpAuthentication = 0;
+    _curl_handle = curl_easy_init();
+
+    //Get the user agent
+    std::string userAgent = s_userAgent;
+    const char* userAgentEnv = getenv("OSGEARTH_USERAGENT");
+    if (userAgentEnv)
+    {
+        userAgent = std::string(userAgentEnv);
+    }
+
+    //Check for a response-code simulation (for testing)
+    const char* simCode = getenv("OSGEARTH_SIMULATE_HTTP_RESPONSE_CODE");
+    if ( simCode )
+    {
+        _simResponseCode = osgEarth::as<long>(std::string(simCode), 404L);
+        OE_WARN << LC << "Simulating a network error with Response Code = " << _simResponseCode << std::endl;
+    }
+
+    // Dumps out HTTP request/response info
+    if ( ::getenv("OSGEARTH_HTTP_DEBUG") )
+    {
+        s_HTTP_DEBUG = true;
+        OE_WARN << LC << "HTTP debugging enabled" << std::endl;
+    }
+
+    OE_DEBUG << LC << "HTTPClient setting userAgent=" << userAgent << std::endl;
+
+    curl_easy_setopt( _curl_handle, CURLOPT_USERAGENT, userAgent.c_str() );
+    curl_easy_setopt( _curl_handle, CURLOPT_WRITEFUNCTION, osgEarth::StreamObjectReadCallback );
+    curl_easy_setopt( _curl_handle, CURLOPT_HEADERFUNCTION, osgEarth::StreamObjectHeaderCallback );
+    curl_easy_setopt( _curl_handle, CURLOPT_FOLLOWLOCATION, (void*)1 );
+    curl_easy_setopt( _curl_handle, CURLOPT_MAXREDIRS, (void*)5 );
+    curl_easy_setopt( _curl_handle, CURLOPT_PROGRESSFUNCTION, &CurlProgressCallback);
+    curl_easy_setopt( _curl_handle, CURLOPT_NOPROGRESS, (void*)0 ); //0=enable.
+    curl_easy_setopt( _curl_handle, CURLOPT_FILETIME, true );
+
+    osg::ref_ptr< CurlConfigHandler > curlConfigHandler = getCurlConfigHandler();
+    if (curlConfigHandler.valid()) {
+        curlConfigHandler->onInitialize(_curl_handle);
+    }
+
+    long timeout = s_timeout;
+    const char* timeoutEnv = getenv("OSGEARTH_HTTP_TIMEOUT");
+    if (timeoutEnv)
+    {
+        timeout = osgEarth::as<long>(std::string(timeoutEnv), 0);
+    }
+    OE_DEBUG << LC << "Setting timeout to " << timeout << std::endl;
+    curl_easy_setopt( _curl_handle, CURLOPT_TIMEOUT, timeout );
+    long connectTimeout = s_connectTimeout;
+    const char* connectTimeoutEnv = getenv("OSGEARTH_HTTP_CONNECTTIMEOUT");
+    if (connectTimeoutEnv)
+    {
+        connectTimeout = osgEarth::as<long>(std::string(connectTimeoutEnv), 0);
+    }
+    OE_DEBUG << LC << "Setting connect timeout to " << connectTimeout << std::endl;
+    curl_easy_setopt( _curl_handle, CURLOPT_CONNECTTIMEOUT, connectTimeout );
+
+    _initialized = true;
+}
+
+HTTPClient::~HTTPClient()
+{
+    if (_curl_handle) curl_easy_cleanup( _curl_handle );
+    _curl_handle = 0;
+}
+
+void
+HTTPClient::setProxySettings( const ProxySettings& proxySettings )
+{
+    s_proxySettings = proxySettings;
+}
+
+const std::string& HTTPClient::getUserAgent()
+{
+    return s_userAgent;
+}
+
+void  HTTPClient::setUserAgent(const std::string& userAgent)
+{
+    s_userAgent = userAgent;
+}
+
+long HTTPClient::getTimeout()
+{
+    return s_timeout;
+}
+
+void HTTPClient::setTimeout( long timeout )
+{
+    s_timeout = timeout;
+}
+
+long HTTPClient::getConnectTimeout()
+{
+    return s_connectTimeout;
+}
+
+void HTTPClient::setConnectTimeout( long timeout )
+{
+    s_connectTimeout = timeout;
+}
+URLRewriter* HTTPClient::getURLRewriter()
+{
+    return s_rewriter.get();
+}
+
+void HTTPClient::setURLRewriter( URLRewriter* rewriter )
+{
+    s_rewriter = rewriter;
+}
+
+CurlConfigHandler* HTTPClient::getCurlConfigHandler()
+{
+    return s_curlConfigHandler.get();
+}
+
+void HTTPClient::setCurlConfighandler(CurlConfigHandler* handler)
+{
+    s_curlConfigHandler = handler;
+}
+
+void
+HTTPClient::globalInit()
+{
+    curl_global_init(CURL_GLOBAL_ALL);
+}
+
+void
+HTTPClient::readOptions(const osgDB::Options* options, std::string& proxy_host, std::string& proxy_port) const
+{
+    // try to set proxy host/port by reading the CURL proxy options
+    if ( options )
+    {
+        std::istringstream iss( options->getOptionString() );
+        std::string opt;
+        while( iss >> opt )
+        {
+            int index = opt.find( "=" );
+            if( opt.substr( 0, index ) == "OSG_CURL_PROXY" )
+            {
+                proxy_host = opt.substr( index+1 );
+            }
+            else if ( opt.substr( 0, index ) == "OSG_CURL_PROXYPORT" )
+            {
+                proxy_port = opt.substr( index+1 );
+            }
+        }
+    }
+}
+
+void
+HTTPClient::decodeMultipartStream(const std::string&   boundary,
+                                  HTTPResponse::Part*  input,
+                                  HTTPResponse::Parts& output) const
+{
+    std::string bstr = std::string("--") + boundary;
+    std::string line;
+    char tempbuf[256];
+
+    // first thing in the stream should be the boundary.
+    input->_stream.read( tempbuf, bstr.length() );
+    tempbuf[bstr.length()] = 0;
+    line = tempbuf;
+    if ( line != bstr )
+    {
+        OE_WARN << LC 
+            << "decodeMultipartStream: protocol violation; "
+            << "expecting boundary; instead got: \"" 
+            << line
+            << "\"" << std::endl;
+        return;
+    }
+
+    for( bool done=false; !done; )
+    {
+        osg::ref_ptr<HTTPResponse::Part> next_part = new HTTPResponse::Part();
+
+        // first finish off the boundary.
+        std::getline( input->_stream, line );
+        if ( line == "--" )
+        {
+            done = true;
+        }
+        else
+        {
+            // read all headers. this ends with a blank line.
+            line = " ";
+            while( line.length() > 0 && !done )
+            {
+                std::getline( input->_stream, line );
+
+                // check for EOS:
+                if ( line == "--" )
+                {
+                    done = true;
+                }
+                else
+                {                    
+                    StringTokenizer tok(":");
+                    StringVector tized;
+                    tok.tokenize(line, tized);            
+                    if ( tized.size() >= 2 )
+                        next_part->_headers[tized[0]] = tized[1];                        
+                }
+            }
+        }
+
+        if ( !done )
+        {
+            // read data until we reach the boundary
+            unsigned int bstr_ptr = 0;
+            std::string temp;
+            //unsigned int c = 0;
+            while( bstr_ptr < bstr.length() )
+            {
+                char b;
+                input->_stream.read( &b, 1 );
+                if ( b == bstr[bstr_ptr] )
+                {
+                    bstr_ptr++;
+                }
+                else
+                {
+                    for( unsigned int i=0; i<bstr_ptr; i++ )
+                    {
+                        next_part->_stream << bstr[i];
+                    }
+                    next_part->_stream << b;
+                    next_part->_size += bstr_ptr + 1;
+                    bstr_ptr = 0;
+                }
+            }
+            output.push_back( next_part.get() );
+        }
+    }
+}
+
+HTTPResponse
+HTTPClient::get( const HTTPRequest&    request,
+                 const osgDB::Options* options,
+                 ProgressCallback*     progress)
+{
+    return getClient().doGet( request, options, progress );
+}
+
+HTTPResponse 
+HTTPClient::get( const std::string&    url,
+                 const osgDB::Options* options,
+                 ProgressCallback*     progress)
+{
+    return getClient().doGet( url, options, progress);
+}
+
+ReadResult
+HTTPClient::readImage(const HTTPRequest&    request,
+                      const osgDB::Options* options,
+                      ProgressCallback*     progress)
+{
+    return getClient().doReadImage( request, options, progress );
+}
+
+ReadResult
+HTTPClient::readNode(const HTTPRequest&    request,
+                     const osgDB::Options* options,
+                     ProgressCallback*     progress)
+{
+    return getClient().doReadNode( request, options, progress );
+}
+
+ReadResult
+HTTPClient::readObject(const HTTPRequest&    request,
+                       const osgDB::Options* options,
+                       ProgressCallback*     progress)
+{
+    return getClient().doReadObject( request, options, progress );
+}
+
+ReadResult
+HTTPClient::readString(const HTTPRequest&    request,
+                       const osgDB::Options* options,
+                       ProgressCallback*     progress)
+{
+    return getClient().doReadString( request, options, progress );
+}
+
+bool
+HTTPClient::download(const std::string& uri,
+                     const std::string& localPath)
+{
+    return getClient().doDownload( uri, localPath );
+}
+
+HTTPResponse
+HTTPClient::doGet(const HTTPRequest&    request,
+                  const osgDB::Options* options, 
+                  ProgressCallback*     progress) const
+{    
+    initialize();
+
+    OE_START_TIMER(http_get);
+
+    const osgDB::AuthenticationMap* authenticationMap = (options && options->getAuthenticationMap()) ? 
+            options->getAuthenticationMap() :
+            osgDB::Registry::instance()->getAuthenticationMap();
+
+    std::string proxy_host;
+    std::string proxy_port = "8080";
+
+    std::string proxy_auth;
+
+    //TODO: don't do all this proxy setup on every GET. Just do it once per client, or only when 
+    // the proxy information changes.
+
+    //Try to get the proxy settings from the global settings
+    if (s_proxySettings.isSet())
+    {
+        proxy_host = s_proxySettings.get().hostName();
+        std::stringstream buf;
+        buf << s_proxySettings.get().port();
+        proxy_port = buf.str();
+
+        std::string proxy_username = s_proxySettings.get().userName();
+        std::string proxy_password = s_proxySettings.get().password();
+        if (!proxy_username.empty() && !proxy_password.empty())
+        {
+            proxy_auth = proxy_username + std::string(":") + proxy_password;
+        }
+    }
+
+    //Try to get the proxy settings from the local options that are passed in.
+    readOptions( options, proxy_host, proxy_port );
+
+    optional< ProxySettings > proxySettings;
+    ProxySettings::fromOptions( options, proxySettings );
+    if (proxySettings.isSet())
+    {       
+        proxy_host = proxySettings.get().hostName();
+        proxy_port = toString<int>(proxySettings.get().port());
+        OE_DEBUG << "Read proxy settings from options " << proxy_host << " " << proxy_port << std::endl;
+    }
+
+    //Try to get the proxy settings from the environment variable
+    const char* proxyEnvAddress = getenv("OSG_CURL_PROXY");
+    if (proxyEnvAddress) //Env Proxy Settings
+    {
+        proxy_host = std::string(proxyEnvAddress);
+
+        const char* proxyEnvPort = getenv("OSG_CURL_PROXYPORT"); //Searching Proxy Port on Env
+        if (proxyEnvPort)
+        {
+            proxy_port = std::string( proxyEnvPort );
+        }
+    }
+
+    const char* proxyEnvAuth = getenv("OSGEARTH_CURL_PROXYAUTH");
+    if (proxyEnvAuth)
+    {
+        proxy_auth = std::string(proxyEnvAuth);
+    }
+
+    // Set up proxy server:
+    std::string proxy_addr;
+    if ( !proxy_host.empty() )
+    {
+        std::stringstream buf;
+        buf << proxy_host << ":" << proxy_port;
+        std::string bufStr;
+        bufStr = buf.str();
+        proxy_addr = bufStr;
+    
+        if ( s_HTTP_DEBUG )
+            OE_NOTICE << LC << "Using proxy: " << proxy_addr << std::endl;
+
+        //curl_easy_setopt( _curl_handle, CURLOPT_HTTPPROXYTUNNEL, 1 ); 
+        curl_easy_setopt( _curl_handle, CURLOPT_PROXY, proxy_addr.c_str() );
+
+        //Setup the proxy authentication if setup
+        if (!proxy_auth.empty())
+        {
+            if ( s_HTTP_DEBUG )
+                OE_NOTICE << LC << "Using proxy authentication " << proxy_auth << std::endl;
+
+            curl_easy_setopt( _curl_handle, CURLOPT_PROXYUSERPWD, proxy_auth.c_str());
+        }
+    }
+    else
+    {
+        OE_DEBUG << "Removing proxy settings" << std::endl;
+        curl_easy_setopt( _curl_handle, CURLOPT_PROXY, 0 );
+    }
+
+    std::string url = request.getURL();
+    // Rewrite the url if the url rewriter is available  
+    osg::ref_ptr< URLRewriter > rewriter = getURLRewriter();
+    if ( rewriter.valid() )
+    {
+        std::string oldURL = url;
+        url = rewriter->rewrite( oldURL );
+        OE_INFO << "Rewrote URL " << oldURL << " to " << url << std::endl;
+    }
+
+    const osgDB::AuthenticationDetails* details = authenticationMap ?
+        authenticationMap->getAuthenticationDetails( url ) :
+        0;
+
+    if (details)
+    {
+        const std::string colon(":");
+        std::string password(details->username + colon + details->password);
+        curl_easy_setopt(_curl_handle, CURLOPT_USERPWD, password.c_str());
+        const_cast<HTTPClient*>(this)->_previousPassword = password;
+
+        // use for https.
+        // curl_easy_setopt(_curl, CURLOPT_KEYPASSWD, password.c_str());
+
+#if LIBCURL_VERSION_NUM >= 0x070a07
+        if (details->httpAuthentication != _previousHttpAuthentication)
+        { 
+            curl_easy_setopt(_curl_handle, CURLOPT_HTTPAUTH, details->httpAuthentication); 
+            const_cast<HTTPClient*>(this)->_previousHttpAuthentication = details->httpAuthentication;
+        }
+#endif
+    }
+    else
+    {
+        if (!_previousPassword.empty())
+        {
+            curl_easy_setopt(_curl_handle, CURLOPT_USERPWD, 0);
+            const_cast<HTTPClient*>(this)->_previousPassword.clear();
+        }
+
+#if LIBCURL_VERSION_NUM >= 0x070a07
+        // need to reset if previously set.
+        if (_previousHttpAuthentication!=0)
+        {
+            curl_easy_setopt(_curl_handle, CURLOPT_HTTPAUTH, 0); 
+            const_cast<HTTPClient*>(this)->_previousHttpAuthentication = 0;
+        }
+#endif
+    }
+
+
+    // Set any headers
+    struct curl_slist *headers=NULL;
+    if (!request.getHeaders().empty())
+    {
+        for (HTTPRequest::Parameters::const_iterator itr = request.getHeaders().begin(); itr != request.getHeaders().end(); ++itr)
+        {
+            std::stringstream buf;
+            buf << itr->first << ": " << itr->second;
+            headers = curl_slist_append(headers, buf.str().c_str());
+        }
+    }    
+
+    // Disable the default Pragma: no-cache that curl adds by default.
+    headers = curl_slist_append(headers, "Pragma: ");
+    curl_easy_setopt(_curl_handle, CURLOPT_HTTPHEADER, headers);
+    
+    osg::ref_ptr<HTTPResponse::Part> part = new HTTPResponse::Part();
+    StreamObject sp( &part->_stream );
+
+    //Take a temporary ref to the callback (why? dangerous.)
+    //osg::ref_ptr<ProgressCallback> progressCallback = callback;
+    curl_easy_setopt( _curl_handle, CURLOPT_URL, url.c_str() );
+    if (progress)
+    {
+        curl_easy_setopt(_curl_handle, CURLOPT_PROGRESSDATA, progress);
+    }
+
+    CURLcode res;
+    long response_code = 0L;
+
+    OE_START_TIMER(get_duration);
+
+    if ( _simResponseCode < 0 )
+    {
+        char errorBuf[CURL_ERROR_SIZE];
+        errorBuf[0] = 0;
+        curl_easy_setopt( _curl_handle, CURLOPT_ERRORBUFFER, (void*)errorBuf );
+        curl_easy_setopt( _curl_handle, CURLOPT_WRITEDATA, (void*)&sp);
+        curl_easy_setopt( _curl_handle, CURLOPT_HEADERDATA, (void*)&sp);
+        
+        osg::ref_ptr< CurlConfigHandler > curlConfigHandler = getCurlConfigHandler();
+        if (curlConfigHandler.valid()) {
+            curlConfigHandler->onGet(_curl_handle);
+        }
+
+        res = curl_easy_perform(_curl_handle);
+        curl_easy_setopt( _curl_handle, CURLOPT_WRITEDATA, (void*)0 );
+        curl_easy_setopt( _curl_handle, CURLOPT_PROGRESSDATA, (void*)0);
+
+        //Disable peer certificate verification to allow us to access in https servers where the peer certificate cannot be verified.
+        curl_easy_setopt( _curl_handle, CURLOPT_SSL_VERIFYPEER, (void*)0 );
+
+        if (!proxy_addr.empty())
+        {
+            long connect_code = 0L;
+            CURLcode r = curl_easy_getinfo(_curl_handle, CURLINFO_HTTP_CONNECTCODE, &connect_code);
+            if ( r != CURLE_OK )
+            {
+                OE_WARN << LC << "Proxy connect error: " << curl_easy_strerror(r) << std::endl;
+                return HTTPResponse(0);
+            }
+        }
+
+        curl_easy_getinfo( _curl_handle, CURLINFO_RESPONSE_CODE, &response_code );        
+    }
+    else
+    {
+        // simulate failure with a custom response code
+        response_code = _simResponseCode;
+        res = response_code == 408 ? CURLE_OPERATION_TIMEDOUT : CURLE_COULDNT_CONNECT;
+    }
+
+    HTTPResponse response( response_code );    
+    
+    // read the response content type:
+    char* content_type_cp;
+
+    curl_easy_getinfo( _curl_handle, CURLINFO_CONTENT_TYPE, &content_type_cp );    
+
+    if ( content_type_cp != NULL )
+    {
+        response._mimeType = content_type_cp;    
+    } 
+
+    // upon success, parse the data:
+    if ( res != CURLE_ABORTED_BY_CALLBACK && res != CURLE_OPERATION_TIMEDOUT )
+    {        
+        // check for multipart content
+        if (response._mimeType.length() > 9 && 
+            ::strstr( response._mimeType.c_str(), "multipart" ) == response._mimeType.c_str() )
+        {
+            OE_DEBUG << LC << "detected multipart data; decoding..." << std::endl;
+
+            //TODO: parse out the "wcs" -- this is WCS-specific
+            decodeMultipartStream( "wcs", part.get(), response._parts );
+        }
+        else
+        {            
+            for (Headers::iterator itr = sp._headers.begin(); itr != sp._headers.end(); ++itr)
+            {                
+                part->_headers[itr->first] = itr->second;                
+            }
+
+            // Write the headers to the metadata
+            response._parts.push_back( part.get() );
+        }
+    }
+    else  /*if (res == CURLE_ABORTED_BY_CALLBACK || res == CURLE_OPERATION_TIMEDOUT) */
+    {        
+        //If we were aborted by a callback, then it was cancelled by a user
+        response._cancelled = true;
+    }
+
+    response._duration_s = OE_STOP_TIMER(get_duration);
+
+    if ( progress )
+    {
+        progress->stats()["http_get_time"] += OE_STOP_TIMER(http_get);
+        progress->stats()["http_get_count"] += 1;
+        if ( response._cancelled )
+            progress->stats()["http_cancel_count"] += 1;
+    }
+
+    if ( s_HTTP_DEBUG )
+    {
+        TimeStamp filetime = getCurlFileTime(_curl_handle);
+
+        OE_NOTICE << LC 
+            << "GET(" << response_code << ", " << response._mimeType << ") : \"" 
+            << url << "\" (" << DateTime(filetime).asRFC1123() << ") t="
+            << std::setprecision(4) << response.getDuration() << "s" << std::endl;
+
+        {
+            Threading::ScopedMutexLock lock(s_HTTP_DEBUG_mutex);
+            s_HTTP_DEBUG_request_count++;
+            s_HTTP_DEBUG_total_duration += response.getDuration();
+
+            if ( s_HTTP_DEBUG_request_count % 60 == 0 )
+            {
+                OE_NOTICE << LC << "Average duration = " << s_HTTP_DEBUG_total_duration/(double)s_HTTP_DEBUG_request_count
+                    << std::endl;
+            }
+        }
+
+#if 0
+        // time details - almost 100% of the time is spent in
+        // STARTTRANSFER, which is the time until the first byte is received.
+        double td[7];
+
+        curl_easy_getinfo(_curl_handle, CURLINFO_TOTAL_TIME,         &td[0]);
+        curl_easy_getinfo(_curl_handle, CURLINFO_NAMELOOKUP_TIME,    &td[1]);
+        curl_easy_getinfo(_curl_handle, CURLINFO_CONNECT_TIME,       &td[2]);
+        curl_easy_getinfo(_curl_handle, CURLINFO_APPCONNECT_TIME,    &td[3]);
+        curl_easy_getinfo(_curl_handle, CURLINFO_PRETRANSFER_TIME,   &td[4]);
+        curl_easy_getinfo(_curl_handle, CURLINFO_STARTTRANSFER_TIME, &td[5]);
+        curl_easy_getinfo(_curl_handle, CURLINFO_REDIRECT_TIME,      &td[6]);
+
+        for(int i=0; i<7; ++i)
+        {
+            OE_NOTICE << LC
+                << std::setprecision(4)
+                << "TIMES: total=" <<td[0]
+                << ", lookup=" <<td[1]<<" ("<<(int)((td[1]/td[0])*100)<<"%)"
+                << ", connect=" <<td[2]<<" ("<<(int)((td[2]/td[0])*100)<<"%)"
+                << ", appconn=" <<td[3]<<" ("<<(int)((td[3]/td[0])*100)<<"%)"
+                << ", prexfer=" <<td[4]<<" ("<<(int)((td[4]/td[0])*100)<<"%)"
+                << ", startxfer=" <<td[5]<<" ("<<(int)((td[5]/td[0])*100)<<"%)"
+                << ", redir=" <<td[6]<<" ("<<(int)((td[6]/td[0])*100)<<"%)"
+                << std::endl;
+        }
+#endif
+
+        // Free the headers
+        if (headers)
+        {
+            curl_slist_free_all(headers);
+        }
+    }
+
+    return response;
+}
+
+bool
+HTTPClient::doDownload(const std::string& url, const std::string& filename)
+{
+    initialize();
+
+    // download the data
+    HTTPResponse response = this->doGet( HTTPRequest(url) );
+
+    if ( response.isOK() )
+    {
+        unsigned int part_num = response.getNumParts() > 1? 1 : 0;
+        std::istream& input_stream = response.getPartStream( part_num );
+
+        std::ofstream fout;
+        fout.open(filename.c_str(), std::ios::out | std::ios::binary);
+
+        input_stream.seekg (0, std::ios::end);
+        int length = input_stream.tellg();
+        input_stream.seekg (0, std::ios::beg);
+
+        char *buffer = new char[length];
+        input_stream.read(buffer, length);
+        fout.write(buffer, length);
+        delete[] buffer;
+        fout.close();
+        return true;
+    }
+    else
+    {
+        OE_WARN << LC << "Error downloading file " << filename
+            << " (" << response.getCode() << ")" << std::endl;
+        return false;
+    } 
+}
+
+namespace
+{
+    osgDB::ReaderWriter*
+    getReader( const std::string& url, const HTTPResponse& response )
+    {        
+        osgDB::ReaderWriter* reader = 0L;
+
+        // try extension first:
+        std::string ext = osgDB::getFileExtension( url );
+        if ( !ext.empty() )
+        {
+            reader = osgDB::Registry::instance()->getReaderWriterForExtension( ext );
+        }
+
+        if ( !reader )
+        {
+            // try to look up a reader by mime-type first:
+            std::string mimeType = response.getMimeType();
+            if ( !mimeType.empty() )
+            {
+                reader = osgDB::Registry::instance()->getReaderWriterForMimeType(mimeType);
+            }
+        }
+
+        if ( !reader )
+        {
+            OE_WARN << LC << "Cannot find an OSG plugin to read response data (ext="
+                << ext << "; mime-type=" << response.getMimeType()
+                << ")" << std::endl;
+        }
+
+        return reader;
+    }
+}
+
+ReadResult
+HTTPClient::doReadImage(const HTTPRequest&    request,
+                        const osgDB::Options* options,
+                        ProgressCallback*     callback)
+{
+    initialize();
+
+    ReadResult result;
+
+    HTTPResponse response = this->doGet(request, options, callback);
+
+    if (response.isOK())
+    {
+        osgDB::ReaderWriter* reader = getReader(request.getURL(), response);
+        if (!reader)
+        {            
+            result = ReadResult(ReadResult::RESULT_NO_READER);
+        }
+
+        else 
+        {
+            osgDB::ReaderWriter::ReadResult rr = reader->readImage(response.getPartStream(0), options);
+            if ( rr.validImage() )
+            {
+                result = ReadResult(rr.takeImage(), response.getHeadersAsConfig() );
+            }
+            else 
+            {
+                if ( !rr.message().empty() )
+                {
+                    OE_WARN << LC << "HTTP error: " << rr.message() << std::endl;
+                }
+                OE_WARN << LC << reader->className() << " failed to read image from " << request.getURL() << std::endl;
+                result = ReadResult(ReadResult::RESULT_READER_ERROR);
+            }
+        }
+        
+        // last-modified (file time)
+        result.setLastModifiedTime( getCurlFileTime(_curl_handle) );
+        
+        // Time of query
+        result.setDuration( response.getDuration() );
+    }
+    else
+    {
+        result = ReadResult(
+            response.isCancelled() ? ReadResult::RESULT_CANCELED :
+            response.getCode() == HTTPResponse::NOT_FOUND ? ReadResult::RESULT_NOT_FOUND :
+            response.getCode() == HTTPResponse::SERVER_ERROR ? ReadResult::RESULT_SERVER_ERROR :
+            response.getCode() == HTTPResponse::NOT_MODIFIED ? ReadResult::RESULT_NOT_MODIFIED :
+            ReadResult::RESULT_UNKNOWN_ERROR );
+
+        //If we have an error but it's recoverable, like a server error or timeout then set the callback to retry.
+        if (HTTPClient::isRecoverable( result.code() ) )
+        {            
+            if (callback)
+            {
+                OE_DEBUG << "Error in HTTPClient for " << request.getURL() << " but it's recoverable" << std::endl;
+                callback->setNeedsRetry( true );
+            }
+        }        
+    }
+
+    // set the source name
+    if ( result.getImage() )
+        result.getImage()->setName( request.getURL() );
+
+    return result;
+}
+
+ReadResult
+HTTPClient::doReadNode(const HTTPRequest&    request,
+                       const osgDB::Options* options,
+                       ProgressCallback*     callback)
+{
+    initialize();
+
+    ReadResult result;
+
+    HTTPResponse response = this->doGet(request, options, callback);
+
+    if (response.isOK())
+    {
+        osgDB::ReaderWriter* reader = getReader(request.getURL(), response);
+        if (!reader)
+        {
+            result = ReadResult(ReadResult::RESULT_NO_READER);
+        }
+
+        else 
+        {
+            osgDB::ReaderWriter::ReadResult rr = reader->readNode(response.getPartStream(0), options);
+            if ( rr.validNode() )
+            {
+                result = ReadResult(rr.takeNode(), response.getHeadersAsConfig());
+            }
+            else 
+            {
+                if ( !rr.message().empty() )
+                {
+                    OE_WARN << LC << "HTTP error: " << rr.message() << std::endl;
+                }
+                OE_WARN << LC << reader->className() << " failed to read node from " << request.getURL() << std::endl;
+                result = ReadResult(ReadResult::RESULT_READER_ERROR);
+            }
+        }
+        
+        // last-modified (file time)
+        result.setLastModifiedTime( getCurlFileTime(_curl_handle) );
+    }
+    else
+    {
+        result = ReadResult(
+            response.isCancelled() ? ReadResult::RESULT_CANCELED :
+            response.getCode() == HTTPResponse::NOT_FOUND ? ReadResult::RESULT_NOT_FOUND :
+            response.getCode() == HTTPResponse::SERVER_ERROR ? ReadResult::RESULT_SERVER_ERROR :
+            response.getCode() == HTTPResponse::NOT_MODIFIED ? ReadResult::RESULT_NOT_MODIFIED :
+            ReadResult::RESULT_UNKNOWN_ERROR );
+
+        //If we have an error but it's recoverable, like a server error or timeout then set the callback to retry.
+        if (HTTPClient::isRecoverable( result.code() ) )
+        {
+            if (callback)
+            {
+                OE_DEBUG << "Error in HTTPClient for " << request.getURL() << " but it's recoverable" << std::endl;
+                callback->setNeedsRetry( true );
+            }
+        }
+    }
+
+    return result;
+}
+
+ReadResult
+HTTPClient::doReadObject(const HTTPRequest&    request,
+                         const osgDB::Options* options,
+                         ProgressCallback*     callback)
+{
+    initialize();
+
+    ReadResult result;
+
+    HTTPResponse response = this->doGet(request, options, callback);
+
+    if (response.isOK())
+    {
+        osgDB::ReaderWriter* reader = getReader(request.getURL(), response);
+        if (!reader)
+        {
+            result = ReadResult(ReadResult::RESULT_NO_READER);
+        }
+
+        else 
+        {
+            osgDB::ReaderWriter::ReadResult rr = reader->readObject(response.getPartStream(0), options);
+            if ( rr.validObject() )
+            {
+                result = ReadResult(rr.takeObject(), response.getHeadersAsConfig());
+            }
+            else 
+            {
+                if ( !rr.message().empty() )
+                {
+                    OE_WARN << LC << "HTTP error: " << rr.message() << std::endl;
+                }
+                OE_WARN << LC << reader->className() << " failed to read object from " << request.getURL() << std::endl;
+                result = ReadResult(ReadResult::RESULT_READER_ERROR);
+            }
+        }
+        
+        // last-modified (file time)
+        result.setLastModifiedTime( getCurlFileTime(_curl_handle) );
+    }
+    else
+    {
+        result = ReadResult(
+            response.isCancelled() ? ReadResult::RESULT_CANCELED :
+            response.getCode() == HTTPResponse::NOT_FOUND ? ReadResult::RESULT_NOT_FOUND :
+            response.getCode() == HTTPResponse::SERVER_ERROR ? ReadResult::RESULT_SERVER_ERROR :
+            response.getCode() == HTTPResponse::NOT_MODIFIED ? ReadResult::RESULT_NOT_MODIFIED :
+            ReadResult::RESULT_UNKNOWN_ERROR );
+
+        //If we have an error but it's recoverable, like a server error or timeout then set the callback to retry.
+        if (HTTPClient::isRecoverable( result.code() ) )
+        {
+            if (callback)
+            {
+                OE_DEBUG << "Error in HTTPClient for " << request.getURL() << " but it's recoverable" << std::endl;
+                callback->setNeedsRetry( true );
+            }
+        }
+    }
+
+    return result;
+}
+
+
+ReadResult
+HTTPClient::doReadString(const HTTPRequest&    request,
+                         const osgDB::Options* options,
+                         ProgressCallback*     callback )
+{
+    initialize();
+
+    ReadResult result;
+
+    HTTPResponse response = this->doGet( request, options, callback );
+    if ( response.isOK() )
+    {
+        result = ReadResult( new StringObject(response.getPartAsString(0)), response.getHeadersAsConfig());
+    }
+
+    else if ( response.getCode() >= 400 && response.getCode() < 500 && response.getCode() != 404 )
+    {
+        // for request errors, return an error result with the part data intact
+        // so the user can parse it as needed. We only do this for readString.
+        result = ReadResult( 
+            ReadResult::RESULT_SERVER_ERROR,
+            new StringObject(response.getPartAsString(0)), 
+            response.getHeadersAsConfig() );
+    }
+
+    else
+    {
+        result = ReadResult(
+            response.isCancelled() ? ReadResult::RESULT_CANCELED :
+            response.getCode() == HTTPResponse::NOT_FOUND ? ReadResult::RESULT_NOT_FOUND :
+            response.getCode() == HTTPResponse::SERVER_ERROR ? ReadResult::RESULT_SERVER_ERROR :
+            response.getCode() == HTTPResponse::NOT_MODIFIED ? ReadResult::RESULT_NOT_MODIFIED :
+            ReadResult::RESULT_UNKNOWN_ERROR );
+
+        //If we have an error but it's recoverable, like a server error or timeout then set the callback to retry.
+        if (HTTPClient::isRecoverable( result.code() ) )
+        {            
+            if (callback)
+            {
+                OE_DEBUG << "Error in HTTPClient for " << request.getURL() << " but it's recoverable" << std::endl;
+                callback->setNeedsRetry( true );
+            }
+        }
+    }
+
+    // last-modified (file time)
+    result.setLastModifiedTime( getCurlFileTime(_curl_handle) );
+
+    return result;
+}
diff --git a/src/osgEarth/HeightFieldUtils b/src/osgEarth/HeightFieldUtils
index a752744..20137d0 100644
--- a/src/osgEarth/HeightFieldUtils
+++ b/src/osgEarth/HeightFieldUtils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -51,7 +51,7 @@ namespace osgEarth
             }
         }
 
-        void getNeighborForNormalizedLocation(double nx, double ny, osg::ref_ptr<osg::HeightField>& hf, double& out_nx, double& out_ny) const {
+        bool getNeighborForNormalizedLocation(double nx, double ny, osg::ref_ptr<osg::HeightField>& hf, double& out_nx, double& out_ny) const {
             int xoffset = nx < 0.0 ? -1 : nx > 1.0 ? 1 : 0;
             int yoffset = ny < 0.0 ? 1 : ny > 1.0 ? -1 : 0;
             if ( xoffset != 0 || yoffset != 0 )
@@ -60,6 +60,7 @@ namespace osgEarth
                 hf = _center.get();
             out_nx = nx < 0.0 ? 1.0+nx : nx > 1.0 ? nx-1.0 : nx;
             out_ny = ny < 0.0 ? 1.0+ny : ny > 1.0 ? ny-1.0 : ny;
+            return hf.valid();
         }
     };
 
@@ -108,11 +109,12 @@ namespace osgEarth
         /**
          * Gets the interpolated elevation at the specified "normalized unit location".
          * i.e., nx => [-1.0...2.0], ny => [-1.0...2.0] since it can query neighbors
-         * as well.
+         * as well. Returns FALSE if there's no heightfield in the hood.
          */
-        static float getHeightAtNormalizedLocation(
+        static bool getHeightAtNormalizedLocation(
             const HeightFieldNeighborhood& hood,
             double nx, double ny,
+            double& output,
             ElevationInterpolation interp = INTERP_BILINEAR);
 
         /**
@@ -135,14 +137,18 @@ namespace osgEarth
         
         /**
          * Creates a heightfield containing MSL heights for the specified extent.
-         * If the SRS (in GeoExtent) has a vertical datum, the height values will be those of
-         * its reference geoid. If there is no vertical datum, we assume that MSL == the reference
-         * ellipsoid, and all the HF values will be zero.
+         *
+         * @param expressHeightsAsHAE
+         *      If the SRS (in GeoExtent) has a vertical datum, and expressHeightsAsHAE==true,
+         *      the height values will be those of its reference geoid. If there is no vertical
+         *      datum, or is expressHeightsAsHAE==false, we assume that MSL == the reference
+         *      ellipsoid and all the HF values will be zero.
          */
         static osg::HeightField* createReferenceHeightField( 
             const GeoExtent& ex, 
             unsigned         numCols,
-            unsigned         numRows );
+            unsigned         numRows,
+            bool             expressHeightsAsHAE =true);
 
         /**
          * Subsamples a heightfield to the specified extent.
@@ -166,7 +172,7 @@ namespace osgEarth
 
         /**
          * Resolves any "invalid" height values in the hieghtfield, replacing them
-         * with valid values from a Geoid (or zero if no geoid).
+         * with geodetic (ellipsoid) relative values from a Geoid (or zero if no geoid).
          */
         static void resolveInvalidHeights(
             osg::HeightField* grid,
diff --git a/src/osgEarth/HeightFieldUtils.cpp b/src/osgEarth/HeightFieldUtils.cpp
index df590a8..8f45292 100644
--- a/src/osgEarth/HeightFieldUtils.cpp
+++ b/src/osgEarth/HeightFieldUtils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -282,17 +282,22 @@ HeightFieldUtils::getHeightAtNormalizedLocation(const osg::HeightField* input,
     return getHeightAtPixel( input, px, py, interp );
 }
 
-float
+bool
 HeightFieldUtils::getHeightAtNormalizedLocation(const HeightFieldNeighborhood& hood,
                                                 double nx, double ny,
+                                                double& output,
                                                 ElevationInterpolation interp)
 {
     osg::ref_ptr<osg::HeightField> hf;
     double nx2, ny2;
-    hood.getNeighborForNormalizedLocation(nx, ny, hf, nx2, ny2);
-    double px = osg::clampBetween(nx2, 0.0, 1.0) * (double)(hf->getNumColumns() - 1);
-    double py = osg::clampBetween(ny2, 0.0, 1.0) * (double)(hf->getNumRows() - 1);
-    return getHeightAtPixel( hf.get(), px, py, interp );
+    if ( hood.getNeighborForNormalizedLocation(nx, ny, hf, nx2, ny2) )
+    {
+        double px = osg::clampBetween(nx2, 0.0, 1.0) * (double)(hf->getNumColumns() - 1);
+        double py = osg::clampBetween(ny2, 0.0, 1.0) * (double)(hf->getNumRows() - 1);
+        output = getHeightAtPixel( hf.get(), px, py, interp );
+        return true;
+    }
+    return false;
 }
 
 bool
@@ -370,6 +375,7 @@ HeightFieldUtils::createSubSample(osg::HeightField* input, const GeoExtent& inpu
     dest->allocate( numCols, numRows );
     dest->setXInterval( dx );
     dest->setYInterval( dy );
+    dest->setBorderWidth( input->getBorderWidth() );
 
     // copy over the skirt height, adjusting it for relative tile size.
     dest->setSkirtHeight( input->getSkirtHeight() * div );
@@ -435,7 +441,10 @@ HeightFieldUtils::resampleHeightField(osg::HeightField*      input,
 
 
 osg::HeightField*
-HeightFieldUtils::createReferenceHeightField( const GeoExtent& ex, unsigned numCols, unsigned numRows )
+HeightFieldUtils::createReferenceHeightField(const GeoExtent& ex,
+                                             unsigned         numCols,
+                                             unsigned         numRows,
+                                             bool             expressAsHAE)
 {
     osg::HeightField* hf = new osg::HeightField();
     hf->allocate( numCols, numRows );
@@ -445,7 +454,7 @@ HeightFieldUtils::createReferenceHeightField( const GeoExtent& ex, unsigned numC
 
     const VerticalDatum* vdatum = ex.isValid() ? ex.getSRS()->getVerticalDatum() : 0L;
 
-    if ( vdatum )
+    if ( vdatum && expressAsHAE )
     {
         // need the lat/long extent for geoid queries:
         GeoExtent geodeticExtent = ex.getSRS()->isGeographic() ? ex : ex.transform( ex.getSRS()->getGeographicSRS() );
@@ -468,7 +477,9 @@ HeightFieldUtils::createReferenceHeightField( const GeoExtent& ex, unsigned numC
     else
     {
         for(unsigned int i=0; i<hf->getHeightList().size(); i++ )
+        {
             hf->getHeightList()[i] = 0.0;
+        }
     }
 
     hf->setBorderWidth( 0 );
diff --git a/src/osgEarth/IOTypes b/src/osgEarth/IOTypes
index 801a663..35493b7 100644
--- a/src/osgEarth/IOTypes
+++ b/src/osgEarth/IOTypes
@@ -1,246 +1,256 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-* GNU Lesser General Public License for more details.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-
-#ifndef OSGEARTH_IOTYPES_H
-#define OSGEARTH_IOTYPES_H 1
-
-#include <osgEarth/Config>
-#include <osgEarth/DateTime>
-
-/**
- * A collectin of types used by the various I/O systems in osgEarth. These
- * are extended variations on some of OSG's ReaderWriter types.
- */
-namespace osgEarth
-{
-    /**
-     * String wrapped in an osg::Object (for I/O purposes)
-     */
-    class OSGEARTH_EXPORT StringObject : public osg::Object
-    {
-    public:
-        StringObject();
-        StringObject( const StringObject& rhs, const osg::CopyOp& op ) : osg::Object(rhs, op), _str(rhs._str) { }
-        StringObject( const std::string& in ) : osg::Object(), _str(in) { }
-
-        /** dtor */
-        virtual ~StringObject();
-        META_Object( osgEarth, StringObject );
-
-        void setString( const std::string& value );
-        const std::string& getString() const;
-    private:
-        std::string _str;
-    };
-
-
-//--------------------------------------------------------------------
-
-    /**
-     * Convenience metadata tags
-     */
-    struct OSGEARTH_EXPORT IOMetadata
-    {
-        static const std::string CONTENT_TYPE;
-    };
-
-//--------------------------------------------------------------------
-
-    /**
-     * Return value from a read* method
-     */
-    struct OSGEARTH_EXPORT ReadResult
-    {
-        /** Read result codes. */
-        enum Code
-        {
-            RESULT_OK,
-            RESULT_CANCELED,
-            RESULT_NOT_FOUND,
-            RESULT_EXPIRED,
-            RESULT_SERVER_ERROR,
-            RESULT_TIMEOUT,
-            RESULT_NO_READER,
-            RESULT_READER_ERROR,
-            RESULT_UNKNOWN_ERROR,
-            RESULT_NOT_IMPLEMENTED
-        };
-
-        /** Construct a result with no object */
-        ReadResult( Code code =RESULT_NOT_FOUND )
-            : _code(code), _fromCache(false), _lmt(0) { }
-
-        /** Construct a successful result */
-        ReadResult( osg::Object* result )
-            : _code(RESULT_OK), _result(result), _fromCache(false), _lmt(0) { }
-
-        /** Construct a successful result with metadata */
-        ReadResult( osg::Object* result, const Config& meta )
-            : _code(RESULT_OK), _result(result), _meta(meta), _fromCache(false), _lmt(0) { }
-
-        /** Construct a result with data, possible with an error code */
-        ReadResult( Code code, osg::Object* result, const Config& meta )
-            : _code(code), _result(result), _meta(meta), _fromCache(false), _lmt(0) { }
-
-        /** Copy construct */
-        ReadResult( const ReadResult& rhs )
-            : _code(rhs._code), _result(rhs._result.get()), _meta(rhs._meta), _fromCache(rhs._fromCache), _lmt(rhs._lmt) { }
-
-        /** dtor */
-        virtual ~ReadResult() { }
-
-        /** Whether the read operation succeeded */
-        bool succeeded() const { return _code == RESULT_OK && _result.valid(); }
-
-        /** Whether the read operation failed */
-        bool failed() const { return _code != RESULT_OK; }
-
-        /** Whether the result contains an object */
-        bool empty() const { return !_result.valid(); }
-
-        /** The result code */
-        const Code& code() const { return _code; }
-
-        /** Last modified timestamp */
-        TimeStamp lastModifiedTime() const { return _lmt; }
-
-        /** True if the object came from the cache */
-        bool isFromCache() const { return _fromCache; }
-
-        /** The result */
-        osg::Object* getObject() const { return _result.get(); }
-        osg::Image*  getImage()  const { return get<osg::Image>(); }
-        osg::Node*   getNode()   const { return get<osg::Node>(); }
-
-        /** The result, transfering ownership to the caller */
-        osg::Object* releaseObject() { return _result.release(); }
-        osg::Image*  releaseImage()  { return release<osg::Image>(); }
-        osg::Node*   releaseNode()   { return release<osg::Node>(); }
-
-        /** The metadata */
-        const Config& metadata() const { return _meta; }
-
-        /** The result, cast to a custom type */
-        template<typename T>
-        T* get() const { return dynamic_cast<T*>(_result.get()); }
-
-        /** The result, cast to a custom type and transfering ownership to the caller*/
-        template<typename T>
-        T* release() { return dynamic_cast<T*>(_result.get())? static_cast<T*>(_result.release()) : 0L; }
-
-        /** The result as a string */
-        const std::string& getString() const { const StringObject* so = dynamic_cast<StringObject*>(_result.get()); return so ? so->getString() : _emptyString; }
-        
-        /** Gets a string describing the read result */
-        static std::string getResultCodeString( unsigned code )
-        {
-            return
-                code == RESULT_OK              ? "OK" :
-                code == RESULT_CANCELED        ? "Read canceled" :
-                code == RESULT_NOT_FOUND       ? "Target not found" :
-                code == RESULT_SERVER_ERROR    ? "Server reported error" :
-                code == RESULT_TIMEOUT         ? "Read timed out" :
-                code == RESULT_NO_READER       ? "No suitable ReaderWriter found" :
-                code == RESULT_READER_ERROR    ? "ReaderWriter error" :
-                code == RESULT_NOT_IMPLEMENTED ? "Not implemented" :
-                "Unknown error";
-        }
-
-        std::string getResultCodeString() const
-        {
-            return getResultCodeString( _code );
-        }
-
-    public:
-        void setIsFromCache(bool value) { _fromCache = value; }
-
-        void setLastModifiedTime(TimeStamp t) { _lmt = t; }
-
-    protected:
-        Code                      _code;
-        osg::ref_ptr<osg::Object> _result;
-        Config                    _meta;
-        std::string               _emptyString;
-        Config                    _emptyConfig;
-        bool                      _fromCache;
-        TimeStamp                 _lmt;
-    };
-
-//--------------------------------------------------------------------
-
-    /**
-     * Callback that allows the developer to re-route URI read calls. 
-     *
-     * If the corresponding callback method returns NOT_IMPLEMENTED, URI will
-     * fall back on its default mechanism.
-     */
-    class OSGEARTH_EXPORT URIReadCallback : public osg::Referenced
-    {
-    public:
-        enum CachingSupport
-        {
-            CACHE_NONE        = 0,
-            CACHE_OBJECTS     = 1 << 0,
-            CACHE_NODES       = 1 << 1,
-            CACHE_IMAGES      = 1 << 2,
-            CACHE_STRINGS     = 1 << 3,
-            CACHE_CONFIGS     = 1 << 4,
-            CACHE_ALL         = ~0
-        };
-
-        /** 
-         * Tells the URI class which data types (if any) from this callback should be subjected
-         * to osgEarth's caching mechamism. By default, the answer is "none" - URI
-         * will not attempt to read or write from its cache when using this callback.
-         */
-        virtual unsigned cachingSupport() const { return CACHE_NONE; }
-
-    public:
-
-        /** Override the readObject() implementation */
-        virtual osgEarth::ReadResult readObject( const std::string& uri, const osgDB::Options* options ) {
-            return osgEarth::ReadResult::RESULT_NOT_IMPLEMENTED; }
-
-        /** Override the readNode() implementation */
-        virtual osgEarth::ReadResult readNode( const std::string& uri, const osgDB::Options* options ) {
-            return osgEarth::ReadResult::RESULT_NOT_IMPLEMENTED; }
-
-        /** Override the readImage() implementation */
-        virtual osgEarth::ReadResult readImage( const std::string& uri, const osgDB::Options* options ) {
-            return osgEarth::ReadResult::RESULT_NOT_IMPLEMENTED; }
-
-        /** Override the readString() implementation */
-        virtual osgEarth::ReadResult readString( const std::string& uri, const osgDB::Options* options ) {
-            return osgEarth::ReadResult::RESULT_NOT_IMPLEMENTED; }
-
-        /** Override the readConfig() implementation */
-        virtual osgEarth::ReadResult readConfig( const std::string& uri, const osgDB::Options* options ) {
-            return osgEarth::ReadResult::RESULT_NOT_IMPLEMENTED; }
-
-    protected:
-
-        URIReadCallback();
-
-        /** dtor */
-        virtual ~URIReadCallback();
-    };
-
-}
-
-#endif // OSGEARTH_IOTYPES_H
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+#ifndef OSGEARTH_IOTYPES_H
+#define OSGEARTH_IOTYPES_H 1
+
+#include <osgEarth/Config>
+#include <osgEarth/DateTime>
+
+/**
+ * A collectin of types used by the various I/O systems in osgEarth. These
+ * are extended variations on some of OSG's ReaderWriter types.
+ */
+namespace osgEarth
+{
+    /**
+     * String wrapped in an osg::Object (for I/O purposes)
+     */
+    class OSGEARTH_EXPORT StringObject : public osg::Object
+    {
+    public:
+        StringObject();
+        StringObject( const StringObject& rhs, const osg::CopyOp& op ) : osg::Object(rhs, op), _str(rhs._str) { }
+        StringObject( const std::string& in ) : osg::Object(), _str(in) { }
+
+        /** dtor */
+        virtual ~StringObject();
+        META_Object( osgEarth, StringObject );
+
+        void setString( const std::string& value );
+        const std::string& getString() const;
+    private:
+        std::string _str;
+    };
+
+
+//--------------------------------------------------------------------
+
+    /**
+     * Convenience metadata tags
+     */
+    struct OSGEARTH_EXPORT IOMetadata
+    {
+        static const std::string CONTENT_TYPE;
+    };
+
+//--------------------------------------------------------------------
+
+    /**
+     * Return value from a read* method
+     */
+    struct OSGEARTH_EXPORT ReadResult
+    {
+        /** Read result codes. */
+        enum Code
+        {
+            RESULT_OK,
+            RESULT_CANCELED,
+            RESULT_NOT_FOUND,
+            RESULT_EXPIRED,
+            RESULT_SERVER_ERROR,
+            RESULT_TIMEOUT,
+            RESULT_NO_READER,
+            RESULT_READER_ERROR,
+            RESULT_UNKNOWN_ERROR,
+            RESULT_NOT_IMPLEMENTED,
+            RESULT_NOT_MODIFIED
+        };
+
+        /** Construct a result with no object */
+        ReadResult( Code code =RESULT_NOT_FOUND )
+            : _code(code), _fromCache(false), _lmt(0) { }
+
+        /** Construct a successful result */
+        ReadResult( osg::Object* result )
+            : _code(RESULT_OK), _result(result), _fromCache(false), _lmt(0) { }
+
+        /** Construct a successful result with metadata */
+        ReadResult( osg::Object* result, const Config& meta )
+            : _code(RESULT_OK), _result(result), _meta(meta), _fromCache(false), _lmt(0) { }
+
+        /** Construct a result with data, possible with an error code */
+        ReadResult( Code code, osg::Object* result, const Config& meta )
+            : _code(code), _result(result), _meta(meta), _fromCache(false), _lmt(0) { }
+
+        /** Copy construct */
+        ReadResult( const ReadResult& rhs )
+            : _code(rhs._code), _result(rhs._result.get()), _meta(rhs._meta), _fromCache(rhs._fromCache), _lmt(rhs._lmt) { }
+
+        /** dtor */
+        virtual ~ReadResult() { }
+
+        /** Whether the read operation succeeded */
+        bool succeeded() const
+        {
+            return _code == RESULT_OK && _result.valid();
+        }
+
+        /** Whether the read operation failed */
+        bool failed() const { return _code != RESULT_OK; }
+
+        /** Whether the result contains an object */
+        bool empty() const { return !_result.valid(); }
+
+        /** The result code */
+        const Code& code() const { return _code; }
+
+        /** Last modified timestamp */
+        TimeStamp lastModifiedTime() const { return _lmt; }
+
+        /** Duration of request/response in seconds */
+        double duration() const { return _duration_s; }
+
+        /** True if the object came from the cache */
+        bool isFromCache() const { return _fromCache; }
+
+        /** The result */
+        osg::Object* getObject() const { return _result.get(); }
+        osg::Image*  getImage()  const { return get<osg::Image>(); }
+        osg::Node*   getNode()   const { return get<osg::Node>(); }
+
+        /** The result, transfering ownership to the caller */
+        osg::Object* releaseObject() { return _result.release(); }
+        osg::Image*  releaseImage()  { return release<osg::Image>(); }
+        osg::Node*   releaseNode()   { return release<osg::Node>(); }
+
+        /** The metadata */
+        const Config& metadata() const { return _meta; }
+
+        /** The result, cast to a custom type */
+        template<typename T>
+        T* get() const { return dynamic_cast<T*>(_result.get()); }
+
+        /** The result, cast to a custom type and transfering ownership to the caller*/
+        template<typename T>
+        T* release() { return dynamic_cast<T*>(_result.get())? static_cast<T*>(_result.release()) : 0L; }
+
+        /** The result as a string */
+        const std::string& getString() const { const StringObject* so = dynamic_cast<StringObject*>(_result.get()); return so ? so->getString() : _emptyString; }
+        
+        /** Gets a string describing the read result */
+        static std::string getResultCodeString( unsigned code )
+        {
+            return
+                code == RESULT_OK              ? "OK" :
+                code == RESULT_CANCELED        ? "Read canceled" :
+                code == RESULT_NOT_FOUND       ? "Target not found" :
+                code == RESULT_SERVER_ERROR    ? "Server reported error" :
+                code == RESULT_TIMEOUT         ? "Read timed out" :
+                code == RESULT_NO_READER       ? "No suitable ReaderWriter found" :
+                code == RESULT_READER_ERROR    ? "ReaderWriter error" :
+                code == RESULT_NOT_IMPLEMENTED ? "Not implemented" :
+                "Unknown error";
+        }
+
+        std::string getResultCodeString() const
+        {
+            return getResultCodeString( _code );
+        }
+
+    public:
+        void setIsFromCache(bool value) { _fromCache = value; }
+
+        void setLastModifiedTime(TimeStamp t) { _lmt = t; }
+
+        void setDuration(double s) { _duration_s = s; }
+
+    protected:
+        Code                      _code;
+        osg::ref_ptr<osg::Object> _result;
+        Config                    _meta;
+        std::string               _emptyString;
+        Config                    _emptyConfig;
+        bool                      _fromCache;
+        TimeStamp                 _lmt;
+        double                    _duration_s;
+    };
+
+//--------------------------------------------------------------------
+
+    /**
+     * Callback that allows the developer to re-route URI read calls. 
+     *
+     * If the corresponding callback method returns NOT_IMPLEMENTED, URI will
+     * fall back on its default mechanism.
+     */
+    class OSGEARTH_EXPORT URIReadCallback : public osg::Referenced
+    {
+    public:
+        enum CachingSupport
+        {
+            CACHE_NONE        = 0,
+            CACHE_OBJECTS     = 1 << 0,
+            CACHE_NODES       = 1 << 1,
+            CACHE_IMAGES      = 1 << 2,
+            CACHE_STRINGS     = 1 << 3,
+            CACHE_CONFIGS     = 1 << 4,
+            CACHE_ALL         = ~0
+        };
+
+        /** 
+         * Tells the URI class which data types (if any) from this callback should be subjected
+         * to osgEarth's caching mechamism. By default, the answer is "none" - URI
+         * will not attempt to read or write from its cache when using this callback.
+         */
+        virtual unsigned cachingSupport() const { return CACHE_NONE; }
+
+    public:
+
+        /** Override the readObject() implementation */
+        virtual osgEarth::ReadResult readObject( const std::string& uri, const osgDB::Options* options ) {
+            return osgEarth::ReadResult::RESULT_NOT_IMPLEMENTED; }
+
+        /** Override the readNode() implementation */
+        virtual osgEarth::ReadResult readNode( const std::string& uri, const osgDB::Options* options ) {
+            return osgEarth::ReadResult::RESULT_NOT_IMPLEMENTED; }
+
+        /** Override the readImage() implementation */
+        virtual osgEarth::ReadResult readImage( const std::string& uri, const osgDB::Options* options ) {
+            return osgEarth::ReadResult::RESULT_NOT_IMPLEMENTED; }
+
+        /** Override the readString() implementation */
+        virtual osgEarth::ReadResult readString( const std::string& uri, const osgDB::Options* options ) {
+            return osgEarth::ReadResult::RESULT_NOT_IMPLEMENTED; }
+
+        /** Override the readConfig() implementation */
+        virtual osgEarth::ReadResult readConfig( const std::string& uri, const osgDB::Options* options ) {
+            return osgEarth::ReadResult::RESULT_NOT_IMPLEMENTED; }
+
+    protected:
+
+        URIReadCallback();
+
+        /** dtor */
+        virtual ~URIReadCallback();
+    };
+
+}
+
+#endif // OSGEARTH_IOTYPES_H
diff --git a/src/osgEarth/IOTypes.cpp b/src/osgEarth/IOTypes.cpp
index 5f03c8d..1bbcce9 100644
--- a/src/osgEarth/IOTypes.cpp
+++ b/src/osgEarth/IOTypes.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -27,7 +27,7 @@ using namespace osgEarth;
 
 //------------------------------------------------------------------------
 
-const std::string IOMetadata::CONTENT_TYPE = "Content-type";
+const std::string IOMetadata::CONTENT_TYPE = "Content-Type";
 
 //------------------------------------------------------------------------
 
diff --git a/src/osgEarth/ImageLayer b/src/osgEarth/ImageLayer
index 7795107..d553dc4 100644
--- a/src/osgEarth/ImageLayer
+++ b/src/osgEarth/ImageLayer
@@ -1,5 +1,5 @@
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -121,6 +121,13 @@ namespace osgEarth
         optional<osg::Texture::FilterMode>& magFilter() { return _magFilter; }
         const optional<osg::Texture::FilterMode>& magFilter() const { return _magFilter; }
 
+        /**
+         * Texture compression mode to use. Default is "unset", which means to 
+         * automatically compute the best compression mode to use.
+         */
+        optional<osg::Texture::InternalFormatMode>& textureCompression() { return _texcomp; }
+        const optional<osg::Texture::InternalFormatMode>& textureCompression() const { return _texcomp; }
+
     public:
 
         virtual Config getConfig() const { return getConfig(false); }
@@ -142,6 +149,7 @@ namespace osgEarth
         optional<bool>        _featherPixels;
         optional<osg::Texture::FilterMode> _minFilter;
         optional<osg::Texture::FilterMode> _magFilter;
+        optional<osg::Texture::InternalFormatMode> _texcomp;
     };
 
     //--------------------------------------------------------------------
@@ -279,12 +287,17 @@ namespace osgEarth
          * image is in the profile of the key and will be reprojected, mosaiced and
          * cropped automatically.
          */
-        virtual GeoImage createImage( const TileKey& key, ProgressCallback* progress = 0, bool forceFallback =false);
+        virtual GeoImage createImage( const TileKey& key, ProgressCallback* progress = 0);
 
         /**
          * Creates an image that is in the image layer's native profile.
          */
-        GeoImage createImageInNativeProfile(const TileKey& key, ProgressCallback* progress, bool forceFallback, bool& out_isFallback);
+        GeoImage createImageInNativeProfile(const TileKey& key, ProgressCallback* progress);
+
+        /**
+         * Applies the texture compression options to a texture.
+         */
+        void applyTextureCompressionMode(osg::Texture* texture) const;
 
     public: // TerrainLayer override
 
@@ -294,16 +307,16 @@ namespace osgEarth
     protected:
 
         // Creates an image that's in the same profile as the provided key.
-        GeoImage createImageInKeyProfile(const TileKey& key, ProgressCallback* progress, bool forceFallback, bool& out_isFallback);
+        GeoImage createImageInKeyProfile(const TileKey& key, ProgressCallback* progress);
 
         // Fetches an image from the underlying TileSource whose data matches that of the
         // key extent.
-        GeoImage createImageFromTileSource(const TileKey& key, ProgressCallback* progress, bool forceFallback, bool& out_isFallback);
+        GeoImage createImageFromTileSource(const TileKey& key, ProgressCallback* progress);
 
         // Fetches multiple images from the TileSource; mosaics/reprojects/crops as necessary, and
         // returns a single tile. This is called by createImageFromTileSource() if the key profile
         // doesn't match the layer profile.
-        GeoImage assembleImageFromTileSource(const TileKey& key, ProgressCallback* progress, bool& out_isFallback);
+        GeoImage assembleImageFromTileSource(const TileKey& key, ProgressCallback* progress);
 
 
         virtual void initTileSource();
diff --git a/src/osgEarth/ImageLayer.cpp b/src/osgEarth/ImageLayer.cpp
index 1ef102c..8e8d378 100644
--- a/src/osgEarth/ImageLayer.cpp
+++ b/src/osgEarth/ImageLayer.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -25,6 +25,9 @@
 #include <osgEarth/StringUtils>
 #include <osgEarth/Progress>
 #include <osgEarth/URI>
+#include <osgEarth/MemCache>
+#include <osgEarth/Registry>
+#include <osgEarth/Capabilities>
 #include <osg/Version>
 #include <osgDB/WriteFile>
 #include <memory.h>
@@ -64,8 +67,9 @@ ImageLayerOptions::setDefaults()
     _maxRange.init( FLT_MAX );
     _lodBlending.init( false );
     _featherPixels.init( false );
-    _minFilter.init( osg::Texture::LINEAR );
+    _minFilter.init( osg::Texture::LINEAR_MIPMAP_LINEAR );
     _magFilter.init( osg::Texture::LINEAR );
+    _texcomp.init( osg::Texture::USE_IMAGE_DATA_FORMAT ); // none
 }
 
 void
@@ -107,6 +111,10 @@ ImageLayerOptions::fromConfig( const Config& conf )
     conf.getIfSet("min_filter","NEAREST",               _minFilter,osg::Texture::NEAREST);
     conf.getIfSet("min_filter","NEAREST_MIPMAP_LINEAR", _minFilter,osg::Texture::NEAREST_MIPMAP_LINEAR);
     conf.getIfSet("min_filter","NEAREST_MIPMAP_NEAREST",_minFilter,osg::Texture::NEAREST_MIPMAP_NEAREST);
+
+    conf.getIfSet("texture_compression", "none", _texcomp, osg::Texture::USE_IMAGE_DATA_FORMAT);
+    conf.getIfSet("texture_compression", "auto", _texcomp, (osg::Texture::InternalFormatMode)~0);
+    //TODO add all the enums
 }
 
 Config
@@ -146,7 +154,12 @@ ImageLayerOptions::getConfig( bool isolate ) const
     conf.updateIfSet("min_filter","NEAREST",               _minFilter,osg::Texture::NEAREST);
     conf.updateIfSet("min_filter","NEAREST_MIPMAP_LINEAR", _minFilter,osg::Texture::NEAREST_MIPMAP_LINEAR);
     conf.updateIfSet("min_filter","NEAREST_MIPMAP_NEAREST",_minFilter,osg::Texture::NEAREST_MIPMAP_NEAREST);
-    
+
+    conf.updateIfSet("texture_compression", "none", _texcomp, osg::Texture::USE_IMAGE_DATA_FORMAT);
+    conf.updateIfSet("texture_compression", "auto", _texcomp, (osg::Texture::InternalFormatMode)~0);
+    conf.updateIfSet("texture_compression", "on",   _texcomp, (osg::Texture::InternalFormatMode)~0);
+    //TODO add all the enums
+
     return conf;
 }
 
@@ -275,6 +288,12 @@ _runtimeOptions( options )
 void
 ImageLayer::init()
 {
+    // Set the tile size to 256 if it's not explicitly set.
+    if (!_runtimeOptions.driver()->tileSize().isSet())
+    {
+        _runtimeOptions.driver()->tileSize().init( 256 );
+    }
+
     _emptyImage = ImageUtils::createEmptyImage();
     //*((unsigned*)_emptyImage->data()) = 0x7F0000FF;
 }
@@ -401,7 +420,7 @@ ImageLayer::initPreCacheOp()
 
 
 CacheBin*
-ImageLayer::getCacheBin( const Profile* profile )
+ImageLayer::getCacheBin( const Profile* profile)
 {
     // specialize ImageLayer to only consider the horizontal signature (ignore vertical
     // datum component for images)
@@ -411,17 +430,18 @@ ImageLayer::getCacheBin( const Profile* profile )
 
 
 GeoImage
-ImageLayer::createImage( const TileKey& key, ProgressCallback* progress, bool forceFallback )
+ImageLayer::createImage(const TileKey&    key,
+                        ProgressCallback* progress)
 {
-    bool isFallback;
-    return createImageInKeyProfile( key, progress, forceFallback, isFallback);
+    return createImageInKeyProfile( key, progress );
 }
 
 
 GeoImage
-ImageLayer::createImageInNativeProfile( const TileKey& key, ProgressCallback* progress, bool forceFallback, bool& out_isFallback)
+ImageLayer::createImageInNativeProfile(const TileKey&    key,
+                                       ProgressCallback* progress)
 {
-    out_isFallback = false;
+    GeoImage result;
 
     const Profile* nativeProfile = getProfile();
     if ( !nativeProfile )
@@ -430,22 +450,17 @@ ImageLayer::createImageInNativeProfile( const TileKey& key, ProgressCallback* pr
         return GeoImage::INVALID;
     }
 
-    if ( key.getProfile()->isEquivalentTo(nativeProfile) )
+    if ( key.getProfile()->isHorizEquivalentTo(nativeProfile) )
     {
         // requested profile matches native profile, move along.
-        return createImageInKeyProfile( key, progress, forceFallback, out_isFallback );
+        result = createImageInKeyProfile( key, progress );
     }
     else
     {
         // find the intersection of keys.
         std::vector<TileKey> nativeKeys;
-        nativeProfile->getIntersectingTiles(key.getExtent(), nativeKeys);
+        nativeProfile->getIntersectingTiles(key, nativeKeys);
 
-
-        //OE_INFO << "KEY = " << key.str() << ":" << std::endl;
-        //for(int i=0; i<nativeKeys.size(); ++i)
-        //    OE_INFO << "    " << nativeKeys[i].str() << std::endl;
-        
         // build a mosaic of the images from the native profile keys:
         bool foundAtLeastOneRealTile = false;
 
@@ -453,12 +468,10 @@ ImageLayer::createImageInNativeProfile( const TileKey& key, ProgressCallback* pr
         for( std::vector<TileKey>::iterator k = nativeKeys.begin(); k != nativeKeys.end(); ++k )
         {
             bool isFallback = false;
-            GeoImage image = createImageInKeyProfile( *k, progress, true, isFallback );
+            GeoImage image = createImageInKeyProfile( *k, progress );
             if ( image.valid() )
             {
                 mosaic.getImages().push_back( TileImage(image.getImage(), *k) );
-                if ( !isFallback )
-                    foundAtLeastOneRealTile = true;
             }
             else
             {
@@ -466,104 +479,57 @@ ImageLayer::createImageInNativeProfile( const TileKey& key, ProgressCallback* pr
                 // empty spots in the mosaic. (By "invalid" we mean a tile that could not
                 // even be resolved through the fallback procedure.)
                 return GeoImage::INVALID;
+                //TODO: probably need to change this so the mosaic uses alpha.
             }
         }
 
         // bail out if we got nothing.
-        if ( mosaic.getImages().size() == 0 )
-            return GeoImage::INVALID;
-
-        // if the mosaic is ALL fallback data, this tile is fallback data.
-        if ( foundAtLeastOneRealTile )
+        if ( mosaic.getImages().size() > 0 )
         {
             // assemble new GeoImage from the mosaic.
             double rxmin, rymin, rxmax, rymax;
             mosaic.getExtents( rxmin, rymin, rxmax, rymax );
 
-            GeoImage result( 
+            result = GeoImage(
                 mosaic.createImage(), 
                 GeoExtent( nativeProfile->getSRS(), rxmin, rymin, rxmax, rymax ) );
-
-#if 1
-            return result;
-
-#else // let's try this. why crop? Just leave it. Faster and more compatible with NPOT
-      // systems (like iOS)
-
-            // calculate a tigher extent that matches the original input key:
-            GeoExtent tightExtent = nativeProfile->clampAndTransformExtent( key.getExtent() );
-
-            // a non-exact crop is critical here to avoid resampling the data
-            return result.crop( tightExtent, false, 0, 0, *_runtimeOptions.driver()->bilinearReprojection() );
-#endif
-        }
-
-        else // all fallback data
-        {
-            GeoImage result;
-
-            if ( forceFallback && key.getLevelOfDetail() > 0 )
-            {
-                result = createImageInNativeProfile(
-                    key.createParentKey(),
-                    progress,
-                    forceFallback,
-                    out_isFallback );
-            }
-
-            out_isFallback = true;
-            return result;
         }
-
-        //if ( !foundAtLeastOneRealTile )
-        //    out_isFallback = true;
-
     }
+
+    return result;
 }
 
 
 GeoImage
-ImageLayer::createImageInKeyProfile( const TileKey& key, ProgressCallback* progress, bool forceFallback, bool& out_isFallback )
+ImageLayer::createImageInKeyProfile(const TileKey&    key, 
+                                    ProgressCallback* progress)
 {
     GeoImage result;
 
-    out_isFallback = false;
-
     // If the layer is disabled, bail out.
     if ( !getEnabled() )
     {
         return GeoImage::INVALID;
     }
 
-    // Check the max data level, which limits the LOD of available data.
-    if ( _runtimeOptions.maxDataLevel().isSet() && key.getLOD() > _runtimeOptions.maxDataLevel().value() )
+    // Make sure the request is in range.
+    if ( !isKeyInRange(key) )
     {
         return GeoImage::INVALID;
     }
 
-    // Check for a "Minumum level" setting on this layer. If we are before the
-    // min level, just return the empty image. Do not cache empties
-    if ( _runtimeOptions.minLevel().isSet() && key.getLOD() < _runtimeOptions.minLevel().value() )
-    {
-        return GeoImage( _emptyImage.get(), key.getExtent() );
-    }
-
-    // Check for a "Minimum resolution" setting on the layer. If we are before the
-    // min resolution, return the empty image. Do not cache empties.
-    if ( _runtimeOptions.minResolution().isSet() )
-    {
-        double keyres = key.getExtent().width() / getTileSize();
-        double keyresInLayerProfile = key.getProfile()->getSRS()->transformUnits(keyres, getProfile()->getSRS());
-
-        if ( keyresInLayerProfile > _runtimeOptions.minResolution().value() )
-        {
-            return GeoImage( _emptyImage.get(), key.getExtent() );
-        }
-    }
-
     OE_DEBUG << LC << "create image for \"" << key.str() << "\", ext= "
         << key.getExtent().toString() << std::endl;
-
+    
+    // Check the layer L2 cache first
+    if ( _memCache.valid() )
+    {
+        CacheBin* bin = _memCache->getOrCreateBin( key.getProfile()->getFullSignature() );        
+        ReadResult result = bin->readObject(key.str() );
+        if ( result.succeeded() )
+            return GeoImage(static_cast<osg::Image*>(result.releaseObject()), key.getExtent());
+        //_memCache->dumpStats(key.getProfile()->getFullSignature());
+    }
 
     // locate the cache bin for the target profile for this layer:
     CacheBin* cacheBin = getCacheBin( key.getProfile() );
@@ -585,30 +551,46 @@ ImageLayer::createImageInKeyProfile( const TileKey& key, ProgressCallback* progr
         return GeoImage::INVALID;
     }
 
+    osg::ref_ptr< osg::Image > cachedImage;        
+
     // First, attempt to read from the cache. Since the cached data is stored in the
     // map profile, we can try this first.
     if ( cacheBin && getCachePolicy().isCacheReadable() )
     {
-        ReadResult r = cacheBin->readImage( key.str(), getCachePolicy().getMinAcceptTime() );
+        ReadResult r = cacheBin->readImage( key.str() );
         if ( r.succeeded() )
         {
-            ImageUtils::normalizeImage( r.getImage() );
-            return GeoImage( r.releaseImage(), key.getExtent() );
+            cachedImage = r.releaseImage();
+            ImageUtils::normalizeImage( cachedImage.get() );            
+            bool expired = getCachePolicy().isExpired(r.lastModifiedTime());
+            if (!expired)
+            {
+                OE_DEBUG << "Got cached image for " << key.str() << std::endl;                
+                return GeoImage( cachedImage.get(), key.getExtent() );                        
+            }
+            else
+            {
+                OE_DEBUG << "Expired image for " << key.str() << std::endl;                
+            }
         }
-        //else if ( r.code() == ReadResult::RESULT_EXPIRED )
-        //{
-        //    OE_INFO << LC << getName() << " : " << key.str() << " record expired!" << std::endl;
-        //}
     }
     
     // The data was not in the cache. If we are cache-only, fail sliently
     if ( isCacheOnly() )
     {
-        return GeoImage::INVALID;
+        // If it's cache only and we have an expired but cached image, just return it.
+        if (cachedImage.valid())
+        {
+            return GeoImage( cachedImage.get(), key.getExtent() );            
+        }
+        else
+        {
+            return GeoImage::INVALID;
+        }
     }
 
     // Get an image from the underlying TileSource.
-    result = createImageFromTileSource( key, progress, forceFallback, out_isFallback );
+    result = createImageFromTileSource( key, progress );
 
     // Normalize the image if necessary
     if ( result.valid() )
@@ -616,12 +598,16 @@ ImageLayer::createImageInKeyProfile( const TileKey& key, ProgressCallback* progr
         ImageUtils::normalizeImage( result.getImage() );
     }
 
-    // If we got a result, the cache is valid and we are caching in the map profile, write to the map cache.
+    // memory cache first:
+    if ( result.valid() && _memCache.valid() )
+    {
+        CacheBin* bin = _memCache->getOrCreateBin( key.getProfile()->getFullSignature() ); 
+        bin->write(key.str(), result.getImage());
+    }
+
+    // If we got a result, the cache is valid and we are caching in the map profile,
+    // write to the map cache.
     if (result.valid()  &&
-        //JB:  Removed the check to not write out fallback data.  If you have a low resolution base dataset (max lod 3) and a high resolution insert (max lod 22)
-        //     then the low res data needs to "fallback" from LOD 4 - 22 so you can display the high res inset.  If you don't cache these intermediate tiles then
-        //     performance can suffer generating all those fallback tiles, especially if you have to do reprojection or mosaicing.
-        //!out_isFallback &&
         cacheBin        && 
         getCachePolicy().isCacheWriteable() )
     {
@@ -631,7 +617,6 @@ ImageLayer::createImageInKeyProfile( const TileKey& key, ProgressCallback* progr
         }
 
         cacheBin->write( key.str(), result.getImage() );
-        //OE_INFO << LC << "WRITING " << key.str() << " to the cache." << std::endl;
     }
 
     if ( result.valid() )
@@ -640,7 +625,13 @@ ImageLayer::createImageInKeyProfile( const TileKey& key, ProgressCallback* progr
     }
     else
     {
-        OE_DEBUG << LC << key.str() << "result INVALID" << std::endl;
+        OE_DEBUG << LC << key.str() << "result INVALID" << std::endl;        
+        // We couldn't get an image from the source.  So see if we have an expired cached image
+        if (cachedImage.valid())
+        {
+            OE_DEBUG << LC << "Using cached but expired image for " << key.str() << std::endl;
+            result = GeoImage( cachedImage.get(), key.getExtent());
+        }
     }
 
     return result;
@@ -650,101 +641,39 @@ ImageLayer::createImageInKeyProfile( const TileKey& key, ProgressCallback* progr
 
 GeoImage
 ImageLayer::createImageFromTileSource(const TileKey&    key,
-                                      ProgressCallback* progress,
-                                      bool              forceFallback,
-                                      bool&             out_isFallback)
-{
-    // Results:
-    // 
-    // * return an osg::Image matching the key extent is all goes well;
-    //
-    // * return NULL to indicate that the key exceeds the maximum LOD of the source data,
-    //   and that the engine may need to generate a "fallback" tile if necessary;
-    //
-    // deprecated:
-    // * return an "empty image" if the LOD is valid BUT the key does not intersect the
-    //   source's data extents.
-
-    out_isFallback = false;
-
+                                      ProgressCallback* progress)
+{
     TileSource* source = getTileSource();
     if ( !source )
         return GeoImage::INVALID;
 
     // If the profiles are different, use a compositing method to assemble the tile.
-    if ( !key.getProfile()->isEquivalentTo( getProfile() ) )
+    if ( !key.getProfile()->isHorizEquivalentTo( getProfile() ) )
     {
-        return assembleImageFromTileSource( key, progress, out_isFallback );
+        return assembleImageFromTileSource( key, progress );
     }
 
     // Good to go, ask the tile source for an image:
     osg::ref_ptr<TileSource::ImageOperation> op = _preCacheOp;
 
-    osg::ref_ptr<osg::Image> result;
-
-    if ( forceFallback )
+    // Fail is the image is blacklisted.
+    if ( source->getBlacklist()->contains(key) )
     {
-        // check if the tile source has any data coverage for the requested key.
-        // the LOD is ignore here and checked later
-        if ( !source->hasDataInExtent( key.getExtent() ) )
-        {
-            OE_DEBUG << LC << "createImageFromTileSource: hasDataInExtent(" << key.str() << ") == false" << std::endl;
-            return GeoImage::INVALID;
-        }
-
-        TileKey finalKey = key;
-        while( !result.valid() && finalKey.valid() )
-        {
-            if ( !source->getBlacklist()->contains( finalKey.getTileId() ) &&
-                source->hasDataForFallback(finalKey))
-            {
-                result = source->createImage( finalKey, op.get(), progress );
-                if ( result.valid() )
-                {
-                    if ( finalKey.getLevelOfDetail() != key.getLevelOfDetail() )
-                    {
-                        // crop the fallback image to match the input key, and ensure that it remains the
-                        // same pixel size; because chances are if we're requesting a fallback that we're
-                        // planning to mosaic it later, and the mosaicer requires same-size images.
-                        GeoImage raw( result.get(), finalKey.getExtent() );
-                        GeoImage cropped = raw.crop( key.getExtent(), true, raw.getImage()->s(), raw.getImage()->t(), *_runtimeOptions.driver()->bilinearReprojection() );
-                        result = cropped.takeImage();
-                    }
-                }
-            }
-            if ( !result.valid() )
-            {
-                finalKey = finalKey.createParentKey();
-                out_isFallback = true;
-            }
-        }
-
-        if ( !result.valid() )
-        {
-            result = 0L;
-            //result = _emptyImage.get();
-            finalKey = key;
-        }
+        OE_DEBUG << LC << "createImageFromTileSource: blacklisted(" << key.str() << ")" << std::endl;
+        return GeoImage::INVALID;
     }
-    else
-    {
-        // Fail is the image is blacklisted.
-        if ( source->getBlacklist()->contains( key.getTileId() ) )
-        {
-            OE_DEBUG << LC << "createImageFromTileSource: blacklisted(" << key.str() << ")" << std::endl;
-            return GeoImage::INVALID;
-        }
     
-        if ( !source->hasData( key ) )
-        {
-            OE_DEBUG << LC << "createImageFromTileSource: hasData(" << key.str() << ") == false" << std::endl;
-            return GeoImage::INVALID;
-        }
-        result = source->createImage( key, op.get(), progress );
+    if ( !source->hasData( key ) )
+    {
+        OE_DEBUG << LC << "createImageFromTileSource: hasData(" << key.str() << ") == false" << std::endl;
+        return GeoImage::INVALID;
     }
 
+    // create an image from the tile source.
+    osg::ref_ptr<osg::Image> result = source->createImage( key, op.get(), progress );
+
     // Process images with full alpha to properly support MP blending.    
-    if ( result != 0L && *_runtimeOptions.featherPixels())
+    if ( result.valid() && *_runtimeOptions.featherPixels())
     {
         ImageUtils::featherAlphaRegions( result.get() );
     }    
@@ -753,7 +682,7 @@ ImageLayer::createImageFromTileSource(const TileKey&    key,
     // blacklist this tile for future requests.
     if ( result == 0L && (!progress || !progress->isCanceled()) )
     {
-        source->getBlacklist()->add( key.getTileId() );
+        source->getBlacklist()->add( key );
     }
 
     return GeoImage(result.get(), key.getExtent());
@@ -762,13 +691,10 @@ ImageLayer::createImageFromTileSource(const TileKey&    key,
 
 GeoImage
 ImageLayer::assembleImageFromTileSource(const TileKey&    key,
-                                        ProgressCallback* progress,
-                                        bool&             out_isFallback)
+                                        ProgressCallback* progress)
 {
     GeoImage mosaicedImage, result;
 
-    out_isFallback = false;
-
     // Scale the extent if necessary to apply an "edge buffer"
     GeoExtent ext = key.getExtent();
     if ( _runtimeOptions.edgeBufferRatio().isSet() )
@@ -779,7 +705,7 @@ ImageLayer::assembleImageFromTileSource(const TileKey&    key,
 
     // Get a set of layer tiles that intersect the requested extent.
     std::vector<TileKey> intersectingKeys;
-    getProfile()->getIntersectingTiles( ext, intersectingKeys );
+    getProfile()->getIntersectingTiles( key, intersectingKeys );
 
     if ( intersectingKeys.size() > 0 )
     {
@@ -788,7 +714,6 @@ ImageLayer::assembleImageFromTileSource(const TileKey&    key,
 
         // if we find at least one "real" tile in the mosaic, then the whole result tile is
         // "real" (i.e. not a fallback tile)
-        bool foundAtLeastOneRealTile = false;
         bool retry = false;
         ImageMosaic mosaic;
 
@@ -797,13 +722,20 @@ ImageLayer::assembleImageFromTileSource(const TileKey&    key,
             double minX, minY, maxX, maxY;
             k->getExtent().getBounds(minX, minY, maxX, maxY);
 
-            bool isFallback = false;
-            GeoImage image = createImageFromTileSource( *k, progress, true, isFallback );
+            GeoImage image = createImageFromTileSource( *k, progress );
             if ( image.valid() )
             {
-                // make sure the image is RGBA.
-                // (TODO: investigate whether we still need this -gw 6/25/2012)
-                if (image.getImage()->getPixelFormat() != GL_RGBA || image.getImage()->getDataType() != GL_UNSIGNED_BYTE || image.getImage()->getInternalTextureFormat() != GL_RGBA8 )
+                ImageUtils::normalizeImage(image.getImage());
+
+                // Make sure all images in mosaic are based on "RGBA - unsigned byte" pixels.
+                // This is not the smarter choice (in some case RGB would be sufficient) but
+                // it ensure consistency between all images / layers.
+                //
+                // The main drawback is probably the CPU memory foot-print which would be reduced by allocating RGB instead of RGBA images.
+                // On GPU side, this should not change anything because of data alignements : often RGB and RGBA textures have the same memory footprint
+                //
+                if (   (image.getImage()->getDataType() != GL_UNSIGNED_BYTE)
+                    || (image.getImage()->getPixelFormat() != GL_RGBA) )
                 {
                     osg::ref_ptr<osg::Image> convertedImg = ImageUtils::convertToRGBA8(image.getImage());
                     if (convertedImg.valid())
@@ -813,8 +745,6 @@ ImageLayer::assembleImageFromTileSource(const TileKey&    key,
                 }
 
                 mosaic.getImages().push_back( TileImage(image.getImage(), *k) );
-                if ( !isFallback )
-                    foundAtLeastOneRealTile = true;
             }
             else
             {
@@ -829,7 +759,7 @@ ImageLayer::assembleImageFromTileSource(const TileKey&    key,
 
         if ( mosaic.getImages().empty() || retry )
         {
-            // if we didn't get any data, fail
+            // if we didn't get any data, fail.
             OE_DEBUG << LC << "Couldn't create image for ImageMosaic " << std::endl;
             return GeoImage::INVALID;
         }
@@ -841,9 +771,6 @@ ImageLayer::assembleImageFromTileSource(const TileKey&    key,
         mosaicedImage = GeoImage(
             mosaic.createImage(),
             GeoExtent( getProfile()->getSRS(), rxmin, rymin, rxmax, rymax ) );
-
-        if ( !foundAtLeastOneRealTile )
-            out_isFallback = true;
     }
     else
     {
@@ -874,3 +801,39 @@ ImageLayer::assembleImageFromTileSource(const TileKey&    key,
 
     return result;
 }
+
+
+void
+ImageLayer::applyTextureCompressionMode(osg::Texture* tex) const
+{
+    if ( tex == 0L )
+        return;
+
+    if ( _runtimeOptions.textureCompression() == (osg::Texture::InternalFormatMode)~0 )
+    {
+        // auto mode:
+        if ( Registry::capabilities().isGLES() )
+        {
+            // Many GLES drivers do not support automatic compression, so by 
+            // default, don't set the internal format.
+            // TODO: later perhaps we can replace this with a CPU-side 
+            // compression step for PV or ETC
+            tex->setInternalFormatMode(osg::Texture::USE_IMAGE_DATA_FORMAT);
+        }
+        else
+        {
+            // compute the best available mode.
+            osg::Texture::InternalFormatMode mode;
+            if (ImageUtils::computeTextureCompressionMode(tex->getImage(0), mode))
+            {
+                tex->setInternalFormatMode(mode);
+            }
+        }
+    }
+
+    else if ( _runtimeOptions.textureCompression().isSet() )
+    {
+        // use specifically picked a mode.
+        tex->setInternalFormatMode( *_runtimeOptions.textureCompression() );
+    }
+}
diff --git a/src/osgEarth/ImageMosaic b/src/osgEarth/ImageMosaic
index 2a654dc..fd866f9 100644
--- a/src/osgEarth/ImageMosaic
+++ b/src/osgEarth/ImageMosaic
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/ImageMosaic.cpp b/src/osgEarth/ImageMosaic.cpp
index 643fd58..6684a3b 100644
--- a/src/osgEarth/ImageMosaic.cpp
+++ b/src/osgEarth/ImageMosaic.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/ImageToHeightFieldConverter b/src/osgEarth/ImageToHeightFieldConverter
index 4886d41..df73fa3 100644
--- a/src/osgEarth/ImageToHeightFieldConverter
+++ b/src/osgEarth/ImageToHeightFieldConverter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/ImageToHeightFieldConverter.cpp b/src/osgEarth/ImageToHeightFieldConverter.cpp
index 59ca884..5df921a 100644
--- a/src/osgEarth/ImageToHeightFieldConverter.cpp
+++ b/src/osgEarth/ImageToHeightFieldConverter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/ImageUtils b/src/osgEarth/ImageUtils
index 113d23c..603a0c7 100644
--- a/src/osgEarth/ImageUtils
+++ b/src/osgEarth/ImageUtils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -22,7 +22,9 @@
 
 #include <osgEarth/Common>
 #include <osg/Image>
+#include <osg/Texture>
 #include <osg/GL>
+#include <vector>
 
 //These formats were not added to OSG until after 2.8.3 so we need to define them to use them.
 #ifndef GL_EXT_texture_compression_rgtc
@@ -67,8 +69,8 @@ namespace osgEarth
          */
         static bool copyAsSubImage(
             const osg::Image* src, 
-            osg::Image* dst, 
-            int dst_start_col, int dst_start_row, int dst_start_img=0 );
+            osg::Image*       dst, 
+            int dst_start_col, int dst_start_row);
 
         /**
          * Resizes an image using nearest-neighbor resampling. Returns a new image, leaving
@@ -87,7 +89,7 @@ namespace osgEarth
             const osg::Image* input, 
             unsigned int new_s, unsigned int new_t,
             osg::ref_ptr<osg::Image>& output,
-            unsigned int mipmapLevel =0 );
+            unsigned int mipmapLevel =0, bool bilinear=false );
 
         /**
          * Crops the input image to the dimensions provided and returns a
@@ -141,6 +143,14 @@ namespace osgEarth
         static osg::Image* createSharpenedImage( const osg::Image* image );
 
         /**
+         * For each "layer" in the input image (each bitmap in the "r" dimension),
+         * create a new, separate image with r=1. If the input image is r=1, it is
+         * simply placed onto the output vector (no copy).
+         * Returns true upon sucess, false upon failure
+         */
+        static bool flattenImage(osg::Image* image, std::vector<osg::ref_ptr<osg::Image> >& output);
+
+        /**
          * Gets whether the input image's dimensions are powers of 2.
          */
         static bool isPowerOfTwo(const osg::Image* image);
@@ -194,6 +204,12 @@ namespace osgEarth
         static osg::Image* convertToRGBA8(const osg::Image* image);
 
         /**
+         * True if the two images are of the same format (pixel format, data type, etc.)
+         * though not necessarily the same size, depth, etc.
+         */
+        static bool sameFormat(const osg::Image* lhs, const osg::Image* rhs);
+
+        /**
          *Compares the image data of two images and determines if they are equivalent
          */
         static bool areEquivalent(const osg::Image *lhs, const osg::Image *rhs);
@@ -253,6 +269,13 @@ namespace osgEarth
         static bool isFloatingPointInternalFormat( GLint internalFormat );
 
         /**
+         * Compute a texture compression format suitable for the image.
+         */
+        static bool computeTextureCompressionMode(
+            const osg::Image* image,
+            osg::Texture::InternalFormatMode& out_mode);
+
+        /**
          * Reads color data out of an image, regardles of its internal pixel format.
          */
         class OSGEARTH_EXPORT PixelReader
@@ -273,6 +296,20 @@ namespace osgEarth
                 return (*_reader)(this, s, t, r, m);
             }
 
+            /** Reads a color from the image */
+            osg::Vec4 operator()(unsigned s, unsigned t, unsigned r=0, int m=0) const {
+                return (*_reader)(this, s, t, r, m);
+            }
+
+            /** Reads a color from the image by unit coords [0..1] */
+            osg::Vec4 operator()(float s, float t, int r=0, int m=0) const {
+                return (*_reader)(this,
+                    (int)(s * (float)(_image->s()-1)),
+                    (int)(t * (float)(_image->t()-1)),
+                    (int)(r * (float)(_image->r()-1)),
+                    m);
+            }
+
             // internals:
             const unsigned char* data(int s=0, int t=0, int r=0, int m=0) const {
                 return m == 0 ?
diff --git a/src/osgEarth/ImageUtils.cpp b/src/osgEarth/ImageUtils.cpp
index 766280a..786f31b 100644
--- a/src/osgEarth/ImageUtils.cpp
+++ b/src/osgEarth/ImageUtils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -19,6 +19,8 @@
 
 #include <osgEarth/ImageUtils>
 #include <osgEarth/ThreadingUtils>
+#include <osgEarth/Registry>
+#include <osgEarth/Capabilities>
 #include <osg/Notify>
 #include <osg/Texture>
 #include <osg/ImageSequence>
@@ -72,11 +74,12 @@ ImageUtils::normalizeImage( osg::Image* image )
 }
 
 bool
-ImageUtils::copyAsSubImage(const osg::Image* src, osg::Image* dst, int dst_start_col, int dst_start_row, int dst_img )
+ImageUtils::copyAsSubImage(const osg::Image* src, osg::Image* dst, int dst_start_col, int dst_start_row)
 {
     if (!src || !dst ||
         dst_start_col + src->s() > dst->s() ||
-        dst_start_row + src->t() > dst->t() )
+        dst_start_row + src->t() > dst->t() ||
+        src->r() != dst->r())
     {
         return false;
     }
@@ -84,14 +87,16 @@ ImageUtils::copyAsSubImage(const osg::Image* src, osg::Image* dst, int dst_start
     // check for fast bytewise copy:
     if (src->getPacking() == dst->getPacking() &&
         src->getDataType() == dst->getDataType() &&
-        src->getPixelFormat() == dst->getPixelFormat() &&
-        src->getInternalTextureFormat() == dst->getInternalTextureFormat() )
+        src->getPixelFormat() == dst->getPixelFormat() )
     {
-        for( int src_row=0, dst_row=dst_start_row; src_row < src->t(); src_row++, dst_row++ )
+        for(int r=0; r<src->r(); ++r) // each layer
         {
-            const void* src_data = src->data( 0, src_row, 0 );
-            void* dst_data = dst->data( dst_start_col, dst_row, dst_img );
-            memcpy( dst_data, src_data, src->getRowSizeInBytes() );
+            for( int src_row=0, dst_row=dst_start_row; src_row < src->t(); src_row++, dst_row++ )
+            {
+                const void* src_data = src->data( 0, src_row, r );
+                void* dst_data = dst->data( dst_start_col, dst_row, r );
+                memcpy( dst_data, src_data, src->getRowSizeInBytes() );
+            }
         }
     }
 
@@ -104,11 +109,14 @@ ImageUtils::copyAsSubImage(const osg::Image* src, osg::Image* dst, int dst_start
         PixelReader read(src);
         PixelWriter write(dst);
 
-        for( int src_t=0, dst_t=dst_start_row; src_t < src->t(); src_t++, dst_t++ )
+        for( int r=0; r<src->r(); ++r)
         {
-            for( int src_s=0, dst_s=dst_start_col; src_s < src->s(); src_s++, dst_s++ )
-            {           
-                write( read(src_s, src_t), dst_s, dst_t );
+            for( int src_t=0, dst_t=dst_start_row; src_t < src->t(); src_t++, dst_t++ )
+            {
+                for( int src_s=0, dst_s=dst_start_col; src_s < src->s(); src_s++, dst_s++ )
+                {           
+                    write(read(src_s, src_t, r), dst_s, dst_t, r);
+                }
             }
         }
     }
@@ -173,7 +181,8 @@ bool
 ImageUtils::resizeImage(const osg::Image* input, 
                         unsigned int out_s, unsigned int out_t, 
                         osg::ref_ptr<osg::Image>& output,
-                        unsigned int mipmapLevel )
+                        unsigned int mipmapLevel,
+                        bool bilinear)
 {
     if ( !input && out_s == 0 && out_t == 0 )
         return false;
@@ -199,13 +208,13 @@ ImageUtils::resizeImage(const osg::Image* input,
 
         if ( PixelWriter::supports(input) )
         {
-            output->allocateImage( out_s, out_t, 1, input->getPixelFormat(), input->getDataType(), input->getPacking() );
+            output->allocateImage( out_s, out_t, input->r(), input->getPixelFormat(), input->getDataType(), input->getPacking() );
             output->setInternalTextureFormat( input->getInternalTextureFormat() );
         }
         else
         {
             // for unsupported write formats, convert to RGBA8 automatically.
-            output->allocateImage( out_s, out_t, 1, GL_RGBA, GL_UNSIGNED_BYTE );
+            output->allocateImage( out_s, out_t, input->r(), GL_RGBA, GL_UNSIGNED_BYTE );
             output->setInternalTextureFormat( GL_RGB8A_INTERNAL );
         }
     }
@@ -233,19 +242,67 @@ ImageUtils::resizeImage(const osg::Image* input,
         {
             // get an appropriate input row
             float output_row_ratio = (float)output_row/(float)out_t;
-            int input_row = (unsigned int)( output_row_ratio * (float)in_t );
+            float input_row = output_row_ratio * (float)in_t;
             if ( input_row >= input->t() ) input_row = in_t-1;
             else if ( input_row < 0 ) input_row = 0;
 
             for( unsigned int output_col = 0; output_col < out_s; output_col++ )
             {
                 float output_col_ratio = (float)output_col/(float)out_s;
-                int input_col = (unsigned int)( output_col_ratio * (float)in_s );
+                float input_col =  output_col_ratio * (float)in_s;
                 if ( input_col >= (int)in_s ) input_col = in_s-1;
-                else if ( input_row < 0 ) input_row = 0;
+                else if ( input_col < 0 ) input_col = 0.0f;                
 
-                osg::Vec4 color = read( input_col, input_row ); // read pixel from mip level 0
-                write( color, output_col, output_row, 0, mipmapLevel ); // write to target mip level
+                osg::Vec4 color;
+
+                for(int layer=0; layer<input->r(); ++layer)
+                {
+                    if (bilinear)
+                    {
+                        // Do a billinear interpolation for the image
+                        int rowMin = osg::maximum((int)floor(input_row), 0);
+                        int rowMax = osg::maximum(osg::minimum((int)ceil(input_row), (int)(input->t()-1)), 0);
+                        int colMin = osg::maximum((int)floor(input_col), 0);
+                        int colMax = osg::maximum(osg::minimum((int)ceil(input_col), (int)(input->s()-1)), 0);                    
+
+                        if (rowMin > rowMax) rowMin = rowMax;
+                        if (colMin > colMax) colMin = colMax;  
+
+                        osg::Vec4 urColor = read(colMax, rowMax, layer);
+                        osg::Vec4 llColor = read(colMin, rowMin, layer);
+                        osg::Vec4 ulColor = read(colMin, rowMax, layer);
+                        osg::Vec4 lrColor = read(colMax, rowMin, layer);
+                    
+                        if ((colMax == colMin) && (rowMax == rowMin))
+                        {
+                            // Exact value
+                            color = urColor;
+                        }
+                        else if (colMax == colMin)
+                        {                     
+                            // Linear interpolate vertically            
+                            color = llColor * ((double)rowMax - input_row) + ulColor * (input_row - (double)rowMin);
+                        }
+                        else if (rowMax == rowMin)
+                        {                     
+                            // Linear interpolate horizontally
+                            color = llColor * ((double)colMax - input_col) + lrColor * (input_col - (double)colMin);
+                        }
+                        else
+                        {                        
+                            // Bilinear interpolate
+                            osg::Vec4 r1 = llColor * ((double)colMax - input_col) + lrColor * (input_col - (double)colMin);
+                            osg::Vec4 r2 = ulColor * ((double)colMax - input_col) + urColor * (input_col - (double)colMin);                      
+                            color = r1 * ((double)rowMax - input_row) + r2 * (input_row - (double)rowMin);
+                        }                         
+                    }
+                    else
+                    {
+                        color = read( (int)input_col, (int)input_row, layer ); // read pixel from mip level 0
+                    }
+
+                    write( color, output_col, output_row, layer, mipmapLevel ); // write to target mip level
+                }
             }
         }
     }
@@ -253,6 +310,39 @@ ImageUtils::resizeImage(const osg::Image* input,
     return true;
 }
 
+bool
+ImageUtils::flattenImage(osg::Image*                             input,
+                         std::vector<osg::ref_ptr<osg::Image> >& output)
+{
+    if (input == 0L)
+        return false;
+
+    if ( input->r() == 1 )
+    {
+        output.push_back( input );
+        return true;
+    }
+
+    for(int r=0; r<input->r(); ++r)
+    {
+        osg::Image* layer = new osg::Image();
+        layer->allocateImage(input->s(), input->t(), 1, input->getPixelFormat(), input->getDataType(), input->getPacking());
+        layer->setPixelAspectRatio(input->getPixelAspectRatio());
+
+#if OSG_MIN_VERSION_REQUIRED(3,1,0)
+        layer->setRowLength(input->getRowLength());
+#endif
+        layer->setOrigin(input->getOrigin());
+        layer->setFileName(input->getFileName());
+        layer->setWriteHint(input->getWriteHint());
+        layer->setInternalTextureFormat(input->getInternalTextureFormat());
+        ::memcpy(layer->data(), input->data(0,0,r), layer->getTotalSizeInBytes());
+        output.push_back(layer);
+    }
+
+    return true;
+}
+
 osg::Image*
 ImageUtils::createMipmapBlendedImage( const osg::Image* primary, const osg::Image* secondary )
 {
@@ -332,7 +422,7 @@ namespace
 bool
 ImageUtils::mix(osg::Image* dest, const osg::Image* src, float a)
 {
-    if (!dest || !src || dest->s() != src->s() || dest->t() != src->t() ||
+    if (!dest || !src || dest->s() != src->s() || dest->t() != src->t() || src->r() != dest->r() ||
         !PixelReader::supports(src) ||
         !PixelWriter::supports(dest) )
     {
@@ -391,18 +481,20 @@ ImageUtils::cropImage(const osg::Image* image,
 
     //Allocate the croppped image
     osg::Image* cropped = new osg::Image;
-    cropped->allocateImage(windowWidth, windowHeight, 1, image->getPixelFormat(), image->getDataType());
+    cropped->allocateImage(windowWidth, windowHeight, image->r(), image->getPixelFormat(), image->getDataType());
     cropped->setInternalTextureFormat( image->getInternalTextureFormat() );
     
     
-    for (int src_row = windowY, dst_row=0; dst_row < windowHeight; src_row++, dst_row++)
+    for (int layer=0; layer<image->r(); ++layer)
     {
-        if (src_row > image->t()-1) OE_NOTICE << "HeightBroke" << std::endl;
-        const void* src_data = image->data(windowX, src_row, 0);
-        void* dst_data = cropped->data(0, dst_row, 0);
-        memcpy( dst_data, src_data, cropped->getRowSizeInBytes());
+        for (int src_row = windowY, dst_row=0; dst_row < windowHeight; src_row++, dst_row++)
+        {
+            if (src_row > image->t()-1) OE_NOTICE << "HeightBroke" << std::endl;
+            const void* src_data = image->data(windowX, src_row, layer);
+            void* dst_data = cropped->data(0, dst_row, layer);
+            memcpy( dst_data, src_data, cropped->getRowSizeInBytes());
+        }
     }
-
     return cropped;
 }
 
@@ -419,27 +511,30 @@ ImageUtils::createSharpenedImage( const osg::Image* input )
 {
     int filter[9] = { 0, -1, 0, -1, 5, -1, 0, -1, 0 };
     osg::Image* output = ImageUtils::cloneImage(input);
-    for( int t=1; t<input->t()-1; t++ )
+    for( int r=0; r<input->r(); ++r)
     {
-        for( int s=1; s<input->s()-1; s++ )
+        for( int t=1; t<input->t()-1; t++ )
         {
-            int pixels[9] = {
-                *(int*)input->data(s-1,t-1), *(int*)input->data(s,t-1), *(int*)input->data(s+1,t-1),
-                *(int*)input->data(s-1,t  ), *(int*)input->data(s,t  ), *(int*)input->data(s+1,t  ),
-                *(int*)input->data(s-1,t+1), *(int*)input->data(s,t+1), *(int*)input->data(s+1,t+1) };
+            for( int s=1; s<input->s()-1; s++ )
+            {
+                int pixels[9] = {
+                    *(int*)input->data(s-1,t-1,r), *(int*)input->data(s,t-1,r), *(int*)input->data(s+1,t-1,r),
+                    *(int*)input->data(s-1,t  ,r), *(int*)input->data(s,t  ,r), *(int*)input->data(s+1,t  ,r),
+                    *(int*)input->data(s-1,t+1,r), *(int*)input->data(s,t+1,r), *(int*)input->data(s+1,t+1,r) };
 
-            int shifts[4] = { 0, 8, 16, 32 };
+                int shifts[4] = { 0, 8, 16, 32 };
 
-            for( int c=0; c<4; c++ ) // components
-            {
-                int mask = 0xff << shifts[c];
-                int sum = 0;
-                for( int i=0; i<9; i++ )
+                for( int c=0; c<4; c++ ) // components
                 {
-                    sum += ((pixels[i] & mask) >> shifts[c]) * filter[i];
+                    int mask = 0xff << shifts[c];
+                    int sum = 0;
+                    for( int i=0; i<9; i++ )
+                    {
+                        sum += ((pixels[i] & mask) >> shifts[c]) * filter[i];
+                    }
+                    sum = sum > 255? 255 : sum < 0? 0 : sum;
+                    output->data(s,t,r)[c] = sum;
                 }
-                sum = sum > 255? 255 : sum < 0? 0 : sum;
-                output->data(s,t)[c] = sum;
             }
         }
     }
@@ -484,13 +579,16 @@ ImageUtils::isEmptyImage(const osg::Image* image, float alphaThreshold)
         return false;
 
     PixelReader read(image);
-    for(unsigned t=0; t<(unsigned)image->t(); ++t) 
+    for(unsigned r=0; r<(unsigned)image->r(); ++r)
     {
-        for(unsigned s=0; s<(unsigned)image->s(); ++s)
+        for(unsigned t=0; t<(unsigned)image->t(); ++t) 
         {
-            osg::Vec4 color = read(s, t);
-            if ( color.a() > alphaThreshold )
-                return false;
+            for(unsigned s=0; s<(unsigned)image->s(); ++s)
+            {
+                osg::Vec4 color = read(s, t, r);
+                if ( color.a() > alphaThreshold )
+                    return false;
+            }
         }
     }
     return true;
@@ -516,31 +614,101 @@ ImageUtils::isSingleColorImage(const osg::Image* image, float threshold)
 
     PixelReader read(image);
 
-    osg::Vec4 referenceColor = read(0, 0);
+    osg::Vec4 referenceColor = read(0, 0, 0);
     float refR = referenceColor.r();
     float refG = referenceColor.g();
     float refB = referenceColor.b();
     float refA = referenceColor.a();
 
-    for(unsigned t=0; t<(unsigned)image->t(); ++t) 
+    for(unsigned r=0; r<(unsigned)image->r(); ++r)
     {
-        for(unsigned s=0; s<(unsigned)image->s(); ++s)
+        for(unsigned t=0; t<(unsigned)image->t(); ++t) 
         {
-            osg::Vec4 color = read(s, t);
-            if (   (fabs(color.r()-refR) > threshold)
-                || (fabs(color.g()-refG) > threshold)
-                || (fabs(color.b()-refB) > threshold)
-                || (fabs(color.a()-refA) > threshold) )
+            for(unsigned s=0; s<(unsigned)image->s(); ++s)
             {
-                return false;
+                osg::Vec4 color = read(s, t, r);
+                if (   (fabs(color.r()-refR) > threshold)
+                    || (fabs(color.g()-refG) > threshold)
+                    || (fabs(color.b()-refB) > threshold)
+                    || (fabs(color.a()-refA) > threshold) )
+                {
+                    return false;
+                }
             }
         }
     }
-
     return true;
 }
 
 bool
+ImageUtils::computeTextureCompressionMode(const osg::Image*                 image,
+                                          osg::Texture::InternalFormatMode& out_mode)
+{
+    if (!image)
+        return false;
+
+    const Capabilities& caps = Registry::capabilities();
+
+#ifndef OSG_GLES2_AVAILABLE
+
+    if (image->getPixelFormat() == GL_RGBA && image->getPixelSizeInBits() == 32) 
+    {
+        if (caps.supportsTextureCompression(osg::Texture::USE_S3TC_DXT5_COMPRESSION))
+        {
+            out_mode = osg::Texture::USE_S3TC_DXT5_COMPRESSION;
+            return true;
+        }
+        //todo: add ETC2
+        else if (caps.supportsTextureCompression(osg::Texture::USE_ARB_COMPRESSION))
+        {
+            out_mode = osg::Texture::USE_ARB_COMPRESSION;
+            return true;
+        }
+    }
+    else if (image->getPixelFormat() == GL_RGB && image->getPixelSizeInBits() == 24)
+    {
+        if (caps.supportsTextureCompression(osg::Texture::USE_S3TC_DXT1_COMPRESSION))
+        {
+            out_mode = osg::Texture::USE_S3TC_DXT1_COMPRESSION;
+            return true;
+        }
+        else if (caps.supportsTextureCompression(osg::Texture::USE_ETC_COMPRESSION))
+        {
+            // ETC1 is RGB only
+            out_mode = osg::Texture::USE_ETC_COMPRESSION;
+            return true;
+        }
+        else if (caps.supportsTextureCompression(osg::Texture::USE_ARB_COMPRESSION))
+        {
+            out_mode = osg::Texture::USE_ARB_COMPRESSION;
+            return true;
+        }
+    }
+
+#else // OSG_GLES2_AVAILABLE
+
+    if (caps.supportsTextureCompression(osg::Texture::USE_PVRTC_4BPP_COMPRESSION))
+    {
+        out_mode = osg::Texture::USE_PVRTC_4BPP_COMPRESSION;
+        return true;
+    }
+    else if (caps.supportsTextureCompression(osg::Texture::USE_PVRTC_2BPP_COMPRESSION))
+    {
+        out_mode = osg::Texture::USE_PVRTC_2BPP_COMPRESSION;
+        return true;
+    }
+    else if (caps.supportsTextureCompression(osg::Texture::USE_ETC_COMPRESSION))
+    {
+        out_mode = osg::Texture::USE_ETC_COMPRESSION;
+        return true;
+    }
+
+#endif
+
+    return false;
+}
+
+bool
 ImageUtils::canConvert( const osg::Image* image, GLenum pixelFormat, GLenum dataType )
 {
     if ( !image ) return false;
@@ -553,6 +721,7 @@ ImageUtils::convert(const osg::Image* image, GLenum pixelFormat, GLenum dataType
     if ( !image )
         return 0L;
 
+    // Very fast conversion if possible : clone image
     if ( image->getPixelFormat() == pixelFormat && image->getDataType() == dataType)
     {
         GLenum texFormat = image->getInternalTextureFormat();
@@ -561,11 +730,47 @@ ImageUtils::convert(const osg::Image* image, GLenum pixelFormat, GLenum dataType
             || (pixelFormat == GL_RGBA && texFormat == GL_RGB8A_INTERNAL))
         return cloneImage(image);
     }
+
+    // Fast conversion if possible : RGB8 to RGBA8
+    if ( dataType == GL_UNSIGNED_BYTE && pixelFormat == GL_RGBA && image->getDataType() == GL_UNSIGNED_BYTE && image->getPixelFormat() == GL_RGB)
+    {
+        // Do fast conversion
+        osg::Image* result = new osg::Image();
+        result->allocateImage(image->s(), image->t(), image->r(), GL_RGBA, GL_UNSIGNED_BYTE);
+        result->setInternalTextureFormat(GL_RGBA8);
+
+        const unsigned char* pSrcData = image->data();
+        unsigned char* pDstData = result->data();
+        int srcIndex = 0;
+        int dstIndex = 0;
+
+        // Convert all pixels except last one by reading 32bits chunks
+        for (int i=0; i<image->t()*image->s()*image->r()-1; i++)
+        {
+            unsigned int srcValue = *((const unsigned int*) (pSrcData + srcIndex)) | 0xFF000000;
+            *((unsigned int*) (pDstData + dstIndex)) = srcValue;
+
+            srcIndex += 3;
+            dstIndex += 4;
+        }
+
+        // Convert last pixel
+        pDstData[dstIndex + 0] = pSrcData[srcIndex + 0];
+        pDstData[dstIndex + 1] = pSrcData[srcIndex + 1];
+        pDstData[dstIndex + 2] = pSrcData[srcIndex + 2];
+        pDstData[dstIndex + 3] = 0xFF;
+
+        return result;
+    }
+
+    // Test if generic conversion is possible
     if ( !canConvert(image, pixelFormat, dataType) )
         return 0L;
 
+    // Generic conversion : use PixelVisitor
     osg::Image* result = new osg::Image();
     result->allocateImage(image->s(), image->t(), image->r(), pixelFormat, dataType);
+    memset(result->data(), 0, result->getTotalSizeInBytes());
 
     if ( pixelFormat == GL_RGB && dataType == GL_UNSIGNED_BYTE )
         result->setInternalTextureFormat( GL_RGB8_INTERNAL );
@@ -598,6 +803,7 @@ ImageUtils::areEquivalent(const osg::Image *lhs, const osg::Image *rhs)
 
     if ((lhs->s() == rhs->s()) &&
         (lhs->t() == rhs->t()) &&
+        (lhs->r() == rhs->r()) &&
         (lhs->getInternalTextureFormat() == rhs->getInternalTextureFormat()) &&
         (lhs->getPixelFormat() == rhs->getPixelFormat()) &&
         (lhs->getDataType() == rhs->getDataType()) &&
@@ -641,10 +847,11 @@ ImageUtils::hasTransparency(const osg::Image* image, float threshold)
         return false;
 
     PixelReader read(image);
-    for( int t=0; t<image->t(); ++t )
-        for( int s=0; s<image->s(); ++s )
-            if ( read(s, t).a() < threshold )
-                return true;
+    for( int r=0; r<image->r(); ++r)
+        for( int t=0; t<image->t(); ++t )
+            for( int s=0; s<image->s(); ++s )
+                if ( read(s, t, r).a() < threshold )
+                    return true;
 
     return false;
 }
@@ -661,57 +868,61 @@ ImageUtils::featherAlphaRegions(osg::Image* image, float maxAlpha)
 
     int ns = image->s();
     int nt = image->t();
+    int nr = image->r();
 
     osg::Vec4 n;
 
-    for( int t=0; t<nt; ++t )
+    for( int r=0; r<nr; ++r )
     {
-        bool rowdone = false;
-        for( int s=0; s<ns && !rowdone; ++s )
+        for( int t=0; t<nt; ++t )
         {
-            osg::Vec4 pixel = read(s, t);
-            if ( pixel.a() <= maxAlpha )
+            bool rowdone = false;
+            for( int s=0; s<ns && !rowdone; ++s )
             {
-                bool wrote = false;
-                if ( s < ns-1 ) {
-                    n = read( s+1, t );
-                    if ( n.a() > maxAlpha ) {
-                        write( n, s, t );
-                        wrote = true;
+                osg::Vec4 pixel = read(s, t, r);
+                if ( pixel.a() <= maxAlpha )
+                {
+                    bool wrote = false;
+                    if ( s < ns-1 ) {
+                        n = read( s+1, t, r);
+                        if ( n.a() > maxAlpha ) {
+                            write( n, s, t, r);
+                            wrote = true;
+                        }
                     }
-                }
-                if ( !wrote && s > 0 ) {
-                    n = read( s-1, t );
-                    if ( n.a() > maxAlpha ) {
-                        write( n, s, t );
-                        rowdone = true;
+                    if ( !wrote && s > 0 ) {
+                        n = read( s-1, t, r);
+                        if ( n.a() > maxAlpha ) {
+                            write( n, s, t, r);
+                            rowdone = true;
+                        }
                     }
                 }
             }
         }
-    }
 
-    for( int s=0; s<ns; ++s )
-    {
-        bool coldone = false;
-        for( int t=0; t<nt && !coldone; ++t )
+        for( int s=0; s<ns; ++s )
         {
-            osg::Vec4 pixel = read(s, t);
-            if ( pixel.a() <= maxAlpha )
+            bool coldone = false;
+            for( int t=0; t<nt && !coldone; ++t )
             {
-                bool wrote = false;
-                if ( t < nt-1 ) {
-                    n = read( s, t+1 );
-                    if ( n.a() > maxAlpha ) {
-                        write( n, s, t );
-                        wrote = true;
+                osg::Vec4 pixel = read(s, t, r);
+                if ( pixel.a() <= maxAlpha )
+                {
+                    bool wrote = false;
+                    if ( t < nt-1 ) {
+                        n = read( s, t+1, r );
+                        if ( n.a() > maxAlpha ) {
+                            write( n, s, t, r );
+                            wrote = true;
+                        }
                     }
-                }
-                if ( !wrote && t > 0 ) {
-                    n = read( s, t-1 );
-                    if ( n.a() > maxAlpha ) {
-                        write( n, s, t );
-                        coldone = true;
+                    if ( !wrote && t > 0 ) {
+                        n = read( s, t-1, r );
+                        if ( n.a() > maxAlpha ) {
+                            write( n, s, t, r);
+                            coldone = true;
+                        }
                     }
                 }
             }
@@ -730,10 +941,12 @@ ImageUtils::convertToPremultipliedAlpha(osg::Image* image)
 
     PixelReader read(image);
     PixelWriter write(image);
-    for(int s=0; s<image->s(); ++s) {
-        for( int t=0; t<image->t(); ++t ) {
-            osg::Vec4f c = read(s, t);
-            write( osg::Vec4f(c.r()*c.a(), c.g()*c.a(), c.b()*c.a(), c.a()), s, t);
+    for(int r=0; r<image->r(); ++r) {
+        for(int s=0; s<image->s(); ++s) {
+            for( int t=0; t<image->t(); ++t ) {
+                osg::Vec4f c = read(s, t, r);
+                write( osg::Vec4f(c.r()*c.a(), c.g()*c.a(), c.b()*c.a(), c.a()), s, t, r);
+            }
         }
     }
     return true;
@@ -780,17 +993,27 @@ ImageUtils::isFloatingPointInternalFormat(GLint i)
         (i >= 0x8814 && i <= 0x881F);   // GL_RGBA32F_ARB, et al
 }
 
+bool
+ImageUtils::sameFormat(const osg::Image* lhs, const osg::Image* rhs)
+{
+    return 
+        lhs != 0L &&
+        rhs != 0L &&
+        lhs->getPixelFormat() == rhs->getPixelFormat() &&
+        lhs->getDataType()    == rhs->getDataType();
+}
+
 //------------------------------------------------------------------------
 
 namespace
 {
-    //static const float r10= 1.0f/1023.0f;
-    //static const float r8 = 1.0f/255.0f;
-    //static const float r6 = 1.0f/63.0f;
-    static const float r5 = 1.0f/31.0f;
-    //static const float r4 = 1.0f/15.0f;
-    static const float r3 = 1.0f/7.0f;
-    static const float r2 = 1.0f/3.0f;
+    //static const double r10= 1.0/1023.0;
+    //static const double r8 = 1.0/255.0;
+    //static const double r6 = 1.0/63.0;
+    static const double r5 = 1.0/31.0;
+    //static const double r4 = 1.0/15.0;
+    static const double r3 = 1.0/7.0;
+    static const double r2 = 1.0/3.0;
 
     // The scale factors to convert from an image data type to a
     // float. This is copied from OSG; I think the factors for the signed
@@ -800,37 +1023,37 @@ namespace
 
     template<> struct GLTypeTraits<GLbyte>
     {
-        static float scale() { return 1.0f/128.0f; } // XXX
+        static double scale() { return 1.0/128.0; } // XXX
     };
 
     template<> struct GLTypeTraits<GLubyte>
     {
-        static float scale() { return 1.0f/255.0f; }
+        static double scale() { return 1.0/255.0; }
     };
 
     template<> struct GLTypeTraits<GLshort>
     {
-        static float scale() { return 1.0f/32768.0f; } // XXX
+        static double scale() { return 1.0/32768.0; } // XXX
     };
 
     template<> struct GLTypeTraits<GLushort>
     {
-        static float scale() { return 1.0f/65535.0f; }
+        static double scale() { return 1.0/65535.0; }
     };
 
     template<> struct GLTypeTraits<GLint>
     {
-        static float scale() { return 1.0f/2147483648.0f; } // XXX
+        static double scale() { return 1.0/2147483648.0; } // XXX
     };
 
     template<> struct GLTypeTraits<GLuint>
     {
-        static float scale() { return 1.0f/4294967295.0f; }
+        static double scale() { return 1.0/4294967295.0; }
     };
 
     template<> struct GLTypeTraits<GLfloat>
     {
-        static float scale() { return 1.0f; }
+        static double scale() { return 1.0; }
     };
 
     // The Reader function that performs the read.
@@ -1052,7 +1275,11 @@ namespace
         {
             GLushort p = *(const GLushort*)ia->data(s, t, r, m);
             //internal format GL_RGB5_A1 is implied
-            return osg::Vec4( r5*(float)(p>>11), r5*(float)((p&0x7c0)>>6), r5*((p&0x3e)>>1), (float)(p&0x1));
+            return osg::Vec4(
+                r5*(float)(p>>11), 
+                r5*(float)((p&0x7c0)>>6), 
+                r5*(float)((p&0x3e)>>1), 
+                (float)(p&0x1));
         }
     };
 
diff --git a/src/osgEarth/JsonUtils b/src/osgEarth/JsonUtils
index 33e74d3..e032f70 100644
--- a/src/osgEarth/JsonUtils
+++ b/src/osgEarth/JsonUtils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/JsonUtils.cpp b/src/osgEarth/JsonUtils.cpp
index 633d8d2..5fe1d9b 100644
--- a/src/osgEarth/JsonUtils.cpp
+++ b/src/osgEarth/JsonUtils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Layer b/src/osgEarth/Layer
index 354b891..bb9902e 100644
--- a/src/osgEarth/Layer
+++ b/src/osgEarth/Layer
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -40,7 +40,7 @@ namespace osgEarth
         /**
          * Gets this layer's unique ID.
          */
-        const UID getUID() const { return _uid; }
+        UID getUID() const { return _uid; }
 
         /**
          * Sequence controller if the layer has one.
diff --git a/src/osgEarth/Layer.cpp b/src/osgEarth/Layer.cpp
index a90b84d..958db78 100644
--- a/src/osgEarth/Layer.cpp
+++ b/src/osgEarth/Layer.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/LineFunctor b/src/osgEarth/LineFunctor
index 0597fb4..6d7c04d 100644
--- a/src/osgEarth/LineFunctor
+++ b/src/osgEarth/LineFunctor
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/LocalTangentPlane b/src/osgEarth/LocalTangentPlane
index 769e725..972223c 100644
--- a/src/osgEarth/LocalTangentPlane
+++ b/src/osgEarth/LocalTangentPlane
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -45,8 +45,8 @@ namespace osgEarth
 
         // This SRS uses a WGS84 lat/long SRS under the hood for reprojection. So we need the
         // pre/post transforms to move from LTP to Geodetic and back.
-        virtual bool preTransform(double& x, double& y, double& z, void* context) const;
-        virtual bool postTransform(double& x, double& y, double& z, void* context) const;
+        virtual const SpatialReference* preTransform(std::vector<osg::Vec3d>& points) const;
+        virtual const SpatialReference* postTransform(std::vector<osg::Vec3d>& points) const;
 
     protected: // SpatialReference overrides
 
diff --git a/src/osgEarth/LocalTangentPlane.cpp b/src/osgEarth/LocalTangentPlane.cpp
index d3e0c31..94a840e 100644
--- a/src/osgEarth/LocalTangentPlane.cpp
+++ b/src/osgEarth/LocalTangentPlane.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -33,7 +33,7 @@ TangentPlaneSpatialReference::TangentPlaneSpatialReference( void* handle, const
 SpatialReference( handle, false ),
 _originLLA      ( originLLA )
 {
-    //todo, set proper init string?
+    //nop
 }
 
 void
@@ -58,28 +58,33 @@ TangentPlaneSpatialReference::_init()
     _world2local.invert( _local2world );
 }
 
-bool
-TangentPlaneSpatialReference::preTransform(double& x, double& y, double& z, void* context) const
+const SpatialReference*
+TangentPlaneSpatialReference::preTransform(std::vector<osg::Vec3d>& points) const
 {
-    osg::Vec3d world = osg::Vec3d(x,y,z) * _local2world;
-    double lat, lon, height;
-    getEllipsoid()->convertXYZToLatLongHeight(world.x(), world.y(), world.z(), lat, lon, height);
-    x = osg::RadiansToDegrees(lon);
-    y = osg::RadiansToDegrees(lat);
-    z = height;
-    return true;
+    for(std::vector<osg::Vec3d>::iterator i = points.begin(); i != points.end(); ++i)
+    {
+        osg::Vec3d world = (*i) * _local2world;
+        double lat, lon, height;
+        getEllipsoid()->convertXYZToLatLongHeight(world.x(), world.y(), world.z(), lat, lon, height);
+        i->x() = osg::RadiansToDegrees(lon);
+        i->y() = osg::RadiansToDegrees(lat);
+        i->z() = height;
+    }
+    return getGeodeticSRS();
 }
 
-bool
-TangentPlaneSpatialReference::postTransform(double& x, double& y, double& z, void* context) const
+const SpatialReference*
+TangentPlaneSpatialReference::postTransform(std::vector<osg::Vec3d>& points) const
 {
     osg::Vec3d world;
-    getEllipsoid()->convertLatLongHeightToXYZ(
-        osg::DegreesToRadians(y), osg::DegreesToRadians(x), z,
-        world.x(), world.y(), world.z() );
-    osg::Vec3d local = world * _world2local;
-    x = local.x(), y = local.y(), z = local.z();
-    return true;
+    for(std::vector<osg::Vec3d>::iterator i = points.begin(); i != points.end(); ++i)
+    {
+        getEllipsoid()->convertLatLongHeightToXYZ(
+            osg::DegreesToRadians(i->y()), osg::DegreesToRadians(i->x()), i->z(),
+            world.x(), world.y(), world.z() );
+        i->set( world * _world2local );
+    }
+    return getGeodeticSRS();
 }
 
 bool
diff --git a/src/osgEarth/Locators b/src/osgEarth/Locators
index 226e343..2a18b83 100644
--- a/src/osgEarth/Locators
+++ b/src/osgEarth/Locators
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Locators.cpp b/src/osgEarth/Locators.cpp
index 4ef0649..61ebb81 100644
--- a/src/osgEarth/Locators.cpp
+++ b/src/osgEarth/Locators.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Map b/src/osgEarth/Map
index b10915c..a8e9f34 100644
--- a/src/osgEarth/Map
+++ b/src/osgEarth/Map
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -52,6 +52,11 @@ namespace osgEarth
 
     public:
         /**
+         * Gets this Map's unique ID 
+         */
+        UID getUID() const { return _uid; }
+
+        /**
          * Gets the options governing this map.
          */
         const MapOptions& getMapOptions() const { return _mapOptions; }
@@ -283,44 +288,30 @@ namespace osgEarth
          * Gets the readable name of this map.
          */
         const std::string& getName() const { return _name; }
-
+        
         /**
-         * Creates a heightfield for the region covered by the given TileKey, falling back on
-         * lower resolutions if necessary.
-         *
-         * NOTE: By default, this method will return a heightfield with HAE (height above ellipsoid) 
-         * values, even if the TileKey profile has an MSL vertical datum. That's because this 
-         * method is usually called to produce a renderable height field. You can override this
-         * behavior by passing in convertToHAE=false.
+         * Creates a flat heightfield (MSL) for the given tilekey.
+         */
+        osg::HeightField* createReferenceHeightField(
+            const TileKey& key,
+            bool           expressHeightsAsHAE =true) const;
+        
+        /**
+         * Populates a heightfield with data from the elevation layers
+         * in the map. The method will create a new heightfield if you
+         * don't supply one to populate.
          *
-         * @param key
-         *      Tile key defining the region (and ideal LOD) for which to return a heightfield
-         * @param fallbackIfNecessary
-         *      If the map can't generate a true heightfield for the key, fall back on lower
-         *      levels of detail until it can make one.
-         * @param out_hf
-         *      Resulting heightfield is written here.
-         * @param out_isFallback
-         *      Output flag telling the caller whether the method had to "fall back" on a 
-         *      lower level of detail.
-         * @param convertToHAE
-         *      Whether to return height-above-ellipsoid values in the heightfield, even if the
-         *      input tile key's SRS specifies a vertical datum (which would normally result in
-         *      a heightfield expressed relative to MSL). This is typical for when you are building
-         *      the actual 3D terrain.
-         * @param samplePolicy
-         *      How to intepolate heightfield samples.
-         * @param progress
-         *      (optional) progress callback.
-         */
-        bool getHeightField(
+         * NOTE: By default, this method will populate the heightfield with HAE (height
+         * above ellipsoid) values, even if the TileKey profile has an MSL vertical
+         * datum. That is because this method is usually called to produce a renderable
+         * height field. You can override this behavior by passing in convertToHAE=false.
+         */
+        bool populateHeightField(
+            osg::ref_ptr<osg::HeightField>& hf,
             const TileKey&                  key,
-            bool                            fallbackIfNeessary,
-            osg::ref_ptr<osg::HeightField>& out_hf,
-            bool*                           out_isFallback =0L,            
-            bool                            convertToHAE   =true,
-            ElevationSamplePolicy           samplePolicy   =SAMPLE_FIRST_VALID,
-            ProgressCallback*               progress       =0L)  const;
+            bool                            expressHeightsAsHAE =true,
+            ElevationSamplePolicy           samplePolicy        =SAMPLE_FIRST_VALID,
+            ProgressCallback*               progress            =0L) const;
 
         /**
          * Sets the Cache for this Map. Set to NULL for no cache.
@@ -375,6 +366,7 @@ namespace osgEarth
 
     private:
 
+        UID _uid;
         MapOptions _mapOptions;
         const MapOptions _initMapOptions;
         std::string _name;
diff --git a/src/osgEarth/Map.cpp b/src/osgEarth/Map.cpp
index 0f91988..02aed66 100644
--- a/src/osgEarth/Map.cpp
+++ b/src/osgEarth/Map.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -55,43 +55,24 @@ _mapOptions          ( options ),
 _initMapOptions      ( options ),
 _dataModelRevision   ( 0 )
 {
-    if (_mapOptions.cachePolicy().isSet() &&
-        _mapOptions.cachePolicy()->usage() == CachePolicy::USAGE_CACHE_ONLY )
-    {
-        OE_INFO << LC << "CACHE-ONLY MODE activated from map" << std::endl;
-    }
+    // Generate a UID.
+    _uid = Registry::instance()->createUID();
 
-    // if the map has a cache policy set, make this the system-wide default, UNLESS
-    // there ALREADY IS a registry default, in which case THAT will override THIS one.
-    // (In other words, whichever one is set first wins. And of course, if the registry
-    // has an override set, that will cancel out all of this.)
-    const optional<CachePolicy> regCachePolicy = Registry::instance()->defaultCachePolicy();
-
-    if ( _mapOptions.cachePolicy().isSet() )
-    {
-        if ( !regCachePolicy.isSet() )
-        {
-            Registry::instance()->setDefaultCachePolicy( *_mapOptions.cachePolicy() );
-            OE_INFO << LC 
-                << "Setting default cache policy from map ("
-                << _mapOptions.cachePolicy()->usageString() << ")" << std::endl;
-        }
-        else
-        {
-            _mapOptions.cachePolicy() = *regCachePolicy;
-            OE_INFO << LC
-                << "Settings map caching policy to default ("
-                << _mapOptions.cachePolicy()->usageString() << ")" << std::endl;
-        }
-    }
-    else if ( regCachePolicy.isSet() )
+    // If the registry doesn't have a default cache policy, but the
+    // map options has one, make the map policy the default.
+    if (_mapOptions.cachePolicy().isSet() &&
+        !Registry::instance()->defaultCachePolicy().isSet())
     {
-        _mapOptions.cachePolicy() = *regCachePolicy;
-        OE_INFO << LC
-            << "Settings map caching policy to default ("
+        Registry::instance()->setDefaultCachePolicy( _mapOptions.cachePolicy().get() );
+        OE_INFO << LC 
+            << "Setting default cache policy from map ("
             << _mapOptions.cachePolicy()->usageString() << ")" << std::endl;
     }
 
+    // Combine the CachePolicy in the map options with the settings in
+    // the registry.
+    Registry::instance()->resolveCachePolicy( _mapOptions.cachePolicy() );
+
     // the map-side dbOptions object holds I/O information for all components.
     _dbOptions = osg::clone( Registry::instance()->getDefaultOptions() );
 
@@ -975,8 +956,9 @@ Map::calculateProfile()
                 else
                 {
                     OE_WARN << LC 
-                        << "Map is geocentric, but the configured profile does not "
-                        << "have a geographic SRS. Falling back on default.."
+                        << "Map is geocentric, but the configured profile SRS ("
+                        << userProfile->getSRS()->getName() << ") is not geographic; "
+                        << "it will be ignored."
                         << std::endl;
                 }
             }
@@ -1085,28 +1067,36 @@ Map::calculateProfile()
     }
 }
 
+osg::HeightField*
+Map::createReferenceHeightField(const TileKey& key,
+                                bool           expressHeightsAsHAE) const
+{
+    unsigned size = std::max(*_mapOptions.elevationTileSize(), 2u);
+    return HeightFieldUtils::createReferenceHeightField(key.getExtent(), size, size, expressHeightsAsHAE);
+}
+
 
 bool
-Map::getHeightField(const TileKey&                  key,
-                    bool                            fallback,
-                    osg::ref_ptr<osg::HeightField>& out_result,
-                    bool*                           out_isFallback,
-                    bool                            convertToHAE,
-                    ElevationSamplePolicy           samplePolicy,
-                    ProgressCallback*               progress) const
+Map::populateHeightField(osg::ref_ptr<osg::HeightField>& hf,
+                         const TileKey&                  key,
+                         bool                            convertToHAE,
+                         ElevationSamplePolicy           samplePolicy, // deprecated (unused)
+                         ProgressCallback*               progress) const
 {
     Threading::ScopedReadLock lock( const_cast<Map*>(this)->_mapDataMutex );
 
     ElevationInterpolation interp = getMapOptions().elevationInterpolation().get();    
 
-    return _elevationLayers.createHeightField(
-        key, 
-        fallback, 
+    if ( !hf.valid() )
+    {
+        hf = createReferenceHeightField(key, convertToHAE);
+    }
+
+    return _elevationLayers.populateHeightField(
+        hf.get(),
+        key,
         convertToHAE ? _profileNoVDatum.get() : 0L,
-        interp, 
-        samplePolicy, 
-        out_result,  
-        out_isFallback,
+        interp,
         progress );
 }
 
diff --git a/src/osgEarth/MapCallback b/src/osgEarth/MapCallback
index c4be1d7..7ca2135 100644
--- a/src/osgEarth/MapCallback
+++ b/src/osgEarth/MapCallback
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/MapCallback.cpp b/src/osgEarth/MapCallback.cpp
index dd654a7..d24f8a1 100644
--- a/src/osgEarth/MapCallback.cpp
+++ b/src/osgEarth/MapCallback.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/MapFrame b/src/osgEarth/MapFrame
index 16c55a8..296cad2 100644
--- a/src/osgEarth/MapFrame
+++ b/src/osgEarth/MapFrame
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -38,12 +38,17 @@ namespace osgEarth
         /**
          * Constructs a new map frame.
          */
-        MapFrame( const Map* map, Map::ModelParts parts =Map::TERRAIN_LAYERS, const std::string& name ="" );
+        MapFrame(
+            const Map*         map, 
+            Map::ModelParts    parts =Map::TERRAIN_LAYERS,
+            const std::string& name  ="" );
 
         /**
          * A copy constructor with a new name (no sync happens)
          */
-        MapFrame( const MapFrame& frame, const std::string& name ="" );
+        MapFrame(
+            const MapFrame& frame, 
+            const std::string& name ="" );
 
         /** dtor */
         virtual ~MapFrame() { }
@@ -59,6 +64,9 @@ namespace osgEarth
          */
         bool needsSync() const;
 
+        /** The source map's UID */
+        UID getUID() const;
+
         /** Accesses the profile/rendering info about the source map. */
         const MapInfo& getMapInfo() const { return _mapInfo; }
 
@@ -96,18 +104,22 @@ namespace osgEarth
         /** Checks whether all the data for the specified key is cached. */
         bool isCached( const TileKey& key ) const;
 
+        /** Access to the map's options */
+        const MapOptions& getMapOptions() const;
+
+        /** The highest set minLevel() amongst all image and elevation layers */
+        unsigned getHighestMinLevel() const { return _highestMinLevel; }
+
         /**
-         * Equivalent to the Map::createHeightField() method, but operates on the elevation stack
-         * snapshot in this MapFrame.
+         * Equivalent to the Map::populateHeightField() method, but operates on the
+         * elevation stack snapshot in this MapFrame.
          */
-        bool getHeightField(
+        bool populateHeightField(
+            osg::ref_ptr<osg::HeightField>& hf,
             const TileKey&                  key,
-            bool                            fallback,
-            osg::ref_ptr<osg::HeightField>& out_hf,
-            bool*                           out_isFallback =0L,   
-            bool                            convertToHAE   =true,         
-            ElevationSamplePolicy           samplePolicy   =SAMPLE_FIRST_VALID,
-            ProgressCallback*               progress       =0L ) const;
+            bool                            expressHeightsAsHAE =true,
+            ElevationSamplePolicy           samplePolicy        =SAMPLE_FIRST_VALID,
+            ProgressCallback*               progress            =0L) const;
 
     private:
         bool _initialized;
@@ -119,9 +131,12 @@ namespace osgEarth
         ImageLayerVector _imageLayers;
         ElevationLayerVector _elevationLayers;
         ModelLayerVector _modelLayers;
-        MaskLayerVector _maskLayers;        
+        MaskLayerVector _maskLayers;
+        unsigned _highestMinLevel;
 
         friend class Map;
+
+        void refreshComputedValues();
     };
 
 }
diff --git a/src/osgEarth/MapFrame.cpp b/src/osgEarth/MapFrame.cpp
index 672de8c..14bf35e 100644
--- a/src/osgEarth/MapFrame.cpp
+++ b/src/osgEarth/MapFrame.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -24,11 +24,12 @@ using namespace osgEarth;
 
 
 MapFrame::MapFrame( const Map* map, Map::ModelParts parts, const std::string& name ) :
-_initialized( false ),
-_map        ( map ),
-_name       ( name ),
-_mapInfo    ( map ),
-_parts      ( parts )
+_initialized    ( false ),
+_map            ( map ),
+_name           ( name ),
+_mapInfo        ( map ),
+_parts          ( parts ),
+_highestMinLevel( 0 )
 {
     sync();
 }
@@ -40,6 +41,7 @@ _map                 ( src._map.get() ),
 _name                ( name ),
 _mapInfo             ( src._mapInfo ),
 _parts               ( src._parts ),
+_highestMinLevel     ( src._highestMinLevel ),
 _mapDataModelRevision( src._mapDataModelRevision ),
 _imageLayers         ( src._imageLayers ),
 _elevationLayers     ( src._elevationLayers ),
@@ -57,14 +59,19 @@ MapFrame::sync()
 
     if ( _map.valid() )
     {
-        changed = _map->sync( *this );        
+        changed = _map->sync( *this );
+
+        if ( changed )
+        {
+            refreshComputedValues();
+        }
     }
     else
     {
         _imageLayers.clear();
         _elevationLayers.clear();
         _modelLayers.clear();
-        _maskLayers.clear();        
+        _maskLayers.clear();
     }
 
     return changed;
@@ -79,30 +86,60 @@ MapFrame::needsSync() const
         (_map->getDataModelRevision() != _mapDataModelRevision || !_initialized);
 }
 
+UID
+MapFrame::getUID() const
+{
+    return _map.valid() ? _map->getUID() : (UID)0;
+}
+
+void
+MapFrame::refreshComputedValues()
+{
+    // cache the min LOD based on all image/elev layers
+    _highestMinLevel = 0;
+
+    for(ImageLayerVector::const_iterator i = _imageLayers.begin(); 
+        i != _imageLayers.end();
+        ++i)
+    {
+        const optional<unsigned>& minLevel = i->get()->getTerrainLayerRuntimeOptions().minLevel();
+        if ( minLevel.isSet() && minLevel.value() > _highestMinLevel )
+            _highestMinLevel = minLevel.value();
+    }
+
+    for(ElevationLayerVector::const_iterator i = _elevationLayers.begin(); 
+        i != _elevationLayers.end();
+        ++i)
+    {
+        const optional<unsigned>& minLevel = i->get()->getTerrainLayerRuntimeOptions().minLevel();
+        if ( minLevel.isSet() && minLevel.value() > _highestMinLevel )
+            _highestMinLevel = minLevel.value();
+    }
+}
 
 bool
-MapFrame::getHeightField(const TileKey&                  key,
-                         bool                            fallback,
-                         osg::ref_ptr<osg::HeightField>& out_hf,
-                         bool*                           out_isFallback,    
-                         bool                            convertToHAE,
-                         ElevationSamplePolicy           samplePolicy,
-                         ProgressCallback*               progress) const
+MapFrame::populateHeightField(osg::ref_ptr<osg::HeightField>& hf,
+                              const TileKey&                  key,
+                              bool                            convertToHAE,
+                              ElevationSamplePolicy           samplePolicy,
+                              ProgressCallback*               progress) const
 {
     if ( !_map.valid() ) 
         return false;
-    
 
+    ElevationInterpolation interp = _map->getMapOptions().elevationInterpolation().get();    
 
-    return _elevationLayers.createHeightField(
+    if ( !hf.valid() )
+    {
+        hf = _map->createReferenceHeightField(key, convertToHAE);
+    }
+
+    return _elevationLayers.populateHeightField(
+        hf.get(),
         key,
-        fallback, 
         convertToHAE ? _map->getProfileNoVDatum() : 0L,
-        _mapInfo.getElevationInterpolation(), 
-        samplePolicy, 
-        out_hf, 
-        out_isFallback,
-        progress );    
+        interp,
+        progress );
 }
 
 
@@ -153,40 +190,79 @@ MapFrame::getImageLayerByName( const std::string& name ) const
 bool
 MapFrame::isCached( const TileKey& key ) const
 {
+    // is there a map cache at all?
+    if ( _map->getCache() == 0L )
+        return false;
+
     //Check to see if the tile will load fast
     // Check the imagery layers
     for( ImageLayerVector::const_iterator i = imageLayers().begin(); i != imageLayers().end(); i++ )
     {   
-        //If we're cache only we should be fast
-        if (i->get()->isCacheOnly()) continue;
+        const ImageLayer* layer = i->get();
+
+        if (!layer->getEnabled())
+            continue;
+
+        // If we're cache only we should be fast
+        if (layer->isCacheOnly())
+            continue;
+
+        // no-cache mode? always slow
+        if (layer->isNoCache())
+            return false;
 
-        osg::ref_ptr< TileSource > source = i->get()->getTileSource();
-        if (!source.valid()) continue;
+        // No tile source? skip it
+        osg::ref_ptr< TileSource > source = layer->getTileSource();
+        if (!source.valid())
+            continue;
 
         //If the tile is blacklisted, it should also be fast.
-        if ( source->getBlacklist()->contains( key.getTileId() ) ) continue;
+        if ( source->getBlacklist()->contains( key ) )
+            continue;
+
         //If no data is available on this tile, we'll be fast
-        if ( !source->hasData( key ) ) continue;
+        if ( !source->hasData( key ) )
+            continue;
 
-        if ( !i->get()->isCached( key ) ) return false;
+        if ( !layer->isCached(key) )
+            return false;
     }
 
     for( ElevationLayerVector::const_iterator i = elevationLayers().begin(); i != elevationLayers().end(); ++i )
     {
+        const ElevationLayer* layer = i->get();
+
+        if (!layer->getEnabled())
+            continue;
+
         //If we're cache only we should be fast
-        if (i->get()->isCacheOnly()) continue;
+        if (layer->isCacheOnly())
+            continue;
 
-        osg::ref_ptr< TileSource > source = i->get()->getTileSource();
-        if (!source.valid()) continue;
+        // no-cache mode? always high-latency.
+        if (layer->isNoCache())
+            return false;
+
+        osg::ref_ptr< TileSource > source = layer->getTileSource();
+        if (!source.valid())
+            continue;
 
         //If the tile is blacklisted, it should also be fast.
-        if ( source->getBlacklist()->contains( key.getTileId() ) ) continue;
-        if ( !source->hasData( key ) ) continue;
+        if ( source->getBlacklist()->contains( key ) )
+            continue;
+
+        if ( !source->hasData( key ) )
+            continue;
+
         if ( !i->get()->isCached( key ) )
-        {
             return false;
-        }
     }
 
     return true;
 }
+
+const MapOptions&
+MapFrame::getMapOptions() const
+{
+    return _map->getMapOptions();
+}
diff --git a/src/osgEarth/MapInfo b/src/osgEarth/MapInfo
index a1d325d..dc2b696 100644
--- a/src/osgEarth/MapInfo
+++ b/src/osgEarth/MapInfo
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/MapInfo.cpp b/src/osgEarth/MapInfo.cpp
index 83b47b9..27ca05a 100644
--- a/src/osgEarth/MapInfo.cpp
+++ b/src/osgEarth/MapInfo.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/MapModelChange b/src/osgEarth/MapModelChange
index 93141f7..6dd7f64 100644
--- a/src/osgEarth/MapModelChange
+++ b/src/osgEarth/MapModelChange
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/MapNode b/src/osgEarth/MapNode
index 878189f..ca26ac6 100644
--- a/src/osgEarth/MapNode
+++ b/src/osgEarth/MapNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -25,7 +25,6 @@
 #include <osgEarth/MapNodeOptions>
 #include <osgEarth/SpatialReference>
 #include <osgEarth/TerrainEngineNode>
-#include <osgEarth/ShaderUtils>
 
 namespace osgEarth
 {
@@ -163,13 +162,6 @@ namespace osgEarth
         Config& externalConfig() { return _externalConf; }
         const Config& externalConfig() const { return _externalConf; }
 
-        /**
-         * Sets a custom texture compositor technique on the underlying terrain engine.
-         * This method is here b/c just calling getTerrainEngine()->getTextureCompositor()
-         * ->setTechnique() has problems (with init order of the terrain engine). Someday
-         * this needs to be cleaned up.
-         */
-        void setCompositorTechnique( class TextureCompositorTechnique* tech );
 
     public: // special purpose
 
@@ -202,7 +194,7 @@ namespace osgEarth
         Config                     _externalConf;
 
         // keep track of nodes created by model layers
-        typedef std::map<ModelLayer*,osg::Node*> ModelLayerNodeMap;
+        typedef std::map<ModelLayer*, osg::ref_ptr< osg::Node > > ModelLayerNodeMap;
         ModelLayerNodeMap        _modelLayerNodes;
         osg::Group*              _maskLayerNode;
         unsigned                 _lastNumBlacklistedFilenames;
@@ -210,8 +202,6 @@ namespace osgEarth
         bool                     _terrainEngineInitialized;
         osg::Group*              _terrainEngineContainer;
 
-        UpdateLightingUniformsHelper _updateLightingUniformsHelper;
-
 
     public: // MapCallback proxy
 
diff --git a/src/osgEarth/MapNode.cpp b/src/osgEarth/MapNode.cpp
index 72438cc..03df8f0 100644
--- a/src/osgEarth/MapNode.cpp
+++ b/src/osgEarth/MapNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -33,6 +33,7 @@
 #include <osgEarth/OverlayDecorator>
 #include <osgEarth/TerrainEngineNode>
 #include <osgEarth/TextureCompositor>
+#include <osgEarth/ShaderGenerator>
 #include <osgEarth/URI>
 #include <osg/ArgumentParser>
 #include <osg/PagedLOD>
@@ -208,6 +209,9 @@ MapNode::init()
     // Protect the MapNode from the Optimizer
     setDataVariance(osg::Object::DYNAMIC);
 
+    // Protect the MapNode from the ShaderGenerator
+    ShaderGenerator::setIgnoreHint(this, true);
+
     // initialize 0Ls
     _terrainEngine          = 0L;
     _terrainEngineContainer = 0L;
@@ -260,12 +264,12 @@ MapNode::init()
     // initialize terrain-level lighting:
     if ( terrainOptions.enableLighting().isSet() )
     {
-        //_terrainEngineContainer->getOrCreateStateSet()->setMode( 
-        //    GL_LIGHTING, 
-        //    terrainOptions.enableLighting().value() ? 1 : 0 );
-
         _terrainEngineContainer->getOrCreateStateSet()->addUniform(
             Registry::shaderFactory()->createUniformForGLMode(GL_LIGHTING, *terrainOptions.enableLighting()) );
+
+        _terrainEngineContainer->getOrCreateStateSet()->setMode( 
+            GL_LIGHTING, 
+            terrainOptions.enableLighting().value() ? 1 : 0 );
     }
 
     if ( _terrainEngine )
@@ -340,22 +344,17 @@ MapNode::init()
     osg::StateSet* stateset = getOrCreateStateSet();
     if ( _mapNodeOptions.enableLighting().isSet() )
     {
+        stateset->addUniform(Registry::shaderFactory()->createUniformForGLMode(
+            GL_LIGHTING, 
+            _mapNodeOptions.enableLighting().value() ? 1 : 0));
+
         stateset->setMode( 
             GL_LIGHTING, 
-            _mapNodeOptions.enableLighting().value() ? 1 : 0 );
+            _mapNodeOptions.enableLighting().value() ? 1 : 0);
     }
 
     dirtyBound();
 
-    // Install a default lighting shader program.
-    if ( Registry::capabilities().supportsGLSL() )
-    {
-        VirtualProgram* vp = VirtualProgram::getOrCreate( stateset );
-        vp->setName( "osgEarth::MapNode" );
-
-        Registry::shaderFactory()->installLightingShaders( vp );
-    }
-
     // register for event traversals so we can deal with blacklisted filenames
     ADJUST_EVENT_TRAV_COUNT( this, 1 );
 
@@ -432,15 +431,6 @@ MapNode::getTerrainEngine() const
     return _terrainEngine;
 }
 
-void
-MapNode::setCompositorTechnique( TextureCompositorTechnique* tech )
-{
-    if ( _terrainEngine )
-    {
-        _terrainEngine->getTextureCompositor()->setTechnique( tech );
-    }
-}
-
 osg::Group*
 MapNode::getModelLayerGroup() const
 {
@@ -485,11 +475,11 @@ MapNode::onModelLayerAdded( ModelLayer* layer, unsigned int index )
     {
         // install a post-processing callback on the ModelLayer's source 
         // so we can update the MapNode on new data that comes in:
-        modelSource->addPostProcessor( new MapNodeObserverInstaller(this) );
+        modelSource->addPostMergeOperation( new MapNodeObserverInstaller(this) );
     }
 
     // create the scene graph:
-    osg::Node* node = layer->createSceneGraph( _map.get(), _map->getDBOptions(), 0L );
+    osg::Node* node = layer->getOrCreateSceneGraph( _map.get(), _map->getDBOptions(), 0L );
 
     if ( node )
     {
@@ -651,9 +641,6 @@ MapNode::traverse( osg::NodeVisitor& nv )
 
     else if ( nv.getVisitorType() == nv.CULL_VISITOR )
     {
-        // update the light model uniforms.
-        _updateLightingUniformsHelper.cullTraverse( this, &nv );
-
         osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
         if ( cv )
         {
@@ -678,12 +665,8 @@ MapNode::traverse( osg::NodeVisitor& nv )
                 cullData->_cameraAltitude = eye.z();
             }
 
-            // window scale matrix:
-            osg::Matrix  m4 = cv->getWindowMatrix();
-            osg::Matrix3 m3( m4(0,0), m4(1,0), m4(2,0),
-                             m4(0,1), m4(1,1), m4(2,1),
-                             m4(0,2), m4(1,2), m4(2,2) );
-            cullData->_windowScaleMatrixUniform->set( m3 );
+            // window matrix:
+            cullData->_windowMatrixUniform->set( cv->getWindowMatrix() );
 
             // traverse:
             cv->pushStateSet( cullData->_stateSet.get() );
diff --git a/src/osgEarth/MapNodeObserver b/src/osgEarth/MapNodeObserver
index 08a3e41..d7c58a8 100644
--- a/src/osgEarth/MapNodeObserver
+++ b/src/osgEarth/MapNodeObserver
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/MapNodeOptions b/src/osgEarth/MapNodeOptions
index 0ae8253..ee79b10 100644
--- a/src/osgEarth/MapNodeOptions
+++ b/src/osgEarth/MapNodeOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/MapNodeOptions.cpp b/src/osgEarth/MapNodeOptions.cpp
index 4e52180..e9ab176 100644
--- a/src/osgEarth/MapNodeOptions.cpp
+++ b/src/osgEarth/MapNodeOptions.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/MapOptions b/src/osgEarth/MapOptions
index 7b9ee42..350a3db 100644
--- a/src/osgEarth/MapOptions
+++ b/src/osgEarth/MapOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -48,8 +48,8 @@ namespace osgEarth
               _cachePolicy           ( ),
               _cstype                ( CSTYPE_GEOCENTRIC ),
               _referenceURI          ( "" ),
-              _elevationInterpolation( INTERP_TRIANGULATE ), //INTERP_BILINEAR ),
-              _elevTileSize          ( 15 )
+              _elevationInterpolation( INTERP_BILINEAR ),
+              _elevTileSize          ( 17 )
         {
             fromConfig(_conf);
         }
diff --git a/src/osgEarth/MapOptions.cpp b/src/osgEarth/MapOptions.cpp
index 86a07d9..2d135e1 100644
--- a/src/osgEarth/MapOptions.cpp
+++ b/src/osgEarth/MapOptions.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/MaskLayer b/src/osgEarth/MaskLayer
index bae409a..ba6c293 100644
--- a/src/osgEarth/MaskLayer
+++ b/src/osgEarth/MaskLayer
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -36,9 +36,9 @@ namespace osgEarth
     class OSGEARTH_EXPORT MaskLayerOptions : public ConfigOptions
     {
     public:        
-        MaskLayerOptions( const ConfigOptions& options =ConfigOptions() );
+        MaskLayerOptions(const ConfigOptions& options =ConfigOptions() );
 
-        MaskLayerOptions( const std::string& name, const MaskSourceOptions& driverOptions =MaskSourceOptions() );
+        MaskLayerOptions(const std::string& name, const MaskSourceOptions& driverOptions =MaskSourceOptions() );
 
         /** dtor */
         virtual ~MaskLayerOptions() { }
@@ -55,6 +55,12 @@ namespace osgEarth
         optional<MaskSourceOptions>& driver() { return _driver; }
         const optional<MaskSourceOptions>& driver() const { return _driver; }
 
+        /**
+         * Gets or sets the minimum of detail for which this layer should generate data.
+         */
+        optional<unsigned>& minLevel() { return _minLevel; }
+        const optional<unsigned>& minLevel() const { return _minLevel; }
+
     public:
         virtual Config getConfig() const;
         virtual void mergeConfig( const Config& conf );
@@ -63,13 +69,16 @@ namespace osgEarth
         void fromConfig( const Config& conf );
         void setDefaults();
 
-        optional<std::string> _name;
+        optional<std::string>       _name;
         optional<MaskSourceOptions> _driver;
+        optional<unsigned>          _minLevel;
     };
 
+
     /**
      * A MaskLayer is a specialized layer used to mask out a part of the terrain.
-     * Typically you would use this if you had a pre-built 3D terrain model for an inset area.
+     * Typically you would use this if you had a pre-built 3D terrain model for
+     * an inset area.
      */
     class OSGEARTH_EXPORT MaskLayer : public Layer
     {
@@ -97,17 +106,24 @@ namespace osgEarth
          */
         MaskSource* getMaskSource() const { return _maskSource.get(); }
 
-    public:
         /** 
          * Gets the name of this mask layer
          */
         const std::string& getName() const { return *_runtimeOptions.name(); }
 
+        /**
+         * Minimum terrain LOD at which masking should occur
+         */
+        unsigned getMinLevel() const { return *_runtimeOptions.minLevel(); }
+
         /** 
          * Gets the geometric boundary polygon representing the area of the
          * terrain to mask out.
          */
-        osg::Vec3dArray* getOrCreateBoundary( float heightScale = 1.0, const SpatialReference* srs = NULL, ProgressCallback* progress =0L );
+        osg::Vec3dArray* getOrCreateMaskBoundary(
+            float                   heightScale,
+            const SpatialReference* srs,
+            ProgressCallback*       progress );
 
     public:
 
diff --git a/src/osgEarth/MaskLayer.cpp b/src/osgEarth/MaskLayer.cpp
index 1466281..273c5a7 100644
--- a/src/osgEarth/MaskLayer.cpp
+++ b/src/osgEarth/MaskLayer.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -25,7 +25,8 @@ using namespace osgEarth;
 //------------------------------------------------------------------------
 
 MaskLayerOptions::MaskLayerOptions( const ConfigOptions& options ) :
-ConfigOptions( options )
+ConfigOptions( options ),
+_minLevel( 0 )
 {
     setDefaults();
     fromConfig( _conf ); 
@@ -43,7 +44,7 @@ ConfigOptions()
 void
 MaskLayerOptions::setDefaults()
 {
-    //nop
+    _minLevel.init( 0 );
 }
 
 Config
@@ -52,6 +53,7 @@ MaskLayerOptions::getConfig() const
     Config conf = ConfigOptions::getConfig();
 
     conf.updateIfSet( "name", _name );
+    conf.updateIfSet( "min_level", _minLevel );
 
     return conf;
 }
@@ -60,6 +62,7 @@ void
 MaskLayerOptions::fromConfig( const Config& conf )
 {
     conf.getIfSet( "name", _name );
+    conf.getIfSet( "min_level", _minLevel );
 }
 
 void
@@ -108,12 +111,12 @@ MaskLayer::initialize( const osgDB::Options* dbOptions, const Map* map )
 
     if ( _maskSource.valid() )
     {
-        _maskSource->initialize( dbOptions, map );
+        _maskSource->initialize( dbOptions );
     }
 }
 
 osg::Vec3dArray*
-MaskLayer::getOrCreateBoundary( float heightScale, const SpatialReference *srs, ProgressCallback* progress )
+MaskLayer::getOrCreateMaskBoundary( float heightScale, const SpatialReference *srs, ProgressCallback* progress )
 {
     if ( _maskSource.valid() )
     {
diff --git a/src/osgEarth/MaskNode b/src/osgEarth/MaskNode
index 5c27927..868c87b 100644
--- a/src/osgEarth/MaskNode
+++ b/src/osgEarth/MaskNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/MaskNode.cpp b/src/osgEarth/MaskNode.cpp
index 3687afd..b4f9672 100644
--- a/src/osgEarth/MaskNode.cpp
+++ b/src/osgEarth/MaskNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/MaskSource b/src/osgEarth/MaskSource
index 75bcab7..b54357a 100644
--- a/src/osgEarth/MaskSource
+++ b/src/osgEarth/MaskSource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -28,7 +28,6 @@
 
 namespace osgEarth
 {   
-    class Map;
     class ProgressCallback;
     
     /**
@@ -69,10 +68,12 @@ namespace osgEarth
         /**
          * Subclass implements this method to create the boundary geometry.
          */
-        virtual osg::Vec3dArray* createBoundary( const SpatialReference* srs, ProgressCallback* progress =0L ) =0;
+        virtual osg::Vec3dArray* createBoundary(
+            const SpatialReference* srs, 
+            ProgressCallback*       progress =0L ) =0;
 
     public:
-        virtual void initialize( const osgDB::Options* dbOptions, const Map* map ) { }
+        virtual void initialize(const osgDB::Options* dbOptions) { }
 
         const MaskSourceOptions& getOptions() const { return _options; }
     public: 
diff --git a/src/osgEarth/MaskSource.cpp b/src/osgEarth/MaskSource.cpp
index cdf404c..422f2db 100644
--- a/src/osgEarth/MaskSource.cpp
+++ b/src/osgEarth/MaskSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -55,8 +55,6 @@ MaskSourceOptions::getConfig() const
 MaskSource::MaskSource( const MaskSourceOptions& options ) :
 _options( options )
 {
-    //TODO: is this really necessary?
-    this->setThreadSafeRefUnref( true );
 }
 
 MaskSource::~MaskSource()
diff --git a/src/osgEarth/MemCache b/src/osgEarth/MemCache
index 83e972a..b1cd4a7 100644
--- a/src/osgEarth/MemCache
+++ b/src/osgEarth/MemCache
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -37,16 +37,23 @@ namespace osgEarth
         /** dtor */
         virtual ~MemCache() { }
 
+        void dumpStats(const std::string& binID);
+
     public: // Cache interface
 
-        virtual CacheBin* addBin( const std::string& binID );
+        virtual CacheBin* addBin(const std::string& binID);
+
+        virtual CacheBin* getOrCreateBin(const std::string& binID);
 
         virtual CacheBin* getOrCreateDefaultBin();
     
     private:
-        MemCache( const MemCache& rhs, const osg::CopyOp& op =osg::CopyOp::DEEP_COPY_ALL ) { }
+        MemCache( const MemCache& rhs, const osg::CopyOp& op =osg::CopyOp::DEEP_COPY_ALL ) : Cache( rhs, op ) { }
 
         unsigned _maxBinSize;
+        float _writes;
+        float _reads;
+        float _hits;
     };
 
 } // namespace osgEarth
diff --git a/src/osgEarth/MemCache.cpp b/src/osgEarth/MemCache.cpp
index aded09b..a475ed1 100644
--- a/src/osgEarth/MemCache.cpp
+++ b/src/osgEarth/MemCache.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -41,7 +41,7 @@ namespace
             //nop
         }
 
-        ReadResult readObject(const std::string& key, TimeStamp minTime)
+        ReadResult readObject(const std::string& key )
         {
             MemCacheLRU::Record rec;
             _lru.get(key, rec);
@@ -63,14 +63,14 @@ namespace
             }
         }
 
-        ReadResult readImage(const std::string& key, TimeStamp minTime)
+        ReadResult readImage(const std::string& key)
         {
-            return readObject( key, minTime );
+            return readObject( key );
         }
 
-        ReadResult readString(const std::string& key, TimeStamp minTime)
+        ReadResult readString(const std::string& key)
         {
-            return readObject( key, minTime );
+            return readObject( key );
         }
 
         bool write( const std::string& key, const osg::Object* object, const Config& meta )
@@ -97,7 +97,7 @@ namespace
             return _lru.get(key, dummy);
         }
 
-        RecordStatus getRecordStatus( const std::string& key, TimeStamp minTime )
+        RecordStatus getRecordStatus( const std::string& key )
         {
             // ignore minTime; MemCache does not support expiration
             return _lru.has(key) ? STATUS_OK : STATUS_NOT_FOUND;
@@ -109,7 +109,6 @@ namespace
             return true;
         }
 
-    private:
         MemCacheLRU _lru;
     };
     
@@ -132,6 +131,15 @@ MemCache::addBin( const std::string& binID )
 }
 
 CacheBin*
+MemCache::getOrCreateBin(const std::string& binID)
+{
+    CacheBin* bin = getBin(binID);
+    if ( !bin )
+        bin = addBin(binID);
+    return bin;
+}
+
+CacheBin*
 MemCache::getOrCreateDefaultBin()
 {
     if ( !_defaultBin.valid() )
@@ -146,3 +154,12 @@ MemCache::getOrCreateDefaultBin()
 
     return _defaultBin.get();
 }
+
+
+void
+MemCache::dumpStats(const std::string& binID)
+{
+    MemCacheBin* bin = static_cast<MemCacheBin*>(getBin(binID));
+    CacheStats stats = bin->_lru.getStats();
+    OE_INFO << LC << "hit ratio = " << stats._hitRatio << std::endl;
+}
diff --git a/src/osgEarth/MimeTypes.cpp b/src/osgEarth/MimeTypes.cpp
index 6b6ede8..f7061af 100644
--- a/src/osgEarth/MimeTypes.cpp
+++ b/src/osgEarth/MimeTypes.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/ModelLayer b/src/osgEarth/ModelLayer
index 7cf08fd..3106b29 100644
--- a/src/osgEarth/ModelLayer
+++ b/src/osgEarth/ModelLayer
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -25,8 +25,11 @@
 #include <osgEarth/Layer>
 #include <osgEarth/Config>
 #include <osgEarth/ModelSource>
+#include <osgEarth/MaskSource>
 #include <osgEarth/ShaderUtils>
+#include <osgEarth/Containers>
 #include <osg/Node>
+#include <osg/Array>
 #include <vector>
 
 namespace osgEarth
@@ -39,9 +42,14 @@ namespace osgEarth
     class OSGEARTH_EXPORT ModelLayerOptions : public ConfigOptions
     {
     public:        
-        ModelLayerOptions( const ConfigOptions& options =ConfigOptions() );
+        /** Construct or deserialize new model layer options. */
+        ModelLayerOptions(
+            const ConfigOptions& options =ConfigOptions());
 
-        ModelLayerOptions( const std::string& name, const ModelSourceOptions& driverOptions =ModelSourceOptions() );
+        /** Construct or deserialize new model layer options. */
+        ModelLayerOptions(
+            const std::string&        name, 
+            const ModelSourceOptions& driverOptions =ModelSourceOptions() );
 
         /** dtor */
         virtual ~ModelLayerOptions() { }
@@ -82,6 +90,28 @@ namespace osgEarth
         optional<float>& opacity() { return _opacity; }
         const optional<float>& opacity() const { return _opacity; }
 
+        /**
+         * Masking options for cutting a hole in the terrain to accommodate this model.
+         * Note; the mask will NOT honor any visibility or opacity settings on the
+         * model layer.
+         */
+        optional<MaskSourceOptions>& maskOptions() { return _maskOptions; }
+        const optional<MaskSourceOptions>& maskOptions() const { return _maskOptions; }
+
+        /**
+         * Minimum terrain LOD at which to apply the mask (if there if one)
+         */
+        optional<unsigned>& maskMinLevel() { return _maskMinLevel; }
+        const optional<unsigned>& maskMinLevel() const { return _maskMinLevel; }
+
+        /**
+         * Whether this layer should be treated as part of the terrain
+         * for the purposes of elevation queries, clamping, etc.
+         */
+        optional<bool>& terrainPatch() { return _terrainPatch; }
+        const optional<bool>& terrainPatch() const { return _terrainPatch; }
+
+
     public:
         virtual Config getConfig() const;
         virtual void mergeConfig( const Config& conf );
@@ -96,6 +126,9 @@ namespace osgEarth
         optional<bool>               _visible;
         optional<float>              _opacity;
         optional<bool>               _lighting;
+        optional<MaskSourceOptions>  _maskOptions;
+        optional<unsigned>           _maskMinLevel;
+        optional<bool>               _terrainPatch;
     };
 
     /**
@@ -155,6 +188,31 @@ namespace osgEarth
          */
         ModelSource* getModelSource() const { return _modelSource.get(); }
 
+        /**
+         * The underlying mask source, if one exists.
+         */
+        MaskSource* getMaskSource() const { return _maskSource.get(); }
+
+        /**
+         * The minimum terrain LOD at which to apply the mask.
+         */
+        unsigned getMaskMinLevel() const { return _initOptions.maskMinLevel().get(); }
+        
+        /**
+         * The boundary geometry for the mask.
+         */
+        osg::Vec3dArray* getOrCreateMaskBoundary(
+            float                   heightScale,
+            const SpatialReference* srs,
+            ProgressCallback*       progress);
+
+        /**
+         * Whether this model layer is a terrain patch for the purposes of 
+         * intersection testing. (convenience function)
+         */
+        bool isTerrainPatch() const { return _initOptions.terrainPatch().get(); }
+
+
     public:
 
         /**
@@ -163,13 +221,19 @@ namespace osgEarth
         void initialize( const osgDB::Options* options );
 
         /**
-         * Creates the scene graph representing this model layer for the given Map.
+         * Creates the scene graph representing this model layer for the given Map,
+         * or returns one that already exists.
          */
-        osg::Node* createSceneGraph( 
+        osg::Node* getOrCreateSceneGraph( 
             const Map*            map, 
             const osgDB::Options* dbOptions,
             ProgressCallback*     progress );
 
+        /**
+         * Gets a scene graph what was previously created with getOrCreateSceneGraph.
+         */
+        osg::Node* getSceneGraph(const UID& mapUID) const;
+
 
     public: // properties
 
@@ -200,21 +264,25 @@ namespace osgEarth
         void removeCallback( ModelLayerCallback* cb );
 
     private:
-        osg::ref_ptr<ModelSource>    _modelSource;
-        const ModelLayerOptions      _initOptions;
-        ModelLayerOptions            _runtimeOptions;
-        Revision                     _modelSourceRev;
-        ModelLayerCallbackList       _callbacks;
-        osg::ref_ptr<AlphaEffect>    _alphaEffect;
-        //UpdateLightingUniformsHelper _updateLightingUniformsHelper;
+        osg::ref_ptr<ModelSource>     _modelSource;
+        osg::ref_ptr<MaskSource>      _maskSource;
+        const ModelLayerOptions       _initOptions;
+        ModelLayerOptions             _runtimeOptions;
+        Revision                      _modelSourceRev;
+        ModelLayerCallbackList        _callbacks;
+        osg::ref_ptr<AlphaEffect>     _alphaEffect;
+        osg::ref_ptr<osg::Vec3dArray> _maskBoundary;
+
+        typedef fast_map<UID, osg::observer_ptr<osg::Node> > Graphs;
+        Graphs _graphs;
 
-        typedef std::set< osg::observer_ptr<osg::Node> > NodeObserverSet;
-        NodeObserverSet _nodeSet;
+        mutable Threading::Mutex _mutex; // general-purpose mutex.
 
         virtual void fireCallback( ModelLayerCallbackMethodPtr method );
 
         void copyOptions();
 
+        void setLightingEnabledNoLock(bool value);
     };
 
     typedef std::vector< osg::ref_ptr<ModelLayer> > ModelLayerVector;
diff --git a/src/osgEarth/ModelLayer.cpp b/src/osgEarth/ModelLayer.cpp
index 2c72d62..751599f 100644
--- a/src/osgEarth/ModelLayer.cpp
+++ b/src/osgEarth/ModelLayer.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -71,38 +71,50 @@ ModelLayerOptions::setDefaults()
     _visible.init     ( true );
     _lighting.init    ( true );
     _opacity.init     ( 1.0f );
+    _maskMinLevel.init( 0 );
+    _terrainPatch.init( false );
 }
 
 Config
 ModelLayerOptions::getConfig() const
 {
-    //Config conf = ConfigOptions::getConfig();
     Config conf = ConfigOptions::newConfig();
 
-    conf.updateIfSet( "name", _name );
-    conf.updateIfSet( "enabled", _enabled );
-    conf.updateIfSet( "visible", _visible );
-    conf.updateIfSet( "lighting", _lighting );
-    conf.updateIfSet( "opacity",  _opacity );
+    conf.updateIfSet( "name",           _name );
+    conf.updateIfSet( "enabled",        _enabled );
+    conf.updateIfSet( "visible",        _visible );
+    conf.updateIfSet( "lighting",       _lighting );
+    conf.updateIfSet( "opacity",        _opacity );
+    conf.updateIfSet( "mask_min_level", _maskMinLevel );
+    conf.updateIfSet( "patch",          _terrainPatch );    
 
     // Merge the ModelSource options
     if ( driver().isSet() )
         conf.merge( driver()->getConfig() );
 
+    // Merge the MaskSource options
+    if ( maskOptions().isSet() )
+        conf.add( "mask", maskOptions()->getConfig() );
+
     return conf;
 }
 
 void
 ModelLayerOptions::fromConfig( const Config& conf )
 {
-    conf.getIfSet( "name", _name );
-    conf.getIfSet( "enabled", _enabled );
-    conf.getIfSet( "visible", _visible );
-    conf.getIfSet( "lighting", _lighting );
+    conf.getIfSet( "name",           _name );
+    conf.getIfSet( "enabled",        _enabled );
+    conf.getIfSet( "visible",        _visible );
+    conf.getIfSet( "lighting",       _lighting );
     conf.getIfSet( "opacity",        _opacity );
+    conf.getIfSet( "mask_min_level", _maskMinLevel );
+    conf.getIfSet( "patch",          _terrainPatch );
 
     if ( conf.hasValue("driver") )
         driver() = ModelSourceOptions(conf);
+
+    if ( conf.hasChild("mask") )
+        maskOptions() = MaskSourceOptions(conf.child("mask"));
 }
 
 void
@@ -142,7 +154,7 @@ _modelSource( new NodeModelSource(node) )
 
 ModelLayer::~ModelLayer()
 {
-    OE_DEBUG << "~ModelLayer" << std::endl;
+    //nop
 }
 
 void
@@ -155,24 +167,60 @@ ModelLayer::copyOptions()
 }
 
 void
-ModelLayer::initialize( const osgDB::Options* dbOptions )
+ModelLayer::initialize(const osgDB::Options* dbOptions)
 {
     if ( !_modelSource.valid() && _initOptions.driver().isSet() )
     {
-        _modelSource = ModelSourceFactory::create( *_initOptions.driver() );
+        OE_INFO << LC << "Initializing model layer \"" << getName() << "\", driver=\"" << _initOptions.driver()->getDriver() << "\"" << std::endl;
 
+        // the model source:
+        _modelSource = ModelSourceFactory::create( *_initOptions.driver() );
         if ( _modelSource.valid() )
         {
             _modelSource->initialize( dbOptions );
+
+            // the mask, if there is one:
+            if ( !_maskSource.valid() && _initOptions.maskOptions().isSet() )
+            {
+                OE_INFO << LC << "...initializing mask, driver=\"" << _initOptions.maskOptions()->getDriver() << std::endl;
+
+                _maskSource = MaskSourceFactory::create( *_initOptions.maskOptions() );
+                if ( _maskSource.valid() )
+                {
+                    _maskSource->initialize( dbOptions );
+                }
+                else
+                {
+                    OE_INFO << LC << "...mask init failed!" << std::endl;
+                }
+            }
         }
     }
 }
 
 osg::Node*
-ModelLayer::createSceneGraph(const Map*            map,
-                             const osgDB::Options* dbOptions,
-                             ProgressCallback*     progress )
+ModelLayer::getSceneGraph(const UID& mapUID) const
 {
+    Threading::ScopedMutexLock lock(_mutex);
+    Graphs::const_iterator i = _graphs.find( mapUID );
+    return i == _graphs.end() ? 0L : i->second.get();
+}
+
+osg::Node*
+ModelLayer::getOrCreateSceneGraph(const Map*            map,
+                                  const osgDB::Options* dbOptions,
+                                  ProgressCallback*     progress )
+{
+    // exclusive lock for cache lookup/update.
+    Threading::ScopedMutexLock lock( _mutex );
+
+    // There can be one node graph per Map. See if it already exists
+    // and if so, return it.
+    Graphs::iterator i = _graphs.find(map->getUID());
+    if ( i != _graphs.end() && i->second.valid() )
+        return i->second.get();
+
+    // need to create it.
     osg::Node* node = 0L;
 
     if ( _modelSource.valid() )
@@ -188,7 +236,7 @@ ModelLayer::createSceneGraph(const Map*            map,
 
             if ( _runtimeOptions.lightingEnabled().isSet() )
             {
-                setLightingEnabled( *_runtimeOptions.lightingEnabled() );
+                setLightingEnabledNoLock( *_runtimeOptions.lightingEnabled() );
             }
 
             if ( _modelSource->getOptions().depthTestEnabled() == false )
@@ -198,30 +246,23 @@ ModelLayer::createSceneGraph(const Map*            map,
                 ss->setRenderBinDetails( 99999, "RenderBin" ); //TODO: configure this bin ...
             }
 
-#if 0 // moved the MapNode level.
-            if ( Registry::capabilities().supportsGLSL() )
-            {
-                // install a callback that keeps the shader uniforms up to date
-                node->addCullCallback( new UpdateLightingUniformsHelper() );
-            }
-#endif
-
             _modelSource->sync( _modelSourceRev );
 
             // save an observer reference to the node so we can change the visibility/lighting/etc.
-            _nodeSet.insert( node );
-        }
-    }
+            //_nodeSet.insert( node );
 
-    // add a parent group for shaders/effects to attach to without overwriting any model programs directly
-    osg::Group* group = 0L;
-    if ( node ) {
-      group = new osg::Group();
-      group->addChild(node);
-      _alphaEffect->attach( group->getOrCreateStateSet() );
+            // add a parent group for shaders/effects to attach to without overwriting any model programs directly
+            osg::Group* group = new osg::Group();
+            group->addChild(node);
+            _alphaEffect->attach( group->getOrCreateStateSet() );
+            node = group;
+
+            // save it.
+            _graphs[map->getUID()] = node;
+        }
     }
 
-    return group;
+    return node;
 }
 
 bool
@@ -243,13 +284,13 @@ ModelLayer::setVisible(bool value)
     {
         _runtimeOptions.visible() = value;
 
-        for( NodeObserverSet::iterator i = _nodeSet.begin(); i != _nodeSet.end(); ++i )
+        _mutex.lock();
+        for(Graphs::iterator i = _graphs.begin(); i != _graphs.end(); ++i)
         {
-            if ( i->valid() )
-            {
-                i->get()->setNodeMask( value ? ~0 : 0 );
-            }
+            if ( i->second.valid() )
+                i->second->setNodeMask( value ? ~0 : 0 );
         }
+        _mutex.unlock();
 
         fireCallback( &ModelLayerCallback::onVisibleChanged );
     }
@@ -277,13 +318,20 @@ ModelLayer::setOpacity(float opacity)
 void
 ModelLayer::setLightingEnabled( bool value )
 {
+    Threading::ScopedMutexLock lock(_mutex);
+    setLightingEnabledNoLock( value );
+}
+
+void
+ModelLayer::setLightingEnabledNoLock(bool value)
+{
     _runtimeOptions.lightingEnabled() = value;
 
-    for( NodeObserverSet::iterator i = _nodeSet.begin(); i != _nodeSet.end(); ++i )
+    for(Graphs::iterator i = _graphs.begin(); i != _graphs.end(); ++i)
     {
-        if ( i->valid() )
+        if ( i->second.valid() )
         {
-            osg::StateSet* stateset = i->get()->getOrCreateStateSet();
+            osg::StateSet* stateset = i->second->getOrCreateStateSet();
 
             stateset->setMode( 
                 GL_LIGHTING, value ? osg::StateAttribute::ON : 
@@ -328,3 +376,27 @@ ModelLayer::fireCallback( ModelLayerCallbackMethodPtr method )
         (cb->*method)( this );
     }
 }
+
+
+osg::Vec3dArray*
+ModelLayer::getOrCreateMaskBoundary(float                   heightScale,
+                                    const SpatialReference* srs, 
+                                    ProgressCallback*       progress )
+{
+    if ( _maskSource.valid() && !_maskBoundary.valid() )
+    {
+        Threading::ScopedMutexLock excl(_mutex);
+
+        if ( !_maskBoundary.valid() ) // double-check pattern
+        {
+            // make the geometry:
+            _maskBoundary = _maskSource->createBoundary( srs, progress );
+
+            // scale to the height scale factor:
+            for (osg::Vec3dArray::iterator vIt = _maskBoundary->begin(); vIt != _maskBoundary->end(); ++vIt)
+                vIt->z() = vIt->z() * heightScale;
+        }
+    }
+
+    return _maskBoundary.get();
+}
\ No newline at end of file
diff --git a/src/osgEarth/ModelSource b/src/osgEarth/ModelSource
index a9cf8aa..8b8caf8 100644
--- a/src/osgEarth/ModelSource
+++ b/src/osgEarth/ModelSource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -22,6 +22,7 @@
 
 #include <osgEarth/Common>
 #include <osgEarth/Config>
+#include <osgEarth/GeoData>
 #include <osgEarth/NodeUtils>
 #include <osgEarth/Revisioning>
 #include <osgEarth/ThreadingUtils>
@@ -103,20 +104,32 @@ namespace osgEarth
             ProgressCallback*     progress ) =0;
 
         /**
-         * Add a post processing opeation - this will be called on any node
-         * that enters the scene graph by the model source.
+         * Add a post processing operation that will run (possibly in a pager
+         * thread) after a new node has been generated.
          */
-        void addPostProcessor( NodeOperation* cb );
+        void addPreMergeOperation( NodeOperation* cb );
+
+        /**
+         * Add a post processing operation that will run (in the update thread)
+         * after a node merges into the scene graph.
+         */
+        void addPostMergeOperation( NodeOperation* cb );
         
         /**
-         * Remove a post processing operation
+         * Remove a pre-merge processing operation
          */
-        void removePostProcessor( NodeOperation* cb );
+        void removePreMergeOperation( NodeOperation* cb );
+        
+        /**
+         * Remove a post-merge processing operation
+         */
+        void removePostMergeOperation( NodeOperation* cb );
 
         /**
          * The vector of post processor callback operations
          */
-        const NodeOperationVector& postProcessors() const { return _postProcessors; }
+        const NodeOperationVector& preMergeOperations() const { return *_preMergeOps; }
+        const NodeOperationVector& postMergeOperations() const { return *_postMergeOps; }
 
 
     public: // META_Object specialization
@@ -135,6 +148,12 @@ namespace osgEarth
         /** Get the options used to create this model source */
         const ModelSourceOptions& getOptions() const { return _options; }
 
+        /**
+         * Gets the list of areas with data for this ModelSource
+         */
+        const DataExtentList& getDataExtents() const { return _dataExtents; }
+        DataExtentList& getDataExtents() { return _dataExtents; }
+
     protected:
         /**
          * Fire the callbacks. The implementation class should call this whenever it adds
@@ -142,14 +161,17 @@ namespace osgEarth
          */
         void firePostProcessors( osg::Node* node );
 
+    protected:
+
+        osg::ref_ptr<RefNodeOperationVector> _postMergeOps;
+        osg::ref_ptr<RefNodeOperationVector> _preMergeOps;
+
     private:
         const ModelSourceOptions _options;
         optional<double> _minRange;
         optional<double> _maxRange;
         optional<int>    _renderOrder;
-
-        NodeOperationVector       _postProcessors;
-        mutable Threading::Mutex  _postProcessorsMutex;
+        DataExtentList   _dataExtents;
 
         friend class Map;
         friend class MapEngine;
diff --git a/src/osgEarth/ModelSource.cpp b/src/osgEarth/ModelSource.cpp
index fd8deda..7107f3c 100644
--- a/src/osgEarth/ModelSource.cpp
+++ b/src/osgEarth/ModelSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -74,10 +74,13 @@ ModelSourceOptions::getConfig() const
 ModelSource::ModelSource( const ModelSourceOptions& options ) :
 _options( options )
 {
+   _preMergeOps  = new RefNodeOperationVector();
+   _postMergeOps = new RefNodeOperationVector();
 }
 
 ModelSource::~ModelSource()
 {
+   //nop
 }
 
 
@@ -96,25 +99,53 @@ ModelSource::createNode(const Map*            map,
 
 
 void 
-ModelSource::addPostProcessor( NodeOperation* op )
+ModelSource::addPreMergeOperation( NodeOperation* op )
 {
     if ( op )
     {
-        Threading::ScopedMutexLock lock( _postProcessorsMutex );
-        _postProcessors.push_back( op );
+        _preMergeOps->mutex().writeLock();
+        _preMergeOps->push_back( op );
+        _preMergeOps->mutex().writeUnlock();
     }
 }
 
 
 void
-ModelSource::removePostProcessor( NodeOperation* op )
+ModelSource::removePreMergeOperation( NodeOperation* op )
 {
     if ( op )
     {
-        Threading::ScopedMutexLock lock( _postProcessorsMutex );
-        NodeOperationVector::iterator i = std::find( _postProcessors.begin(), _postProcessors.end(), op );
-        if ( i != _postProcessors.end() )
-            _postProcessors.erase( i );
+        _preMergeOps->mutex().writeLock();
+        NodeOperationVector::iterator i = std::find( _preMergeOps->begin(), _preMergeOps->end(), op );
+        if ( i != _postMergeOps->end() )
+            _preMergeOps->erase( i );
+        _preMergeOps->mutex().writeUnlock();
+    }
+}
+
+
+void 
+ModelSource::addPostMergeOperation( NodeOperation* op )
+{
+    if ( op )
+    {
+        _postMergeOps->mutex().writeLock();
+        _postMergeOps->push_back( op );
+        _postMergeOps->mutex().writeUnlock();
+    }
+}
+
+
+void
+ModelSource::removePostMergeOperation( NodeOperation* op )
+{
+    if ( op )
+    {
+        _postMergeOps->mutex().writeLock();
+        NodeOperationVector::iterator i = std::find( _postMergeOps->begin(), _postMergeOps->end(), op );
+        if ( i != _postMergeOps->end() )
+            _postMergeOps->erase( i );
+        _postMergeOps->mutex().writeUnlock();
     }
 }
 
@@ -124,11 +155,21 @@ ModelSource::firePostProcessors( osg::Node* node )
 {
     if ( node )
     {
-        Threading::ScopedMutexLock lock( _postProcessorsMutex );
-        for( NodeOperationVector::iterator i = _postProcessors.begin(); i != _postProcessors.end(); ++i )
+        // pres:
+        _preMergeOps->mutex().readLock();
+        for( NodeOperationVector::iterator i = _preMergeOps->begin(); i != _preMergeOps->end(); ++i )
         {
             i->get()->operator()( node );
         }
+        _preMergeOps->mutex().readUnlock();
+
+        // posts:
+        _postMergeOps->mutex().readLock();
+        for( NodeOperationVector::iterator i = _postMergeOps->begin(); i != _postMergeOps->end(); ++i )
+        {
+            i->get()->operator()( node );
+        }
+        _postMergeOps->mutex().readUnlock();
     }
 }
 
@@ -155,14 +196,9 @@ ModelSourceFactory::create( const ModelSourceOptions& options )
         rwopts->setPluginData( MODEL_SOURCE_OPTIONS_TAG, (void*)&options );
 
         modelSource = dynamic_cast<ModelSource*>( osgDB::readObjectFile( driverExt, rwopts.get() ) );
-        if ( modelSource )
-        {
-            //modelSource->setName( options.getName() );
-            OE_INFO << "Loaded ModelSource driver \"" << options.getDriver() << "\" OK" << std::endl;
-        }
-        else
+        if ( !modelSource )
         {
-            OE_WARN << "FAIL, unable to load model source driver for \"" << options.getDriver() << "\"" << std::endl;
+            OE_WARN << "FAILED to load model source driver \"" << options.getDriver() << "\"" << std::endl;
         }
     }
     else
diff --git a/src/osgEarth/NodeUtils b/src/osgEarth/NodeUtils
index 6e9e2dc..b9e1cdd 100644
--- a/src/osgEarth/NodeUtils
+++ b/src/osgEarth/NodeUtils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -40,7 +40,13 @@ namespace osgEarth
 
     typedef std::vector< osg::ref_ptr<NodeOperation> > NodeOperationVector;
 
-    struct RefNodeOperationVector : public osg::Referenced, public NodeOperationVector { };
+    struct RefNodeOperationVector : public osg::Referenced, public NodeOperationVector
+    {
+    public:
+       Threading::ReadWriteMutex& mutex() const { return _mutex; }
+    private:
+       mutable Threading::ReadWriteMutex _mutex;
+    };
 
     /**
      * A PagedLOD that will fire node operation callbacks when it merges
@@ -236,7 +242,7 @@ namespace osgEarth
         {
             for(unsigned i=0; i<oldGroup->getNumChildren(); ++i)
             {
-                newGroup->addChild( oldGroup->getChild(0) );
+                newGroup->addChild( oldGroup->getChild(i) );
             }
 
             osg::Node::ParentList parents = oldGroup->getParents();
@@ -247,6 +253,20 @@ namespace osgEarth
         }
     }
 
+    /** Insert a group between a parent and its children. */
+    inline void insertGroup(osg::Group* newGroup, osg::Group* parent)
+    {
+        if (parent && newGroup)
+        {
+            for(unsigned i=0; i<parent->getNumChildren(); ++i)
+            {
+                newGroup->addChild( parent->getChild(i) );
+            }
+            parent->removeChildren(0, parent->getNumChildren());
+            parent->addChild( newGroup );
+        }
+    }
+
     /**
      * Remove all a group's children.
      */
diff --git a/src/osgEarth/NodeUtils.cpp b/src/osgEarth/NodeUtils.cpp
index a4c0487..08e1a4f 100644
--- a/src/osgEarth/NodeUtils.cpp
+++ b/src/osgEarth/NodeUtils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -43,10 +43,12 @@ PagedLODWithNodeOperations::runPostMerge( osg::Node* node )
 {
     if ( _postMergeOps.valid() )
     {
+        _postMergeOps->mutex().readLock();
         for( NodeOperationVector::iterator i = _postMergeOps->begin(); i != _postMergeOps->end(); ++i )
         {
             i->get()->operator()( node );
         }
+        _postMergeOps->mutex().readUnlock();
     }
 }
 
@@ -151,10 +153,9 @@ _polygon( 0 )
 void
 PrimitiveSetTypeCounter::apply(osg::Geode& geode)
 {
-    const osg::Geode::DrawableList& drawables = geode.getDrawableList();
-    for( osg::Geode::DrawableList::const_iterator i = drawables.begin(); i != drawables.end(); ++i )
+    for(unsigned i=0; i<geode.getNumDrawables(); ++i)
     {
-        osg::Geometry* g = i->get()->asGeometry();
+        osg::Geometry* g = geode.getDrawable(i)->asGeometry();
         if ( g )
         {
             const osg::Geometry::PrimitiveSetList& primSets = g->getPrimitiveSetList();
diff --git a/src/osgEarth/Notify b/src/osgEarth/Notify
index 574cb70..6bc82f6 100644
--- a/src/osgEarth/Notify
+++ b/src/osgEarth/Notify
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -22,6 +22,7 @@
 
 #include <osgEarth/Export>
 #include <osg/Notify>
+#include <osg/Timer>
 #include <string>
 
 namespace osgEarth
@@ -54,4 +55,7 @@ namespace osgEarth
 #define OE_DEBUG OE_NOTIFY(osg::DEBUG_INFO,"[osgEarth]  ")
 #define OE_NULL if(false) osgEarth::notify(osg::ALWAYS)
 
+#define OE_START_TIMER(VAR) osg::Timer_t VAR##_timer = osg::Timer::instance()->tick()
+#define OE_STOP_TIMER(VAR) osg::Timer::instance()->delta_s( VAR##_timer, osg::Timer::instance()->tick() )
+
 #endif // OSGEARTH_NOTIFY_H
diff --git a/src/osgEarth/Notify.cpp b/src/osgEarth/Notify.cpp
index 8127756..b6d2331 100644
--- a/src/osgEarth/Notify.cpp
+++ b/src/osgEarth/Notify.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/OverlayDecorator b/src/osgEarth/OverlayDecorator
index 5d38f17..193274e 100644
--- a/src/osgEarth/OverlayDecorator
+++ b/src/osgEarth/OverlayDecorator
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -99,7 +99,7 @@ namespace osgEarth
             const double*                 _horizonDistance;  // points to the PVD horizon distance.
             osg::Group*                   _terrainParent;    // the terrain is in getChild(0).
             osg::Vec3d                    _eyeWorld;         // eyepoint in world coords
-            osgShadow::ConvexPolyhedron   _frustumPH;        // polyhedron representing the frustum
+            osgShadow::ConvexPolyhedron   _visibleFrustumPH; // polyhedron representing the frustum
         };
 
         // One of these per view (camera). The terrain state set
diff --git a/src/osgEarth/OverlayDecorator.cpp b/src/osgEarth/OverlayDecorator.cpp
index 45a0a11..9d05838 100755
--- a/src/osgEarth/OverlayDecorator.cpp
+++ b/src/osgEarth/OverlayDecorator.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -45,49 +45,38 @@ using namespace osgEarth;
 
 namespace
 {
-    struct ComputeVisibleBounds : public osg::NodeVisitor
+    struct ComputeVisibleBounds : public OverlayDecorator::InternalNodeVisitor
     {
-        ComputeVisibleBounds(osg::Polytope& tope, osg::Matrix& local2world) 
-            : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN)
+        ComputeVisibleBounds(osg::Polytope& tope, osg::Matrix& local2tope)
         {
-            _matrixStack.push(local2world);
-            _topeStack.push(tope);
+            setTraversalMode( TRAVERSE_ACTIVE_CHILDREN );
+            _matrixStack.push(local2tope);
+            _tope = tope;
         }
 
         void apply(osg::Geode& node)
         {
             const osg::BoundingSphere& bs = node.getBound();
             osg::Vec3 p = bs.center() * _matrixStack.top();
-            if ( _topeStack.top().contains(p) )
+            if ( _tope.contains(p) )
             {
                 _bs.expandBy( osg::BoundingSphere(p, bs.radius()) );
             }
-            //if ( _topeStack.top().contains(bs) )
-            //{
-            //    osg::Vec3 p = _matrixStack.top() * bs.center();
-            //    _bs.expandBy( osg::BoundingSphere(p, bs.radius()) );
-            //}
         }
 
         void apply(osg::Transform& xform)
         {
-            osg::Matrix m;
+            osg::Matrix m( _matrixStack.top() );
             xform.computeLocalToWorldMatrix(m, this);
-
-            _matrixStack.push( _matrixStack.top() );
-            _matrixStack.top().preMult( m );
-
-            //_topeStack.push( _topeStack.top() );
-            //_topeStack.top().transformProvidingInverse(m);
+            _matrixStack.push( m );
 
             traverse(xform);
 
             _matrixStack.pop();
-            //_topeStack.pop();
         }
 
         std::stack<osg::Matrix>   _matrixStack;
-        std::stack<osg::Polytope> _topeStack;
+        osg::Polytope             _tope;
         osg::BoundingSphere       _bs;
     };
 
@@ -107,6 +96,22 @@ namespace
         }
     }
 
+    void clampToNearFar(osg::Matrix& m, double newNear, double newFar)
+    {
+        if ( osg::equivalent(m(0,3),0.0) && osg::equivalent(m(1,3),0.0) && osg::equivalent(m(2,3),0.0) )
+        {
+            double l,r,b,t,n,f;
+            m.getOrtho(l,r,b,t,n,f);
+            m.makeOrtho(l,r,b,t, std::max(n, newNear), std::min(f,newFar));
+        }
+        else
+        {
+            double v,a,n,f;
+            m.getPerspective(v,a,n,f);
+            m.makePerspective(v,a, std::max(n, newNear), std::min(f, newFar));
+        }
+    }
+
 
     /**
      * Interects a finite ray with a sphere of radius R. The ray is defined
@@ -230,7 +235,7 @@ namespace
                           double& xmax, double& ymax,
                           double& maxDistance)
     {
-        xmin = DBL_MAX, ymin = DBL_MAX, xmax = -DBL_MAX, ymax = -DBL_MAX;
+        xmin  = DBL_MAX, ymin = DBL_MAX, xmax = -DBL_MAX, ymax = -DBL_MAX;
         double maxDist2 = 0.0;
 
         for( std::vector<osg::Vec3d>::iterator i = verts.begin(); i != verts.end(); ++i )
@@ -248,12 +253,32 @@ namespace
 
         maxDistance = sqrt(maxDist2);
     }
+
+
+    /**
+     * 
+     */
+    void
+    getNearFar(const osg::Matrix&      viewMatrix,
+              std::vector<osg::Vec3d>& verts,
+              double& znear,
+              double& zfar)
+    {
+        znear = DBL_MAX, zfar = 0.0;
+
+        for( std::vector<osg::Vec3d>::iterator i = verts.begin(); i != verts.end(); ++i )
+        {
+            osg::Vec3d d = (*i) * viewMatrix; // world to view
+            if ( -d.z() < znear ) znear = -d.z();
+            if ( -d.z() > zfar  ) zfar  = -d.z();
+        }
+    }
 }
 
 //---------------------------------------------------------------------------
 
 OverlayDecorator::OverlayDecorator() :
-_useShaders          ( false ),
+_useShaders          ( true ),
 _dumpRequested       ( false ),
 _rttTraversalMask    ( ~0 ),
 _maxHorizonDistance  ( DBL_MAX ),
@@ -352,12 +377,6 @@ OverlayDecorator::onInstall( TerrainEngineNode* engine )
     _srs = info.getProfile()->getSRS();
     _ellipsoid = info.getProfile()->getSRS()->getEllipsoid();
 
-    //todo: need this? ... probably not anymore
-    _useShaders = 
-        Registry::capabilities().supportsGLSL() && (
-            !engine->getTextureCompositor() ||
-            engine->getTextureCompositor()->usesShaderComposition() );
-
     for(Techniques::iterator t = _techniques.begin(); t != _techniques.end(); ++t )
     {
         t->get()->onInstall( engine );
@@ -595,13 +614,39 @@ OverlayDecorator::cullTerrainAndCalculateRTTParams(osgUtil::CullVisitor* cv,
 #endif
         if ( visibleOverlayBS.valid() )
         {
-            osg::BoundingBox visibleOverlayBB;
-            visibleOverlayBB.expandBy( visibleOverlayBS );
+            // form an axis-aligned polytope around the bounding sphere of the
+            // overlay geometry. Use that to cut the camera frustum polytope.
+            // This will minimize the coverage area and also ensure inclusion
+            // of geometry that falls outside the camera frustum but inside
+            // the overlay area.
             osg::Polytope visibleOverlayPT;
-            visibleOverlayPT.setToBoundingBox( visibleOverlayBB );
+
+            osg::Vec3d tangent(0,0,1);
+            if (fabs(worldUp*tangent) > 0.9999)
+                tangent.set(0,1,0);
+
+            osg::Vec3d westVec  = worldUp^tangent; westVec.normalize();
+            osg::Vec3d southVec = worldUp^westVec; southVec.normalize();
+            osg::Vec3d eastVec  = -westVec;
+            osg::Vec3d northVec = -southVec;
+
+            osg::Vec3d westPt  = visibleOverlayBS.center() + westVec*visibleOverlayBS.radius();
+            osg::Vec3d eastPt  = visibleOverlayBS.center() + eastVec*visibleOverlayBS.radius();
+            osg::Vec3d northPt = visibleOverlayBS.center() + northVec*visibleOverlayBS.radius();
+            osg::Vec3d southPt = visibleOverlayBS.center() + southVec*visibleOverlayBS.radius();
+
+            visibleOverlayPT.add(osg::Plane(-westVec,  westPt));
+            visibleOverlayPT.add(osg::Plane(-eastVec,  eastPt));
+            visibleOverlayPT.add(osg::Plane(-southVec, southPt));
+            visibleOverlayPT.add(osg::Plane(-northVec, northPt));
+
             visiblePH.cut( visibleOverlayPT );
         }
 
+        // for dumping, we want the previous fram's projection matrix
+        // becasue the technique itself may have modified it.
+        osg::Matrix prevProjMatrix = params._rttProjMatrix;
+
         // extract the verts associated with the frustum's PH:
         std::vector<osg::Vec3d> verts;
         visiblePH.getPoints( verts );
@@ -624,17 +669,37 @@ OverlayDecorator::cullTerrainAndCalculateRTTParams(osgUtil::CullVisitor* cv,
             if ( _isGeocentric )
                 dist = std::min(dist, eyeLen);
 
+            // Even through using xmin and xmax directly results in a tighter fit, 
+            // it offsets the eyepoint from the center of the projection frustum.
+            // This causes problems for the draping projection matrix optimizer, so
+            // for now instead of re-doing that code we will just center the eyepoint
+            // here by using the larger of xmin and xmax. -gw.
+#if 1
+            double x = std::max( fabs(xmin), fabs(xmax) );
+            rttProjMatrix.makeOrtho(-x, x, ymin, ymax, 0.0, dist+zspan);
+#else
+            //Note: this was the original setup, which is technically optimal:
             rttProjMatrix.makeOrtho(xmin, xmax, ymin, ymax, 0.0, dist+zspan);
+#endif
 
-            //OE_WARN << LC << "verts size = " << verts.size()
-            //    << "xmin=" << xmin << ", xmax=" << xmax
-            //    << ", ymin=" << ymin << ", ymax=" << ymax
-            //    << std::endl;
-
+            // Clamp the view frustum's N/F to the visible geometry. This clamped
+            // frustum is the one we'll send to the technique.
+            double visNear, visFar;
+            getNearFar( *cv->getModelViewMatrix(), verts, visNear, visFar );
+            osg::Matrix clampedProjMat( projMatrix );
+            clampToNearFar( clampedProjMat, visNear, visFar );
+            osg::Matrix clampedMVP = *cv->getModelViewMatrix() * clampedProjMat;
+            osg::Matrix inverseClampedMVP;
+            inverseClampedMVP.invert(clampedMVP);
+            osgShadow::ConvexPolyhedron clampedFrustumPH;
+            clampedFrustumPH.setToUnitFrustum(true, true);
+            clampedFrustumPH.transform( inverseClampedMVP, clampedMVP );
+
+            // assign the matrices to the technique.
             params._rttViewMatrix.set( rttViewMatrix );
             params._rttProjMatrix.set( rttProjMatrix );
             params._eyeWorld = eye;
-            params._frustumPH = frustumPH;
+            params._visibleFrustumPH = clampedFrustumPH; //frustumPH;
         }
 
         // service a "dump" of the polyhedrons for dubugging purposes
@@ -650,6 +715,11 @@ OverlayDecorator::cullTerrainAndCalculateRTTParams(osgUtil::CullVisitor* cv,
             osg::Node* camNode = osgDB::readNodeFile(fn);
             camNode->setName("camera");
 
+            // visible overlay BEFORE cutting:
+            //uncutVisiblePH.dumpGeometry(0,0,0,fn,osg::Vec4(0,1,1,1),osg::Vec4(0,1,1,.25));
+            //osg::Node* overlay = osgDB::readNodeFile(fn);
+            //overlay->setName("overlay");
+
             // visible overlay Polyherdron AFTER cuting:
             visiblePH.dumpGeometry(0,0,0,fn,osg::Vec4(1,.5,1,1),osg::Vec4(1,.5,0,.25));
             osg::Node* intersection = osgDB::readNodeFile(fn);
@@ -659,7 +729,7 @@ OverlayDecorator::cullTerrainAndCalculateRTTParams(osgUtil::CullVisitor* cv,
             {
                 osgShadow::ConvexPolyhedron rttPH;
                 rttPH.setToUnitFrustum( true, true );
-                osg::Matrixd MVP = params._rttViewMatrix * params._rttProjMatrix;
+                osg::Matrixd MVP = params._rttViewMatrix * prevProjMatrix; //params._rttProjMatrix;
                 osg::Matrixd inverseMVP;
                 inverseMVP.invert(MVP);
                 rttPH.transform( inverseMVP, MVP );
@@ -679,6 +749,7 @@ OverlayDecorator::cullTerrainAndCalculateRTTParams(osgUtil::CullVisitor* cv,
             osg::Group* g = new osg::Group();
             g->getOrCreateStateSet()->setAttribute(new osg::Program(), 0);
             g->addChild(camNode);
+            //g->addChild(overlay);
             g->addChild(intersection);
             g->addChild(rttNode);
             g->addChild(dsgmt);
@@ -726,31 +797,34 @@ OverlayDecorator::getPerViewData(osg::Camera* key)
 void
 OverlayDecorator::traverse( osg::NodeVisitor& nv )
 {
-    if ( true ) //if (_totalOverlayChildren > 0 )
+    bool defaultTraversal = true;
+
+    // in the CULL traversal, find the per-view data associated with the 
+    // cull visitor's current camera view and work with that:
+    if ( nv.getVisitorType() == nv.CULL_VISITOR )
     {
-        // in the CULL traversal, find the per-view data associated with the 
-        // cull visitor's current camera view and work with that:
-        if ( nv.getVisitorType() == nv.CULL_VISITOR )
+        osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
+        osg::Camera* camera = cv->getCurrentCamera();
+
+        if ( camera != 0L && (_rttTraversalMask & nv.getTraversalMask()) != 0 )
         {
-            osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
-            osg::Camera* camera = cv->getCurrentCamera();
+            // access per-camera data to support multi-threading:
+            PerViewData& pvd = getPerViewData( camera );
 
-            if ( camera != 0L && (_rttTraversalMask & nv.getTraversalMask()) != 0 )
+            // technique-specific setup prior to traversing:
+            bool hasOverlayData = false;
+            for(unsigned i=0; i<_techniques.size(); ++i)
             {
-                PerViewData& pvd = getPerViewData( camera );
-
-                //TODO:
-                // check whether we need to recalculate the RTT camera params.
-                // don't do it if the main camera hasn't moved;
-                // also, tell the ClampingTech not to re-snap the depth texture
-                // unless something has changed (e.g. camera params, terrain bounds..?
-                // what about paging..?)
-
-                // technique-specific setup prior to traversing:
-                for(unsigned i=0; i<_techniques.size(); ++i)
+                if ( _techniques[i]->hasData(pvd._techParams[i]) )
                 {
+                    hasOverlayData = true;
                     _techniques[i]->preCullTerrain( pvd._techParams[i], cv );
                 }
+            }
+
+            if ( hasOverlayData )
+            {
+                defaultTraversal = false;
 
                 // shared terrain culling pass:
                 cullTerrainAndCalculateRTTParams( cv, pvd );
@@ -762,25 +836,20 @@ OverlayDecorator::traverse( osg::NodeVisitor& nv )
                     _techniques[i]->cullOverlayGroup( params, cv );
                 }
             }
-            else
-            {
-                osg::Group::traverse(nv);
-            }
         }
+    }
 
-        else
+    else
+    {
+        // Some other type of visitor (like update or intersection). Skip the technique
+        // and traverse the geometry directly.
+        for(unsigned i=0; i<_overlayGroups.size(); ++i)
         {
-            // Some other type of visitor (like update or intersection). Skip the technique
-            // and traverse the geometry directly.
-            for(unsigned i=0; i<_overlayGroups.size(); ++i)
-            {
-                _overlayGroups[i]->accept( nv );
-            }
-
-            osg::Group::traverse( nv );
+            _overlayGroups[i]->accept( nv );
         }
     }
-    else
+
+    if ( defaultTraversal )
     {
         osg::Group::traverse( nv );
     }
diff --git a/src/osgEarth/OverlayNode b/src/osgEarth/OverlayNode
index da137e7..3037a86 100644
--- a/src/osgEarth/OverlayNode
+++ b/src/osgEarth/OverlayNode
@@ -77,12 +77,12 @@ namespace osgEarth
     protected:
         /** dtor */
         virtual ~OverlayNode();
+        osg::ref_ptr<osg::Group> _overlayProxyContainer;
 
     private:
         bool                          _active;
         bool                          _dirty;
         bool                          _newActive;
-        osg::ref_ptr<osg::Group>      _overlayProxyContainer;
         osg::observer_ptr<MapNode>    _mapNode;
         TechniqueProvider             _getGroup;
 
diff --git a/src/osgEarth/OverlayNode.cpp b/src/osgEarth/OverlayNode.cpp
index 2da2216..e6ad651 100644
--- a/src/osgEarth/OverlayNode.cpp
+++ b/src/osgEarth/OverlayNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -150,11 +150,18 @@ _getGroup ( provider )
 
     setMapNode( mapNode );
 
-    if ( mapNode )
+    if ( mapNode && _getGroup )
     {
-        // If draping is requested, set up to apply it on the first update traversal.
-        // Can't apply it until then since we need safe access to the MapNode.
-        setActive( active );
+        if (_getGroup(mapNode) != 0L)
+        {
+            // If draping is requested, set up to apply it on the first update traversal.
+            // Can't apply it until then since we need safe access to the MapNode.
+            setActive( active );
+        }
+        else
+        {
+            OE_WARN << LC << "Overlay technique not available; disabled." << std::endl;
+        }
     }
 }
 
@@ -363,7 +370,11 @@ OverlayNode::traverse( osg::NodeVisitor& nv )
                   {
                     // Insert newlly found intersections into the original intersector.
                     for (PrimitiveIntersector::Intersections::iterator it = pi2->getIntersections().begin(); it != pi2->getIntersections().end(); ++it)
-                      pi->insertIntersection(*it);
+                    {
+                      PrimitiveIntersector::Intersection intersection(*it);
+                      intersection.ratio = 1.0;
+                      pi->insertIntersection(intersection);
+                    }
                   }
                 }
                 else
diff --git a/src/osgEarth/AlphaEffect b/src/osgEarth/PhongLightingEffect
similarity index 63%
copy from src/osgEarth/AlphaEffect
copy to src/osgEarth/PhongLightingEffect
index dba3b41..8a23169 100644
--- a/src/osgEarth/AlphaEffect
+++ b/src/osgEarth/PhongLightingEffect
@@ -16,8 +16,8 @@
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
-#ifndef OSGEARTH_ALPHA_EFFECT_H
-#define OSGEARTH_ALPHA_EFFECT_H
+#ifndef OSGEARTH_PHONG_LIGHTING_EFFECT_H
+#define OSGEARTH_PHONG_LIGHTING_EFFECT_H
 
 #include <osgEarth/Common>
 #include <osg/StateSet>
@@ -27,21 +27,24 @@
 namespace osgEarth
 {
     /**
-     * Shader effect that lets you adjust the alpha channel.
+     * Shader effect that performs simple Phong lighting.
      */
-    class OSGEARTH_EXPORT AlphaEffect : public osg::Referenced
+    class OSGEARTH_EXPORT PhongLightingEffect : public osg::Referenced
     {
     public:
         /** constructs a new effect */
-        AlphaEffect();
+        PhongLightingEffect();
 
         /** contructs a new effect and attaches it to a stateset. */
-        AlphaEffect(osg::StateSet* stateset);
+        PhongLightingEffect(osg::StateSet* stateset);
 
-    public:
-        /** The alpha channel value [0..1] */
-        void setAlpha(float value);
-        float getAlpha() const;
+        /** is it supported by the h/w? */
+        bool supported() const { return _supported; }
+
+        /** whether to create its own lighting mode uniform (default = true).
+          * Set this to false if you are creating the lighting uniform elsewhere.
+          * The lighting uniform is returned by ShaderFactory::createUniformForGLMode(GL_LIGHTING). */
+        void setCreateLightingUniform(bool value);
 
     public:
         /** attach this effect to a stateset. */
@@ -53,16 +56,18 @@ namespace osgEarth
         void detach(osg::StateSet* stateset);
 
     protected:
-        virtual ~AlphaEffect();
+        virtual ~PhongLightingEffect();
 
         typedef std::list< osg::observer_ptr<osg::StateSet> > StateSetList;
 
+        bool _supported;
         StateSetList _statesets;
-        osg::ref_ptr<osg::Uniform>       _alphaUniform;
+        bool _createLightingUniform;
+        osg::ref_ptr<osg::Uniform> _lightingUniform;
 
         void init();
     };
 
 } // namespace osgEarth::Util
 
-#endif // OSGEARTH_ALPHA_EFFECT_H
+#endif // OSGEARTH_PHONG_LIGHTING_EFFECT_H
diff --git a/src/osgEarth/PhongLightingEffect.cpp b/src/osgEarth/PhongLightingEffect.cpp
new file mode 100644
index 0000000..615b2b2
--- /dev/null
+++ b/src/osgEarth/PhongLightingEffect.cpp
@@ -0,0 +1,217 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+#include <osgEarth/PhongLightingEffect>
+#include <osgEarth/Registry>
+#include <osgEarth/Capabilities>
+#include <osgEarth/ShaderFactory>
+#include <osgEarth/StringUtils>
+#include <osgEarth/VirtualProgram>
+
+using namespace osgEarth;
+
+namespace
+{
+#ifdef OSG_GLES2_AVAILABLE
+    static const char* Phong_Vertex =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+        "uniform bool oe_mode_GL_LIGHTING; \n"
+        "varying vec4 oe_lighting_adjustment; \n"
+        "varying vec4 oe_lighting_zero_vec; \n"
+        "varying vec3 oe_Normal; \n"
+
+        "void oe_phong_vertex(inout vec4 VertexVIEW) \n"
+        "{ \n"
+        "    if ( oe_mode_GL_LIGHTING == false ) return; \n"
+        "    oe_lighting_adjustment = vec4(1.0); \n"
+        "    vec3 N = oe_Normal; \n"
+        "    float NdotL = dot( N, normalize(gl_LightSource[0].position.xyz) ); \n"
+        "    NdotL = max( 0.0, NdotL ); \n"
+
+        // NOTE: See comment in the fragment shader below for an explanation of
+        //       this oe_zero_vec value.
+        "    oe_lighting_zero_vec = vec4(0.0); \n"
+
+        "    vec4 adj = \n"
+        "        gl_FrontLightProduct[0].ambient + \n"
+        "        gl_FrontLightProduct[0].diffuse * NdotL; \n"
+        "    oe_lighting_adjustment = clamp( adj, 0.0, 1.0 ); \n"
+        "} \n";
+
+    static const char* Phong_Fragment =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+        
+        "uniform bool oe_mode_GL_LIGHTING; \n"
+        "varying vec4 oe_lighting_adjustment; \n"
+        "varying vec4 oe_lighting_zero_vec; \n"
+
+        "void oe_phong_fragment(inout vec4 color) \n"
+        "{ \n"
+        "    if ( oe_mode_GL_LIGHTING == false ) return; \n"
+        //NOTE: The follow was changed from the single line
+        //      "color *= oe_lighting_adjustment" to the current code to fix
+        //      an issue on iOS devices.  Adding a varying vec4 value set to
+        //      (0.0,0.0,0.0,0.0) to the color should not make a difference,
+        //      but it is part of the solution to the issue we were seeing.
+        //      Without it and the additional lines of code, the globe was
+        //      rendering textureless (just a white surface with lighting).
+        "    float alpha = color.a; \n"
+        "    color = color * oe_lighting_adjustment + oe_lighting_zero_vec; \n"
+        "    color.a = alpha; \n"
+        "} \n";
+
+#else
+
+    static const char* Phong_Vertex =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+        
+        "uniform bool oe_mode_GL_LIGHTING; \n"
+        "varying vec3 oe_phong_vertexView3; \n"
+
+        "void oe_phong_vertex(inout vec4 VertexVIEW) \n"
+        "{ \n"
+        "    if ( oe_mode_GL_LIGHTING == false ) return; \n"
+        "    oe_phong_vertexView3 = VertexVIEW.xyz / VertexVIEW.w; \n"
+        "} \n";
+
+    static const char* Phong_Fragment =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+        "uniform bool oe_mode_GL_LIGHTING; \n"
+        "varying vec3 oe_phong_vertexView3; \n"
+        "varying vec3 oe_Normal; \n"
+
+        "void oe_phong_fragment(inout vec4 color) \n"
+        "{ \n"        
+        "    if ( oe_mode_GL_LIGHTING == false ) return; \n"
+
+        "    vec3 L = normalize(gl_LightSource[0].position.xyz); \n"
+        "    vec3 N = normalize(oe_Normal); \n"
+        
+        "    vec4 ambient = gl_FrontLightProduct[0].ambient; \n"
+
+        "    float NdotL = max(dot(N,L), 0.0); \n"
+
+        "    vec4 diffuse = gl_FrontLightProduct[0].diffuse * NdotL; \n"
+        
+        "    vec4 specular= vec4(0); \n"
+        "    if (NdotL > 0.0) \n"
+        "    { \n"
+        "        vec3 V = normalize(oe_phong_vertexView3); \n"
+        "        vec3 H = normalize(L-V); \n"
+        "        float HdotN = max(dot(H,N), 0.0); \n"
+        "        float shine = clamp(gl_FrontMaterial.shininess, 1.0, 128.0); \n"
+        "        specular = gl_FrontLightProduct[0].specular * pow(HdotN, shine); \n"
+        "    } \n"
+
+        "    color.rgb *= ambient.rgb + diffuse.rgb + specular.rgb; \n"
+        "} \n";
+#endif
+}
+
+PhongLightingEffect::PhongLightingEffect()
+{
+    init();
+}
+
+PhongLightingEffect::PhongLightingEffect(osg::StateSet* stateset)
+{
+    init();
+    attach( stateset );
+}
+
+void
+PhongLightingEffect::init()
+{
+    _supported = Registry::capabilities().supportsGLSL();
+    if ( _supported )
+    {
+        _lightingUniform = Registry::shaderFactory()->createUniformForGLMode( GL_LIGHTING, 1 );
+    }
+}
+
+void
+PhongLightingEffect::setCreateLightingUniform(bool value)
+{
+    if ( !value )
+    {        
+        _lightingUniform = 0L;
+    }
+}
+
+PhongLightingEffect::~PhongLightingEffect()
+{
+    detach();
+}
+
+void
+PhongLightingEffect::attach(osg::StateSet* stateset)
+{
+    if ( stateset && _supported )
+    {
+        _statesets.push_back(stateset);
+        VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
+        vp->setName( "osgEarth.PhongLightingEffect" );
+        vp->setFunction( "oe_phong_vertex", Phong_Vertex, ShaderComp::LOCATION_VERTEX_VIEW );
+        vp->setFunction( "oe_phong_fragment", Phong_Fragment, ShaderComp::LOCATION_FRAGMENT_LIGHTING );
+        if ( _lightingUniform.valid() )
+            stateset->addUniform( _lightingUniform.get() );
+    }
+}
+
+void
+PhongLightingEffect::detach()
+{
+    if ( _supported )
+    {
+        for (StateSetList::iterator it = _statesets.begin(); it != _statesets.end(); ++it)
+        {
+            osg::ref_ptr<osg::StateSet> stateset;
+            if ( (*it).lock(stateset) )
+            {
+                detach( stateset );
+                (*it) = 0L;
+            }
+        }
+
+        _statesets.clear();
+    }
+}
+
+void
+PhongLightingEffect::detach(osg::StateSet* stateset)
+{
+    if ( stateset && _supported )
+    {
+        if ( _lightingUniform.valid() )
+            stateset->removeUniform( _lightingUniform.get() );
+
+        VirtualProgram* vp = VirtualProgram::get( stateset );
+        if ( vp )
+        {
+            vp->removeShader( "oe_phong_vertex" );
+            vp->removeShader( "oe_phong_fragment" );
+        }
+    }
+}
diff --git a/src/osgEarth/Pickers b/src/osgEarth/Pickers
index 3657ad2..aa0765d 100644
--- a/src/osgEarth/Pickers
+++ b/src/osgEarth/Pickers
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Pickers.cpp b/src/osgEarth/Pickers.cpp
index a8fdee5..24f4578 100644
--- a/src/osgEarth/Pickers.cpp
+++ b/src/osgEarth/Pickers.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/PrimitiveIntersector b/src/osgEarth/PrimitiveIntersector
index 71b9e2c..7fe0241 100644
--- a/src/osgEarth/PrimitiveIntersector
+++ b/src/osgEarth/PrimitiveIntersector
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -46,6 +46,8 @@ public:
             ratio(-1.0),
             primitiveIndex(0) {}
 
+        Intersection(const Intersection &rhs);
+
         bool operator < (const Intersection& rhs) const { return ratio < rhs.ratio; }
 
         typedef std::vector<unsigned int>   IndexList;
@@ -109,6 +111,8 @@ protected:
     bool intersects(const osg::BoundingSphere& bs);
     bool intersectAndClip(osg::Vec3d& s, osg::Vec3d& e,const osg::BoundingBox& bb);
 
+    unsigned int findPrimitiveIndex(osg::Drawable* drawable, unsigned int index);
+
     PrimitiveIntersector* _parent;
 
     osg::Vec3d  _start;
diff --git a/src/osgEarth/PrimitiveIntersector.cpp b/src/osgEarth/PrimitiveIntersector.cpp
index 3232c0f..17e78b0 100644
--- a/src/osgEarth/PrimitiveIntersector.cpp
+++ b/src/osgEarth/PrimitiveIntersector.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -19,7 +19,7 @@
 
 #include <osgEarth/PrimitiveIntersector>
 #include <osgEarth/StringUtils>
-
+#include <osgEarth/Utils>
 #include <osg/Geode>
 #include <osg/KdTree>
 #include <osg/Notify>
@@ -342,6 +342,19 @@ PrimitiveIntersector::PrimitiveIntersector(CoordinateFrame cf, const osg::Vec3d&
   setThickness(thickness);
 }
 
+PrimitiveIntersector::Intersection::Intersection(const PrimitiveIntersector::Intersection &rhs)
+{
+  ratio = rhs.ratio;
+  nodePath = rhs.nodePath;
+  drawable = rhs.drawable;
+  matrix = rhs.matrix;
+  localIntersectionPoint = rhs.localIntersectionPoint;
+  localIntersectionNormal = rhs.localIntersectionNormal;
+  indexList = rhs.indexList;
+  ratioList = rhs.ratioList;
+  primitiveIndex = rhs.primitiveIndex;
+}
+
 void PrimitiveIntersector::setThickness(double thickness)
 {
   _thicknessVal = thickness;
@@ -427,7 +440,8 @@ void PrimitiveIntersector::intersect(osgUtil::IntersectionVisitor& iv, osg::Draw
 {
     if (reachedLimit()) return;
 
-    osg::BoundingBox bb = drawable->getBound();
+    osg::BoundingBox bb = Utils::getBoundingBox(drawable);
+
     if (bb.valid())
         bb.expandBy(osg::BoundingSphere(bb.center(), (_thickness - _start).length()));
 
@@ -471,7 +485,7 @@ void PrimitiveIntersector::intersect(osgUtil::IntersectionVisitor& iv, osg::Draw
             hit.matrix = iv.getModelMatrix();
             hit.nodePath = iv.getNodePath();
             hit.drawable = drawable;
-            hit.primitiveIndex = triHit._index;
+            hit.primitiveIndex = findPrimitiveIndex(drawable, triHit._index);
 
             hit.localIntersectionPoint = _start*(1.0-remap_ratio) + _end*remap_ratio;
 
@@ -679,3 +693,62 @@ bool PrimitiveIntersector::intersectAndClip(osg::Vec3d& s, osg::Vec3d& e,const o
 
     return true;
 }
+
+unsigned int PrimitiveIntersector::findPrimitiveIndex(osg::Drawable* drawable, unsigned int index)
+{
+    if (!drawable)
+      return index;
+
+    const osg::Geometry* geom = drawable->asGeometry();
+    if ( geom )
+    {
+        unsigned int primIndex = 0;
+        unsigned int encounteredPrims = 0;
+
+        const osg::Geometry::PrimitiveSetList& primSets = geom->getPrimitiveSetList();
+        for( osg::Geometry::PrimitiveSetList::const_iterator i = primSets.begin(); i != primSets.end(); ++i )
+        {
+            bool simple = false;
+            unsigned int numPrims = 0;
+
+            const osg::PrimitiveSet* pset = i->get();
+            switch( pset->getMode() )
+            {
+            case osg::PrimitiveSet::TRIANGLE_STRIP:
+            case osg::PrimitiveSet::TRIANGLE_FAN:
+                numPrims = osg::maximum(pset->getNumIndices() - 2, 0U);
+                encounteredPrims += numPrims;
+                break;
+            case osg::PrimitiveSet::QUAD_STRIP:
+                numPrims = osg::maximum((pset->getNumIndices() - 2) / 2, 0U);
+                encounteredPrims += numPrims;
+                break;
+            case osg::PrimitiveSet::LINE_STRIP:
+                numPrims = osg::maximum(pset->getNumIndices() - 1, 0U);
+                encounteredPrims += numPrims;
+                break;
+            case osg::PrimitiveSet::LINE_LOOP:
+                numPrims = pset->getNumIndices();
+                encounteredPrims += numPrims;
+                break;
+            default:
+                numPrims = pset->getNumPrimitives();
+                primIndex += osg::minimum(numPrims, index - encounteredPrims);
+                encounteredPrims += numPrims;
+                simple = true;
+            }
+
+            if (encounteredPrims > index)
+                return primIndex;
+
+            // primIndex already incremented above for simple primitives
+            if (!simple)
+                primIndex++;
+        }
+    }
+
+    //Should never reach here
+    OE_DEBUG << LC << "Could not find primitive index!" << std::endl;
+
+    return index;
+}
diff --git a/src/osgEarth/Profile b/src/osgEarth/Profile
index 97b3b97..7307249 100644
--- a/src/osgEarth/Profile
+++ b/src/osgEarth/Profile
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -229,6 +229,7 @@ namespace osgEarth
          */
         virtual void getIntersectingTiles(
             const GeoExtent& extent,
+            unsigned localLOD,
             std::vector<TileKey>& out_intersectingKeys) const;
 
         /** 
@@ -271,7 +272,7 @@ namespace osgEarth
          * Given another Profile and an LOD in that Profile, determine 
          * the LOD in this Profile that is nearly equivalent.
          */
-        unsigned int getEquivalentLOD( const Profile* profile, unsigned int lod ) const;
+        virtual unsigned getEquivalentLOD(const Profile* profile, unsigned lod) const;
 
     public:
 
@@ -301,6 +302,7 @@ namespace osgEarth
 
         virtual void addIntersectingTiles(
             const GeoExtent& key_ext,
+            unsigned localLOD,
             std::vector<TileKey>& out_intersectingKeys) const;
 
 
diff --git a/src/osgEarth/Profile.cpp b/src/osgEarth/Profile.cpp
index ec6902a..3d7ef9e 100644
--- a/src/osgEarth/Profile.cpp
+++ b/src/osgEarth/Profile.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -509,13 +509,19 @@ Profile::createTileKey( double x, double y, unsigned int level ) const
 {
     if ( _extent.contains( x, y ) )
     {
-        int tilesX = (int)_numTilesWideAtLod0 * (1 << (int)level);
-        int tilesY = (int)_numTilesHighAtLod0 * (1 << (int)level);
+        unsigned int tilesX = (unsigned int)_numTilesWideAtLod0 * (1 << (unsigned int)level);
+        unsigned int tilesY = (unsigned int)_numTilesHighAtLod0 * (1 << (unsigned int)level);
+
+        if (((_numTilesWideAtLod0 != 0) && ((tilesX / _numTilesWideAtLod0) != (1 << (unsigned int) level))) ||
+            ((_numTilesHighAtLod0 != 0) && ((tilesY / _numTilesHighAtLod0) != (1 << (unsigned int) level))))
+        {	// check for overflow condition
+            return (TileKey::INVALID);
+        }
 
         double rx = (x - _extent.xMin()) / _extent.width();
-        int tileX = osg::clampBelow( (int)(rx * (double)tilesX), tilesX-1 );
+        int tileX = osg::clampBelow( (unsigned int)(rx * (double)tilesX), tilesX-1 );
         double ry = (y - _extent.yMin()) / _extent.height();
-        int tileY = osg::clampBelow( (int)((1.0-ry) * (double)tilesY), tilesY-1 );
+        int tileY = osg::clampBelow( (unsigned int)((1.0-ry) * (double)tilesY), tilesY-1 );
 
         return TileKey( level, tileX, tileY, this );
     }
@@ -594,7 +600,7 @@ namespace
 }
 
 void
-Profile::addIntersectingTiles(const GeoExtent& key_ext, std::vector<TileKey>& out_intersectingKeys) const
+Profile::addIntersectingTiles(const GeoExtent& key_ext, unsigned localLOD, std::vector<TileKey>& out_intersectingKeys) const
 {
     // assume a non-crossing extent here.
     if ( key_ext.crossesAntimeridian() )
@@ -605,78 +611,27 @@ Profile::addIntersectingTiles(const GeoExtent& key_ext, std::vector<TileKey>& ou
 
     int tileMinX, tileMaxX;
     int tileMinY, tileMaxY;
-    int destLOD;
-
-    // Special path for mercator (does NOT work for cube, e.g.)
-    if ( key_ext.getSRS()->isMercator() )
-    {
-        int precision = 5;
-        double eps = 0.001;
-
-        double keyWidth = round(key_ext.width(), precision);
-        destLOD = 0;
-        double w, h;
-        getTileDimensions(0, w, h);
-        for(; (round(w,precision) - keyWidth) > eps; w*=0.5, h*=0.5, destLOD++ );
 
-        double destTileWidth, destTileHeight;
-        getTileDimensions( destLOD, destTileWidth, destTileHeight );
-        destTileWidth = round(destTileWidth, precision);
-        destTileHeight = round(destTileHeight, precision);
+    double destTileWidth, destTileHeight;
+    getTileDimensions(localLOD, destTileWidth, destTileHeight);
 
-        tileMinX = quantize( ((key_ext.xMin() - _extent.xMin()) / destTileWidth), eps );
-        tileMaxX = (int)((key_ext.xMax() - _extent.xMin()) / destTileWidth);
-
-        tileMinY = quantize( ((_extent.yMax() - key_ext.yMax()) / destTileHeight), eps );
-        tileMaxY = (int) ((_extent.yMax() - key_ext.yMin()) / destTileHeight);
-    }
-
-    else
-    {
-        double keyWidth = key_ext.width();
-        double keyHeight = key_ext.height();
-
-        // bail out if the key has a null extent. This might happen is the original key represents an
-        // area in one profile that is out of bounds in this profile.
-        if ( keyWidth <= 0.0 && keyHeight <= 0.0 )
-            return;
-
-        double keySpan = std::min( keyWidth, keyHeight );
-        double keyArea = keyWidth * keyHeight;
-        double keyAvg  = 0.5*(keyWidth+keyHeight);
-
-        destLOD = 1;
-        double destTileWidth, destTileHeight;
-
-        int currLOD = 0;
-        destLOD = currLOD;
-        getTileDimensions(destLOD, destTileWidth, destTileHeight);
-
-        while( true )
-        {
-            currLOD++;
-            double w, h;
-            getTileDimensions(currLOD, w, h);
-            
-            if ( w < keyAvg || h < keyAvg ) break;
-            destLOD = currLOD;
-            destTileWidth = w;
-            destTileHeight = h;
-        }
+    //OE_DEBUG << std::fixed << "  Source Tile: " << key.getLevelOfDetail() << " (" << keyWidth << ", " << keyHeight << ")" << std::endl;
+    //OE_DEBUG << std::fixed << "  Dest Size: " << destLOD << " (" << destTileWidth << ", " << destTileHeight << ")" << std::endl;
 
+    double east = key_ext.xMax() - _extent.xMin();
+    bool xMaxOnTileBoundary = fmod(east, destTileWidth) == 0.0;
 
-        //OE_DEBUG << std::fixed << "  Source Tile: " << key.getLevelOfDetail() << " (" << keyWidth << ", " << keyHeight << ")" << std::endl;
-        //OE_DEBUG << std::fixed << "  Dest Size: " << destLOD << " (" << destTileWidth << ", " << destTileHeight << ")" << std::endl;
+    double south = _extent.yMax() - key_ext.yMin();
+    bool yMaxOnTileBoundary = fmod(south, destTileHeight) == 0.0;
 
-        tileMinX = (int)((key_ext.xMin() - _extent.xMin()) / destTileWidth);
-        tileMaxX = (int)((key_ext.xMax() - _extent.xMin()) / destTileWidth);
+    tileMinX = (int)((key_ext.xMin() - _extent.xMin()) / destTileWidth);
+    tileMaxX = (int)(east / destTileWidth) - (xMaxOnTileBoundary ? 1 : 0);
 
-        tileMinY = (int)((_extent.yMax() - key_ext.yMax()) / destTileHeight); 
-        tileMaxY = (int)((_extent.yMax() - key_ext.yMin()) / destTileHeight); 
-    }
+    tileMinY = (int)((_extent.yMax() - key_ext.yMax()) / destTileHeight); 
+    tileMaxY = (int)(south / destTileHeight) - (yMaxOnTileBoundary ? 1 : 0);
 
     unsigned int numWide, numHigh;
-    getNumTiles(destLOD, numWide, numHigh);
+    getNumTiles(localLOD, numWide, numHigh);
 
     // bail out if the tiles are out of bounds.
     if ( tileMinX >= (int)numWide || tileMinY >= (int)numHigh ||
@@ -697,17 +652,9 @@ Profile::addIntersectingTiles(const GeoExtent& key_ext, std::vector<TileKey>& ou
         for (int j = tileMinY; j <= tileMaxY; ++j)
         {
             //TODO: does not support multi-face destination keys.
-            out_intersectingKeys.push_back( TileKey(destLOD, i, j, this) );
+            out_intersectingKeys.push_back( TileKey(localLOD, i, j, this) );
         }
     }
-
-    //if ( key_ext.getSRS()->isMercator() && tileMinX != tileMaxX )
-    //{
-    //    OE_WARN << LC << "MERC GIT got too many horizontal tiles (" << tileMaxX-tileMinX+1 << ", vert=(" <<
-    //        tileMaxY-tileMinY+1 << ")" << std::endl;
-    //}
-
-    //OE_INFO << "    Found " << out_intersectingKeys.size() << " keys " << std::endl;
 }
 
 
@@ -717,24 +664,31 @@ Profile::getIntersectingTiles(const TileKey& key, std::vector<TileKey>& out_inte
     OE_DEBUG << "GET ISECTING TILES for key " << key.str() << " -----------------" << std::endl;
 
     //If the profiles are exactly equal, just add the given tile key.
-    if ( isEquivalentTo( key.getProfile() ) )
+    if ( isHorizEquivalentTo( key.getProfile() ) )
     {
         //Clear the incoming list
         out_intersectingKeys.clear();
-
         out_intersectingKeys.push_back(key);
-        return;
     }
-    return getIntersectingTiles(key.getExtent(), out_intersectingKeys);
+    else
+    {
+        // figure out which LOD in the local profile is a best match for the LOD
+        // in the source LOD in terms of resolution.
+        unsigned localLOD = getEquivalentLOD(key.getProfile(), key.getLOD());
+        getIntersectingTiles(key.getExtent(), localLOD, out_intersectingKeys);
+
+        OE_DEBUG << LC << "GIT, key="<< key.str() << ", localLOD=" << localLOD
+            << ", resulted in " << out_intersectingKeys.size() << " tiles" << std::endl;
+    }
 }
 
 void
-Profile::getIntersectingTiles(const GeoExtent& extent, std::vector<TileKey>& out_intersectingKeys) const
+Profile::getIntersectingTiles(const GeoExtent& extent, unsigned localLOD, std::vector<TileKey>& out_intersectingKeys) const
 {
     GeoExtent ext = extent;
 
     // reproject into the profile's SRS if necessary:
-    if ( ! getSRS()->isEquivalentTo( extent.getSRS() ) )
+    if ( !getSRS()->isHorizEquivalentTo( extent.getSRS() ) )
     {
         // localize the extents and clamp them to legal values
         ext = clampAndTransformExtent( extent );
@@ -747,54 +701,61 @@ Profile::getIntersectingTiles(const GeoExtent& extent, std::vector<TileKey>& out
         GeoExtent first, second;
         if (ext.splitAcrossAntimeridian( first, second ))
         {
-            addIntersectingTiles( first, out_intersectingKeys );
-            addIntersectingTiles( second, out_intersectingKeys );
+            addIntersectingTiles( first, localLOD, out_intersectingKeys );
+            addIntersectingTiles( second, localLOD, out_intersectingKeys );
         }
     }
     else
     {
-        addIntersectingTiles( ext, out_intersectingKeys );
+        addIntersectingTiles( ext, localLOD, out_intersectingKeys );
     }
 }
 
-
-unsigned int
-Profile::getEquivalentLOD( const Profile* profile, unsigned int lod ) const
+unsigned
+Profile::getEquivalentLOD( const Profile* rhsProfile, unsigned rhsLOD ) const
 {    
     //If the profiles are equivalent, just use the incoming lod
-    if (profile->isEquivalentTo( this ) ) 
-        return lod;
+    if (rhsProfile->isHorizEquivalentTo( this ) ) 
+        return rhsLOD;
 
     double rhsWidth, rhsHeight;
-    profile->getTileDimensions( lod, rhsWidth, rhsHeight );
+    rhsProfile->getTileDimensions( rhsLOD, rhsWidth, rhsHeight );    
 
     // safety catch
     if ( osg::equivalent(rhsWidth, 0.0) || osg::equivalent(rhsHeight, 0.0) )
     {
         OE_WARN << LC << "getEquivalentLOD: zero dimension" << std::endl;
-        return lod;
+        return rhsLOD;
     }
 
-    double targetWidth = rhsWidth, targetHeight = rhsHeight;
-
-    if ( !profile->getSRS()->isHorizEquivalentTo(getSRS()) )
-    {
-        targetWidth = profile->getSRS()->transformUnits( rhsWidth, getSRS() );
-        targetHeight = profile->getSRS()->transformUnits( rhsHeight, getSRS() );
-    }
+    const SpatialReference* rhsSRS = rhsProfile->getSRS();
+    double rhsTargetHeight = rhsSRS->transformUnits( rhsHeight, getSRS() );    
     
     int currLOD = 0;
     int destLOD = currLOD;
 
-    //Find the LOD that most closely matches the area of the incoming key without going under.
+    double delta = DBL_MAX;
+
+    // Find the LOD that most closely matches the resolution of the incoming key.
+    // We use the closest (under or over) so that you can match back and forth between profiles and be sure to get the same results each time.
     while( true )
     {
+        double prevDelta = delta;
+
         currLOD++;
         double w, h;
         getTileDimensions(currLOD, w, h);
-        if ( w < targetWidth || h < targetHeight ) break;
-        //double a = w * h;
-        //if (a < keyArea) break;
+        delta = osg::absolute( h - rhsTargetHeight );
+        if (delta < prevDelta)
+        {
+            // We're getting closer so keep going
+            destLOD = currLOD;
+        }
+        else
+        {
+            // We are further away from the previous lod so stop.
+            break;
+        }        
         destLOD = currLOD;
     }
     return destLOD;
diff --git a/src/osgEarth/Progress b/src/osgEarth/Progress
index 580193f..88acd55 100644
--- a/src/osgEarth/Progress
+++ b/src/osgEarth/Progress
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,6 +21,7 @@
 #define OSGEARTH_PROGRESS_H 1
 
 #include <osgEarth/Common>
+#include <map>
 
 namespace osgEarth
 {
@@ -81,16 +82,21 @@ namespace osgEarth
         /**
          * Sets the cancelation flag
          */
-        void cancel() { _canceled = true; }
+        virtual void cancel() { _canceled = true; }
 
         /**
          * Whether cancelation was requested
          */
-        virtual bool isCanceled() const { return _canceled; }
+        virtual bool isCanceled() { return _canceled; }
 
         std::string& message() { return _message; }
 
         /**
+         * Resets the canceled flag.
+         */
+        void reset() { _canceled = false; }
+
+        /**
         *Whether or not the task should be retried.
         */
         bool needsRetry() const { return _needsRetry; }
@@ -100,11 +106,17 @@ namespace osgEarth
          */
         void setNeedsRetry( bool needsRetry ) { _needsRetry = needsRetry; }
 
+        /**
+         * Access user stats
+         */
+        std::map<std::string,double>& stats() { return _stats; }
+
     protected:
         volatile unsigned _numStages;
         std::string       _message;
         mutable  bool     _needsRetry;
         mutable  bool     _canceled;
+        mutable  std::map<std::string, double> _stats;
     };
 
 
diff --git a/src/osgEarth/Progress.cpp b/src/osgEarth/Progress.cpp
index 94525f3..2f28216 100644
--- a/src/osgEarth/Progress.cpp
+++ b/src/osgEarth/Progress.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Random b/src/osgEarth/Random
index 2dff36f..1941b06 100644
--- a/src/osgEarth/Random
+++ b/src/osgEarth/Random
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Random.cpp b/src/osgEarth/Random.cpp
index 9fe2282..fc84266 100644
--- a/src/osgEarth/Random.cpp
+++ b/src/osgEarth/Random.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Registry b/src/osgEarth/Registry
index 98f1c1c..81331b0 100644
--- a/src/osgEarth/Registry
+++ b/src/osgEarth/Registry
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -22,13 +22,16 @@
 
 #include <osgEarth/Common>
 #include <osgEarth/CachePolicy>
+#include <osgEarth/SharedSARepo>
 #include <osgEarth/Units>
+#include <osgEarth/ShaderGenerator>
 #include <OpenThreads/ReentrantMutex>
 #include <OpenThreads/ScopedLock>
 #include <osgEarth/ThreadingUtils>
 #include <osg/Referenced>
 #include <osgDB/ReaderWriter>
 #include <osgText/Font>
+#include <set>
 
 #define GDAL_SCOPED_LOCK \
     OpenThreads::ScopedLock<OpenThreads::ReentrantMutex> _slock( osgEarth::Registry::instance()->getGDALMutex() )\
@@ -44,6 +47,8 @@ namespace osgEarth
     class URIReadCallback;
     class ColorFilterRegistry;
     class StateSetCache;
+    
+    typedef SharedSARepo<osg::Program> ProgramSharedRepo;
 
     /**
      * Application-wide global repository.
@@ -76,20 +81,25 @@ namespace osgEarth
         Cache* getCache() const;
         void setCache( Cache* cache );
 
+        /** The default cache policy (used when no policy is set elsewhere) */
+        const optional<CachePolicy>& defaultCachePolicy() const { return _defaultCachePolicy; }
+        void setDefaultCachePolicy( const CachePolicy& policy );
+
         /** The override cache policy (overrides all others if set) */
         const optional<CachePolicy>& overrideCachePolicy() const { return _overrideCachePolicy; }
         void setOverrideCachePolicy( const CachePolicy& policy );
 
-        /** The default cache policy (used when no policy is set) */
-        const optional<CachePolicy>& defaultCachePolicy() const { return _defaultCachePolicy; }
-        void setDefaultCachePolicy( const CachePolicy& policy );
+        /** The default cache driver. */
+        void setDefaultCacheDriverName( const std::string& name );
+        const std::string& getDefaultCacheDriverName() const { return _cacheDriver; }
 
         /**
-         * Gets the cache policy. 
-         * First checks for an override policy; then looks in the DB::Options for one,
-         * then checks for the default policy; then returns false as a last resort.
+         * Given a CachePolicy, composites in the default and override cache policies
+         * as necessary to create an effective CachePolicy. First it will populate
+         * any unset properties in "cp" with defaults if they are available. Then it
+         * will override any properties in "cp" with overrides that are available.
          */
-        bool getCachePolicy( optional<CachePolicy>& cp, const osgDB::Options* options =0L ) const;
+        bool resolveCachePolicy(optional<CachePolicy>& cp) const;
 
         /**
          * Whether the given filename is blacklisted
@@ -134,6 +144,13 @@ namespace osgEarth
         static const ShaderFactory* shaderFactory() { return instance()->getShaderFactory(); }
 
         /**
+         * The default shader generator.
+         */
+        ShaderGeneratorProxy getShaderGenerator() const;
+        void setShaderGenerator(ShaderGenerator* gen);
+        static ShaderGeneratorProxy shaderGenerator() { return instance()->getShaderGenerator(); }
+
+        /**
          * A default StateSetCache to use by any process that uses one.
          * A StateSetCache assist in stateset sharing across multiple nodes.
          * Note: A registry-wide SSC is only supported in OSG 3.1.4+. See
@@ -144,6 +161,13 @@ namespace osgEarth
         static StateSetCache* stateSetCache() { return instance()->getStateSetCache(); }
 
         /**
+         * A shared cache for osg::Program objects created by the shader 
+         * composition subsystem (VirtualProgram).
+         */
+        ProgramSharedRepo* getProgramSharedRepo();
+        static ProgramSharedRepo* programSharedRepo() { return instance()->getProgramSharedRepo(); }
+        
+        /**
          * Gets a reference to the global task service manager.
          */
         TaskServiceManager* getTaskServiceManager() {
@@ -188,6 +212,30 @@ namespace osgEarth
         void setDefaultTerrainEngineDriverName( const std::string& name );
         const std::string& getDefaultTerrainEngineDriverName() const { return _terrainEngineDriver; }
 
+        /**
+         * For debugging - tracks activities in progress.
+         */
+        void startActivity(const std::string& name);
+        void endActivity(const std::string& name);
+        void getActivities(std::set<std::string>& output);
+
+        /**
+         * Gets the mime-type corresponding to a given extension.
+         */
+        std::string getMimeTypeForExtension(const std::string& extension);
+
+        /**
+         * Gets the file extension corresponding to a given mime-type.
+         */
+        std::string getExtensionForMimeType(const std::string& mimeType);
+
+        /**
+         * Sets the policy for calling osg::Texture::setUnRefImageDataAfterApply
+         * in the osgEarth terrain engine.
+         */
+        optional<bool>& unRefImageDataAfterApply() { return _unRefImageDataAfterApply; }
+        const optional<bool>& unRefImageDataAfterApply() const { return _unRefImageDataAfterApply; }
+
     protected:
         virtual ~Registry();
         Registry();
@@ -204,10 +252,6 @@ namespace osgEarth
 
         Threading::ReadWriteMutex _regMutex;  
         int _numGdalMutexGets;
-        
-        typedef std::map< std::string, std::string> MimeTypeExtensionMap;
-        // maps mime-types to extensions.
-        MimeTypeExtensionMap _mimeTypeExtMap;
 
         osg::ref_ptr<Cache> _cache;
         optional<CachePolicy> _defaultCachePolicy;
@@ -218,7 +262,7 @@ namespace osgEarth
         Threading::ReadWriteMutex _blacklistMutex;
 
         osg::ref_ptr<ShaderFactory> _shaderLib;
-
+        osg::ref_ptr<ShaderGenerator> _shaderGen;
         osg::ref_ptr<TaskServiceManager> _taskServiceManager;
 
         // unique ID generator:
@@ -243,6 +287,14 @@ namespace osgEarth
         osg::ref_ptr<StateSetCache> _stateSetCache;
 
         std::string _terrainEngineDriver;
+        std::string _cacheDriver;
+
+        std::set<std::string> _activities;
+        mutable Threading::Mutex _activityMutex;
+        
+        ProgramSharedRepo _programRepo;
+
+        optional<bool> _unRefImageDataAfterApply;
     };
 }
 
diff --git a/src/osgEarth/Registry.cpp b/src/osgEarth/Registry.cpp
index 0fa9fb5..4b35d56 100644
--- a/src/osgEarth/Registry.cpp
+++ b/src/osgEarth/Registry.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,12 +21,14 @@
 #include <osgEarth/Cube>
 #include <osgEarth/VirtualProgram>
 #include <osgEarth/ShaderFactory>
+#include <osgEarth/ShaderGenerator>
 #include <osgEarth/TaskService>
 #include <osgEarth/IOTypes>
 #include <osgEarth/ColorFilter>
 #include <osgEarth/StateSetCache>
 #include <osgEarth/HTTPClient>
-#include <osgEarthDrivers/cache_filesystem/FileSystemCache>
+#include <osgEarth/StringUtils>
+#include <osgEarth/TerrainEngineNode>
 #include <osg/Notify>
 #include <osg/Version>
 #include <osgDB/Registry>
@@ -36,7 +38,6 @@
 #include <locale>
 
 using namespace osgEarth;
-using namespace osgEarth::Drivers;
 using namespace OpenThreads;
 
 #define STR_GLOBAL_GEODETIC    "global-geodetic"
@@ -47,8 +48,6 @@ using namespace OpenThreads;
 
 #define LC "[Registry] "
 
-// from MimeTypes.cpp
-extern const char* builtinMimeTypeExtMappings[];
 
 Registry::Registry() :
 osg::Referenced     ( true ),
@@ -57,11 +56,16 @@ _numGdalMutexGets   ( 0 ),
 _uidGen             ( 0 ),
 _caps               ( 0L ),
 _defaultFont        ( 0L ),
-_terrainEngineDriver( "mp" )
+_terrainEngineDriver( "mp" ),
+_cacheDriver        ( "filesystem" )
 {
     // set up GDAL and OGR.
     OGRRegisterAll();
     GDALAllRegister();
+    
+    // support Chinese character in the file name and attributes in ESRI's shapefile
+    CPLSetConfigOption("GDAL_FILENAME_IS_UTF8","NO");
+    CPLSetConfigOption("SHAPE_ENCODING","");
 
     // global initialization for CURL (not thread safe)
     HTTPClient::globalInit();
@@ -69,6 +73,9 @@ _terrainEngineDriver( "mp" )
     // generates the basic shader code for the terrain engine and model layers.
     _shaderLib = new ShaderFactory();
 
+    // shader generator used internally by osgEarth. Can be replaced.
+    _shaderGen = new ShaderGenerator();
+
     // thread pool for general use
     _taskServiceManager = new TaskServiceManager();
 
@@ -76,6 +83,9 @@ _terrainEngineDriver( "mp" )
     // performance boost
     _stateSetCache = new StateSetCache();
 
+    // Default unref-after apply policy:
+    _unRefImageDataAfterApply = true;
+
     // activate KMZ support
     osgDB::Registry::instance()->addArchiveExtension  ( "kmz" );
     osgDB::Registry::instance()->addFileExtensionAlias( "kmz", "kml" );
@@ -88,6 +98,7 @@ _terrainEngineDriver( "mp" )
     osgDB::Registry::instance()->addMimeTypeExtensionMapping( "text/json",                            "osgb" );
     osgDB::Registry::instance()->addMimeTypeExtensionMapping( "text/x-json",                          "osgb" );
     osgDB::Registry::instance()->addMimeTypeExtensionMapping( "image/jpg",                            "jpg" );
+    osgDB::Registry::instance()->addMimeTypeExtensionMapping( "image/dds",                            "dds" );
     
     // pre-load OSG's ZIP plugin so that we can use it in URIs
     std::string zipLib = osgDB::Registry::instance()->createLibraryNameForExtension( "zip" );
@@ -97,53 +108,61 @@ _terrainEngineDriver( "mp" )
     // set up our default r/w options to NOT cache archives!
     _defaultOptions = new osgDB::Options();
     _defaultOptions->setObjectCacheHint( osgDB::Options::CACHE_NONE );
-    //_defaultOptions->setObjectCacheHint( (osgDB::Options::CacheHintOptions)
-    //    ((int)_defaultOptions->getObjectCacheHint() & ~osgDB::Options::CACHE_ARCHIVES) );
-
-    // see if there's a cache in the envvar
-    const char* cachePath = ::getenv("OSGEARTH_CACHE_PATH");
-    if ( cachePath )
+    
+    // activate no-cache mode from the environment
+    if ( ::getenv(OSGEARTH_ENV_NO_CACHE) )
     {
-        FileSystemCacheOptions options;
-        options.rootPath() = std::string(cachePath);
-
-        osg::ref_ptr<Cache> cache = CacheFactory::create(options);
-        if ( cache->isOK() )
+        _overrideCachePolicy = CachePolicy::NO_CACHE;
+        OE_INFO << LC << "NO-CACHE MODE set from environment" << std::endl;
+    }
+    else
+    {
+        // activate cache-only mode from the environment
+        if ( ::getenv(OSGEARTH_ENV_CACHE_ONLY) )
         {
-            setCache( cache.get() );
-            OE_INFO << LC << "CACHE PATH set from environment variable: \"" << cachePath << "\"" << std::endl;
+            _overrideCachePolicy->usage() = CachePolicy::USAGE_CACHE_ONLY;
+            OE_INFO << LC << "CACHE-ONLY MODE set from environment" << std::endl;
         }
-        else
+
+        // see if the environment specifies a default caching driver.
+        const char* cacheDriver = ::getenv(OSGEARTH_ENV_CACHE_DRIVER);
+        if ( cacheDriver )
+        {
+            setDefaultCacheDriverName( cacheDriver );
+            OE_INFO << LC << "Cache driver set from environment: "
+                << getDefaultCacheDriverName() << std::endl;
+        }        
+
+        // cache max age?
+        const char* cacheMaxAge = ::getenv(OSGEARTH_ENV_CACHE_MAX_AGE);
+        if ( cacheMaxAge )
         {
-            OE_WARN << LC << "FAILED to initialize cache from env.var." << std::endl;
+            TimeSpan maxAge = osgEarth::as<long>( std::string(cacheMaxAge), INT_MAX );
+            _overrideCachePolicy->maxAge() = maxAge;
         }
-    }
-
-    // activate cache-only mode from the environment
-    if ( ::getenv("OSGEARTH_CACHE_ONLY") )
-    {
-        _overrideCachePolicy->usage() = CachePolicy::USAGE_CACHE_ONLY;
-        //setOverrideCachePolicy( CachePolicy::CACHE_ONLY );
-        OE_INFO << LC << "CACHE-ONLY MODE set from environment variable" << std::endl;
-    }
 
-    // activate no-cache mode from the environment
-    else if ( ::getenv("OSGEARTH_NO_CACHE") )
-    {
-        _overrideCachePolicy->usage() = CachePolicy::USAGE_NO_CACHE;
-        //setOverrideCachePolicy( CachePolicy::NO_CACHE );
-        OE_INFO << LC << "NO-CACHE MODE set from environment variable" << std::endl;
-    }
+        // see if there's a cache in the envvar; if so, create a cache.
+        // Note: the value of the OSGEARTH_CACHE_PATH is not used here; rather
+        // it's used in the driver(s) itself.
+        const char* cachePath = ::getenv(OSGEARTH_ENV_CACHE_PATH);
+        if ( cachePath )
+        {
+            CacheOptions options;
+            options.setDriver( getDefaultCacheDriverName() );
 
-    // cache max age?
-    const char* cacheMaxAge = ::getenv("OSGEARTH_CACHE_MAX_AGE");
-    if ( cacheMaxAge )
-    {
-        TimeSpan maxAge = osgEarth::as<long>( std::string(cacheMaxAge), INT_MAX );
-        _overrideCachePolicy->maxAge() = maxAge;
+            osg::ref_ptr<Cache> cache = CacheFactory::create(options);
+            if ( cache->isOK() )
+            {
+                setCache( cache.get() );
+            }
+            else
+            {
+                OE_WARN << LC << "FAILED to initialize cache from environment" << std::endl;
+            }
+        }
     }
 
-    const char* teStr = ::getenv("OSGEARTH_TERRAIN_ENGINE");
+    const char* teStr = ::getenv(OSGEARTH_ENV_TERRAIN_ENGINE_DRIVER);
     if ( teStr )
     {
         _terrainEngineDriver = std::string(teStr);
@@ -161,6 +180,12 @@ _terrainEngineDriver( "mp" )
         _defaultFont = osgText::readFontFile("arial.ttf");
 #endif
     }
+    if ( _defaultFont.valid() )
+    {
+        // mitigates mipmapping issues that cause rendering artifacts
+        // for some fonts/placement
+        _defaultFont->setGlyphImageMargin( 2 );
+    }
 
     // register the system stock Units.
     Units::registerAll( this );
@@ -299,27 +324,26 @@ Registry::setOverrideCachePolicy( const CachePolicy& value )
 }
 
 bool
-Registry::getCachePolicy( optional<CachePolicy>& cp, const osgDB::Options* options ) const
+Registry::resolveCachePolicy(optional<CachePolicy>& cp) const
 {
-    if ( overrideCachePolicy().isSet() )
-    {
-        // if there is a system-wide override in place, use it.
-        cp = overrideCachePolicy().value();
-    }
-    else 
-    {
-        // Try to read the cache policy from the db-options
-        CachePolicy::fromOptions( options, cp );
+    optional<CachePolicy> new_cp;
 
-        if ( !cp.isSet() )
-        {
-            if ( defaultCachePolicy().isSet() )
-            {
-                cp = defaultCachePolicy().value();
-            }
-        }
-    }
+    // start with the defaults
+    if ( defaultCachePolicy().isSet() )
+        new_cp = defaultCachePolicy();
+
+    // merge in any set properties from the caller's CP, since they override
+    // the defaults:
+    if ( cp.isSet() )
+        new_cp->mergeAndOverride( cp );
+
+    // finally, merge in any set props from the OVERRIDE CP, which take
+    // priority over everything else.
+    if ( overrideCachePolicy().isSet() )
+        new_cp->mergeAndOverride( overrideCachePolicy() );
 
+    // return the new composited cache policy.
+    cp = new_cp;
     return cp.isSet();
 }
 
@@ -403,6 +427,19 @@ Registry::setShaderFactory( ShaderFactory* lib )
     if ( lib != 0L && lib != _shaderLib.get() )
         _shaderLib = lib;
 }
+
+ShaderGeneratorProxy
+Registry::getShaderGenerator() const
+{
+    return ShaderGeneratorProxy(_shaderGen.get());
+}
+
+void
+Registry::setShaderGenerator(ShaderGenerator* shaderGen)
+{
+    if ( shaderGen != 0L && shaderGen != _shaderGen.get() )
+        _shaderGen = shaderGen;
+}
         
 void
 Registry::setURIReadCallback( URIReadCallback* callback ) 
@@ -486,6 +523,12 @@ Registry::setDefaultTerrainEngineDriverName(const std::string& name)
 }
 
 void
+Registry::setDefaultCacheDriverName(const std::string& name)
+{
+    _cacheDriver = name;
+}
+
+void
 Registry::setStateSetCache( StateSetCache* cache )
 {
     _stateSetCache = cache;
@@ -497,6 +540,65 @@ Registry::getStateSetCache() const
     return _stateSetCache.get();
 }
 
+ProgramSharedRepo*
+Registry::getProgramSharedRepo()
+{
+    return &_programRepo;
+}
+
+void
+Registry::startActivity(const std::string& activity)
+{
+    Threading::ScopedMutexLock lock(_activityMutex);
+    _activities.insert(activity);
+}
+
+void
+Registry::endActivity(const std::string& activity)
+{
+    Threading::ScopedMutexLock lock(_activityMutex);
+    _activities.erase(activity);
+}
+
+void
+Registry::getActivities(std::set<std::string>& output)
+{
+    Threading::ScopedMutexLock lock(_activityMutex);
+    output = _activities;
+}
+
+std::string 
+Registry::getExtensionForMimeType(const std::string& mt)
+{            
+    std::string mt_lower = osgEarth::toLower(mt);
+
+    const osgDB::Registry::MimeTypeExtensionMap& exmap = osgDB::Registry::instance()->getMimeTypeExtensionMap();
+    for( osgDB::Registry::MimeTypeExtensionMap::const_iterator i = exmap.begin(); i != exmap.end(); ++i )
+    {
+        if ( i->first == mt_lower )
+        {
+            return i->first;
+        }
+    }
+    return std::string();
+}
+
+std::string 
+Registry::getMimeTypeForExtension(const std::string& ext)
+{            
+    std::string ext_lower = osgEarth::toLower(ext);
+
+    const osgDB::Registry::MimeTypeExtensionMap& exmap = osgDB::Registry::instance()->getMimeTypeExtensionMap();
+    for( osgDB::Registry::MimeTypeExtensionMap::const_iterator i = exmap.begin(); i != exmap.end(); ++i )
+    {
+        if ( i->second == ext_lower )
+        {
+            return i->first;
+        }
+    }
+    return std::string();
+}
+
 
 //Simple class used to add a file extension alias for the earth_tile to the earth plugin
 class RegisterEarthTileExtension
diff --git a/src/osgEarth/Revisioning b/src/osgEarth/Revisioning
index be6bc00..ae2ff7f 100644
--- a/src/osgEarth/Revisioning
+++ b/src/osgEarth/Revisioning
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Revisioning.cpp b/src/osgEarth/Revisioning.cpp
index 36381dd..5dd5a65 100644
--- a/src/osgEarth/Revisioning.cpp
+++ b/src/osgEarth/Revisioning.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/ShaderFactory b/src/osgEarth/ShaderFactory
index 8e14c58..8627f53 100644
--- a/src/osgEarth/ShaderFactory
+++ b/src/osgEarth/ShaderFactory
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -44,6 +44,25 @@ namespace osgEarth
     {
     public:
         /**
+         * Defines the ordering of the COLORING and LIGHTING stages in
+         * the fragment program.
+         */
+        enum FragmentStageOrder
+        {
+            /** Apply lighting before coloring/texturing. This mimics the FFP. */
+            FRAGMENT_STAGE_ORDER_LIGHTING_COLORING,
+
+            /** Apply coloring/texturing before lighting. */
+            FRAGMENT_STAGE_ORDER_COLORING_LIGHTING
+        };
+
+    public:
+        /**
+         * Construtor
+         */
+        ShaderFactory();
+
+        /**
          * Creates a vertex shader main() function for use with VirtualPrograms.
          * Do not call this function directly; VirtualProgram will call it to
          * install its main() vertex function.
@@ -60,24 +79,6 @@ namespace osgEarth
             const ShaderComp::FunctionLocationMap& functions) const;
 
         /**
-         * Gets the uniform/shader name of the sampler corresponding the the provider
-         * texture image unit
-         *
-         * @deprecated Only supported by older texture compositors, which will likely
-         *             go away in a future version
-         */
-        virtual std::string getSamplerName( unsigned texImageUnit ) const;
-
-        /**
-         * Install lighting shaders in a VirtualProgram.
-         *
-         * Note: this will likely go away in a future version. At this point it makes
-         * more sense to decouple the default lighting shaders from the main shader
-         * factory and simply use a VirtualProgram rather than replacing the ShaderFactory.
-         */
-        virtual void installLightingShaders(VirtualProgram* vp) const;
-
-        /**
          * Builds a shader function that executes an image filter chain.
          * @param functionName Name to give to the resulting shader function
          * @param chain        Color filter chain to execute
@@ -94,8 +95,30 @@ namespace osgEarth
             osg::StateAttribute::GLMode      mode,
             osg::StateAttribute::GLModeValue value ) const;
 
+        /**
+         * The name of the range uniform created by createRangeUniform().
+         */
+        virtual std::string getRangeUniformName() const;
+
+        /**
+         * Creates a uniform that's used by the RangeUniformCullCallback to convey
+         * "distance to viewpoint" in a shader program.
+         */
+        osg::Uniform* createRangeUniform() const;
+
+        /**
+         * Sets the order in which the shader program generator will create
+         * code for the fragment program's COLORING and LIGHTING stages.
+         */
+        void setFragmentStageOrder(const FragmentStageOrder& value);
+        const FragmentStageOrder& getFragmentStageOrder() const { return _fragStageOrder; }
+
+
+    protected:
         /** dtor */
         virtual ~ShaderFactory() { }
+
+        FragmentStageOrder _fragStageOrder;
     };
 
 
diff --git a/src/osgEarth/ShaderFactory.cpp b/src/osgEarth/ShaderFactory.cpp
index 2de7ccd..23e0605 100755
--- a/src/osgEarth/ShaderFactory.cpp
+++ b/src/osgEarth/ShaderFactory.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -36,19 +36,37 @@
 #endif
 
 #define INDENT "    "
-
+#define RANGE  osgEarth::Registry::instance()->shaderFactory()->getRangeUniformName()
 
 using namespace osgEarth;
 using namespace osgEarth::ShaderComp;
 
 
-std::string
-ShaderFactory::getSamplerName( unsigned unit ) const
+namespace
 {
-    return Stringify() << "osgearth_tex" << unit;
+    void insertRangeConditionals(const Function& f, std::ostream& buf)
+    {
+        if ( f._minRange.isSet() && !f._maxRange.isSet() )
+        {
+            buf << INDENT << "if (" << RANGE << " >= float(" << f._minRange.value() << "))\n" << INDENT;
+        }
+        else if ( !f._minRange.isSet() && f._maxRange.isSet() )
+        {
+            buf << INDENT << "if (" << RANGE << " <= float(" << f._maxRange.value() << "))\n" << INDENT;
+        }
+        else if ( f._minRange.isSet() && f._maxRange.isSet() )
+        {
+            buf << INDENT << "if (" << RANGE << " >= float(" << f._minRange.value() << ") && " << RANGE << " <= float(" << f._maxRange.value() << "))\n" << INDENT;
+        }
+    }
 }
 
 
+ShaderFactory::ShaderFactory()
+{
+    _fragStageOrder = FRAGMENT_STAGE_ORDER_COLORING_LIGHTING;
+}
+
 osg::Shader*
 ShaderFactory::createVertexShaderMain(const FunctionLocationMap& functions) const
 {
@@ -68,27 +86,34 @@ ShaderFactory::createVertexShaderMain(const FunctionLocationMap& functions) cons
     std::stringstream buf;
     buf << 
         "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n";
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+        "uniform float " << RANGE << ";\n";
 
     // prototypes for model stage methods:
     if ( modelStage )
     {
         for( OrderedFunctionMap::const_iterator i = modelStage->begin(); i != modelStage->end(); ++i )
-            buf << "void " << i->second << "(inout vec4 VertexMODEL); \n";
+        {
+            buf << "void " << i->second._name << "(inout vec4 VertexMODEL); \n";
+        }
     }
 
     // prototypes for view stage methods:
     if ( viewStage )
     {
         for( OrderedFunctionMap::const_iterator i = viewStage->begin(); i != viewStage->end(); ++i )
-            buf << "void " << i->second << "(inout vec4 VertexVIEW); \n";
+        {
+            buf << "void " << i->second._name << "(inout vec4 VertexVIEW); \n";
+        }
     }
 
     // prototypes for clip stage methods:
     if ( clipStage )
     {
         for( OrderedFunctionMap::const_iterator i = clipStage->begin(); i != clipStage->end(); ++i )
-            buf << "void " << i->second << "(inout vec4 VertexCLIP); \n";
+        {
+            buf << "void " << i->second._name << "(inout vec4 VertexCLIP); \n";
+        }
     }
 
     // main:
@@ -107,7 +132,8 @@ ShaderFactory::createVertexShaderMain(const FunctionLocationMap& functions) cons
 
         for( OrderedFunctionMap::const_iterator i = modelStage->begin(); i != modelStage->end(); ++i )
         {
-            buf << INDENT << i->second << "(vertex); \n";
+            insertRangeConditionals( i->second, buf );
+            buf << INDENT << i->second._name << "(vertex); \n";
         }
 
         buf << INDENT << "oe_Normal = normalize(gl_NormalMatrix * oe_Normal); \n";
@@ -124,7 +150,8 @@ ShaderFactory::createVertexShaderMain(const FunctionLocationMap& functions) cons
 
         for( OrderedFunctionMap::const_iterator i = viewStage->begin(); i != viewStage->end(); ++i )
         {
-            buf << INDENT << i->second << "(vertex); \n";
+            insertRangeConditionals( i->second, buf );
+            buf << INDENT << i->second._name << "(vertex); \n";
         }
     }
 
@@ -142,7 +169,8 @@ ShaderFactory::createVertexShaderMain(const FunctionLocationMap& functions) cons
 
         for( OrderedFunctionMap::const_iterator i = clipStage->begin(); i != clipStage->end(); ++i )
         {
-            buf << INDENT << i->second << "(vertex); \n";
+            insertRangeConditionals( i->second, buf );
+            buf << INDENT << i->second._name << "(vertex); \n";
         }
     }
 
@@ -179,20 +207,36 @@ ShaderFactory::createFragmentShaderMain(const FunctionLocationMap& functions) co
     FunctionLocationMap::const_iterator j = functions.find( LOCATION_FRAGMENT_LIGHTING );
     const OrderedFunctionMap* lighting = j != functions.end() ? &j->second : 0L;
 
+    FunctionLocationMap::const_iterator k = functions.find( LOCATION_FRAGMENT_OUTPUT );
+    const OrderedFunctionMap* output = k != functions.end() ? &k->second : 0L;
+
     std::stringstream buf;
     buf << "#version " << GLSL_VERSION_STR << "\n"
-        << GLSL_DEFAULT_PRECISION_FLOAT << "\n";
+        << GLSL_DEFAULT_PRECISION_FLOAT << "\n"
+        << "uniform float " << RANGE << ";\n";
 
     if ( coloring )
     {
         for( OrderedFunctionMap::const_iterator i = coloring->begin(); i != coloring->end(); ++i )
-            buf << "void " << i->second << "( inout vec4 color ); \n";
+        {
+            buf << "void " << i->second._name << "( inout vec4 color ); \n";
+        }
     }
 
     if ( lighting )
     {
         for( OrderedFunctionMap::const_iterator i = lighting->begin(); i != lighting->end(); ++i )
-            buf << "void " << i->second << "( inout vec4 color ); \n";
+        {
+            buf << "void " << i->second._name << "( inout vec4 color ); \n";
+        }
+    }
+
+    if ( output )
+    {
+        for( OrderedFunctionMap::const_iterator i = output->begin(); i != output->end(); ++i )
+        {
+            buf << "void " << i->second._name << "( inout vec4 color ); \n";
+        }
     }
 
     buf << 
@@ -201,21 +245,45 @@ ShaderFactory::createFragmentShaderMain(const FunctionLocationMap& functions) co
         "{ \n"
         INDENT "vec4 color = osg_FrontColor; \n";
 
-    if ( coloring )
+    int coloringPass = _fragStageOrder == FRAGMENT_STAGE_ORDER_COLORING_LIGHTING ? 0 : 1;
+    int lightingPass = 1-coloringPass;
+
+    for(int pass=0; pass<2; ++pass)
     {
-        for( OrderedFunctionMap::const_iterator i = coloring->begin(); i != coloring->end(); ++i )
-            buf << INDENT << i->second << "( color ); \n";
+        if ( coloring && (pass == coloringPass) )
+        {
+            for( OrderedFunctionMap::const_iterator i = coloring->begin(); i != coloring->end(); ++i )
+            {
+                insertRangeConditionals( i->second, buf );
+                buf << INDENT << i->second._name << "( color ); \n";
+            }
+        }
+
+        if ( lighting && (pass == lightingPass) )
+        {
+            for( OrderedFunctionMap::const_iterator i = lighting->begin(); i != lighting->end(); ++i )
+            {
+                insertRangeConditionals( i->second, buf );
+                buf << INDENT << i->second._name << "( color ); \n";
+            }
+        }
     }
 
-    if ( lighting )
+    if ( output )
     {
-        for( OrderedFunctionMap::const_iterator i = lighting->begin(); i != lighting->end(); ++i )
-            buf << INDENT << i->second << "( color ); \n";
+        for( OrderedFunctionMap::const_iterator i = output->begin(); i != output->end(); ++i )
+        {
+            insertRangeConditionals( i->second, buf );
+            buf << INDENT << i->second._name << "( color ); \n";
+        }
     }
-
-    buf << 
-        INDENT "gl_FragColor = color; \n"
-        "} \n";  
+    else
+    {
+        // in the absense of any output functions, generate a default output statement
+        // that simply writes to gl_FragColor.
+        buf << INDENT "gl_FragColor = color;\n";
+    }
+    buf << "}\n";
 
     std::string str;
     str = buf.str();
@@ -225,68 +293,6 @@ ShaderFactory::createFragmentShaderMain(const FunctionLocationMap& functions) co
 }
 
 
-void
-ShaderFactory::installLightingShaders(VirtualProgram* vp) const
-{
-    const char* vs =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-
-        "uniform bool oe_mode_GL_LIGHTING; \n"
-        "varying vec4 oe_lighting_adjustment; \n"
-        "varying vec4 oe_lighting_zero_vec; \n"
-        "varying vec3 oe_Normal; \n"
-
-        "void oe_lighting_vertex(inout vec4 VertexVIEW) \n"
-        "{ \n"
-        "    oe_lighting_adjustment = vec4(1.0); \n"
-        "    if (oe_mode_GL_LIGHTING) \n"
-        "    { \n"
-        "        vec3 N = oe_Normal; \n" //normalize(gl_NormalMatrix * gl_Normal); \n"
-        "        float NdotL = dot( N, normalize(gl_LightSource[0].position.xyz) ); \n"
-        "        NdotL = max( 0.0, NdotL ); \n"
-
-        // NOTE: See comment in the fragment shader below for an explanation of
-        //       this oe_zero_vec value.
-        "        oe_lighting_zero_vec = vec4(0.0); \n"
-
-        "        vec4 adj = \n"
-        "            gl_FrontLightProduct[0].ambient + \n"
-        "            gl_FrontLightProduct[0].diffuse * NdotL; \n"
-        "        oe_lighting_adjustment = clamp( adj, 0.0, 1.0 ); \n"
-        "    } \n"
-        "} \n";
-
-    const char* fs =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
-
-        "varying vec4 oe_lighting_adjustment; \n"
-        "varying vec4 oe_lighting_zero_vec; \n"
-
-         "uniform bool oe_mode_GL_LIGHTING; \n"
-         "void oe_lighting_fragment( inout vec4 color ) \n"
-         "{ \n"
-         //NOTE: The follow was changed from the single line
-         //      "color *= oe_lighting_adjustment" to the current code to fix
-         //      an issue on iOS devices.  Adding a varying vec4 value set to
-         //      (0.0,0.0,0.0,0.0) to the color should not make a difference,
-         //      but it is part of the solution to the issue we were seeing.
-         //      Without it and the additional lines of code, the globe was
-         //      rendering textureless (just a white surface with lighting).
-         "    if ( oe_mode_GL_LIGHTING ) \n"
-         "    { \n"
-         "        float alpha = color.a; \n"
-         "        color = color * oe_lighting_adjustment + oe_lighting_zero_vec; \n"
-         "        color.a = alpha; \n"
-         "    } \n"
-        "} \n";
-
-    vp->setFunction( "oe_lighting_vertex",   vs, ShaderComp::LOCATION_VERTEX_VIEW, 0.0 );
-    vp->setFunction( "oe_lighting_fragment", fs, ShaderComp::LOCATION_FRAGMENT_LIGHTING, 0.0 );
-}
-
-
 osg::Shader*
 ShaderFactory::createColorFilterChainFragmentShader(const std::string&      function, 
                                                     const ColorFilterChain& chain ) const
@@ -336,3 +342,15 @@ ShaderFactory::createUniformForGLMode(osg::StateAttribute::GLMode      mode,
 
     return u;
 }
+
+std::string
+ShaderFactory::getRangeUniformName() const
+{
+    return "oe_range_to_bs";
+}
+
+osg::Uniform*
+ShaderFactory::createRangeUniform() const
+{
+    return new osg::Uniform(osg::Uniform::FLOAT, getRangeUniformName());
+}
diff --git a/src/osgEarth/ShaderGenerator b/src/osgEarth/ShaderGenerator
index aa38a97..3e9179c 100644
--- a/src/osgEarth/ShaderGenerator
+++ b/src/osgEarth/ShaderGenerator
@@ -25,49 +25,105 @@
 #include <osgEarth/VirtualProgram>
 #include <osg/NodeVisitor>
 #include <osg/State>
+#include <osg/Version>
+#include <sstream>
+#include <set>
+
+// forward declarations
+namespace osg
+{
+    class TexEnv;
+    class TexGen;
+    class TexMat;
+    class Texture1D;
+    class Texture2D;
+    class Texture3D;
+    class TextureRectangle;
+    class Texture2DArray;
+    class Texture2DMultisample;
+}
 
 namespace osgEarth
 {
     /**
-     * Utility class that will traverse a scene graph and generate a
-     * set of VirtualProgram attributes to render it as best it can using
-     * shaders.
+     * Traverses a scene graph and generates VirtualProgram attributes to
+     * render the geometry using GLSL shaders.
+     *
+     * You can use this class directly, but they osgEarth Registry hold
+     * a system-wide implementation that the user can replace. So the best
+     * way to use this class is:
+     *
+     *   osgEarth::Registry::shaderGenerator().run(graph);
+     *
+     * After generating shaders, the scene graph will have MANY additional
+     * StateSets. For performance reasons you should run a StateSet sharing
+     * pass afterwards. You can do this by running the StateSetCache 
+     * optimization function:
      *
-     * Usage:
+     *   osgEarth::StateSetCache::optimize(graph)
      *
-     *   ShaderGenerator gen;
-     *   gen.run( graph );
+     * Or you can pass a StateSetCache instance into the ShaderGenerator::run()
+     * method and it will perform state sharing internally.
      *
-     * If osgEarth detects a lack of GLSL support, the ShaderGenerator
-     * will do nothing.
+     * Implementation Notes:
+     *
+     * ShaderGenerator WILL NOT modify existing StateSets. Instead, when 
+     * a state change is necessary (to inject uniforms or virtual programs)
+     * it will clone the existing StateSet and replace it with a
+     * modified version. We do this to avoid altering StateSets that might
+     * be shared or in the live scene graph.
      */
     class OSGEARTH_EXPORT ShaderGenerator : public osg::NodeVisitor
     {
     public:
+        /** Constructs a new shader generator */
+        ShaderGenerator();
+
+        /** Copy constructor */
+        ShaderGenerator(const ShaderGenerator& rhs, const osg::CopyOp& copy);
+
+    public: // ShaderGeneratorInterface
+
         /**
-         * Constructs a new shader generator
+         * Runs the shader generator on a graph.
+         * @param graph Graph for which to generate shader components.
+         * @param name Name to give to the top level Virtual Program.
+         * @param cache StateSet cache to use for sharing state when finished.
          */
-        ShaderGenerator();
+        void run(osg::Node* graph, const std::string& name, StateSetCache* cache);
+
+
+    public: // statics
+
+        /**
+         * Marks a node with a hint that the shader generator should ignore it in
+         * the future.
+         */
+        static void setIgnoreHint(osg::Object* object, bool ignore);
 
         /**
-         * Sets the name to give any VP's we create
+         * Whether an object has been marked for ignore 
          */
-        void setProgramName(const std::string& name);
+        static bool ignore(const osg::Object* object);
+
+    public: // deprecated
 
         /**
-         * Runs the shader generator and then optimizes state sharing when it's done.
+         * Sets the name to give any VP's we create           
+         * @deprecated Now a NO-OP. Use run(graph, name, ...) instead.
          */
-        void run(osg::Node* graph, StateSetCache* cache =0L);
+        void setProgramName(const std::string& name) const { }
 
     public:
         /**
          * User callback that lets you selectly reject shader generation for
          * specific state attributes.
          */
-        struct AcceptCallback : public osg::Referenced
+        struct OSGEARTH_EXPORT AcceptCallback : public osg::Referenced
         {
             /** Return true to generate shader code for the SA; false to ignore and skip it */
             virtual bool accept(const osg::StateAttribute* sa) const =0;
+            virtual ~AcceptCallback() { }
         };
 
         /**
@@ -85,23 +141,54 @@ namespace osgEarth
 
     public: // osg::NodeVisitor
 
-        void apply( osg::Node& );
+        virtual void apply( osg::Node& );
+        virtual void apply( osg::Group& );
+        virtual void apply( osg::Geode& );
+        virtual void apply( osg::PagedLOD& );
+        virtual void apply( osg::ProxyNode& );
+        virtual void apply( osg::ClipNode& );
 
-        void apply( osg::Geode& );
 
-        void apply( osg::PagedLOD& );
+    protected: // high-level entry points:
 
-        void apply( osg::ProxyNode& );
+        virtual void optimizeStateSharing(osg::Node* graph, StateSetCache* cache);
 
-        void apply( osg::ClipNode& );
+        virtual void apply( osg::Drawable* );
+
+        virtual bool processGeometry(const osg::StateSet* stateSet, osg::ref_ptr<osg::StateSet>& replacement);
+
+        virtual bool processText(const osg::StateSet* stateSet, osg::ref_ptr<osg::StateSet>& replacement);
 
-    protected:
 
-        void apply( osg::Drawable* );
+    protected: // overridable texture handlers:
 
-        bool processGeometry(const osg::StateSet* stateSet, osg::ref_ptr<osg::StateSet>& replacement);
+        struct GenBuffers
+        {
+            std::stringstream vertHead, vertBody;
+            std::stringstream fragHead, fragBody;
+            osg::StateSet*    stateSet;
+        };
+
+        virtual bool apply(osg::Texture* tex, osg::TexGen* texgen, osg::TexEnv* texenv, osg::TexMat* texmat, int unit, GenBuffers& buf);
+
+        virtual bool apply(osg::TexEnv* texenv, int unit, GenBuffers& buf);
+
+        virtual bool apply(osg::TexGen* texgen, int unit, GenBuffers& buf);
+
+        virtual bool apply(osg::TexMat* texmat, int unit, GenBuffers& buf);
+
+        virtual bool apply(osg::Texture1D* tex, int unit, GenBuffers& buf);
+
+        virtual bool apply(osg::Texture2D* tex, int unit, GenBuffers& buf);
+
+        virtual bool apply(osg::Texture3D* tex, int unit, GenBuffers& buf);
+
+        virtual bool apply(osg::TextureRectangle* tex, int unit, GenBuffers& buf);
 
-        bool processText(const osg::StateSet* stateSet, osg::ref_ptr<osg::StateSet>& replacement);
+        virtual bool apply(osg::Texture2DArray* tex, int unit, GenBuffers& buf);
+
+
+    protected:
 
         osg::ref_ptr<osg::State> _state;
 
@@ -115,6 +202,32 @@ namespace osgEarth
         bool accept(const osg::StateAttribute* sa) const;
     };
 
+    
+    /** Proxy interface for a ShaderGenerator - used by the registry. */
+    class ShaderGeneratorProxy //header only
+    {
+    public:
+        void run(osg::Node* graph, const std::string& name, StateSetCache* cache) {
+            _instance->run(graph, name, cache);
+        }
+        void run(osg::Node* graph) {
+            run(graph, "ShaderGenerator", 0L);
+        }
+        void run(osg::Node* graph, StateSetCache* cache) {
+            run(graph, "ShaderGenerator", cache);
+        }
+        void run(osg::Node* graph, const std::string& name) {
+            run(graph, name, 0L);
+        }
+
+    public:
+        ShaderGeneratorProxy(const ShaderGenerator* temp)
+            : _instance( new ShaderGenerator(*temp, osg::CopyOp::SHALLOW_COPY) ) { }
+
+    private:
+        osg::ref_ptr<ShaderGenerator> _instance;
+    };
+
 } // namespace osgEarth
 
 #endif // OSGEARTH_SHADER_GENERATOR_H
diff --git a/src/osgEarth/ShaderGenerator.cpp b/src/osgEarth/ShaderGenerator.cpp
index 93616ff..d910ace 100644
--- a/src/osgEarth/ShaderGenerator.cpp
+++ b/src/osgEarth/ShaderGenerator.cpp
@@ -1,6 +1,7 @@
+
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -17,11 +18,11 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 
+#include <osgEarth/ShaderGenerator>
 #include <osgEarth/Capabilities>
 #include <osgEarth/ImageUtils>
 #include <osgEarth/Registry>
 #include <osgEarth/ShaderFactory>
-#include <osgEarth/ShaderGenerator>
 #include <osgEarth/StringUtils>
 
 #include <osg/Drawable>
@@ -32,9 +33,13 @@
 #include <osg/Texture2D>
 #include <osg/Texture3D>
 #include <osg/TextureRectangle>
+#include <osg/Texture2DMultisample>
+#include <osg/Texture2DArray>
 #include <osg/TexEnv>
 #include <osg/TexGen>
+#include <osg/TexMat>
 #include <osg/ClipNode>
+#include <osg/ValueObject>
 #include <osgDB/FileNameUtils>
 #include <osgDB/FileUtils>
 #include <osgDB/ReadFile>
@@ -44,6 +49,8 @@
 
 #define SHADERGEN_PL_EXTENSION "osgearth_shadergen"
 
+#define SHADERGEN_HINT_IGNORE "osgEarth.ShaderGenerator.ignore"
+
 using namespace osgEarth;
 
 //------------------------------------------------------------------------
@@ -69,6 +76,7 @@ using namespace osgEarth;
 #define SAMPLER_TEXT   "oe_sg_sampler_text"
 #define ATTRIB         "oe_sg_attrib"
 #define TEXENV_COLOR   "oe_sg_texenvcolor"
+#define TEX_MATRIX     "oe_sg_texmat"
 
 #define VERTEX_FUNCTION   "oe_sg_vert"
 #define FRAGMENT_FUNCTION "oe_sg_frag"
@@ -110,10 +118,12 @@ struct OSGEarthShaderGenPseudoLoader : public osgDB::ReaderWriter
         OE_INFO << LC << "Loading " << stripped << " and generating shaders." << std::endl;
         
         osg::ref_ptr<osg::Node> node = osgDB::readNodeFile(stripped, options);
-        if ( node )
+        if ( node.valid() )
         {
-            ShaderGenerator gen;
-            gen.run(node, Registry::stateSetCache());
+            osgEarth::Registry::shaderGenerator().run(
+                node.get(),
+                osgDB::getSimpleFileName(stripped),
+                Registry::stateSetCache() );
         }
 
         return node.valid() ? ReadResult(node.release()) : ReadResult::ERROR_IN_READING_FILE;
@@ -126,6 +136,37 @@ REGISTER_OSGPLUGIN(SHADERGEN_PL_EXTENSION, OSGEarthShaderGenPseudoLoader)
 
 namespace
 {
+    struct ActiveAttributeCollector : public osg::StateAttribute::ModeUsage
+    {
+        ActiveAttributeCollector(osg::StateSet* stateset, const osg::StateAttribute* sa, unsigned unit=0) :
+            _stateset(stateset),
+            _sa      (const_cast<osg::StateAttribute*>(sa)),
+            _unit    (unit) {}
+
+        virtual ~ActiveAttributeCollector() {}
+
+        virtual void usesMode(osg::StateAttribute::GLMode mode)
+        {
+            if (_stateset->getMode(mode) & osg::StateAttribute::ON)
+            {
+                _stateset->setAttribute(_sa, osg::StateAttribute::ON);
+            }
+        }
+
+        virtual void usesTextureMode(osg::StateAttribute::GLMode mode)
+        {
+            if (_stateset->getTextureMode(_unit, mode) & osg::StateAttribute::ON)
+            {
+                _stateset->setTextureAttribute(_unit, _sa, osg::StateAttribute::ON);
+            }
+        }
+
+        osg::StateSet*       _stateset;
+        osg::StateAttribute* _sa;
+        unsigned             _unit;
+    };
+
+
     /**
      * The OSG State extended with mode/attribute accessors.
      */
@@ -133,60 +174,113 @@ namespace
     {
     public:
         StateEx() : State() {}
-        
-        osg::StateAttribute::GLModeValue getMode(osg::StateAttribute::GLMode mode,
-                                                 osg::StateAttribute::GLModeValue def = osg::StateAttribute::INHERIT) const
-        {
-            return getMode(_modeMap, mode, def);
-        }
-        
-        osg::StateAttribute* getAttribute(osg::StateAttribute::Type type, unsigned int member = 0) const
-        {
-            return getAttribute(_attributeMap, type, member);
-        }
-        
-        osg::StateAttribute::GLModeValue getTextureMode(unsigned int unit,
-                                                        osg::StateAttribute::GLMode mode,
-                                                        osg::StateAttribute::GLModeValue def = osg::StateAttribute::INHERIT) const
-        {
-            return unit < _textureModeMapList.size() ? getMode(_textureModeMapList[unit], mode, def) : def;
-        }
 
-        unsigned getNumTextureAttributes() const 
+        // Captures the ACTIVE state into a state set. i.e., only state attributes
+        // set to ON.
+        osg::StateSet* capture() const
         {
-            return _textureAttributeMapList.size();
-        }
+            osg::StateSet* stateset = new osg::StateSet();
 
-        osg::StateAttribute* getTextureAttribute(unsigned int unit, osg::StateAttribute::Type type) const
-        {
-            return unit < _textureAttributeMapList.size() ? getAttribute(_textureAttributeMapList[unit], type, 0) : 0;
+            // add ON modes to the new stateset:
+            for(ModeMap::const_iterator i=_modeMap.begin();
+                i!=_modeMap.end();
+                ++i)
+            {
+                // note GLMode = mitr->first
+                const ModeStack& ms = i->second;
+                if (!ms.valueVec.empty())
+                {
+                    stateset->setMode(i->first,ms.valueVec.back());
+                }
+            }
+
+            // add ON texture modes to the new stateset:
+            for(unsigned unit=0; unit<_textureModeMapList.size(); ++unit)
+            {
+                const ModeMap& modeMap = _textureModeMapList[unit];
+                for(ModeMap::const_iterator i = modeMap.begin(); i != modeMap.end(); ++i)
+                {
+                    const ModeStack& ms = i->second;
+                    if (!ms.valueVec.empty())
+                    {
+                        stateset->setTextureMode(unit, i->first, ms.valueVec.back());
+                    }
+                }
+            }
+
+            for(AttributeMap::const_iterator i=_attributeMap.begin();
+                i!=_attributeMap.end();
+                ++i)
+            {
+                const AttributeStack& as = i->second;
+                if (!as.attributeVec.empty())
+                {
+                    const osg::State::AttributePair& pair = as.attributeVec.back();
+                    osg::StateAttribute* sa = const_cast<osg::StateAttribute*>(pair.first);
+                    ActiveAttributeCollector collector(stateset, sa);
+                    bool modeless = !sa->getModeUsage(collector);
+                    if (modeless || isModeless(sa))
+                    {
+                        // if getModeUsage returns false, there are no modes associated with
+                        // this attr, so just add it (it can't be forcably disabled)
+                        stateset->setAttribute(sa, osg::StateAttribute::ON);
+                    }
+                }
+            }
+
+            for(unsigned unit=0; unit<_textureAttributeMapList.size(); ++unit)
+            {
+                const AttributeMap& attrMap = _textureAttributeMapList[unit];
+                for(AttributeMap::const_iterator i = attrMap.begin(); i != attrMap.end(); ++i)
+                {                    
+                    const AttributeStack& as = i->second;
+                    if (!as.attributeVec.empty())
+                    {
+                        const osg::State::AttributePair& pair = as.attributeVec.back();
+                        osg::StateAttribute* sa = const_cast<osg::StateAttribute*>(pair.first);
+                        ActiveAttributeCollector collector(stateset, sa, unit);
+                        bool modeless = !sa->getModeUsage(collector);
+                        if (modeless || isModeless(sa))
+                        {
+                            // if getModeUsage returns false, there are no modes associated with
+                            // this attr, so just add it (it can't be forcably disabled)
+                            stateset->setTextureAttribute(unit, sa, osg::StateAttribute::ON);
+                        }
+                    }
+                }
+            }
+
+            return stateset;
         }
-        
-        osg::Uniform* getUniform(const std::string& name) const
+
+        // some attrs dont' properly report mode usage until OSG 3.3.1.
+        // ref: https://github.com/openscenegraph/osg/commit/22af59482ac4f727eeed5b97476a3a47d7fe8a69
+        bool isModeless(osg::StateAttribute* sa) const
         {
-            UniformMap::const_iterator it = _uniformMap.find(name);
-            return it != _uniformMap.end() ? 
-            const_cast<osg::Uniform *>(it->second.uniformVec.back().first) : 0;
+#if OSG_VERSION_LESS_THAN(3,3,1)            
+            return
+                dynamic_cast<osg::Texture2DArray*>(sa) ||
+                dynamic_cast<osg::Texture2DMultisample*>(sa);
+#else
+            return false;
+#endif
         }
-        
-    protected:
-        
-        osg::StateAttribute::GLModeValue getMode(const ModeMap &modeMap,
-                                                 osg::StateAttribute::GLMode mode, 
-                                                 osg::StateAttribute::GLModeValue def = osg::StateAttribute::INHERIT) const
+    };
+
+    // if the node has a stateset, clone it and replace it with the clone.
+    // otherwise, just create a new stateset on the node.
+    osg::StateSet* cloneOrCreateStateSet(osg::Node* node)
+    {
+        if ( node->getStateSet() )
         {
-            ModeMap::const_iterator it = modeMap.find(mode);
-            return (it != modeMap.end() && it->second.valueVec.size()) ? it->second.valueVec.back() : def;
+            node->setStateSet( osg::clone(node->getStateSet(), osg::CopyOp::SHALLOW_COPY) );
+            return node->getStateSet();
         }
-        
-        osg::StateAttribute* getAttribute(const AttributeMap &attributeMap,
-                                          osg::StateAttribute::Type type, unsigned int member = 0) const
+        else
         {
-            AttributeMap::const_iterator it = attributeMap.find(std::make_pair(type, member));
-            return (it != attributeMap.end() && it->second.attributeVec.size()) ? 
-            const_cast<osg::StateAttribute*>(it->second.attributeVec.back().first) : 0;
+            return node->getOrCreateStateSet();
         }
-    };
+    }
 }
 
 //------------------------------------------------------------------------
@@ -196,22 +290,45 @@ ShaderGenerator::ShaderGenerator()
     // find everything regardless of node masking
     setTraversalMode( TRAVERSE_ALL_CHILDREN );
     setNodeMaskOverride( ~0 );
+    _state = new StateEx();
+    _active = true;
+}
 
-    // set a default program name:
-    setProgramName( "osgEarth.ShaderGenerator" );
+// pre-3.3.0, NodeVisitor didn't have a copy constructor.
+#if OSG_VERSION_LESS_THAN(3,3,0)
+ShaderGenerator::ShaderGenerator(const ShaderGenerator& rhs, const osg::CopyOp& copy) :
+osg::NodeVisitor(),
+_active(rhs._active)
+{
+    _visitorType      = rhs._visitorType;
+    _traversalMode    = rhs._traversalMode;
+    _traversalMask    = rhs._traversalMask;
+    _nodeMaskOverride = rhs._nodeMaskOverride;
+    _state = new StateEx();
+}
+#else
+ShaderGenerator::ShaderGenerator(const ShaderGenerator& rhs, const osg::CopyOp& copy) :
+osg::NodeVisitor(rhs, copy),
+_active         (rhs._active)
+{
+    _state = new StateEx();
+}
+#endif
 
-    // make sure we support shaders:
-    _active = Registry::capabilities().supportsGLSL();
-    if ( _active )
+void
+ShaderGenerator::setIgnoreHint(osg::Object* object, bool ignore)
+{
+    if (object)
     {
-        _state = new StateEx();
+        object->setUserValue( SHADERGEN_HINT_IGNORE, ignore );
     }
 }
 
-void
-ShaderGenerator::setProgramName(const std::string& name)
+bool
+ShaderGenerator::ignore(const osg::Object* object)
 {
-    _name = name;
+    bool value;
+    return object && object->getUserValue(SHADERGEN_HINT_IGNORE, value) && value;
 }
 
 void
@@ -226,6 +343,9 @@ ShaderGenerator::accept(const osg::StateAttribute* sa) const
     if ( sa == 0L )
         return false;
 
+    if ( ignore(sa) )
+        return false;
+
     for(AcceptCallbackVector::const_iterator i = _acceptCallbacks.begin(); i != _acceptCallbacks.end(); ++i )
     {
         if ( !i->get()->accept(sa) )
@@ -235,7 +355,9 @@ ShaderGenerator::accept(const osg::StateAttribute* sa) const
 }
 
 void
-ShaderGenerator::run(osg::Node* graph, StateSetCache* cache)
+ShaderGenerator::run(osg::Node*         graph,
+                     const std::string& vpName, 
+                     StateSetCache*     cache)
 {
     if ( graph )
     {
@@ -243,50 +365,128 @@ ShaderGenerator::run(osg::Node* graph, StateSetCache* cache)
         graph->accept( *this );
 
         // perform GL state sharing
-        if ( cache )
-            cache->optimize( graph );
+        optimizeStateSharing( graph, cache );
+
+        osg::StateSet* stateset = cloneOrCreateStateSet(graph);
 
         // install a blank VP at the top as the default.
-        VirtualProgram* vp = VirtualProgram::get(graph->getStateSet());
+        VirtualProgram* vp = VirtualProgram::get(stateset);
         if ( !vp )
         {
-            vp = VirtualProgram::getOrCreate( graph->getOrCreateStateSet() );
+            vp = VirtualProgram::getOrCreate(stateset);
             vp->setInheritShaders( true );
-            vp->setName( _name );
+            vp->setName( vpName );
         }
     }
 }
 
+void
+ShaderGenerator::optimizeStateSharing(osg::Node* node, StateSetCache* cache)
+{
+    if ( node && cache )
+        cache->optimize(node);
+}
+
 void 
 ShaderGenerator::apply( osg::Node& node )
 {
-    if ( !_active ) return;
+    if ( !_active )
+        return;
+
+    if ( ignore(&node) )
+        return;
 
-    if ( node.getStateSet() )
-        _state->pushStateSet( node.getStateSet() );
+    osg::ref_ptr<osg::StateSet> stateset = node.getStateSet();
+    if ( stateset.valid() )
+    {
+        _state->pushStateSet( stateset.get() );
+    }
 
     traverse(node);
 
-    if ( node.getStateSet() )
+    if ( stateset.valid() )
+    {
         _state->popStateSet();
+    }
 }
 
+void
+ShaderGenerator::apply( osg::Group& group )
+{
+    apply( static_cast<osg::Node&>(group) );
+}
 
 void 
-ShaderGenerator::apply( osg::Geode& geode )
+ShaderGenerator::apply( osg::Geode& node )
 {
-    if ( !_active ) return;
+    if ( !_active )
+        return;
 
-    if ( geode.getStateSet() )
-        _state->pushStateSet( geode.getStateSet() );
+    if ( ignore(&node) )
+        return;
 
-    for( unsigned d = 0; d < geode.getNumDrawables(); ++d )
+    osg::ref_ptr<osg::StateSet> stateset = node.getStateSet();
+    if ( stateset.valid() )
     {
-        apply( geode.getDrawable(d) );
+        _state->pushStateSet( stateset.get() );
     }
 
-    if ( geode.getStateSet() )
+    unsigned numDrawables = node.getNumDrawables();
+    bool traverseDrawables = true;
+
+    // This block checks whether all the geode's drawables are equivalent,
+    // i.e., they are the same type (geometry or text) and none of them
+    // have their own state sets. IF that's the case, we can create a 
+    // single shader program for the entire geode. This is an optimization.
+    if ( stateset.valid() )
+    {
+        unsigned d;
+        unsigned numInheritingText = 0, numInheritingGeometry = 0;
+        for( d = 0; d < numDrawables; ++d )
+        {
+            osg::Drawable* drawable = node.getDrawable(d);
+            if ( drawable->getStateSet() == 0L )
+            {
+                if ( drawable->asGeometry() )
+                    numInheritingGeometry++;
+                else if ( dynamic_cast<osgText::Text*>(drawable) )
+                    numInheritingText++;
+            }
+        }
+
+        if (numInheritingGeometry == numDrawables )
+        {
+            osg::ref_ptr<osg::StateSet> replacement;
+            if ( processGeometry(stateset.get(), replacement) )
+            {
+                node.setStateSet(replacement.get() );
+                traverseDrawables = false;
+            }
+        }
+        else if (numInheritingText == numDrawables )
+        {
+            osg::ref_ptr<osg::StateSet> replacement;
+            if ( processText(stateset.get(), replacement) )
+            {
+                node.setStateSet(replacement.get() );
+                traverseDrawables = false;
+            }
+        }
+    }
+
+    // Drawables have state sets, so let's traverse them.
+    if ( traverseDrawables )
+    {
+        for( unsigned d = 0; d < node.getNumDrawables(); ++d )
+        {
+            apply( node.getDrawable(d) );
+        }
+    }
+
+    if ( stateset.valid() )
+    {
         _state->popStateSet();
+    }
 }
 
 
@@ -328,17 +528,6 @@ ShaderGenerator::apply( osg::Drawable* drawable )
         {
             _state->popStateSet();
         }
-
-#if 0
-        // optimize state set sharing
-        if ( _stateSetCache.valid() && replacement.valid() )
-        {
-            if ( _stateSetCache->share(replacement, replacement) )
-            {
-                drawable->setStateSet( replacement.get() );
-            }
-        }
-#endif
     }
 }
 
@@ -346,16 +535,23 @@ ShaderGenerator::apply( osg::Drawable* drawable )
 void
 ShaderGenerator::apply(osg::PagedLOD& node)
 {
-    if ( !_active ) return;
+    if ( !_active )
+        return;
+    
+    if ( ignore(&node) )
+        return;
 
     for( unsigned i=0; i<node.getNumFileNames(); ++i )
     {
+        static Threading::Mutex s_mutex;
+        s_mutex.lock();
         const std::string& filename = node.getFileName( i );
         if (!filename.empty() && 
             osgDB::getLowerCaseFileExtension(filename).compare(SHADERGEN_PL_EXTENSION) != 0 )
         {
             node.setFileName( i, Stringify() << filename << "." << SHADERGEN_PL_EXTENSION );
         }
+        s_mutex.unlock();
     }
 
     apply( static_cast<osg::LOD&>(node) );
@@ -365,7 +561,11 @@ ShaderGenerator::apply(osg::PagedLOD& node)
 void
 ShaderGenerator::apply(osg::ProxyNode& node)
 {
-    if ( !_active ) return;
+    if ( !_active )
+        return;
+
+    if ( ignore(&node) )
+        return;
 
     if ( node.getLoadingExternalReferenceMode() != osg::ProxyNode::LOAD_IMMEDIATELY )
     {
@@ -391,16 +591,21 @@ ShaderGenerator::apply(osg::ClipNode& node)
 {
     static const char* s_clip_source =
         "#version " GLSL_VERSION_STR "\n"
-        "void sg_set_clipvertex(inout vec4 vertexVIEW)\n"
+        "void oe_sg_set_clipvertex(inout vec4 vertexVIEW)\n"
         "{\n"
         "    gl_ClipVertex = vertexVIEW; \n"
         "}\n";
 
-    if ( !_active ) return;
+    if ( !_active )
+        return;
+
+    if ( ignore(&node) )
+        return;
 
-    VirtualProgram* vp = VirtualProgram::getOrCreate(node.getOrCreateStateSet());
+    osg::StateSet* stateSet = cloneOrCreateStateSet(&node);
+    VirtualProgram* vp = VirtualProgram::getOrCreate(stateSet);
     if ( vp->referenceCount() == 1 ) vp->setName( _name );
-    vp->setFunction( "sg_set_clipvertex", s_clip_source, ShaderComp::LOCATION_VERTEX_VIEW );
+    vp->setFunction( "oe_sg_set_clipvertex", s_clip_source, ShaderComp::LOCATION_VERTEX_VIEW );
 
     apply( static_cast<osg::Group&>(node) );
 }
@@ -413,32 +618,29 @@ ShaderGenerator::processText(const osg::StateSet* ss, osg::ref_ptr<osg::StateSet
     if ( !_active )
         return false;
 
-    // State object with extra accessors:
-    StateEx* state = static_cast<StateEx*>(_state.get());
+    // Capture the active current state:
+    osg::ref_ptr<osg::StateSet> current = static_cast<StateEx*>(_state.get())->capture();
 
     // check for a real osg::Program. If it exists, bail out so that OSG
     // can use the program already in the graph
-    osg::StateAttribute* program = state->getAttribute(osg::StateAttribute::PROGRAM);
+    osg::StateAttribute* program = current->getAttribute(osg::StateAttribute::PROGRAM);
     if ( dynamic_cast<osg::Program*>(program) != 0L )
         return false;
 
-    // new state set:
+    // New state set. We never modify existing statesets.
     replacement = ss ? osg::clone(ss, osg::CopyOp::SHALLOW_COPY) : new osg::StateSet();
 
     // new VP:
-    VirtualProgram* vp = 0L;
-    if ( VirtualProgram::get(replacement.get()) )
-        vp =  osg::clone(VirtualProgram::get(replacement.get()), osg::CopyOp::DEEP_COPY_ALL);
-    else
-        vp = VirtualProgram::getOrCreate(replacement.get());
-
-    if ( vp->referenceCount() == 1 )
+    osg::ref_ptr<VirtualProgram> vp = VirtualProgram::cloneOrCreate(replacement.get());
+    
+    // give the VP a name if it needs one.
+    if ( vp->getName().empty() )
         vp->setName( _name );
 
     std::string vertSrc =
         "#version " GLSL_VERSION_STR "\n" GLSL_PRECISION "\n"
         "varying " MEDIUMP "vec4 " TEX_COORD_TEXT ";\n"
-        "void " VERTEX_FUNCTION "(inout vec4 vertex_view)\n"
+        "void " VERTEX_FUNCTION "(inout vec4 vertexVIEW)\n"
         "{ \n"
         INDENT TEX_COORD_TEXT " = gl_MultiTexCoord0;\n"
         "} \n";
@@ -462,189 +664,89 @@ ShaderGenerator::processText(const osg::StateSet* ss, osg::ref_ptr<osg::StateSet
 
 
 bool
-ShaderGenerator::processGeometry( const osg::StateSet* ss, osg::ref_ptr<osg::StateSet>& replacement )
+ShaderGenerator::processGeometry(const osg::StateSet*         original, 
+                                 osg::ref_ptr<osg::StateSet>& replacement)
 {
     // do nothing if there's no GLSL support
     if ( !_active )
         return false;
-
-    // State object with extra accessors:
-    StateEx* state = static_cast<StateEx*>(_state.get());
+    
+    // capture the active current state:
+    osg::ref_ptr<osg::StateSet> current = static_cast<StateEx*>(_state.get())->capture();
 
     // check for a real osg::Program in the whole state stack. If it exists, bail out
     // so that OSG can use the program already in the graph. We never override a
     // full Program.
-    osg::StateAttribute* program = state->getAttribute(osg::StateAttribute::PROGRAM);
+    osg::StateAttribute* program = current->getAttribute(osg::StateAttribute::PROGRAM);
     if ( dynamic_cast<osg::Program*>(program) != 0L )
         return false;
 
-    // prepare to generate:
-    osg::ref_ptr<osg::StateSet> new_stateset = ss ? osg::clone(ss, osg::CopyOp::SHALLOW_COPY) : new osg::StateSet();
-    VirtualProgram* vp = VirtualProgram::cloneOrCreate(ss, new_stateset);
+    // Copy or create a new stateset (that we may or may not use depending on
+    // what we find). Never modify an existing stateset!
+    osg::ref_ptr<osg::StateSet> new_stateset = 
+        original ? osg::clone(original, osg::CopyOp::SHALLOW_COPY) :
+        new osg::StateSet();
+
+    // likewise, create a VP that we might populate.
+    osg::ref_ptr<VirtualProgram> vp = VirtualProgram::cloneOrCreate(original, new_stateset);
+
+    // we'll set this to true if the new stateset goes into effect and
+    // needs to be returned.
     bool need_new_stateset = false;
     
-    if ( vp->referenceCount() == 1 )
+    // give the VP a name if it needs one.
+    if ( vp->getName().empty() )
         vp->setName( _name );
 
     // Check whether the lighting state has changed and install a mode uniform.
     // TODO: fix this
-    if ( ss && ss->getMode(GL_LIGHTING) != osg::StateAttribute::INHERIT )
+    if ( original && original->getMode(GL_LIGHTING) != osg::StateAttribute::INHERIT )
     {
         need_new_stateset = true;
 
-        osg::StateAttribute::GLModeValue value = state->getMode(GL_LIGHTING); // from the state, not the ss.
+        osg::StateAttribute::GLModeValue value = current->getMode(GL_LIGHTING);
         new_stateset->addUniform( Registry::shaderFactory()->createUniformForGLMode(GL_LIGHTING, value) );
     }
 
     // if the stateset changes any texture attributes, we need a new virtual program:
-    if (state->getNumTextureAttributes() > 0)
+    if (current->getTextureAttributeList().size() > 0)
     {
-        // work off the state's accumulated texture attribute set:
-        int texCount = state->getNumTextureAttributes();
-
         // start generating the shader source.
-        std::stringstream vertHead, vertBody, fragHead, fragBody;
+        GenBuffers buf;
+        buf.stateSet = new_stateset;
 
         // compatibility strings make it work in GL or GLES.
-        vertHead << "#version " GLSL_VERSION_STR "\n" GLSL_PRECISION;
-        fragHead << "#version " GLSL_VERSION_STR "\n" GLSL_PRECISION;
+        buf.vertHead << "#version " GLSL_VERSION_STR "\n" GLSL_PRECISION;
+        buf.fragHead << "#version " GLSL_VERSION_STR "\n" GLSL_PRECISION;
 
         // function declarations:
-        vertBody << "void " VERTEX_FUNCTION "(inout vec4 vertex_view)\n{\n";
-
-        fragBody << "void " FRAGMENT_FUNCTION "(inout vec4 color)\n{\n";
+        buf.vertBody << "void " VERTEX_FUNCTION "(inout vec4 vertex_view)\n{\n";
+        buf.fragBody << "void " FRAGMENT_FUNCTION "(inout vec4 color)\n{\n";
 
         bool wroteTexelDecl = false;
 
-        for( int t = 0; t < texCount; ++t )
+        // Loop over all possible texture image units.
+        int maxUnit = Registry::capabilities().getMaxGPUTextureUnits();
+
+        for( int unit = 0; unit < maxUnit; ++unit )
         {
             if ( !wroteTexelDecl )
             {
-                fragBody << INDENT << MEDIUMP "vec4 texel; \n";
+                buf.fragBody << INDENT << MEDIUMP "vec4 texel; \n";
                 wroteTexelDecl = true;
             }
 
-            osg::Texture* tex = dynamic_cast<osg::Texture*>(state->getTextureAttribute(t, osg::StateAttribute::TEXTURE));
+            osg::Texture* tex = dynamic_cast<osg::Texture*>( current->getTextureAttribute(unit, osg::StateAttribute::TEXTURE) );
 
             if (accept(tex) && !ImageUtils::isFloatingPointInternalFormat(tex->getInternalFormat()))
             {
-                // made it this far, new stateset required.
-                need_new_stateset = true;
-
-                // see if we have a texenv; if so get its blending mode.
-                osg::TexEnv::Mode blendingMode = osg::TexEnv::MODULATE;
-                osg::TexEnv* env = dynamic_cast<osg::TexEnv*>(state->getTextureAttribute(t, osg::StateAttribute::TEXENV) );
-                if ( accept(env) )
-                {
-                    blendingMode = env->getMode();
-                    if ( blendingMode == osg::TexEnv::BLEND )
-                    {
-                        new_stateset->getOrCreateUniform( Stringify() << TEXENV_COLOR << t, osg::Uniform::FLOAT_VEC4 )->set( env->getColor() );
-                    }
-                }
-
-                osg::TexGen::Mode texGenMode = osg::TexGen::OBJECT_LINEAR;
-                osg::TexGen* texGen = dynamic_cast<osg::TexGen*>(state->getTextureAttribute(t, osg::StateAttribute::TEXGEN));
-                if ( accept(texGen) )
-                {
-                    texGenMode = texGen->getMode();
-                }
-
-                vertHead << "varying " MEDIUMP "vec4 " TEX_COORD << t << ";\n";
-                fragHead << "varying " MEDIUMP "vec4 " TEX_COORD << t << ";\n";
-
-                // handle different TexGen modes.
-                switch(texGenMode)
-                {
-                case osg::TexGen::SPHERE_MAP:
-                    vertBody 
-                        //todo: consolidate.
-                        << INDENT "{\n" // scope it in case there are > 1
-                        << INDENT "vec3 v = normalize(vec3(vertex_view));\n"
-                        << INDENT "vec3 n = normalize(gl_NormalMatrix * gl_Normal);\n"
-                        << INDENT "vec3 r = reflect(v, n);\n"
-                        << INDENT "float m = 2.0 * sqrt(r.x*r.x + r.y*r.y + (r.z+1.0)*(r.z+1.0));\n"
-                        << INDENT TEX_COORD << t << ".s = r.x/m + 0.5;\n"
-                        << INDENT TEX_COORD << t << ".t = r.y/m + 0.5;\n"
-                        << INDENT "}\n";
-                    break;
-
-                default:
-                    vertBody 
-                        << INDENT << TEX_COORD << t << " = gl_MultiTexCoord" << t << ";\n";
-                    break;
-                }
-
-
-                if ( dynamic_cast<osg::Texture1D*>(tex) )
-                {
-                    fragHead << "uniform sampler1D " SAMPLER << t << ";\n";
-                    fragBody << INDENT "texel = texture1D(" SAMPLER << t << ", " TEX_COORD << t << ".x);\n";
-                    new_stateset->getOrCreateUniform( Stringify() << SAMPLER << t, osg::Uniform::SAMPLER_1D )->set( t );
-                }
-                else if ( dynamic_cast<osg::Texture2D*>(tex) )
-                {
-                    fragHead << "uniform sampler2D " SAMPLER << t << ";\n";
-                    fragBody << INDENT "texel = texture2D(" SAMPLER << t << ", " TEX_COORD << t << ".xy);\n";
-                    new_stateset->getOrCreateUniform( Stringify() << SAMPLER << t, osg::Uniform::SAMPLER_2D )->set( t );
-                }
+                osg::TexGen* texgen = dynamic_cast<osg::TexGen*>(current->getTextureAttribute(unit, osg::StateAttribute::TEXGEN));
+                osg::TexEnv* texenv = dynamic_cast<osg::TexEnv*>(current->getTextureAttribute(unit, osg::StateAttribute::TEXENV));
+                osg::TexMat* texmat = dynamic_cast<osg::TexMat*>(current->getTextureAttribute(unit, osg::StateAttribute::TEXMAT));
 
-#if 0 // works, but requires a higher version of GL?
-                else if ( dynamic_cast<osg::TextureRectangle*>(tex) )
+                if ( apply(tex, texgen, texenv, texmat, unit, buf) == true )
                 {
-                    fragHead << "uniform sampler2Drect " SAMPLER << t << ";\n";
-                    fragBody << INDENT "texel = texture2Drect(" SAMPLER << t << ", " TEX_COORD << t << ".xy);\n";
-                    new_stateset->getOrCreateUniform( Stringify() << SAMPLER << t, osg::Uniform::SAMPLER_2D_RECT )->set( t );
-                }
-#endif
-                // doesn't work. why?
-                else if ( dynamic_cast<osg::TextureRectangle*>(tex) )
-                {
-                    osg::Image* image = static_cast<osg::TextureRectangle*>(tex)->getImage();
-
-                    vertBody 
-                        << INDENT << TEX_COORD << t << ".x /= " << (image->s()-1) << ".0;\n"
-                        << INDENT << TEX_COORD << t << ".y /= " << (image->t()-1) << ".0;\n";
-
-                    fragHead << "uniform sampler2D " SAMPLER << t << ";\n";
-                    fragBody << INDENT "texel = texture2D(" SAMPLER << t << ", " TEX_COORD << t << ".xy);\n";
-                    new_stateset->getOrCreateUniform( Stringify() << SAMPLER << t, osg::Uniform::SAMPLER_2D )->set( t );
-                }
-
-                else if ( dynamic_cast<osg::Texture3D*>(tex) )
-                {
-                    fragHead << "uniform sampler3D " SAMPLER << t << ";\n";
-                    fragBody << INDENT "texel = texture3D(" SAMPLER << t << ", " TEX_COORD << t << ".xyz);\n";
-                    new_stateset->getOrCreateUniform( Stringify() << SAMPLER << t, osg::Uniform::SAMPLER_3D )->set( t );
-                }
-
-                // See http://www.opengl.org/sdk/docs/man/xhtml/glTexEnv.xml
-                switch( blendingMode )
-                {
-                case osg::TexEnv::REPLACE:
-                    fragBody
-                        << INDENT "color = texel; \n";
-                    break;
-                case osg::TexEnv::MODULATE:
-                    fragBody
-                        << INDENT "color = color * texel; \n";
-                    break;
-                case osg::TexEnv::DECAL:
-                    fragBody
-                        << INDENT "color.rgb = color.rgb * (1.0 - texel.a) + (texel.rgb * texel.a); \n";
-                    break;
-                case osg::TexEnv::BLEND:
-                    fragHead
-                        << "uniform " MEDIUMP "vec4 " TEXENV_COLOR << t << "\n;";
-                    fragBody
-                        << INDENT "color.rgb = color.rgb * (1.0 - texel.rgb) + (" << TEXENV_COLOR << t << ".rgb * texel.rgb); \n"
-                        << INDENT "color.a   = color.a * texel.a; \n";
-                    break;
-                case osg::TexEnv::ADD:
-                default:
-                    fragBody
-                        << INDENT "color.rgb = color.rgb + texel.rgb; \n"
-                        << INDENT "color.a   = color.a * texel.a; \n";
+                   need_new_stateset = true;
                 }
             }
         }
@@ -652,17 +754,17 @@ ShaderGenerator::processGeometry( const osg::StateSet* ss, osg::ref_ptr<osg::Sta
         if ( need_new_stateset )
         {
             // close out functions:
-            vertBody << "}\n";
-            fragBody << "}\n";
+            buf.vertBody << "}\n";
+            buf.fragBody << "}\n";
 
             // Extract the shader source strings (win compat method)
             std::string vertBodySrc, vertSrc, fragBodySrc, fragSrc;
-            vertBodySrc = vertBody.str();
-            vertHead << vertBodySrc;
-            vertSrc = vertHead.str();
-            fragBodySrc = fragBody.str();
-            fragHead << fragBodySrc;
-            fragSrc = fragHead.str();
+            vertBodySrc = buf.vertBody.str();
+            buf.vertHead << vertBodySrc;
+            vertSrc = buf.vertHead.str();
+            fragBodySrc = buf.fragBody.str();
+            buf.fragHead << fragBodySrc;
+            fragSrc = buf.fragHead.str();
 
             // inject the shaders:
             vp->setFunction( VERTEX_FUNCTION,   vertSrc, ShaderComp::LOCATION_VERTEX_VIEW );
@@ -676,3 +778,279 @@ ShaderGenerator::processGeometry( const osg::StateSet* ss, osg::ref_ptr<osg::Sta
     }
     return replacement.valid();
 }
+
+
+bool
+ShaderGenerator::apply(osg::Texture* tex, 
+                       osg::TexGen*  texgen,
+                       osg::TexEnv*  texenv,
+                       osg::TexMat*  texmat,
+                       int           unit,
+                       GenBuffers&   buf)
+{
+   bool ok = true;
+
+   buf.vertHead << "varying " MEDIUMP "vec4 " TEX_COORD << unit << ";\n";
+   buf.fragHead << "varying " MEDIUMP "vec4 " TEX_COORD << unit << ";\n";
+
+   apply( texgen, unit, buf );
+   apply( texmat, unit, buf );
+
+   if ( dynamic_cast<osg::Texture1D*>(tex) )
+   {
+      apply(static_cast<osg::Texture1D*>(tex), unit, buf);
+   }
+   else if ( dynamic_cast<osg::Texture2D*>(tex) )
+   {
+      apply(static_cast<osg::Texture2D*>(tex), unit, buf);
+   }
+   else if ( dynamic_cast<osg::Texture3D*>(tex) )
+   {
+      apply(static_cast<osg::Texture3D*>(tex), unit, buf);
+   }
+   else if ( dynamic_cast<osg::TextureRectangle*>(tex) )
+   {
+      apply(static_cast<osg::TextureRectangle*>(tex), unit, buf);
+   }
+   else if ( dynamic_cast<osg::Texture2DArray*>(tex) )
+   {
+      apply(static_cast<osg::Texture2DArray*>(tex), unit, buf);
+   }
+   else
+   {
+      OE_WARN << LC << "Unsupported texture type: " << tex->className() << std::endl;
+      ok = false;
+   }
+
+   if ( ok )
+   {
+      apply( texenv, unit, buf );
+   }
+
+   return ok;
+}
+
+
+bool
+ShaderGenerator::apply(osg::TexEnv* texenv, int unit, GenBuffers& buf)
+{
+    // see if we have a texenv; if so get its blending mode.
+    osg::TexEnv::Mode blendingMode = osg::TexEnv::MODULATE;
+
+    if ( accept(texenv) )
+    {
+        blendingMode = texenv->getMode();
+
+        if ( blendingMode == osg::TexEnv::BLEND )
+        {
+            std::string texEnvColorUniform = Stringify() << TEXENV_COLOR << unit;
+            buf.stateSet
+                ->getOrCreateUniform(texEnvColorUniform, osg::Uniform::FLOAT_VEC4)
+                ->set( texenv->getColor() );
+        }
+    }
+
+    // See http://www.opengl.org/sdk/docs/man/xhtml/glTexEnv.xml
+    switch( blendingMode )
+    {
+    case osg::TexEnv::REPLACE:
+        buf.fragBody
+            << INDENT "color = texel; \n";
+        break;
+    case osg::TexEnv::MODULATE:
+        buf.fragBody
+            << INDENT "color = color * texel; \n";
+        break;
+    case osg::TexEnv::DECAL:
+        buf.fragBody
+            << INDENT "color.rgb = color.rgb * (1.0 - texel.a) + (texel.rgb * texel.a); \n";
+        break;
+    case osg::TexEnv::BLEND:
+        buf.fragHead
+            << "uniform " MEDIUMP "vec4 " TEXENV_COLOR << unit << "\n;";
+        buf.fragBody
+            << INDENT "color.rgb = color.rgb * (1.0 - texel.rgb) + (" << TEXENV_COLOR << unit << ".rgb * texel.rgb); \n"
+            << INDENT "color.a   = color.a * texel.a; \n";
+        break;
+    case osg::TexEnv::ADD:
+    default:
+        buf.fragBody
+            << INDENT "color.rgb = color.rgb + texel.rgb; \n"
+            << INDENT "color.a   = color.a * texel.a; \n";
+    }
+
+    return true;
+}
+
+bool
+ShaderGenerator::apply(osg::TexGen* texgen, int unit, GenBuffers& buf)
+{
+    bool genDefault = false;
+
+    // by default, do not use texture coordinate generation:
+    if ( !accept(texgen) )
+    {
+        genDefault = true;
+    }
+
+    else
+    {
+        // Handle different TexGen modes.
+        // From the GLSL Orange Book.
+        switch( texgen->getMode() )
+        {
+        case osg::TexGen::OBJECT_LINEAR:
+            buf.vertBody
+                << INDENT "{\n"
+                << INDENT TEX_COORD << unit << " = "
+                <<      "gl_Vertex.x*gl_ObjectPlaneS[" <<unit<< "] + "
+                <<      "gl_Vertex.y*gl_ObjectPlaneT[" <<unit<< "] + "
+                <<      "gl_Vertex.z*gl_ObjectPlaneR[" <<unit<< "] + "
+                <<      "gl_Vertex.w*gl_ObjectPlaneQ[" <<unit<< "]; \n"
+                << INDENT "}\n";
+            break;
+
+        case osg::TexGen::EYE_LINEAR:
+            buf.vertBody
+                << INDENT "{\n"
+                << INDENT TEX_COORD << unit << " = "
+                <<      "vertex_view.x*gl_EyePlaneS[" <<unit<< "] + "
+                <<      "vertex_view.y*gl_EyePlaneT[" <<unit<< "] + "
+                <<      "vertex_view.z*gl_EyePlaneR[" <<unit<< "] + "
+                <<      "vertex_view.w*gl_EyePlaneQ[" <<unit<< "]; \n"
+                << INDENT "}\n";
+            break;
+
+        case osg::TexGen::SPHERE_MAP:
+            buf.vertHead
+                << "varying vec3 oe_Normal;\n";
+            buf.vertBody 
+                << INDENT "{\n" // scope it in case there are > 1
+                << INDENT "vec3 view_vec = normalize(vertex_view.xyz/vertex_view.w); \n"
+                << INDENT "vec3 r = reflect(view_vec, oe_Normal);\n"
+                << INDENT "r.z += 1.0; \n"
+                << INDENT "float m = 2.0 * sqrt(dot(r,r)); \n"
+                << INDENT TEX_COORD << unit << " = vec4(r.x/m + 0.5, r.y/m + 0.5, 0.0, 1.0); \n"
+                << INDENT "}\n";
+            break;
+
+        case osg::TexGen::REFLECTION_MAP:
+            buf.vertHead
+                << "varying vec3 oe_Normal;\n";
+            buf.vertBody
+                << INDENT "{\n"
+                << INDENT "vec3 view_vec = normalize(vertex_view.xyz/vertex_view.w);\n"
+                << INDENT TEX_COORD << unit << " = vec4(reflect(view_vec, oe_Normal), 1.0); \n"
+                << INDENT "}\n";
+            break;
+
+        case osg::TexGen::NORMAL_MAP:
+            buf.vertHead
+                << "varying vec3 oe_Normal;\n";
+            buf.vertBody
+                << INDENT "{\n"
+                << INDENT TEX_COORD << unit << " = vec4(oe_Normal, 1.0); \n"
+                << INDENT "}\n";
+            break;
+
+        default: // fall back on non-gen setup.
+            genDefault = true;
+            break;
+        }
+    }
+    
+    if ( genDefault )
+    {
+        // GLSL only supports built-in "gl_MultiTexCoord{0..7}"
+        if ( unit <= 7 )
+        {
+            buf.vertBody
+                << INDENT << TEX_COORD << unit << " = gl_MultiTexCoord" << unit << ";\n";
+        }
+        else
+        {
+            OE_INFO << LC
+                << "Texture coordinate on unit (" << unit << ") "
+                << "requires a custom vertex attribute (osg_MultiTexCoord" << unit << ")."
+                << std::endl;
+
+            buf.vertBody 
+                << INDENT << TEX_COORD << unit << " = osg_MultiTexCoord" << unit << ";\n";
+        }
+    }
+
+    return true;
+}
+
+bool
+ShaderGenerator::apply(osg::TexMat* texmat, int unit, GenBuffers& buf)
+{
+    if ( accept(texmat) )
+    {
+        std::string texMatUniform = Stringify() << TEX_MATRIX << unit;
+
+        buf.vertHead << "uniform mat4 " << texMatUniform << ";\n";
+        //buf.vertBody << INDENT << TEX_COORD << unit << " *= " << texMatUniform << ";\n";
+        buf.vertBody << INDENT << TEX_COORD << unit << " = " << texMatUniform << " * " << TEX_COORD<<unit << ";\n";
+
+        buf.stateSet
+            ->getOrCreateUniform(texMatUniform, osg::Uniform::FLOAT_MAT4)
+            ->set( texmat->getMatrix() );
+    }
+
+    return true;
+}
+
+bool
+ShaderGenerator::apply(osg::Texture1D* tex, int unit, GenBuffers& buf)
+{
+    buf.fragHead << "uniform sampler1D " SAMPLER << unit << ";\n";
+    buf.fragBody << INDENT "texel = texture1D(" SAMPLER << unit << ", " TEX_COORD << unit << ".x);\n";
+    buf.stateSet->getOrCreateUniform( Stringify() << SAMPLER << unit, osg::Uniform::SAMPLER_1D )->set( unit );
+
+    return true;
+}
+
+bool
+ShaderGenerator::apply(osg::Texture2D* tex, int unit, GenBuffers& buf)
+{
+    buf.fragHead << "uniform sampler2D " SAMPLER << unit << ";\n";
+    buf.fragBody << INDENT "texel = texture2D(" SAMPLER << unit << ", " TEX_COORD << unit << ".xy);\n";
+    buf.stateSet->getOrCreateUniform( Stringify() << SAMPLER << unit, osg::Uniform::SAMPLER_2D )->set( unit );
+
+    return true;
+}
+
+bool
+ShaderGenerator::apply(osg::Texture3D* tex, int unit, GenBuffers& buf)
+{
+    buf.fragHead << "uniform sampler3D " SAMPLER << unit << ";\n";
+    buf.fragBody << INDENT "texel = texture3D(" SAMPLER << unit << ", " TEX_COORD << unit << ".xyz);\n";
+    buf.stateSet->getOrCreateUniform( Stringify() << SAMPLER << unit, osg::Uniform::SAMPLER_3D )->set( unit );
+
+    return true;
+}
+
+bool
+ShaderGenerator::apply(osg::TextureRectangle* tex, int unit, GenBuffers& buf)
+{
+    buf.vertHead << "#extension GL_ARB_texture_rectangle : enable\n";
+
+    buf.fragHead << "uniform sampler2DRect " SAMPLER << unit << ";\n";
+    buf.fragBody << INDENT "texel = texture2DRect(" SAMPLER << unit << ", " TEX_COORD << unit << ".xy);\n";
+    buf.stateSet->getOrCreateUniform( Stringify() << SAMPLER << unit, osg::Uniform::SAMPLER_2D )->set( unit );
+
+    return true;
+}
+
+bool
+ShaderGenerator::apply(osg::Texture2DArray* tex, int unit, GenBuffers& buf)
+{    
+    buf.fragHead <<  "#extension GL_EXT_texture_array : enable \n";    
+
+    buf.fragHead << "uniform sampler2DArray " SAMPLER << unit << ";\n";
+    buf.fragBody << INDENT "texel = texture2DArray(" SAMPLER << unit << ", " TEX_COORD << unit << ".xyz);\n";
+    buf.stateSet->getOrCreateUniform( Stringify() << SAMPLER << unit, osg::Uniform::SAMPLER_2D_ARRAY )->set( unit );         
+
+    return true;
+}
diff --git a/src/osgEarth/ShaderUtils b/src/osgEarth/ShaderUtils
index e3b14b1..81265f6 100644
--- a/src/osgEarth/ShaderUtils
+++ b/src/osgEarth/ShaderUtils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -121,7 +121,7 @@ namespace osgEarth
         bool  _useUpdateTrav;
         OpenThreads::Mutex _stateSetMutex;
 
-        osg::ref_ptr<osg::Uniform> _lightingEnabledUniform;
+        //osg::ref_ptr<osg::Uniform> _lightingEnabledUniform;
         osg::ref_ptr<osg::Uniform> _lightEnabledUniform;
          
         std::vector<osg_LightSourceParameters>   _osgLightSourceParameters; 
@@ -186,6 +186,40 @@ namespace osgEarth
 
         void ensureCapacity( unsigned newSize );
     };
+
+
+    /**
+     * Cull callback that installs a range (distance to view point) uniform
+     * in the State based on the bounding center of the node being culled.
+     * The actual name of the range uniform can is returned by
+     * ShaderFactory::getRangeUniformName().
+     */
+    class OSGEARTH_EXPORT RangeUniformCullCallback : public osg::NodeCallback
+    {
+    public:
+        RangeUniformCullCallback();
+        void operator()(osg::Node*, osg::NodeVisitor* nv);
+
+        // testing
+        void setDump(bool v) { _dump = true; }
+
+    private:
+        osg::ref_ptr<osg::StateSet> _stateSet;
+        osg::ref_ptr<osg::Uniform>  _uniform;
+        bool                        _dump;
+    };
+
+
+    /**
+     * Shader that will discard fragments whose alpha component falls below
+     * the specified threshold.
+     */
+    class OSGEARTH_EXPORT DiscardAlphaFragments
+    {
+    public:
+        void install(osg::StateSet* ss, float minAlpha) const;
+        void uninstall(osg::StateSet* ss) const;
+    };
 }
 
 #endif // OSGEARTH_SHADER_UTILS_H
diff --git a/src/osgEarth/ShaderUtils.cpp b/src/osgEarth/ShaderUtils.cpp
index 9f567ec..7c37559 100644
--- a/src/osgEarth/ShaderUtils.cpp
+++ b/src/osgEarth/ShaderUtils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -17,9 +17,11 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarth/ShaderUtils>
+#include <osgEarth/ShaderFactory>
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
 #include <osgEarth/CullingUtils>
+#include <osg/ComputeBoundsVisitor>
 #include <list>
 
 using namespace osgEarth;
@@ -130,10 +132,10 @@ namespace
 
 //------------------------------------------------------------------------
 
-namespace State_Utils
+namespace
 {
     // Code borrowed from osg::State.cpp
-    bool replace(std::string& str, const std::string& original_phrase, const std::string& new_phrase)
+    static bool replace(std::string& str, const std::string& original_phrase, const std::string& new_phrase)
     {
         bool replacedStr = false;
         std::string::size_type pos = 0;
@@ -159,7 +161,7 @@ namespace State_Utils
         return replacedStr;
     }
 
-    void replaceAndInsertDeclaration(std::string& source, std::string::size_type declPos, const std::string& originalStr, const std::string& newStr, const std::string& declarationPrefix, const std::string& declarationSuffix ="")
+    static void replaceAndInsertDeclaration(std::string& source, std::string::size_type declPos, const std::string& originalStr, const std::string& newStr, const std::string& declarationPrefix, const std::string& declarationSuffix ="")
     {
         if (replace(source, originalStr, newStr))
         {
@@ -195,7 +197,7 @@ ShaderPreProcessor::run(osg::Shader* shader)
 
         for( int i=0; i<maxLights; ++i )
         {
-            State_Utils::replaceAndInsertDeclaration(
+            replaceAndInsertDeclaration(
                 source, declPos,
                 Stringify() << "gl_LightSource[" << i << "]",
                 Stringify() << "osg_LightSource" << i,
@@ -203,7 +205,7 @@ ShaderPreProcessor::run(osg::Shader* shader)
                     << osg_LightSourceParameters::glslDefinition() << "\n"
                     << "uniform osg_LightSourceParameters " );
 
-            State_Utils::replaceAndInsertDeclaration(
+            replaceAndInsertDeclaration(
                 source, declPos,
                 Stringify() << "gl_FrontLightProduct[" << i << "]", 
                 Stringify() << "osg_FrontLightProduct" << i,
@@ -364,39 +366,20 @@ osg_LightSourceParameters::glslDefinition()
 #define LC "[UpdateLightingUniformHelper] "
 
 UpdateLightingUniformsHelper::UpdateLightingUniformsHelper( bool useUpdateTrav ) :
-_lightingEnabled( true ),
 _dirty          ( true ),
 _applied        ( false ),
 _useUpdateTrav  ( useUpdateTrav )
 {
     _maxLights = Registry::instance()->getCapabilities().getMaxLights();
-
-    _lightEnabled = new bool[ _maxLights ];
-    if ( _maxLights > 0 ){
-        _lightEnabled[0] = true;
-        //allocate light
-        _osgLightSourceParameters.push_back(osg_LightSourceParameters(0));
-    }
-    for(int i=1; i<_maxLights; ++i ){
-        _lightEnabled[i] = true;
-        _osgLightSourceParameters.push_back(osg_LightSourceParameters(i));
-    }
-
-    _lightingEnabledUniform = new osg::Uniform( osg::Uniform::BOOL, "oe_mode_GL_LIGHTING" );
-    _lightEnabledUniform    = new osg::Uniform( osg::Uniform::BOOL, "oe_mode_GL_LIGHT", _maxLights );
-
-    if ( !_useUpdateTrav )
+    for(int i=0; i<_maxLights; ++i )
     {
-        // setting the data variance the DYNAMIC makes it safe to change the uniform values
-        // during the CULL traversal.
-        _lightingEnabledUniform->setDataVariance( osg::Object::DYNAMIC );
-        _lightEnabledUniform->setDataVariance( osg::Object::DYNAMIC );
+        _osgLightSourceParameters.push_back(osg_LightSourceParameters(i));
     }
 }
 
 UpdateLightingUniformsHelper::~UpdateLightingUniformsHelper()
 {
-    delete [] _lightEnabled;
+    //nop
 }
 
 void
@@ -421,6 +404,7 @@ UpdateLightingUniformsHelper::cullTraverse( osg::Node* node, osg::NodeVisitor* n
             sg = sg->_parent;
         }
 
+#if 0
         // Update the overall lighting-enabled value:
         bool lightingEnabled =
             ( getModeValue(stateSetStack, GL_LIGHTING) & osg::StateAttribute::ON ) != 0;
@@ -433,6 +417,7 @@ UpdateLightingUniformsHelper::cullTraverse( osg::Node* node, osg::NodeVisitor* n
             else
                 _lightingEnabledUniform->set( _lightingEnabled );
         }
+#endif
 
         osg::View* view = cv->getCurrentCamera()->getView();
         if ( view )
@@ -445,6 +430,7 @@ UpdateLightingUniformsHelper::cullTraverse( osg::Node* node, osg::NodeVisitor* n
             }
         }
 
+#if 0
         else
         {
             // Update the list of enabled lights:
@@ -478,6 +464,7 @@ UpdateLightingUniformsHelper::cullTraverse( osg::Node* node, osg::NodeVisitor* n
                 }
             }	
         }
+#endif
 
         // apply if necessary:
         if ( !_applied && !_useUpdateTrav )
@@ -485,8 +472,8 @@ UpdateLightingUniformsHelper::cullTraverse( osg::Node* node, osg::NodeVisitor* n
             OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _stateSetMutex );
             if (!_applied)
             {
-                node->getOrCreateStateSet()->addUniform( _lightingEnabledUniform.get() );
-                node->getStateSet()->addUniform( _lightEnabledUniform.get() );
+                //node->getOrCreateStateSet()->addUniform( _lightingEnabledUniform.get() );
+                //node->getStateSet()->addUniform( _lightEnabledUniform.get() );
                 for( int i=0; i < _maxLights; ++i )
                 {
                     _osgLightSourceParameters[i].applyState(node->getStateSet());
@@ -502,18 +489,18 @@ UpdateLightingUniformsHelper::updateTraverse( osg::Node* node )
 {
     if ( _dirty )
     {
-        _lightingEnabledUniform->set( _lightingEnabled );
+        //_lightingEnabledUniform->set( _lightingEnabled );
 
-        for( int i=0; i < _maxLights; ++i )
-            _lightEnabledUniform->setElement( i, _lightEnabled[i] );
+        //for( int i=0; i < _maxLights; ++i )
+            //_lightEnabledUniform->setElement( i, _lightEnabled[i] );
 
         _dirty = false;
 
         if ( !_applied )
         {
             osg::StateSet* stateSet = node->getOrCreateStateSet();
-            stateSet->addUniform( _lightingEnabledUniform.get() );
-            stateSet->addUniform( _lightEnabledUniform.get() );
+            //stateSet->addUniform( _lightingEnabledUniform.get() );
+            //stateSet->addUniform( _lightEnabledUniform.get() );
             for( int i=0; i < _maxLights; ++i )
             {
                 _osgLightSourceParameters[i].applyState(stateSet);
@@ -736,3 +723,82 @@ ArrayUniform::detach()
         }
     }
 }
+
+//...................................................................
+
+RangeUniformCullCallback::RangeUniformCullCallback() :
+_dump( false )
+{
+    _uniform = osgEarth::Registry::instance()->shaderFactory()->createRangeUniform();
+
+    _stateSet = new osg::StateSet();
+    _stateSet->addUniform( _uniform.get() );
+}
+
+void
+RangeUniformCullCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
+{
+    osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
+
+    const osg::BoundingSphere& bs = node->getBound();
+
+    float range = nv->getDistanceToViewPoint( bs.center(), true );
+
+    // range = distance from the viewpoint to the outside of the bounding sphere.
+    _uniform->set( range - bs.radius() );
+
+    if ( _dump )
+    {
+        OE_NOTICE
+            << "Range = " << range 
+            << ", center = " << bs.center().x() << "," << bs.center().y()
+            << ", radius = " << bs.radius() << std::endl;
+    }
+    
+    cv->pushStateSet( _stateSet.get() );
+    traverse(node, nv);
+    cv->popStateSet();
+}
+
+//------------------------------------------------------------------------
+
+namespace
+{
+    
+}
+
+void
+DiscardAlphaFragments::install(osg::StateSet* ss, float minAlpha) const
+{
+    if ( ss && minAlpha < 1.0f )
+    {
+        VirtualProgram* vp = VirtualProgram::getOrCreate(ss);
+        if ( vp )
+        {
+            std::string code = Stringify()
+                << "#version " GLSL_VERSION_STR "\n"
+                << "void oe_discardalpha_frag(inout vec4 color) { \n"
+                << "    if ( color.a < " << std::setprecision(1) << minAlpha << ") discard;\n"
+                << "} \n";
+
+            vp->setFunction(
+                "oe_discardalpha_frag",
+                code,
+                ShaderComp::LOCATION_FRAGMENT_COLORING,
+                0L, 2.0f);
+        }
+    }
+}
+ 
+void
+DiscardAlphaFragments::uninstall(osg::StateSet* ss) const
+{
+    if ( ss )
+    {
+        VirtualProgram* vp = VirtualProgram::get(ss);
+        if ( vp )
+        {
+            vp->removeShader("oe_discardalpha_frag");
+        }
+    }
+}
diff --git a/src/osgEarth/SharedSARepo b/src/osgEarth/SharedSARepo
new file mode 100644
index 0000000..e9edfa6
--- /dev/null
+++ b/src/osgEarth/SharedSARepo
@@ -0,0 +1,120 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTH_SHARED_SA_REPO_H
+#define OSGEARTH_SHARED_SA_REPO_H 1
+
+#include <osgEarth/Common>
+#include <osgEarth/ThreadingUtils>
+#include <osg/observer_ptr>
+#include <list>
+
+namespace osgEarth
+{
+    /**
+     * A thread-safe object sharing container for osg::StateAttribute instances
+     * (or anything else implementing the compare() method). The VirtualProgram
+     * uses this to share identical Program objects.
+     *
+     * Note. This object holds observers, so there is no need to deal with
+     * releaseGLObjects et al. since it can never be the final owner.
+     */
+    template<typename T>
+    struct SharedSARepo
+    {
+    private:
+        typedef std::list< osg::observer_ptr<T> > SAUniqueSet;
+        SAUniqueSet      _set;
+        Threading::Mutex _mx;
+        bool             _enabled;
+
+    public:
+        SharedSARepo() : _enabled(true) { }
+
+        ~SharedSARepo() 
+        {
+            clear();
+        }
+
+        void setEnabled(bool value)
+        {
+            _enabled = value;
+            if ( !_enabled )
+            {
+                clear();
+            }
+        }
+
+        bool isEnabled() const
+        {
+            return _enabled;
+        }
+
+        bool share(osg::ref_ptr<T>& out)
+        {
+            if ( !_enabled )
+                return false;
+
+            _mx.lock();
+
+            bool found = false;
+            for (typename SAUniqueSet::iterator i = _set.begin(); !found && i != _set.end(); )
+            {
+                osg::ref_ptr<T> temp;
+                if ( i->lock(temp) )
+                {
+                    if ( temp->compare( *out.get() ) == 0 )
+                    {
+                        out = temp.get();
+                        //OE_DEBUG << LC << "Shared a program; repo size = " << _set.size() << std::endl;
+                        found = true;
+                    }
+                    else
+                    {
+                        ++i;
+                    }
+                }
+                else 
+                {
+                    // found an orphaned observer; prune it
+                    typename SAUniqueSet::iterator j = i++;
+                    _set.erase( j );
+                    //OE_DEBUG << LC << "Pruned a program; repo size = " << _set.size() << std::endl;
+                }
+            }
+
+            if ( !found )
+            {
+                _set.push_back(out.get());
+                //OE_DEBUG << LC << "Added a program; repo size = " << _set.size() << std::endl;
+            }
+
+            _mx.unlock();
+            return found;
+        }
+
+        void clear()
+        {
+            _mx.lock();
+            _set.clear();
+            _mx.unlock();
+        }
+    };
+}
+
+#endif // OSGEARTH_SHARED_SA_REPO_H
diff --git a/src/osgEarth/SparseTexture2DArray b/src/osgEarth/SparseTexture2DArray
index 66e27d7..cdcba2a 100644
--- a/src/osgEarth/SparseTexture2DArray
+++ b/src/osgEarth/SparseTexture2DArray
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/SparseTexture2DArray.cpp b/src/osgEarth/SparseTexture2DArray.cpp
index d6ff162..272951f 100644
--- a/src/osgEarth/SparseTexture2DArray.cpp
+++ b/src/osgEarth/SparseTexture2DArray.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/SpatialReference b/src/osgEarth/SpatialReference
index 14bef5f..25ff5d4 100644
--- a/src/osgEarth/SpatialReference
+++ b/src/osgEarth/SpatialReference
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -166,7 +166,22 @@ namespace osgEarth
 
     public: // properties
 
-        typedef std::pair<std::string,std::string> Key;
+        /** uniquely identifies an SRS. */
+        struct Key {
+            Key() { }
+            Key(const std::string& h, const std::string& v)
+                : horiz(h), horizLower(toLower(h)), vert(v), vertLower(toLower(v)) { }
+            std::string horiz, horizLower;
+            std::string vert,  vertLower;
+            bool operator < (const Key& rhs) const {
+                int h = horizLower.compare(rhs.horizLower);
+                if ( h < 0 ) return true;
+                if ( h > 0 ) return false;
+                int v = vertLower.compare(rhs.vertLower);
+                if ( v < 0 ) return true;
+                return false;
+            }
+        };
 
         /** True if this is a geographic SRS (lat/long/msl) */
         virtual bool isGeographic() const;
@@ -354,9 +369,9 @@ namespace osgEarth
         virtual void _init();
         virtual bool _isEquivalentTo( const SpatialReference* srs, bool considerVDatum =true ) const;
 
-        virtual bool preTransform(std::vector<osg::Vec3d>&) const { return true; }
+        virtual const SpatialReference* preTransform(std::vector<osg::Vec3d>&) const { return this; }
 
-        virtual bool postTransform(std::vector<osg::Vec3d>&) const { return true; }
+        virtual const SpatialReference* postTransform(std::vector<osg::Vec3d>&) const { return this; }
 
         bool transformXYPointArrays(
             double*  x,
diff --git a/src/osgEarth/SpatialReference.cpp b/src/osgEarth/SpatialReference.cpp
index 9501584..ab5e39c 100644
--- a/src/osgEarth/SpatialReference.cpp
+++ b/src/osgEarth/SpatialReference.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -47,12 +47,7 @@ namespace
         const char* val = OSRGetAttrValue( _handle, name.c_str(), child_num );
         if ( val )
         {
-            std::string t = val;
-            if ( lowercase )
-            {
-                std::transform( t.begin(), t.end(), t.begin(), ::tolower );
-            }
-            return t;
+            return lowercase ? toLower(val) : val;
         }
         return "";
     }    
@@ -191,11 +186,8 @@ SpatialReference::createFromWKT( const std::string& wkt, const std::string& name
 }
 
 SpatialReference*
-SpatialReference::create( const std::string& horiz_init, const std::string& vert_init )
+SpatialReference::create( const std::string& horiz, const std::string& vert )
 {
-    std::string horiz = toLower(horiz_init);
-    std::string vert  = toLower(vert_init);
-
     return create( Key(horiz, vert), true );
 }
 
@@ -219,11 +211,14 @@ SpatialReference::create( const Key& key, bool useCache )
     // now try to resolve the horizontal SRS:
     osg::ref_ptr<SpatialReference> srs;
 
-    const std::string& horiz = key.first;
-    const std::string& vert  = key.second;
+    //const std::string& horiz = key.first;
+    //const std::string& vert  = key.second;
 
     // shortcut for spherical-mercator:
-    if (horiz == "spherical-mercator" || horiz == "epsg:900913" || horiz == "epsg:3785" || horiz == "epsg:102113")
+    if (key.horizLower == "spherical-mercator" || 
+        key.horizLower == "epsg:900913"        || 
+        key.horizLower == "epsg:3785"          || 
+        key.horizLower == "epsg:102113")
     {
         // note the use of nadgrids=@null (see http://proj.maptools.org/faq.html)
         srs = createFromPROJ4(
@@ -233,9 +228,13 @@ SpatialReference::create( const Key& key, bool useCache )
 
     // ellipsoidal ("world") mercator:
     else 
-        if (horiz == "world-mercator" ||
-            horiz == "epsg:54004"  || horiz == "epsg:9804"   || horiz == "epsg:3832" ||
-            horiz == "epsg:102100" || horiz == "esri:102100" || horiz == "osgeo:41001" )
+        if (key.horizLower == "world-mercator" ||
+            key.horizLower == "epsg:54004"     ||
+            key.horizLower == "epsg:9804"      ||
+            key.horizLower == "epsg:3832"      ||
+            key.horizLower == "epsg:102100"    ||
+            key.horizLower == "esri:102100"    || 
+            key.horizLower == "osgeo:41001" )
 
     {
         srs = createFromPROJ4(
@@ -244,7 +243,9 @@ SpatialReference::create( const Key& key, bool useCache )
     }
 
     // common WGS84:
-    else if (horiz == "epsg:4326" || horiz == "wgs84")
+    else
+        if (key.horizLower == "epsg:4326" ||
+            key.horizLower == "wgs84")
     {
         srs = createFromPROJ4(
             "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs",
@@ -252,7 +253,7 @@ SpatialReference::create( const Key& key, bool useCache )
     }
 
     // WGS84 Plate Carre:
-    else if (horiz == "plate-carre")
+    else if (key.horizLower == "plate-carre")
     {
         srs = createFromPROJ4(
             "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs",
@@ -262,22 +263,24 @@ SpatialReference::create( const Key& key, bool useCache )
     }
 
     // custom srs for the unified cube
-    else if ( horiz == "unified-cube" )
+    else if (key.horizLower == "unified-cube" )
     {
         srs = createCube();
     }
 
-    else if ( horiz.find( "+" ) == 0 )
+    else if (key.horizLower.find( "+" ) == 0 )
     {
-        srs = createFromPROJ4( horiz, horiz );
+        srs = createFromPROJ4( key.horiz, key.horiz );
     }
-    else if ( horiz.find( "epsg:" ) == 0 || horiz.find( "osgeo:" ) == 0 )
+    else if (key.horizLower.find( "epsg:" )  == 0 ||
+             key.horizLower.find( "osgeo:" ) == 0 )
     {
-        srs = createFromPROJ4( std::string("+init=") + horiz, horiz );
+        srs = createFromPROJ4( std::string("+init=") + key.horiz, key.horiz );
     }
-    else if ( horiz.find( "projcs" ) == 0 || horiz.find( "geogcs" ) == 0 )
+    else if (key.horizLower.find( "projcs" ) == 0 || 
+             key.horizLower.find( "geogcs" ) == 0 )
     {
-        srs = createFromWKT( horiz, horiz );
+        srs = createFromWKT( key.horiz, key.horiz );
     }
 
     // bail out if no SRS exists by this point
@@ -287,12 +290,12 @@ SpatialReference::create( const Key& key, bool useCache )
     }
 
     // next, resolve the vertical SRS:
-    if ( !vert.empty() )
+    if ( !key.vert.empty() && !ciEquals(key.vert, "geodetic") )
     {
-        srs->_vdatum = VerticalDatum::get(vert);
+        srs->_vdatum = VerticalDatum::get( key.vert );
         if ( !srs->_vdatum.valid() )
         {
-            OE_WARN << LC << "Failed to locate vertical datum \"" << vert << "\"" << std::endl;
+            OE_WARN << LC << "Failed to locate vertical datum \"" << key.vert << "\"" << std::endl;
         }
     }
 
@@ -532,7 +535,7 @@ SpatialReference::getHorizInitString() const
 { 
     if ( !_initialized )
         const_cast<SpatialReference*>(this)->init();
-    return _key.first;
+    return _key.horiz;
 }
 
 const std::string&
@@ -540,7 +543,7 @@ SpatialReference::getVertInitString() const
 { 
     if ( !_initialized )
         const_cast<SpatialReference*>(this)->init();
-    return _key.second;
+    return _key.vert;
 }
 
 bool
@@ -600,8 +603,8 @@ SpatialReference::_isEquivalentTo( const SpatialReference* rhs, bool considerVDa
     if ( considerVDatum && (_vdatum.get() != rhs->_vdatum.get()) )
         return false;
 
-    if (_key.first == rhs->_key.first &&
-        (!considerVDatum || (_key.second == rhs->_key.second) ) )
+    if (_key.horizLower == rhs->_key.horizLower &&
+        (!considerVDatum || (_key.vertLower == rhs->_key.vertLower) ) )
     {
         return true;
     }
@@ -876,7 +879,7 @@ SpatialReference::populateCoordinateSystemNode( osg::CoordinateSystemNode* csn )
     else
     {
         csn->setFormat( _init_type );
-        csn->setCoordinateSystem( getKey().first );
+        csn->setCoordinateSystem( getKey().horiz );
     }
     
     csn->setEllipsoidModel( _ellipsoid.get() );
@@ -938,7 +941,8 @@ SpatialReference::createLocalToWorld(const osg::Vec3d& xyz, osg::Matrixd& out_lo
     }
     else if ( isECEF() )
     {
-        out_local2world = ECEF::createLocalToWorld(xyz);
+        //out_local2world = ECEF::createLocalToWorld(xyz);
+        _ellipsoid->computeLocalToWorldTransformFromXYZ(xyz.x(), xyz.y(), xyz.z(), out_local2world);
     }
     else
     {
@@ -952,7 +956,8 @@ SpatialReference::createLocalToWorld(const osg::Vec3d& xyz, osg::Matrixd& out_lo
         if ( !transform(geodetic, getGeodeticSRS()->getECEF(), ecef) )
             return false;
 
-        out_local2world = ECEF::createLocalToWorld(ecef);
+        //out_local2world = ECEF::createLocalToWorld(ecef);        
+        _ellipsoid->computeLocalToWorldTransformFromXYZ(ecef.x(), ecef.y(), ecef.z(), out_local2world);
     }
     return true;
 }
@@ -1004,36 +1009,38 @@ SpatialReference::transform(std::vector<osg::Vec3d>& points,
     bool success = false;
 
     // do the pre-transformation pass:
-    preTransform( points );
+    const SpatialReference* inputSRS = preTransform( points );
+    if ( !inputSRS )
+        return false;
 
     // Spherical Mercator is a special case transformation, because we want to bypass
     // any normal horizontal datum conversion. In other words we ignore the ellipsoid
     // of the other SRS and just do a straight spherical conversion.
-    if ( isGeographic() && outputSRS->isSphericalMercator() )
+    if ( inputSRS->isGeographic() && outputSRS->isSphericalMercator() )
     {        
-        transformZ( points, outputSRS, true );
+        inputSRS->transformZ( points, outputSRS, true );
         success = geographicToSphericalMercator( points );
         return success;
     }
 
-    else if ( isSphericalMercator() && outputSRS->isGeographic() )
+    else if ( inputSRS->isSphericalMercator() && outputSRS->isGeographic() )
     {     
         success = sphericalMercatorToGeographic( points );
-        transformZ( points, outputSRS, true );
+        inputSRS->transformZ( points, outputSRS, true );
         return success;
     }
 
-    else if ( isECEF() && !outputSRS->isECEF() )
+    else if ( inputSRS->isECEF() && !outputSRS->isECEF() )
     {
         const SpatialReference* outputGeoSRS = outputSRS->getGeodeticSRS();
         ECEFtoGeodetic(points, outputGeoSRS->getEllipsoid());
         return outputGeoSRS->transform(points, outputSRS);
     }
 
-    else if ( !isECEF() && outputSRS->isECEF() )
+    else if ( !inputSRS->isECEF() && outputSRS->isECEF() )
     {
         const SpatialReference* outputGeoSRS = outputSRS->getGeodeticSRS();
-        success = transform(points, outputGeoSRS);
+        success = inputSRS->transform(points, outputGeoSRS);
         geodeticToECEF(points, outputGeoSRS->getEllipsoid());
         return success;
     }
@@ -1041,9 +1048,9 @@ SpatialReference::transform(std::vector<osg::Vec3d>& points,
     // if the points are starting as geographic, do the Z's first to avoid an unneccesary
     // transformation in the case of differing vdatums.
     bool z_done = false;
-    if ( isGeographic() )
+    if ( inputSRS->isGeographic() )
     {
-        z_done = transformZ( points, outputSRS, true );
+        z_done = inputSRS->transformZ( points, outputSRS, true );
     }
 
     // move the xy data into straight arrays that OGR can use
@@ -1057,11 +1064,11 @@ SpatialReference::transform(std::vector<osg::Vec3d>& points,
         y[i] = points[i].y();
     }
 
-    success = transformXYPointArrays( x, y, count, outputSRS );
+    success = inputSRS->transformXYPointArrays( x, y, count, outputSRS );
 
     if ( success )
     {
-        if ( isProjected() && outputSRS->isGeographic() )
+        if ( inputSRS->isProjected() && outputSRS->isGeographic() )
         {
             // special case: when going from projected to geographic, clamp the 
             // points to the maximum geographic extent. Sometimes the conversion from
@@ -1089,7 +1096,7 @@ SpatialReference::transform(std::vector<osg::Vec3d>& points,
     // calculate the Zs if we haven't already done so
     if ( !z_done )
     {
-        z_done = transformZ( points, outputSRS, outputSRS->isGeographic() );
+        z_done = inputSRS->transformZ( points, outputSRS, outputSRS->isGeographic() );
     }   
 
     // run the user post-transform code
@@ -1242,10 +1249,18 @@ SpatialReference::transformFromWorld(const osg::Vec3d& world,
                                      osg::Vec3d&       output,
                                      double*           out_haeZ ) const
 {
-    if ( (isGeographic() && !isPlateCarre()) || isCube() ) //isGeographic() && !_is_plate_carre )
+    if ( (isGeographic() && !isPlateCarre()) || isCube() )
     {
         //return transformFromECEF(world, output, out_haeZ);
-        return getECEF()->transform(world, this, output);
+        bool ok = getECEF()->transform(world, this, output);
+        if ( ok && out_haeZ )
+        {
+            if ( _vdatum.valid() )
+                *out_haeZ = _vdatum->msl2hae(output.y(), output.x(), output.z());
+            else
+                *out_haeZ = output.z();
+        }
+        return ok;
     }
     else // isProjected || _is_plate_carre
     {
@@ -1547,17 +1562,20 @@ SpatialReference::_init()
     // Build a 'normalized' initialization key.
     if ( !_proj4.empty() )
     {
-        _key.first = _proj4;
+        _key.horiz = _proj4;
+        _key.horizLower = toLower(_key.horiz);
         _init_type = "PROJ4";
     }
     else if ( !_wkt.empty() )
     {
-        _key.first = _wkt;
+        _key.horiz = _wkt;
+        _key.horizLower = toLower(_key.horiz);
         _init_type = "WKT";
     }
     if ( _vdatum.valid() )
     {
-        _key.second = _vdatum->getInitString();
+        _key.vert = _vdatum->getInitString();
+        _key.vertLower = toLower(_key.vert);
     }
 
     _initialized = true;
diff --git a/src/osgEarth/StateSetCache b/src/osgEarth/StateSetCache
index 73e3ca7..a60bd1d 100644
--- a/src/osgEarth/StateSetCache
+++ b/src/osgEarth/StateSetCache
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -37,6 +37,10 @@ namespace osgEarth
          */
         StateSetCache();
 
+        /**
+         * Caps the size of the cache.
+         */
+        void setMaxSize(unsigned maxSize);
 
         /**
          * Check whether a StateSet is eligible for sharing.
@@ -48,11 +52,23 @@ namespace osgEarth
          */
         bool eligible( osg::StateAttribute* attr ) const;
 
+        /** 
+         * Scans a graph and combines equivalents state attributes into
+         * single shared attribute. Skips any attribute (or stateset)
+         * with dynamic data variance
+         */
+        void consolidateStateAttributes(osg::Node* node);
+
         /**
-         * Traverse the node and consolidate equivalent state sets, updating
-         * the cache along the way.
+         * Scans a graph and combines equivalent statesets into a single
+         * shared stateset. Skips any stateset with dynamic data variance.
          */
-        void optimize( osg::Node* node );
+        void consolidateStateSets(osg::Node* node);
+
+        /**
+         * Calls consolidateStateAttributes followed by consolidateStateSets.
+         */
+        void optimize(osg::Node* node);
 
         /**
          * Looks in the cache for a stateset matching the input. If found,
@@ -88,6 +104,8 @@ namespace osgEarth
          */
         void clear();
 
+        void dumpStats();
+
     protected: 
 
         virtual ~StateSetCache();
@@ -116,7 +134,14 @@ namespace osgEarth
 
         void prune();
         void pruneIfNecessary();
+        unsigned _maxSize;
         unsigned _pruneCount;
+
+        //stats
+        unsigned _attrShareAttempts;
+        unsigned _attrsIneligible;
+        unsigned _attrShareHits;
+        unsigned _attrShareMisses;
     };
 }
 
diff --git a/src/osgEarth/StateSetCache.cpp b/src/osgEarth/StateSetCache.cpp
index 3f6e44a..063c131 100644
--- a/src/osgEarth/StateSetCache.cpp
+++ b/src/osgEarth/StateSetCache.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -23,7 +23,11 @@
 
 #define LC "[StateSetCache] "
 
-#define PRUNE_ACCESS_COUNT 40
+#define DEFAULT_PRUNE_ACCESS_COUNT 40
+
+#if OSG_MIN_VERSION_REQUIRED(3,1,4)
+#   define STATESET_SHARING_SUPPORTED 1
+#endif
 
 using namespace osgEarth;
 
@@ -31,6 +35,62 @@ using namespace osgEarth;
 
 namespace
 {
+    bool isEligible(osg::StateAttribute* attr)
+    {
+        if ( !attr )
+            return false;
+
+        // DYNAMIC means the user intends to change it later. So it needs to
+        // stay independent.
+        if ( attr->getDataVariance() == osg::Object::DYNAMIC )
+            return false;
+
+        // cannot share BIB's. They don't clone well since they have underlying buffer objects
+        // that may be in use. It results in OpenGL invalid enumerant errors and errors such as
+        // "uniform block xxx has no binding"
+        if (dynamic_cast<osg::BufferIndexBinding*>(attr) != 0L)
+            return false;
+
+        return true;
+    }
+
+    bool isEligible(osg::StateSet* stateSet)
+    {
+#ifdef STATESET_SHARING_SUPPORTED
+        if ( !stateSet )
+            return false;
+
+        // DYNAMIC means the user intends to change it later. So it needs to
+        // stay independent.
+        if ( stateSet->getDataVariance() == osg::Object::DYNAMIC )
+            return false;
+
+        const osg::StateSet::AttributeList& attrs = stateSet->getAttributeList();
+        for( osg::StateSet::AttributeList::const_iterator i = attrs.begin(); i != attrs.end(); ++i )
+        {
+            osg::StateAttribute* a = i->second.first.get();
+            if ( !isEligible(a) )
+                return false;
+        }
+
+        const osg::StateSet::TextureAttributeList& texattrs = stateSet->getTextureAttributeList();
+        for( osg::StateSet::TextureAttributeList::const_iterator i = texattrs.begin(); i != texattrs.end(); ++i )
+        {
+            const osg::StateSet::AttributeList& texattrlist = *i;
+            for( osg::StateSet::AttributeList::const_iterator i = texattrlist.begin(); i != texattrlist.end(); ++i )
+            {
+                osg::StateAttribute* a = i->second.first.get();
+                if ( !isEligible(a) )
+                    return false;
+            }
+        }
+
+        return true;
+#else
+        return false;
+#endif
+    }
+
     /**
      * Visitor that calls StateSetCache::share on all attributes found
      * in a scene graph.
@@ -39,15 +99,25 @@ namespace
     {
         StateSetCache* _cache;
 
-        ShareStateAttributes(StateSetCache* cache) :
-            osg::NodeVisitor( osg::NodeVisitor::TRAVERSE_ALL_CHILDREN ),
-            _cache          ( cache ) { }
+        ShareStateAttributes(StateSetCache* cache)
+            : _cache(cache)
+        {
+            setTraversalMode( TRAVERSE_ALL_CHILDREN );
+            setNodeMaskOverride( ~0 );
+        }
 
         void apply(osg::Node& node)
         {
-            if ( node.getStateSet() && node.getStateSet()->getDataVariance() != osg::Object::DYNAMIC )
+            if (node.getStateSet())
             {
-                applyStateSet( node.getStateSet() );
+                // ref for thread-safety; another thread might replace this stateset
+                // during optimization while we're looking at it.
+                osg::ref_ptr<osg::StateSet> stateset = node.getStateSet();
+                if (stateset.valid() && 
+                    stateset->getDataVariance() != osg::Object::DYNAMIC )            
+                {
+                    applyStateSet( stateset.get() );
+                }
             }
             traverse(node);
         }
@@ -58,14 +128,22 @@ namespace
             for( unsigned i=0; i<numDrawables; ++i )
             {
                 osg::Drawable* d = geode.getDrawable(i);
-                if ( d && d->getStateSet() && d->getStateSet()->getDataVariance() != osg::Object::DYNAMIC )
+                if (d && d->getStateSet() )
                 {
-                    applyStateSet( d->getStateSet() );
+                    // ref for thread-safety; another thread might replace this stateset
+                    // during optimization while we're looking at it.
+                    osg::ref_ptr<osg::StateSet> stateset = d->getStateSet();
+                    if (stateset.valid() &&
+                        stateset->getDataVariance() != osg::Object::DYNAMIC)
+                    {
+                        applyStateSet( stateset.get() );
+                    }
                 }
             }
             apply((osg::Node&)geode);
         }
 
+        // assume: stateSet is safely referenced by caller
         void applyStateSet(osg::StateSet* stateSet)
         {
             osg::StateSet::AttributeList& attrs = stateSet->getAttributeList();
@@ -109,25 +187,32 @@ namespace
         unsigned       _shares;
         //std::vector<osg::StateSet*> _misses; // for debugging
 
-        ShareStateSets(StateSetCache* cache) :
-            osg::NodeVisitor( osg::NodeVisitor::TRAVERSE_ALL_CHILDREN ),
-            _cache    ( cache ),
-            _stateSets( 0 ),
-            _shares   ( 0 ) { }
+        ShareStateSets(StateSetCache* cache)
+            : _cache(cache),
+              _stateSets( 0 ),
+              _shares   ( 0 )
+        {
+            setTraversalMode( TRAVERSE_ALL_CHILDREN );
+            setNodeMaskOverride( ~0 );
+        }
 
         void apply(osg::Node& node)
         {
-            if ( node.getStateSet() && node.getStateSet()->getDataVariance() != osg::Object::DYNAMIC )
+            if (node.getStateSet())
             {
-                _stateSets++;
-                osg::ref_ptr<osg::StateSet> in, shared;
-                in = node.getStateSet();
-                if ( in.valid() && _cache->share(in, shared) )
+                osg::ref_ptr<osg::StateSet> stateset = node.getStateSet();
+
+                if ( isEligible(stateset.get()) )
                 {
-                    node.setStateSet( shared.get() );
-                    _shares++;
+                    _stateSets++;
+                    osg::ref_ptr<osg::StateSet> shared;
+                    if ( _cache->share(stateset, shared) )
+                    {
+                        node.setStateSet( shared.get() );
+                        _shares++;
+                    }
+                    //else _misses.push_back(in.get());
                 }
-                //else _misses.push_back(in.get());
             }
             traverse(node);
         }
@@ -138,17 +223,20 @@ namespace
             for( unsigned i=0; i<numDrawables; ++i )
             {
                 osg::Drawable* d = geode.getDrawable(i);
-                if ( d && d->getStateSet() && d->getStateSet()->getDataVariance() != osg::Object::DYNAMIC )
+                if ( d && d->getStateSet())
                 {
-                    _stateSets++;
-                    osg::ref_ptr<osg::StateSet> in, shared;
-                    in = d->getStateSet();
-                    if ( in.valid() && _cache->share(in, shared) )
+                    osg::ref_ptr<osg::StateSet> stateset = d->getStateSet();
+                    if (stateset.valid() && isEligible(stateset.get()))
                     {
-                        d->setStateSet( shared.get() );
-                        _shares++;
+                        _stateSets++;
+                        osg::ref_ptr<osg::StateSet> shared;
+                        if ( _cache->share(stateset, shared) )
+                        {
+                            d->setStateSet( shared.get() );
+                            _shares++;
+                        }
+                        //else _misses.push_back(in.get());
                     }
-                    //else _misses.push_back(in.get());
                 }
             }
             apply((osg::Node&)geode);
@@ -159,7 +247,12 @@ namespace
 //------------------------------------------------------------------------
 
 StateSetCache::StateSetCache() :
-_pruneCount( 0 )
+_pruneCount       ( 0 ),
+_maxSize          ( DEFAULT_PRUNE_ACCESS_COUNT ),
+_attrShareAttempts( 0 ),
+_attrsIneligible  ( 0 ),
+_attrShareHits    ( 0 ),
+_attrShareMisses  ( 0 )
 {
     //nop
 }
@@ -171,71 +264,59 @@ StateSetCache::~StateSetCache()
 }
 
 void
-StateSetCache::optimize(osg::Node* node)
+StateSetCache::setMaxSize(unsigned value)
 {
-    if ( node )
+    _maxSize = value;
     {
-        // replace all equivalent attributes with a single instance
-        ShareStateAttributes v1( this );
-        node->accept( v1 );
-
-        
-#if OSG_MIN_VERSION_REQUIRED(3,1,4)
-        // replace all equivalent static statesets with a single instance
-        // only supported in OSG 3.1.4+ because of the Uniform mutex 
-        // protection.
-        ShareStateSets v2( this );
-        node->accept( v2 );
-#endif
+        Threading::ScopedMutexLock lock( _mutex );
+        pruneIfNecessary();
     }
 }
 
+void
+StateSetCache::consolidateStateAttributes(osg::Node* node)
+{
+    if ( !node )
+        return;
 
-bool
-StateSetCache::eligible(osg::StateSet* stateSet) const
+    ShareStateAttributes v( this );
+    node->accept( v );
+}
+
+void
+StateSetCache::consolidateStateSets(osg::Node* node)
 {
-#if OSG_MIN_VERSION_REQUIRED(3,1,4)
-    if ( !stateSet )
-        return false;
+    if ( !node )
+        return;
 
-    // DYNAMIC means the user intends to change it later. So it needs to
-    // stay independent.
-    if ( stateSet->getDataVariance() == osg::Object::DYNAMIC )
-        return false;
+#ifdef STATESET_SHARING_SUPPORTED
+    ShareStateSets v( this );
+    node->accept( v );
+#endif
+}
 
-    const osg::StateSet::AttributeList& attrs = stateSet->getAttributeList();
-    for( osg::StateSet::AttributeList::const_iterator i = attrs.begin(); i != attrs.end(); ++i )
+void
+StateSetCache::optimize(osg::Node* node)
+{
+    if ( node )
     {
-        osg::StateAttribute* a = i->second.first.get();
-        if ( !eligible(a) )
-            return false;
+        consolidateStateAttributes( node );
+        consolidateStateSets( node );
     }
-
-    return true;
-#else
-    return false;
-#endif
 }
 
 
 bool
-StateSetCache::eligible(osg::StateAttribute* attr) const
+StateSetCache::eligible(osg::StateSet* stateSet) const
 {
-    if ( !attr )
-        return false;
-
-    // DYNAMIC means the user intends to change it later. So it needs to
-    // stay independent.
-    if ( attr->getDataVariance() == osg::Object::DYNAMIC )
-        return false;
+    return isEligible(stateSet);
+}
 
-    // cannot share BIB's. They don't clone well since they have underlying buffer objects
-    // that may be in use. It results in OpenGL invalid enumerant errors and errors such as
-    // "uniform block xxx has no binding"
-    if (dynamic_cast<osg::BufferIndexBinding*>(attr) != 0L)
-        return false;
 
-    return true;
+bool
+StateSetCache::eligible(osg::StateAttribute* attr) const
+{
+    return isEligible(attr);
 }
 
 
@@ -245,7 +326,7 @@ StateSetCache::share(osg::ref_ptr<osg::StateSet>& input,
                      bool                         checkEligible)
 {
     bool shared     = false;
-    bool shareattrs = true;
+    //bool shareattrs = true;
 
     if ( !checkEligible || eligible(input.get()) )
     {
@@ -253,14 +334,14 @@ StateSetCache::share(osg::ref_ptr<osg::StateSet>& input,
 
         pruneIfNecessary();
 
-        shareattrs = false;
+        //shareattrs = false;
 
         std::pair<StateSetSet::iterator,bool> result = _stateSetCache.insert( input );
         if ( result.second )
         {
             // first use
             output = input.get();
-            shareattrs = true;
+            //shareattrs = true;
             shared = false;
         }
         else
@@ -276,11 +357,12 @@ StateSetCache::share(osg::ref_ptr<osg::StateSet>& input,
         shared = false;
     }
 
-    if ( shareattrs )
-    {
-        ShareStateAttributes sa(this);
-        sa.applyStateSet( input.get() );
-    }
+    // OBE.
+    //if ( shareattrs )
+    //{
+    //    ShareStateAttributes sa(this);
+    //    sa.applyStateSet( input.get() );
+    //}
 
     return shared;
 }
@@ -292,6 +374,8 @@ StateSetCache::share(osg::ref_ptr<osg::StateAttribute>& input,
                      osg::ref_ptr<osg::StateAttribute>& output,
                      bool                               checkEligible)
 {
+    _attrShareAttempts++;
+
     if ( !checkEligible || eligible(input.get()) )
     {
         Threading::ScopedMutexLock lock( _mutex );
@@ -303,17 +387,20 @@ StateSetCache::share(osg::ref_ptr<osg::StateAttribute>& input,
         {
             // first use
             output = input.get();
+            _attrShareMisses++;
             return false;
         }
         else
         {
             // found a share!
             output = result.first->get();
+            _attrShareHits++;
             return true;
         }
     }
     else
     {
+        _attrsIneligible++;
         output = input.get();
         return false;
     }
@@ -323,7 +410,7 @@ void
 StateSetCache::pruneIfNecessary()
 {
     // assume an exclusve mutex is taken
-    if ( _pruneCount++ == PRUNE_ACCESS_COUNT )
+    if ( _pruneCount++ >= _maxSize )
     {
         prune();
         _pruneCount = 0;
@@ -339,9 +426,10 @@ StateSetCache::prune()
 
     for( StateSetSet::iterator i = _stateSetCache.begin(); i != _stateSetCache.end(); )
     {
-        if ( i->get()->referenceCount() == 1 )
+        if ( i->get()->referenceCount() <= 1 )
         {
             // do not call releaseGLObjects since the attrs themselves might still be shared
+            // TODO: review this.
             _stateSetCache.erase( i++ );
             ss_count++;
         }
@@ -353,7 +441,7 @@ StateSetCache::prune()
 
     for( StateAttributeSet::iterator i = _stateAttributeCache.begin(); i != _stateAttributeCache.end(); )
     {
-        if ( i->get()->referenceCount() == 1 )
+        if ( i->get()->referenceCount() <= 1 )
         {
             i->get()->releaseGLObjects( 0L );
             _stateAttributeCache.erase( i++ );
@@ -376,3 +464,16 @@ StateSetCache::clear()
     _stateAttributeCache.clear();
     _stateSetCache.clear();
 }
+
+
+void
+StateSetCache::dumpStats()
+{
+    Threading::ScopedMutexLock lock( _mutex );
+
+    OE_NOTICE << LC << "StateSetCache Dump:" << std::endl
+        << "    attr attempts     = " << _attrShareAttempts << std::endl
+        << "    ineligibles attrs = " << _attrsIneligible << std::endl
+        << "    attr share hits   = " << _attrShareHits << std::endl
+        << "    attr share misses = " << _attrShareMisses << std::endl;
+}
diff --git a/src/osgEarth/StateSetLOD b/src/osgEarth/StateSetLOD
new file mode 100644
index 0000000..b14b4ff
--- /dev/null
+++ b/src/osgEarth/StateSetLOD
@@ -0,0 +1,92 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTH_STATESET_LOD
+#define OSGEARTH_STATESET_LOD
+
+#include <osgEarth/Common>
+#include <osg/Group>
+#include <osg/StateSet>
+#include <vector>
+
+namespace osgEarth
+{
+    /**
+     * Apply statesets based on value ranges. 
+     *
+     * By default, this class works like osg::LOD and uses the camera's 
+     * distance to the bounding center as the value used to select active
+     * statesets. You can change the metric by calling setGetValueFunction().
+     */
+    class OSGEARTH_EXPORT StateSetLOD : public osg::Group
+    {
+    public:
+        StateSetLOD() { }
+
+        /** Adds a stateset to be applied within a value range. */
+        void addStateSet(osg::StateSet* stateset, float min, float max);
+        
+        /** Number of LOD ranges */
+        unsigned getNumRanges() const { return (unsigned)_ranges.size(); }
+
+        /** Set the min and max value for a particular range */
+        void setRange(unsigned index, float min, float max);
+
+        /** Gets the minimum value for a range */
+        float getMinRange(unsigned index) const { return _ranges[index].first; }
+
+        /** Gets the maximum value for a range */
+        float getMaxRange(unsigned index) const { return _ranges[index].second; }
+
+        /** Gets the state set at a range */
+        osg::StateSet* getStateSet(unsigned index) const { return _stateSets[index].get(); }
+
+    public:
+        class GetValue : public osg::Referenced 
+        {
+        public:
+            virtual float operator()(osg::NodeVisitor& nv) = 0;
+        };
+
+        /**
+         * Sets a custom value generator. By default, the value will be the 
+         * distance from the camera to the subgraph's bounding center. But you
+         * can get a custom metric by calling this method.
+         */
+        void setGetValueFunction(GetValue* c) { _getValue = c; }
+
+    public: // osg::Group
+
+        virtual void traverse(osg::NodeVisitor& nv);
+
+    protected:
+        virtual ~StateSetLOD() { }
+        
+        typedef std::pair<float, float> MinMaxPair;
+        typedef std::vector<MinMaxPair> RangeList;
+        typedef std::vector<osg::ref_ptr<osg::StateSet> > StateSetList;
+
+        RangeList    _ranges;
+        StateSetList _stateSets;
+        osg::ref_ptr<GetValue> _getValue;
+    };
+
+
+} // namespace osgEarth
+
+#endif //OSGEARTH_STATESET_LOD
diff --git a/src/osgEarth/StateSetLOD.cpp b/src/osgEarth/StateSetLOD.cpp
new file mode 100644
index 0000000..03cd95d
--- /dev/null
+++ b/src/osgEarth/StateSetLOD.cpp
@@ -0,0 +1,86 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include <osgEarth/StateSetLOD>
+#include <osgEarth/CullingUtils>
+
+using namespace osgEarth;
+
+#undef  LC
+#define LC "[StateSetLOD] "
+
+void
+StateSetLOD::addStateSet(osg::StateSet* stateSet, float min, float max)
+{
+    if ( stateSet )
+    {
+        _ranges.push_back(std::make_pair(min, max));
+        _stateSets.push_back(stateSet);
+    }
+}
+
+void
+StateSetLOD::setRange(unsigned index, float min, float max)
+{
+    if ( index < (unsigned)_ranges.size() )
+    {
+        _ranges[index].first = min;
+        _ranges[index].second = max;
+    }
+}
+
+void
+StateSetLOD::traverse(osg::NodeVisitor& nv)
+{
+    if (nv.getVisitorType() == nv.CULL_VISITOR)
+    {
+        osgUtil::CullVisitor* cv = osgEarth::Culling::asCullVisitor(nv);
+        
+        float value;
+        if ( !_getValue.valid() )
+        {
+            osg::BoundingSphere::vec_type center = getBound().center();
+            value = nv.getDistanceToViewPoint( center, true );
+        }
+        else
+        {
+            value = (*_getValue.get())(nv);
+        }
+
+        unsigned count = 0;
+        for(unsigned i=0; i<(unsigned)_ranges.size(); ++i)
+        {
+            if (_ranges[i].first <= value && value < _ranges[i].second)
+            {
+                cv->pushStateSet(_stateSets[i].get());
+                ++count;
+            }
+        }
+
+        osg::Group::traverse(nv);
+
+        for(unsigned i=0; i<count; ++i)
+        {
+            cv->popStateSet();
+        }
+    }
+    else
+    {
+        osg::Group::traverse(nv);
+    }
+}
diff --git a/src/osgEarth/StringUtils b/src/osgEarth/StringUtils
index 4fb54b4..a1bc9fb 100644
--- a/src/osgEarth/StringUtils
+++ b/src/osgEarth/StringUtils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -111,6 +111,16 @@ namespace osgEarth
 
     /** Generates a hashed integer for a string (poor man's MD5) */
     extern OSGEARTH_EXPORT unsigned hashString( const std::string& input );
+
+    /**
+    * Gets the total number of seconds formatted as H:M:S
+    */
+    extern OSGEARTH_EXPORT std::string prettyPrintTime( double seconds );
+
+    /**
+    * Gets a pretty printed version of the given size in MB.
+    */
+    extern OSGEARTH_EXPORT std::string prettyPrintSize( double mb );
     
     //------------------------------------------------------------------------
     // conversion templates
@@ -125,14 +135,38 @@ namespace osgEarth
         return temp;
     }
 
+    // template specialization for integers (to handle hex)
+#define AS_INT_DEC_OR_HEX(TYPE) \
+    template<> inline TYPE \
+    as< TYPE >(const std::string& str, const TYPE & dv) { \
+        TYPE temp = dv; \
+        std::istringstream strin( trim(str) ); \
+        if ( !strin.eof() ) { \
+            if ( str.length() >= 2 && str.at(0) == '0' && str.at(1) == 'x' ) { \
+                strin.seekg( 2 ); \
+                strin >> std::hex >> temp; \
+            } \
+            else { \
+                strin >> temp; \
+            } \
+        } \
+        return temp; \
+    }
+
+    AS_INT_DEC_OR_HEX(int)
+    AS_INT_DEC_OR_HEX(unsigned)
+    AS_INT_DEC_OR_HEX(short)
+    AS_INT_DEC_OR_HEX(unsigned short)
+    AS_INT_DEC_OR_HEX(long)
+    AS_INT_DEC_OR_HEX(unsigned long)
+
     // template specialization for a bool
     template<> inline bool
     as<bool>( const std::string& str, const bool& default_value )
     {
-        std::string temp = str;
-        std::transform( temp.begin(), temp.end(), temp.begin(), ::tolower );
+        std::string temp = toLower(str);
         return
-            temp == "true" || temp == "yes" || temp == "on" ? true :
+            temp == "true"  || temp == "yes" || temp == "on" ? true :
             temp == "false" || temp == "no" || temp == "off" ? false :
             default_value;
     }
diff --git a/src/osgEarth/StringUtils.cpp b/src/osgEarth/StringUtils.cpp
index a7584e6..e9d9b87 100644
--- a/src/osgEarth/StringUtils.cpp
+++ b/src/osgEarth/StringUtils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -19,7 +19,6 @@
 
 #include <osgEarth/StringUtils>
 #include <osgDB/FileNameUtils>
-//#include <ctype.h>
 #include <cctype>
 
 using namespace osgEarth;
@@ -79,6 +78,8 @@ StringTokenizer::tokenize( const std::string& input, StringVector& output ) cons
 
     std::stringstream buf;
     bool quoted = false;
+    char lastQuoteChar = '\0';
+
     for( std::string::const_iterator i = input.begin(); i != input.end(); ++i )
     {
         char c = *i;    
@@ -87,9 +88,10 @@ StringTokenizer::tokenize( const std::string& input, StringVector& output ) cons
 
         if ( quoted )
         {
-            if ( q != _quotes.end() )
+            if( q != _quotes.end() && lastQuoteChar == c)
             {
                 quoted = false;
+                lastQuoteChar = '\0';
                 if ( q->second )
                     buf << c;
             }
@@ -103,6 +105,7 @@ StringTokenizer::tokenize( const std::string& input, StringVector& output ) cons
             if ( q != _quotes.end() )
             {
                 quoted = true;
+                lastQuoteChar = c;
                 if ( q->second )
                     buf << c;
             }
@@ -211,8 +214,7 @@ osgEarth::hashString( const std::string& input )
 osg::Vec4f
 osgEarth::htmlColorToVec4f( const std::string& html )
 {
-    std::string t = html;
-    std::transform( t.begin(), t.end(), t.begin(), ::tolower );
+    std::string t = osgEarth::toLower(html);
     osg::Vec4ub c(0,0,0,255);
     if ( t.length() >= 7 ) {
         c.r() |= t[1]<='9' ? (t[1]-'0')<<4 : (10+(t[1]-'a'))<<4;
@@ -388,6 +390,41 @@ osgEarth::toLower( const std::string& input )
     return output;
 }
 
+std::string
+osgEarth::prettyPrintTime( double seconds )
+{
+    int hours = (int)floor(seconds / (3600.0) );
+    seconds -= hours * 3600.0;
+
+    int minutes = (int)floor(seconds/60.0);
+    seconds -= minutes * 60.0;
+
+    std::stringstream buf;
+    buf << hours << ":" << minutes << ":" << seconds;
+    return buf.str();
+}
+
+std::string
+osgEarth::prettyPrintSize( double mb )
+{
+    std::stringstream buf;
+    // Convert to terabytes
+    if ( mb > 1024 * 1024 )
+    {
+        buf << (mb / (1024.0*1024.0)) << " TB";
+    }
+    else if (mb > 1024)
+    {
+        buf << (mb / 1024.0) << " GB";
+    }
+    else 
+    {
+        buf << mb << " MB";
+    }
+    return buf.str();
+}
+
+
 namespace
 {
     template<typename charT>
diff --git a/src/osgEarth/TaskService b/src/osgEarth/TaskService
index 76511fd..e42d55b 100644
--- a/src/osgEarth/TaskService
+++ b/src/osgEarth/TaskService
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -24,6 +24,7 @@
 #include <osgEarth/ThreadingUtils>
 #include <osg/Referenced>
 #include <osg/Timer>
+#include <OpenThreads/ReentrantMutex>
 #include <queue>
 #include <list>
 #include <string>
@@ -91,6 +92,16 @@ namespace osgEarth
         Threading::Event* _completedEvent;
     };
 
+    /**
+     * Special marker class that is used to signify the end of the work.  TaskRequestThreads should shut down when they receive a PoisonPill
+     */
+    class OSGEARTH_EXPORT PoisonPill : public TaskRequest
+    {        
+        virtual void operator()( ProgressCallback* progress )
+        {
+        }
+    };
+
     typedef std::list< osg::ref_ptr<TaskRequest> > TaskRequestList;
 
     typedef std::vector< osg::ref_ptr<TaskRequest> > TaskRequestVector;
@@ -125,24 +136,31 @@ namespace osgEarth
     class TaskRequestQueue : public osg::Referenced
     {
     public:
-        TaskRequestQueue();
+        TaskRequestQueue(unsigned int maxSize=0);
 
         void add( TaskRequest* request );
         TaskRequest* get();
         void clear();
+        void cancel();
 
         void setDone();
 
+        unsigned int getMaxSize() const { return _maxSize;}
+
         void setStamp( int value ) { _stamp = value; }
         int getStamp() const { return _stamp; }
 
         unsigned int getNumRequests() const;
 
+
     private:
         TaskRequestPriorityMap _requests;
         OpenThreads::Mutex _mutex;
+        OpenThreads::Mutex _addMutex;
         OpenThreads::Condition _cond;
+        OpenThreads::Condition _addCond;
         volatile bool _done;
+        unsigned int _maxSize;
 
         int _stamp;
     };
@@ -167,7 +185,7 @@ namespace osgEarth
     class OSGEARTH_EXPORT TaskService : public osg::Referenced
     {
     public:
-        TaskService( const std::string& name ="", int numThreads =4 );
+        TaskService( const std::string& name ="", int numThreads =4, unsigned int maxSize=0 );
 
         void add( TaskRequest* request );
 
@@ -185,6 +203,12 @@ namespace osgEarth
          */
         unsigned int getNumRequests() const;
 
+        void waitforThreadsToComplete();
+
+        bool areThreadsRunning();
+
+        void cancelAll();
+
     private:
         void adjustThreadCount();
         void removeFinishedThreads();
diff --git a/src/osgEarth/TaskService.cpp b/src/osgEarth/TaskService.cpp
index e05d6ea..b0024fb 100644
--- a/src/osgEarth/TaskService.cpp
+++ b/src/osgEarth/TaskService.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -65,9 +65,10 @@ TaskRequest::wasCanceled() const
 
 //------------------------------------------------------------------------
 
-TaskRequestQueue::TaskRequestQueue() :
+TaskRequestQueue::TaskRequestQueue(unsigned int maxSize) :
 osg::Referenced( true ),
-_done( false )
+_done( false ),
+_maxSize( maxSize )
 {
 }
 
@@ -78,6 +79,16 @@ TaskRequestQueue::clear()
     _requests.clear();
 }
 
+void
+TaskRequestQueue::cancel()
+{
+    ScopedLock<Mutex> lock(_mutex);
+    for (TaskRequestPriorityMap::iterator it = _requests.begin(); it != _requests.end(); ++it)
+        (*it).second->cancel();
+
+    _requests.clear();
+}
+
 unsigned int
 TaskRequestQueue::getNumRequests() const
 {
@@ -94,13 +105,29 @@ TaskRequestQueue::add( TaskRequest* request )
     if ( !request->getProgressCallback() )
         request->setProgressCallback( new ProgressCallback() );
 
-    ScopedLock<Mutex> lock(_mutex);
+    // Lock on the add mutex so no one else can add.
+    ScopedLock<Mutex> lock( _addMutex );
 
+    if (_maxSize > 0)
+    {
+        if (_requests.size() == _maxSize)    
+        {        
+            //OE_NOTICE << "Waiting on add...." << std::endl;
+            _addCond.wait( &_addMutex );        
+            //OE_NOTICE << "I have awakend!" << std::endl;
+        }
+    }
+
+
+    // Now lock on the main mutex to protect the queue
+    ScopedLock<Mutex> lock2( _mutex );
     // insert by priority.
     _requests.insert( std::pair<float,TaskRequest*>(request->getPriority(), request) );
 
+    //OE_NOTICE << "There are now " << _requests.size() << " tasks" << std::endl;
+
     // since there is data in the queue, wake up one waiting task thread.
-    _cond.signal();
+    _cond.signal(); 
 }
 
 TaskRequest* 
@@ -109,9 +136,8 @@ TaskRequestQueue::get()
     ScopedLock<Mutex> lock(_mutex);
 
     while ( !_done && _requests.empty() )
-    {
-        // releases the mutex and waits on the condition.
-        _cond.wait( &_mutex );
+    {                
+        _cond.wait( &_mutex );        
     }
 
     if ( _done )
@@ -124,8 +150,9 @@ TaskRequestQueue::get()
 
     // I'm done, someone else take a turn:
     // (technically this shouldn't be necessary since add() bumps the semaphore once
-    // for each request in the queue)
-    _cond.signal();
+    // for each request in the queue)    
+    _cond.signal();    
+    _addCond.signal();
 
     return next.release();
 }
@@ -167,6 +194,16 @@ TaskThread::run()
 
         if (_request.valid())
         { 
+            PoisonPill* poison = dynamic_cast< PoisonPill* > ( _request.get());
+            if ( poison )
+            {
+                OE_DEBUG << this->getThreadId() << " received poison pill.  Shutting down" << std::endl;
+                // Add the poison pill back to the queue to kill any other threads.  If I'm going down, you're all going down with me!
+                _queue->add( poison );
+                break;
+            }
+            
+
             // discard a completed or canceled request:
             if ( _request->getState() != TaskRequest::STATE_PENDING )
             {
@@ -197,6 +234,7 @@ TaskThread::run()
             // Release the request
             _request = 0;
         }
+        
     }
 }
 
@@ -222,13 +260,13 @@ TaskThread::cancel()
 
 //------------------------------------------------------------------------
 
-TaskService::TaskService( const std::string& name, int numThreads ):
+TaskService::TaskService( const std::string& name, int numThreads, unsigned int maxSize ):
 osg::Referenced( true ),
 _lastRemoveFinishedThreadsStamp(0),
 _name(name),
 _numThreads( 0 )
 {
-    _queue = new TaskRequestQueue();
+    _queue = new TaskRequestQueue( maxSize );
     setNumThreads( numThreads );
 }
 
@@ -245,6 +283,27 @@ TaskService::add( TaskRequest* request )
     _queue->add( request );
 }
 
+void TaskService::waitforThreadsToComplete()
+{        
+    for( TaskThreads::iterator i = _threads.begin(); i != _threads.end(); i++ )
+    {
+        (*i)->join();
+    }    
+}
+
+bool TaskService::areThreadsRunning()
+{
+    for( TaskThreads::iterator i = _threads.begin(); i != _threads.end(); i++ )
+    {                
+        if ((*i)->isRunning())
+        {
+            return true;
+        }
+    }    
+    return false;
+}
+
+
 TaskService::~TaskService()
 {
     _queue->setDone();
@@ -362,6 +421,18 @@ TaskService::removeFinishedThreads()
     }
 }
 
+void
+TaskService::cancelAll()
+{
+    if (_numThreads > 0)
+    {
+        _numThreads = 0;
+        adjustThreadCount();
+
+        OE_INFO << LC << "Cancelled all threads in TaskService [" << _name << "]" << std::endl;
+    }
+}
+
 //------------------------------------------------------------------------
 
 TaskServiceManager::TaskServiceManager( int numThreads ) :
diff --git a/src/osgEarth/Terrain b/src/osgEarth/Terrain
index 8989645..fca0b3b 100644
--- a/src/osgEarth/Terrain
+++ b/src/osgEarth/Terrain
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -24,6 +24,7 @@
 #include <osgEarth/ThreadingUtils>
 #include <osgEarth/TerrainOptions>
 #include <osg/OperationThread>
+#include <osg/View>
 
 namespace osgEarth
 {
@@ -79,14 +80,45 @@ namespace osgEarth
         virtual void onTileAdded(
             const TileKey&          key, 
             osg::Node*              tile, 
-            TerrainCallbackContext& context) =0;
+            TerrainCallbackContext& context) { }
 
         /** dtor */
         virtual ~TerrainCallback() { }
     };
 
 
-    class /*interface-only*/ TerrainHeightProvider
+    /**
+     * Convenience adapter for hooking a callback into a class. The
+     * class must be derived from osg::Referenced. When the class
+     * destructs, the callback will automatically remove itself from
+     * the Terrain object.
+     */
+    template<typename T>
+    class TerrainCallbackAdapter : public TerrainCallback
+    {
+    public:
+        TerrainCallbackAdapter(T* t) : _t(t) { }
+        void onTileAdded(const TileKey&          key, 
+                         osg::Node*              tile, 
+                         TerrainCallbackContext& context)
+        {
+            osg::ref_ptr<T> t;
+            if (_t.lock(t))
+                t->onTileAdded(key, tile, context);
+            else
+                context.remove();
+        }
+    protected:
+        virtual ~TerrainCallbackAdapter() { }
+        osg::observer_ptr<T> _t;
+    };
+
+
+    /**
+     * Interface for an object that can resolve the terrain elevation
+     * at a given map location.
+     */
+    class /*interface-only*/ TerrainResolver
     {
     public:
         virtual bool getHeight(
@@ -96,10 +128,21 @@ namespace osgEarth
             double*                 out_heightAboveMSL,
             double*                 out_heightAboveEllipsoid =0L) const =0;
 
+        virtual bool getHeight(
+            osg::Node*              patch,
+            const SpatialReference* srs,
+            double                  x,
+            double                  y,
+            double*                 out_heightAboveMSL,
+            double*                 out_heightAboveEllipsoid =0L) const =0;
+
         /** dtor */
-        virtual ~TerrainHeightProvider() { }
+        virtual ~TerrainResolver() { }
     };
 
+    // backwards compatibility
+    typedef TerrainResolver TerrainHeightProvider;
+
 
     /**
      * Services for interacting with the live terrain graph. This differs from
@@ -109,7 +152,7 @@ namespace osgEarth
      * All returned map coordinate values are in the units conveyed in the
      * spatial reference at getSRS().
      */
-    class OSGEARTH_EXPORT Terrain : public osg::Referenced, public TerrainHeightProvider
+    class OSGEARTH_EXPORT Terrain : public osg::Referenced, public TerrainResolver
     {
     public:
         /**
@@ -129,7 +172,7 @@ namespace osgEarth
         bool isGeocentric() const { return _geocentric; }
 
 
-    public: // Intersection Utilities
+    public: // TerrainResolver interface
 
         /**
          * Intersects the terrain at the location x, y and returns the height data.
@@ -163,6 +206,8 @@ namespace osgEarth
             double*                 out_heightAboveMSL,
             double*                 out_heightAboveEllipsoid =0L) const;
 
+    public:
+
         /**
          * Returns the world coordinates under the mouse.
          * @param view
@@ -216,6 +261,10 @@ namespace osgEarth
         // fires the onTileAdded callback (internal)
         void fireTileAdded( const TileKey& key, osg::Node* tile );
 
+        // queues the onTileRemoved callback (internal)
+        void notifyTilesRemoved(const std::vector<TileKey>& keys);
+        void fireTilesRemoved(const std::vector<TileKey>& keys);
+
         /** dtor */
         virtual ~Terrain() { }
 
@@ -228,6 +277,8 @@ namespace osgEarth
 
         CallbackList                 _callbacks;
         Threading::ReadWriteMutex    _callbacksMutex;
+        OpenThreads::Atomic          _callbacksSize; // separate size tracker for MT size check w/o a lock
+
         osg::ref_ptr<const Profile>  _profile;
         osg::observer_ptr<osg::Node> _graph;
         bool                         _geocentric;
@@ -243,11 +294,14 @@ namespace osgEarth
      * terrain tile, i.e. one that is not in the scene graph yet -- making it
      * MT-safe.
      */
-    class OSGEARTH_EXPORT TerrainPatch : public TerrainHeightProvider
+    class OSGEARTH_EXPORT TerrainPatch : public TerrainResolver
     {
     public:
         TerrainPatch( osg::Node* patch, const Terrain* terrain );
 
+
+    public: // TerrainResolver interface
+
         /**
         * Queries the elevation under the specified point. This method is
         * identical to calling Terrain::getHeight with the specified patch.
diff --git a/src/osgEarth/Terrain.cpp b/src/osgEarth/Terrain.cpp
index 7ee7a98..c9ed037 100644
--- a/src/osgEarth/Terrain.cpp
+++ b/src/osgEarth/Terrain.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -33,8 +33,8 @@ namespace
 {
     struct BaseOp : public osg::Operation
     {
-        BaseOp(Terrain* terrain ) : osg::Operation("",false), _terrain(terrain) { }
-        osg::ref_ptr<Terrain> _terrain;
+        BaseOp(Terrain* terrain, bool keepByDefault) : osg::Operation("",keepByDefault), _terrain(terrain) { }
+        osg::observer_ptr<Terrain> _terrain;
     };
 
     struct OnTileAddedOperation : public BaseOp
@@ -42,39 +42,38 @@ namespace
         TileKey _key;
         osg::observer_ptr<osg::Node> _node;
         unsigned _count;
-        //osg::ref_ptr<osg::Node> _node;
 
         OnTileAddedOperation(const TileKey& key, osg::Node* node, Terrain* terrain)
-            : BaseOp(terrain), _key(key), _node(node), _count(0) { }
+            : BaseOp(terrain, true), _key(key), _node(node), _count(0) { }
 
         void operator()(osg::Object*)
         {
-            ++_count;
-            this->setKeep( false );
+            if ( getKeep() == false )
+                return;
 
+            ++_count;
+            osg::ref_ptr<Terrain>   terrain;
             osg::ref_ptr<osg::Node> node;
-            if ( _terrain.valid() && _node.lock(node) )
+
+            if ( _terrain.lock(terrain) && _node.lock(node) )
             {
                 if ( node->getNumParents() > 0 )
                 {
                     //OE_NOTICE << LC << "FIRING onTileAdded for " << _key.str() << " (tries=" << _count << ")" << std::endl;
-                    _terrain->fireTileAdded( _key, node.get() );
+                    terrain->fireTileAdded( _key, node.get() );
+                    this->setKeep( false );
                 }
                 else
                 {
                     //OE_NOTICE << LC << "Deferring onTileAdded for " << _key.str() << std::endl;
-                    this->setKeep( true );
                 }
             }
             else
             {
                 // nop; tile expired; let it go.
                 //OE_NOTICE << "Tile expired before notification: " << _key.str() << std::endl;
+                this->setKeep( false );
             }
-            //if ( _node.valid() && _node->referenceCount() > 1 && _terrain.valid() )
-            //{
-            //    _terrain->fireTileAdded( _key, _node.get() );
-            //}
         }
     };
 }
@@ -287,6 +286,7 @@ Terrain::addTerrainCallback( TerrainCallback* cb )
     {        
         Threading::ScopedWriteLock exclusiveLock( _callbacksMutex );
         _callbacks.push_back( cb );
+        ++_callbacksSize; // atomic increment
     }
 }
 
@@ -300,6 +300,7 @@ Terrain::removeTerrainCallback( TerrainCallback* cb )
         if ( i->get() == cb )
         {
             i = _callbacks.erase( i );
+            --_callbacksSize;
         }
         else
         {
@@ -316,9 +317,10 @@ Terrain::notifyTileAdded( const TileKey& key, osg::Node* node )
         OE_WARN << LC << "notify with a null node!" << std::endl;
     }
 
-    if ( _updateOperationQueue.valid() )
+    osg::ref_ptr<osg::OperationQueue> queue;
+    if ( _callbacksSize > 0 && _updateOperationQueue.lock(queue) )
     {
-        _updateOperationQueue->add( new OnTileAddedOperation(key, node, this) );
+        queue->add( new OnTileAddedOperation(key, node, this) );
     }
 }
 
@@ -333,10 +335,10 @@ Terrain::fireTileAdded( const TileKey& key, osg::Node* node )
         i->get()->onTileAdded( key, node, context );
 
         // if the callback set the "remove" flag, discard the callback.
-        if ( !context._remove )
-            ++i;
-        else
+        if ( context.markedForRemoval() )
             i = _callbacks.erase( i );
+        else
+            ++i;
     }
 }
 
diff --git a/src/osgEarth/TerrainEffect b/src/osgEarth/TerrainEffect
index 493d6ec..a97348e 100644
--- a/src/osgEarth/TerrainEffect
+++ b/src/osgEarth/TerrainEffect
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/TerrainEngineNode b/src/osgEarth/TerrainEngineNode
index 61b3875..1208553 100644
--- a/src/osgEarth/TerrainEngineNode
+++ b/src/osgEarth/TerrainEngineNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -29,6 +29,8 @@
 #include <osg/Geode>
 #include <osg/NodeCallback>
 
+#define OSGEARTH_ENV_TERRAIN_ENGINE_DRIVER "OSGEARTH_TERRAIN_ENGINE"
+
 namespace osgEarth
 {
     class TerrainEffect;
@@ -61,6 +63,22 @@ namespace osgEarth
         /** Removes a terrain effect */
         void removeEffect( TerrainEffect* effect );
 
+        /**
+         * Marks the terrain tiles intersecting the provied extent as invalid.
+         * If the terrain engine supports incremental update, it will reload
+         * invalid tiles. If not, it will simple regenerate all tiles in the 
+         * terrain (which might be slow).
+         */
+        virtual void invalidateRegion(
+            const GeoExtent& extent,
+            unsigned         minLevel,
+            unsigned         maxLevel) { }
+
+        // See invalidateRegion() above.
+        void invalidateRegion(const GeoExtent& extent) {
+            invalidateRegion(extent, 0u, INT_MAX);
+        }
+            
 
     public: // Runtime properties
 
@@ -72,13 +90,6 @@ namespace osgEarth
           * #deprecated */
         float getVerticalScale() const { return _verticalScale; }
 
-        /** Sets the sampling ratio for elevation grid data. Default is 1.0.
-          * @deprecated */
-        void setElevationSamplingRatio( float value );
-
-        /** Gets the sampling ratio for elevation grid data.
-          * @depreceated */
-        float getElevationSamplingRatio() const { return _elevationSamplingRatio; }
 
     protected:
         TerrainEngineNode();
@@ -99,8 +110,6 @@ namespace osgEarth
         friend class MapNode;
         friend class TerrainEngineNodeFactory;
 
-        virtual void validateTerrainOptions( TerrainOptions& options );
-
         /** Attaches a map to the terrain engine and initialized it.*/
         virtual void preInitialize( const Map* map, const TerrainOptions& options );
         virtual void postInitialize( const Map* map, const TerrainOptions& options );
@@ -127,7 +136,6 @@ namespace osgEarth
         void ctor();
         void onMapInfoEstablished( const MapInfo& mapInfo ); // not virtual!
         void onMapModelChanged( const MapModelChange& change );
-        void updateImageUniforms();
         virtual void updateTextureCombining() { }
 
     private:
@@ -141,10 +149,6 @@ namespace osgEarth
             void onColorFiltersChanged( ImageLayer* layer );      
             void onVisibleRangeChanged( ImageLayer* layer );
 
-            ArrayUniform _layerOpacityUniform;
-            ArrayUniform _layerVisibleUniform;
-            ArrayUniform _layerRangeUniform;
-
         private:
             MapFrame           _mapf;
             TerrainEngineNode* _engine;
@@ -153,14 +157,10 @@ namespace osgEarth
 
         osg::ref_ptr<ImageLayerController> _imageLayerController;
         osg::ref_ptr<const Map>            _map;
-        osg::ref_ptr<osg::Uniform>         _startFrameTimeUniform;
-        osg::ref_ptr<osg::Uniform>         _cameraElevationUniform;
         bool                               _redrawRequired;
         float                              _verticalScale;
-        float                              _elevationSamplingRatio;
         osg::ref_ptr<Terrain>              _terrainInterface;
         unsigned                           _dirtyCount;
-        //UpdateLightingUniformsHelper       _updateLightingUniformsHelper;
         
         enum InitStage {
             INIT_NONE,
diff --git a/src/osgEarth/TerrainEngineNode.cpp b/src/osgEarth/TerrainEngineNode.cpp
index 6ae25bd..bf6b440 100644
--- a/src/osgEarth/TerrainEngineNode.cpp
+++ b/src/osgEarth/TerrainEngineNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -105,16 +105,6 @@ TerrainEngineNode::getTextureCompositor() const
 void
 TerrainEngineNode::ImageLayerController::onVisibleChanged( TerrainLayer* layer )
 {
-    if ( !Registry::instance()->getCapabilities().supportsGLSL() )
-        return;
-
-    _mapf.sync();
-    int layerNum = _mapf.indexOf( static_cast<ImageLayer*>(layer) );
-    if ( layerNum >= 0 )
-        _layerVisibleUniform.setElement( layerNum, layer->getVisible() );
-    else
-        OE_WARN << LC << "Odd, onVisibleChanged did not find layer" << std::endl;
-
     _engine->dirty();
 }
 
@@ -123,35 +113,12 @@ TerrainEngineNode::ImageLayerController::onVisibleChanged( TerrainLayer* layer )
 void
 TerrainEngineNode::ImageLayerController::onOpacityChanged( ImageLayer* layer )
 {
-    if ( !Registry::instance()->getCapabilities().supportsGLSL() )
-        return;
-
-    _mapf.sync();
-    int layerNum = _mapf.indexOf( layer );
-    if ( layerNum >= 0 )
-        _layerOpacityUniform.setElement( layerNum, layer->getOpacity() );
-    else
-        OE_WARN << LC << "Odd, onOpacityChanged did not find layer" << std::endl;
-
     _engine->dirty();
 }
 
 void
 TerrainEngineNode::ImageLayerController::onVisibleRangeChanged( ImageLayer* layer )
 {
-    if ( !Registry::instance()->getCapabilities().supportsGLSL() )
-        return;
-
-    _mapf.sync();
-    int layerNum = _mapf.indexOf( layer );
-    if ( layerNum >= 0 )
-    {
-         _layerRangeUniform.setElement( (2*layerNum),   layer->getMinVisibleRange() );
-         _layerRangeUniform.setElement( (2*layerNum)+1, layer->getMaxVisibleRange() );
-    }        
-    else
-        OE_WARN << LC << "Odd, onVisibleRangeChanged did not find layer" << std::endl;
-
     _engine->dirty();
 }
 
@@ -169,7 +136,6 @@ TerrainEngineNode::ImageLayerController::onColorFiltersChanged( ImageLayer* laye
 
 TerrainEngineNode::TerrainEngineNode() :
 _verticalScale         ( 1.0f ),
-_elevationSamplingRatio( 1.0f ),
 _initStage             ( INIT_NONE ),
 _dirtyCount            ( 0 )
 {
@@ -220,36 +186,15 @@ TerrainEngineNode::preInitialize( const Map* map, const TerrainOptions& options
         this->setEllipsoidModel( NULL );
     
     // install the proper layer composition technique:
-    _texCompositor = new TextureCompositor( options );
-
-    // prime the compositor with pre-existing image layers:
-    MapFrame mapf(map, Map::IMAGE_LAYERS);
-    for( unsigned i=0; i<mapf.imageLayers().size(); ++i )
-    {
-        _texCompositor->applyMapModelChange( MapModelChange(
-            MapModelChange::ADD_IMAGE_LAYER,
-            mapf.getRevision(),
-            mapf.getImageLayerAt(i),
-            i ) );
-    }
+    _texCompositor = new TextureCompositor();
 
     // then register the callback so we can process further map model changes
     _map->addMapCallback( new TerrainEngineNodeCallbackProxy( this ) );
 
     // enable backface culling
     osg::StateSet* set = getOrCreateStateSet();
-    //set->setAttributeAndModes( new osg::CullFace( osg::CullFace::BACK ), osg::StateAttribute::ON );
     set->setMode( GL_CULL_FACE, 1 );
 
-    // elevation uniform
-    // NOTE: wrong...this should be per-CullVisitor...consider putting in the Culling::CullUserData
-    _cameraElevationUniform = new osg::Uniform( osg::Uniform::FLOAT, "osgearth_CameraElevation" );
-    _cameraElevationUniform->set( 0.0f );
-    set->addUniform( _cameraElevationUniform.get() );
-    
-    set->getOrCreateUniform( "osgearth_ImageLayerAttenuation", osg::Uniform::FLOAT )->set(
-        *options.attentuationDistance() );
-
     if ( options.enableMercatorFastPath().isSet() )
     {
         OE_INFO 
@@ -278,8 +223,6 @@ TerrainEngineNode::postInitialize( const Map* map, const TerrainOptions& options
         {
             i->get()->addCallback( _imageLayerController.get() );
         }
-
-        updateImageUniforms();
     }
 
     _initStage = INIT_POSTINIT_COMPLETE;
@@ -290,7 +233,11 @@ TerrainEngineNode::computeBound() const
 {
     if ( getEllipsoidModel() )
     {
-        return osg::BoundingSphere( osg::Vec3(0,0,0), getEllipsoidModel()->getRadiusEquator()+25000 );
+        double maxRad = std::max(
+            getEllipsoidModel()->getRadiusEquator(),
+            getEllipsoidModel()->getRadiusPolar());
+
+        return osg::BoundingSphere( osg::Vec3(0,0,0), maxRad+25000 );
     }
     else
     {
@@ -306,13 +253,6 @@ TerrainEngineNode::setVerticalScale( float value )
 }
 
 void
-TerrainEngineNode::setElevationSamplingRatio( float value )
-{
-    _elevationSamplingRatio = value;
-    onElevationSamplingRatioChanged();
-}
-
-void
 TerrainEngineNode::onMapInfoEstablished( const MapInfo& mapInfo )
 {
     // set up the CSN values   
@@ -336,92 +276,12 @@ TerrainEngineNode::onMapModelChanged( const MapModelChange& change )
         {
             change.getImageLayer()->removeCallback( _imageLayerController.get() );
         }
-
-        if (change.getAction() == MapModelChange::ADD_IMAGE_LAYER ||
-            change.getAction() == MapModelChange::REMOVE_IMAGE_LAYER ||
-            change.getAction() == MapModelChange::MOVE_IMAGE_LAYER )
-        {
-            updateImageUniforms();
-        }
-    }
-
-    // if post-initialization has not yet happened, we need to make sure the 
-    // compositor is up to date with the map model. (After post-initialization,
-    // this happens in the subclass...something that probably needs to change
-    // since this is unclear)
-    else if ( _texCompositor.valid() && change.getImageLayer() )
-    {
-        _texCompositor->applyMapModelChange( change );
     }
 
     // notify that a redraw is required.
     dirty();
 }
 
-void
-TerrainEngineNode::updateImageUniforms()
-{
-    // don't bother if this is a hurting old card
-    if ( !Registry::instance()->getCapabilities().supportsGLSL() )
-        return;
-
-    // update the layer uniform arrays:
-    osg::StateSet* stateSet = this->getOrCreateStateSet();
-
-    // get a copy of the image layer stack:
-    MapFrame mapf( _map.get(), Map::IMAGE_LAYERS );
-
-    _imageLayerController->_layerVisibleUniform.detach();
-    _imageLayerController->_layerOpacityUniform.detach();
-    _imageLayerController->_layerRangeUniform.detach();
-    
-    if ( mapf.imageLayers().size() > 0 )
-    {
-        // the "enabled" uniform is fixed size. this is handy to account for layers that are in flux...i.e., their source
-        // layer count has changed, but the shader has not yet caught up. In the future we might use this to disable
-        // "ghost" layers that used to exist at a given index, but no longer do.
-        
-        _imageLayerController->_layerVisibleUniform.attach( "osgearth_ImageLayerVisible", osg::Uniform::BOOL,  stateSet, mapf.imageLayers().size() );
-        _imageLayerController->_layerOpacityUniform.attach( "osgearth_ImageLayerOpacity", osg::Uniform::FLOAT, stateSet, mapf.imageLayers().size() );
-        _imageLayerController->_layerRangeUniform.attach  ( "osgearth_ImageLayerRange",   osg::Uniform::FLOAT, stateSet, 2 * mapf.imageLayers().size() );
-
-        for( ImageLayerVector::const_iterator i = mapf.imageLayers().begin(); i != mapf.imageLayers().end(); ++i )
-        {
-            ImageLayer* layer = i->get();
-            int index = (int)(i - mapf.imageLayers().begin());
-
-            _imageLayerController->_layerVisibleUniform.setElement( index, layer->getVisible() );
-            _imageLayerController->_layerOpacityUniform.setElement( index, layer->getOpacity() );
-            _imageLayerController->_layerRangeUniform.setElement( (2*index), layer->getMinVisibleRange() );
-            _imageLayerController->_layerRangeUniform.setElement( (2*index)+1, layer->getMaxVisibleRange() );
-        }
-
-        // set the remainder of the layers to disabled 
-        for( int j=mapf.imageLayers().size(); j<_imageLayerController->_layerVisibleUniform.getNumElements(); ++j)
-        {
-            _imageLayerController->_layerVisibleUniform.setElement( j, false );
-        }
-    }
-
-    dirty();
-}
-
-void
-TerrainEngineNode::validateTerrainOptions( TerrainOptions& options )
-{
-    // make sure all the requested properties are compatible, and fall back as necessary.
-    //const Capabilities& caps = Registry::instance()->getCapabilities();
-
-    // warn against mixing multipass technique with preemptive/sequential mode:
-    if (options.compositingTechnique() == TerrainOptions::COMPOSITING_MULTIPASS &&
-        options.loadingPolicy()->mode() != LoadingPolicy::MODE_STANDARD )
-    {
-        OE_WARN << LC << "MULTIPASS compositor is incompatible with preemptive/sequential loading policy; "
-            << "falling back on STANDARD mode" << std::endl;
-        options.loadingPolicy()->mode() = LoadingPolicy::MODE_STANDARD;
-    }
-}
-
 namespace
 {
     Threading::Mutex s_opqlock;
@@ -455,30 +315,6 @@ TerrainEngineNode::traverse( osg::NodeVisitor& nv )
                 }
             }
         }
-
-
-        if ( Registry::capabilities().supportsGLSL() )
-        {
-            //_updateLightingUniformsHelper.cullTraverse( this, &nv );
-
-            // TODO: the "camera elevation" uniform is only used by the old
-            // multi- and texarray- texture compositors. Once we get rid of those
-            // we can get rid of this too.
-            osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
-            if ( cv )
-            {
-                osg::Vec3d eye = cv->getEyePoint();
-
-                float elevation;
-                if ( _map->isGeocentric() )
-                    elevation = eye.length() - osg::WGS_84_RADIUS_EQUATOR;
-                else
-                    elevation = eye.z();
-
-                //TODO: no good. cannot be setting this in cull.
-                _cameraElevationUniform->set( elevation );
-            }
-        }
     }
 
     else if ( nv.getVisitorType() == osg::NodeVisitor::EVENT_VISITOR )
@@ -505,12 +341,7 @@ TerrainEngineNodeFactory::create( Map* map, const TerrainOptions& options )
 
     std::string driverExt = std::string( ".osgearth_engine_" ) + driver;
     result = dynamic_cast<TerrainEngineNode*>( osgDB::readObjectFile( driverExt ) );
-    if ( result )
-    {
-        TerrainOptions terrainOptions( options );
-        result->validateTerrainOptions( terrainOptions );
-    }
-    else
+    if ( !result )
     {
         OE_WARN << "WARNING: Failed to load terrain engine driver for \"" << driver << "\"" << std::endl;
     }
diff --git a/src/osgEarth/TerrainLayer b/src/osgEarth/TerrainLayer
index 739b6e2..d9645f2 100644
--- a/src/osgEarth/TerrainLayer
+++ b/src/osgEarth/TerrainLayer
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,7 +21,6 @@
 #define OSGEARTH_TERRAIN_LAYER_H 1
 
 #include <osgEarth/Common>
-#include <osgEarth/Cache>
 #include <osgEarth/CachePolicy>
 #include <osgEarth/Config>
 #include <osgEarth/Layer>
@@ -32,6 +31,10 @@
 
 namespace osgEarth
 {
+    class Cache;
+    class CacheBin;
+    class MemCache;
+
     /**
      * Initialization (and serializable) options for a terrain layer.
      */
@@ -89,15 +92,6 @@ namespace osgEarth
          */
         optional<double>& maxResolution() { return _maxResolution; }
         const optional<double>& maxResolution() const { return _maxResolution; }
-
-        /**
-         * Overrides the maximum available LOD of the underlying tile source. The layer
-         * will never ask the tile source (or the cache) for an LOD higher than this.
-         * Data from this layer may still appear in map tiles above the maxDataLevel,
-         * but it will be upsampled from the maxDataLevel.
-         */
-        optional<unsigned>& maxDataLevel() { return _maxDataLevel; }
-        const optional<unsigned>& maxDataLevel() const { return _maxDataLevel; }
         
         /**
          * Whether to use this layer with the map. Setting this to false means that 
@@ -269,9 +263,10 @@ namespace osgEarth
         TileSource* getTileSource() const;
 
         /**
-         * Gets the tile size of the this MapLayer
+         * Gets the size (i.e. number of samples in each dimension) or the source
+         * data for this layer.
          */
-        unsigned int getTileSize() const;
+        unsigned getTileSize() const;
         
         /**
          * Whether the layer represents dynamic data, i.e. tile data that can change.
@@ -279,9 +274,10 @@ namespace osgEarth
         bool isDynamic() const;
 
         /**
-         * Whether the given key is valid for this layer
+         * Whether the given key falls within the range limits set in the options;
+         * i.e. min/maxLevel or min/maxResolution.
          */
-        virtual bool isKeyValid(const TileKey& key) const;
+        virtual bool isKeyInRange(const TileKey& key) const;
 
         /** 
          * Whether the data for the specified tile key is in the cache.
@@ -314,6 +310,15 @@ namespace osgEarth
                 _runtimeOptions->cachePolicy().isSet() &&
                 _runtimeOptions->cachePolicy()->usage() == CachePolicy::USAGE_CACHE_ONLY;
         }
+        
+        /**
+         * Convenience check for no-cache mode.
+         */
+        bool isNoCache() const {
+            return 
+                _runtimeOptions->cachePolicy().isSet() &&
+                _runtimeOptions->cachePolicy()->usage() == CachePolicy::USAGE_NO_CACHE;
+        }
 
     public: // Layer interface
 
@@ -328,43 +333,67 @@ namespace osgEarth
          */
         struct CacheBinMetadata
         {
-            CacheBinMetadata() { }
+            CacheBinMetadata() :
+                _valid(false) { }
 
             CacheBinMetadata( const CacheBinMetadata& rhs ) :
-                _empty        ( rhs._empty ),
-                _cacheBinId   ( rhs._cacheBinId ),
-                _sourceName   ( rhs._sourceName ),
-                _sourceDriver ( rhs._sourceDriver ),                
-                _sourceProfile( rhs._sourceProfile ),
-                _cacheProfile ( rhs._cacheProfile ) { }
+                _valid          ( rhs._valid ),
+                _cacheBinId     ( rhs._cacheBinId ),
+                _sourceName     ( rhs._sourceName ),
+                _sourceDriver   ( rhs._sourceDriver ),
+                _sourceTileSize ( rhs._sourceTileSize ),
+                _sourceProfile  ( rhs._sourceProfile ),
+                _cacheProfile   ( rhs._cacheProfile ),   
+                _cacheCreateTime( rhs._cacheCreateTime ) { }
 
             CacheBinMetadata( const Config& conf )
             {
-                _empty = conf.empty();
-                conf.getIfSet   ( "cachebin_id",    _cacheBinId );
-                conf.getIfSet   ( "source_name",    _sourceName );
-                conf.getIfSet   ( "source_driver",  _sourceDriver );                
-                conf.getObjIfSet( "source_profile", _sourceProfile );
-                conf.getObjIfSet( "cache_profile",  _cacheProfile );
+                _valid = !conf.empty();
+
+                conf.getIfSet   ( "cachebin_id",      _cacheBinId );
+                conf.getIfSet   ( "source_name",      _sourceName );
+                conf.getIfSet   ( "source_driver",    _sourceDriver );   
+                conf.getIfSet   ( "source_tile_size", _sourceTileSize );           
+                conf.getObjIfSet( "source_profile",   _sourceProfile );
+                conf.getObjIfSet( "cache_profile",    _cacheProfile ); 
+                conf.getIfSet   ( "cache_create_time", _cacheCreateTime ); 
+                
+                // check for validity. This will reject older caches that don't have
+                // sufficient attribution.
+                if ( _valid )
+                {
+                    if (!conf.hasValue("source_tile_size") ||
+                        !conf.hasChild("source_profile")   ||
+                        !conf.hasChild("cache_profile"))
+                    {
+                        _valid = false;
+                    }
+                }
             }
 
+            bool isValid() const { return _valid; }
+
             Config getConfig() const
             {
                 Config conf( "osgearth_terrainlayer_cachebin" );
-                conf.addIfSet   ( "cachebin_id",    _cacheBinId );
-                conf.addIfSet   ( "source_name",    _sourceName );
-                conf.addIfSet   ( "source_driver",  _sourceDriver );                
-                conf.addObjIfSet( "source_profile", _sourceProfile );
-                conf.addObjIfSet( "cache_profile",  _cacheProfile );
+                conf.addIfSet   ( "cachebin_id",       _cacheBinId );
+                conf.addIfSet   ( "source_name",       _sourceName );
+                conf.addIfSet   ( "source_driver",     _sourceDriver );      
+                conf.addIfSet   ( "source_tile_size",  _sourceTileSize );
+                conf.addObjIfSet( "source_profile",    _sourceProfile );
+                conf.addObjIfSet( "cache_profile",     _cacheProfile );
+                conf.addIfSet   ( "cache_create_time", _cacheCreateTime );
                 return conf;
             }
 
-            bool                     _empty;
+            bool                     _valid;
             optional<std::string>    _cacheBinId;
             optional<std::string>    _sourceName;
-            optional<std::string>    _sourceDriver;            
+            optional<std::string>    _sourceDriver;   
+            optional<int>            _sourceTileSize;
             optional<ProfileOptions> _sourceProfile;
             optional<ProfileOptions> _cacheProfile;
+            optional<TimeStamp>      _cacheCreateTime;
         };
 
         /**
@@ -376,17 +405,25 @@ namespace osgEarth
 
         virtual void initTileSource();
 
+        void applyProfileOverrides();
+
         CacheBin* getCacheBin( const Profile* profile, const std::string& binId );
 
     protected:
 
         osg::ref_ptr<TileSource>       _tileSource;
-        osg::ref_ptr<const Profile>    _profile;
         osg::ref_ptr<const Profile>    _targetProfileHint;
         bool                           _tileSourceInitAttempted;
         bool                           _tileSourceInitFailed;
         unsigned                       _tileSize;  
         osg::ref_ptr<osgDB::Options>   _dbOptions;
+        osg::ref_ptr<MemCache>         _memCache;
+
+        // profile from tile source or cache, before any overrides applied
+        mutable osg::ref_ptr<const Profile> _profileOriginal;
+
+        // profile to use
+        mutable osg::ref_ptr<const Profile> _profile;
 
         void setCachePolicy( const CachePolicy& cp );
         const CachePolicy& getCachePolicy() const;
@@ -394,9 +431,9 @@ namespace osgEarth
     private:
         std::string                    _name;
         std::string                    _referenceURI;
-        OpenThreads::Mutex             _initTileSourceMutex;
         TerrainLayerOptions            _initOptions;
         TerrainLayerOptions*           _runtimeOptions;
+        mutable Threading::Mutex       _initTileSourceMutex;
 
         osg::ref_ptr<Cache>            _cache;
 
@@ -426,6 +463,9 @@ namespace osgEarth
          * TODO: is it legal to set this at any time?
          */
         void setCache( Cache* cache );
+
+        // read the tile source's cache policy hint and apply as necessary
+        void refreshTileSourceCachePolicyHint();
     };
 
     typedef std::vector<osg::ref_ptr<TerrainLayer> > TerrainLayerVector;
diff --git a/src/osgEarth/TerrainLayer.cpp b/src/osgEarth/TerrainLayer.cpp
index d5e5e8e..8555a04 100644
--- a/src/osgEarth/TerrainLayer.cpp
+++ b/src/osgEarth/TerrainLayer.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -22,6 +22,8 @@
 #include <osgEarth/StringUtils>
 #include <osgEarth/TimeControl>
 #include <osgEarth/URI>
+#include <osgEarth/MemCache>
+#include <osgEarth/CacheBin>
 #include <osgDB/WriteFile>
 #include <osg/Version>
 #include <OpenThreads/ScopedLock>
@@ -37,7 +39,7 @@ using namespace OpenThreads;
 TerrainLayerOptions::TerrainLayerOptions( const ConfigOptions& options ) :
 ConfigOptions       ( options ),
 _minLevel           ( 0 ),
-_maxLevel           ( 30 ),
+_maxLevel           ( 23 ),
 _cachePolicy        ( CachePolicy::DEFAULT ),
 _loadingWeight      ( 1.0f ),
 _exactCropping      ( false ),
@@ -69,7 +71,7 @@ TerrainLayerOptions::setDefaults()
     _cachePolicy.init( CachePolicy() );
     _loadingWeight.init( 1.0f );
     _minLevel.init( 0 );
-    _maxLevel.init( 30 );
+    _maxLevel.init( 23 );
     _maxDataLevel.init( 99 );
 }
 
@@ -190,6 +192,14 @@ TerrainLayer::init()
     
     initializeCachePolicy( _dbOptions.get() );
     storeProxySettings( _dbOptions.get() );
+
+    // Create an L2 mem cache that sits atop the main cache, if necessary.
+    // For now: use the same L2 cache size at the driver.
+    int memCacheSize = _initOptions.driver()->L2CacheSize().get();
+    if ( memCacheSize > 0 )
+    {
+        _memCache = new MemCache(memCacheSize);
+    }
 }
 
 void
@@ -227,6 +237,10 @@ TerrainLayer::setCache( Cache* cache )
                 hashConf.remove( "cache_enabled" );
                 hashConf.remove( "cache_policy" );
                 hashConf.remove( "cacheid" );
+                
+                // need this, b/c data is vdatum-transformed before caching.
+                if ( layerConf.hasValue("vdatum") )
+                    hashConf.add("vdatum", layerConf.value("vdatum"));
 
                 cacheId = Stringify() << std::hex << osgEarth::hashString(hashConf.toJSON());
 
@@ -268,6 +282,26 @@ TerrainLayer::setTargetProfileHint( const Profile* profile )
         CacheBinMetadata meta;
         getCacheBinMetadata( profile, meta );
     }
+
+    // If the tilesource was already initialized, re-read the 
+    // cache policy hint since it may change due to the target
+    // profile change.
+    refreshTileSourceCachePolicyHint();
+}
+
+void
+TerrainLayer::refreshTileSourceCachePolicyHint()
+{
+    if ( _tileSource.valid() && !_initOptions.cachePolicy().isSet() )
+    {
+        CachePolicy hint = _tileSource->getCachePolicyHint( _targetProfileHint.get() );
+
+        if ( hint.usage().isSetTo(CachePolicy::USAGE_NO_CACHE) )
+        {
+            setCachePolicy( hint );
+            OE_INFO << LC << "Caching disabled (by policy hint)" << std::endl;
+        }
+    }
 }
 
 TileSource* 
@@ -279,7 +313,7 @@ TerrainLayer::getTileSource() const
     if ((_tileSource.valid() && !_tileSourceInitAttempted) ||
         (!_tileSource.valid() && !isCacheOnly()))
     {
-        OpenThreads::ScopedLock< OpenThreads::Mutex > lock(const_cast<TerrainLayer*>(this)->_initTileSourceMutex );
+        Threading::ScopedMutexLock lock(_initTileSourceMutex);
         
         // double-check pattern
         if ((_tileSource.valid() && !_tileSourceInitAttempted) ||
@@ -289,17 +323,10 @@ TerrainLayer::getTileSource() const
             const_cast<TerrainLayer*>(this)->initTileSource();
 
             // read the cache policy hint from the tile source unless user expressly set 
-            // a policy in the initialization options.
-            if ( _tileSource.valid() && !_initOptions.cachePolicy().isSet() )
-            {
-                CachePolicy hint = _tileSource->getCachePolicyHint( _targetProfileHint.get() );
-
-                if ( hint.usage().isSetTo(CachePolicy::USAGE_NO_CACHE) )
-                {
-                    const_cast<TerrainLayer*>(this)->setCachePolicy( hint );
-                    OE_INFO << LC << "Caching disabled (by policy hint)" << std::endl;
-                }
-            }
+            // a policy in the initialization options. In other words, the hint takes
+            // ultimate priority (even over the Registry override) unless expressly
+            // overridden in the layer options!
+            const_cast<TerrainLayer*>(this)->refreshTileSourceCachePolicyHint();
 
             // Unless the user has already configured an expiration policy, use the "last modified"
             // timestamp of the TileSource to set a minimum valid cache entry timestamp.
@@ -329,23 +356,6 @@ TerrainLayer::getProfile() const
             // Call getTileSource to make sure the TileSource is initialized
             getTileSource();
         }
-
-        if ( _tileSource.valid() && !_profile.valid() && !_tileSourceInitFailed )
-        {
-            const_cast<TerrainLayer*>(this)->_profile = _tileSource->getProfile();
-
-            // check for a vertical datum override:
-            if ( _profile.valid() && _runtimeOptions->verticalDatum().isSet() )
-            {
-                std::string vdatum = toLower( *_runtimeOptions->verticalDatum() );
-                if ( _profile->getSRS()->getVertInitString() != vdatum )
-                {
-                    ProfileOptions po = _profile->toProfileOptions();
-                    po.vsrsString() = vdatum;
-                    const_cast<TerrainLayer*>(this)->_profile = Profile::create(po);
-                }
-            }
-        }
     }
     
     return _profile.get();
@@ -354,8 +364,9 @@ TerrainLayer::getProfile() const
 unsigned
 TerrainLayer::getTileSize() const
 {
-    TileSource* ts = getTileSource();
-    return ts ? ts->getPixelsPerTile() : _tileSize;
+    // force tile source initialization (which sets _tileSize)
+    getTileSource();
+    return _tileSize; //ts ? ts->getPixelsPerTile() : _tileSize;
 }
 
 bool
@@ -366,8 +377,11 @@ TerrainLayer::isDynamic() const
 }
 
 CacheBin*
-TerrainLayer::getCacheBin( const Profile* profile )
+TerrainLayer::getCacheBin(const Profile* profile)
 {
+    // make sure we've initialized the tile source first.
+    getTileSource();
+
     if ( getCachePolicy() == CachePolicy::NO_CACHE )
     {
         return 0L;
@@ -380,8 +394,11 @@ TerrainLayer::getCacheBin( const Profile* profile )
 }
 
 CacheBin*
-TerrainLayer::getCacheBin( const Profile* profile, const std::string& binId )
+TerrainLayer::getCacheBin(const Profile* profile, const std::string& binId)
 {
+    // make sure we've initialized the tile source first.
+    TileSource* tileSource = getTileSource();
+
     // in no-cache mode, there are no cache bins.
     if ( getCachePolicy() == CachePolicy::NO_CACHE )
     {
@@ -420,13 +437,13 @@ TerrainLayer::getCacheBin( const Profile* profile, const std::string& binId )
             // attempt to read the cache metadata:
             CacheBinMetadata meta( newBin->readMetadata() );
 
-            if ( !meta._empty ) // cache exists
+            if ( meta.isValid() ) // cache exists and is valid.
             {
                 // verify that the cache if compatible with the tile source:
-                if ( getTileSource() && getProfile() )
+                if ( tileSource && getProfile() )
                 {
                     //todo: check the profile too
-                    if ( *meta._sourceDriver != getTileSource()->getOptions().getDriver() )
+                    if ( *meta._sourceDriver != tileSource->getOptions().getDriver() )
                     {
                         OE_WARN << LC << "Cache has an incompatible driver or profile... disabling"
                             << std::endl;
@@ -440,6 +457,7 @@ TerrainLayer::getCacheBin( const Profile* profile, const std::string& binId )
                     // in cacheonly mode, create a profile from the first cache bin accessed
                     // (they SHOULD all be the same...)
                     _profile = Profile::create( *meta._sourceProfile );
+                    _tileSize = *meta._sourceTileSize;
                 }
             }
 
@@ -447,29 +465,34 @@ TerrainLayer::getCacheBin( const Profile* profile, const std::string& binId )
             {
                 // cache does not exist, so try to create it. A valid TileSource is necessary
                 // for this.
-                if ( getTileSource() && getProfile() )
+                if ( tileSource && getProfile() )
                 {
                     // no existing metadata; create some.
-                    meta._cacheBinId    = binId;
-                    meta._sourceName    = this->getName();
-                    meta._sourceDriver  = getTileSource()->getOptions().getDriver();
-                    meta._sourceProfile = getProfile()->toProfileOptions();
-                    meta._cacheProfile  = profile->toProfileOptions();
+                    meta._cacheBinId      = binId;
+                    meta._sourceName      = this->getName();
+                    meta._sourceDriver    = tileSource->getOptions().getDriver();
+                    meta._sourceTileSize  = getTileSize();
+                    meta._sourceProfile   = getProfile()->toProfileOptions();
+                    meta._cacheProfile    = profile->toProfileOptions();
+                    meta._cacheCreateTime = DateTime().asTimeStamp();
 
                     // store it in the cache bin.
                     newBin->writeMetadata( meta.getConfig() );
                 }
                 else if ( isCacheOnly() )
                 {
-                    OE_WARN << LC << "Failed to open a cache for layer [" << getName() << "] "
-                        << " because cache_only policy is in effect and bin [" << binId << "] cound not be located."
+                    OE_WARN << LC <<
+                        "Failed to open a cache for layer "
+                        "because cache_only policy is in effect and bin [" << binId << "] "
+                        "could not be located."
                         << std::endl;
                     return 0L;
                 }
                 else
                 {
-                    OE_WARN << LC << "Failed to create cache bin [" << binId << "] "
-                        << "for layer [" << getName() << "] because there is no valid tile source."
+                    OE_WARN << LC <<
+                        "Failed to create cache bin [" << binId << "] "
+                        "because there is no valid tile source."
                         << std::endl;
                     return 0L;
                 }
@@ -480,13 +503,17 @@ TerrainLayer::getCacheBin( const Profile* profile, const std::string& binId )
             newInfo._metadata = meta;
             newInfo._bin      = newBin.get();
 
-            OE_INFO << LC << "Opened cache bin [" << binId << "]" << std::endl;
+            OE_INFO << LC <<
+                "Opened cache bin [" << binId << "]" << std::endl;
+
+            // If we loaded a profile from the cache metadata, apply the overrides:
+            applyProfileOverrides();
         }
         else
         {
             // bin creation failed, so disable caching for this layer.
             setCachePolicy( CachePolicy::NO_CACHE );
-            OE_WARN << LC << "Failed to create a caching bin for layer; cache disabled." << std::endl;
+            OE_WARN << LC << "Failed to create a cache bin; cache disabled." << std::endl;
         }
 
         return newBin.get(); // not release()
@@ -515,6 +542,8 @@ TerrainLayer::getCacheBinMetadata( const Profile* profile, CacheBinMetadata& out
 void
 TerrainLayer::initTileSource()
 {
+    _tileSourceInitAttempted = true;
+
     OE_DEBUG << LC << "Initializing tile source ..." << std::endl;
 
     // Instantiate it from driver options if it has not already been created.
@@ -523,7 +552,7 @@ TerrainLayer::initTileSource()
     {
         if ( _runtimeOptions->driver().isSet() )
         {
-            _tileSource = TileSourceFactory::create( *_runtimeOptions->driver() );
+            _tileSource = TileSourceFactory::create(*_runtimeOptions->driver());
         }
     }
 
@@ -539,6 +568,7 @@ TerrainLayer::initTileSource()
             URIContext( _runtimeOptions->referrer() ).apply( _dbOptions.get() );
         }
 
+
         // report on a manual override profile:
         if ( _tileSource->getProfile() )
         {
@@ -546,16 +576,34 @@ TerrainLayer::initTileSource()
                 << _tileSource->getProfile()->toString() << std::endl;
         }
 
-        // Start up the tile source (if it hasn't already been started)
+        // Open the tile source (if it hasn't already been started)
         TileSource::Status status = _tileSource->getStatus();
         if ( status != TileSource::STATUS_OK )
         {
-            status = _tileSource->startup( _dbOptions.get() );
+            status = _tileSource->open(TileSource::MODE_READ, _dbOptions.get());
         }
 
         if ( status == TileSource::STATUS_OK )
         {
             _tileSize = _tileSource->getPixelsPerTile();
+
+#if 0 //debugging 
+            // dump out data extents:
+            if ( _tileSource->getDataExtents().size() > 0 )
+            {
+                OE_INFO << LC << "Data extents reported:" << std::endl;
+                for(DataExtentList::const_iterator i = _tileSource->getDataExtents().begin();
+                    i != _tileSource->getDataExtents().end(); ++i)
+                {
+                    const DataExtent& de = *i;
+                    OE_INFO << "    "
+                        << "X(" << i->xMin() << ", " << i->xMax() << ") "
+                        << "Y(" << i->yMin() << ", " << i->yMax() << ") "
+                        << "Z(" << i->minLevel().get() << ", " << i->maxLevel().get() << ")"
+                        << std::endl;
+                }                
+            }
+#endif
         }
         else
         {
@@ -569,7 +617,19 @@ TerrainLayer::initTileSource()
     // Set the profile from the TileSource if possible:
     if ( _tileSource.valid() )
     {
-        _profile = _tileSource->getProfile();
+        if ( !_profile.valid() && !_tileSourceInitFailed )
+        {
+            _profile = _tileSource->getProfile();
+        }
+
+
+        if ( _profile.valid() )
+        {
+            // create the final profile from any overrides:
+            applyProfileOverrides();
+
+            OE_INFO << LC << "Profile=" << _profile->toString() << std::endl;
+        }
     }
 
     // Otherwise, force cache-only mode (since there is no tilesource). The layer will try to 
@@ -579,40 +639,67 @@ TerrainLayer::initTileSource()
         OE_NOTICE << LC << "Could not initialize TileSource " << _name << ", but a cache exists. Setting layer to cache-only mode." << std::endl;
         setCachePolicy( CachePolicy::CACHE_ONLY );
     }
+}
+
+void
+TerrainLayer::applyProfileOverrides()
+{
+    // Check for a vertical datum override.
+    bool changed = false;
+    if ( _profile.valid() && _runtimeOptions->verticalDatum().isSet() )
+    {
+        std::string vdatum = *_runtimeOptions->verticalDatum();
+        OE_INFO << "override vdatum = " << vdatum << ", profile vdatum = " << _profile->getSRS()->getVertInitString() << std::endl;
+        if ( !ciEquals(_profile->getSRS()->getVertInitString(), vdatum) )
+        {
+            ProfileOptions po = _profile->toProfileOptions();
+            po.vsrsString() = vdatum;
+            _profile = Profile::create(po);
+            changed = true;
+        }
+    }
 
-    _tileSourceInitAttempted = true;
+    if (changed && _profile.valid())
+    {
+        OE_INFO << LC << "Override profile: " << _profile->toString() << std::endl;
+    }
 }
 
 bool
-TerrainLayer::isKeyValid(const TileKey& key) const
+TerrainLayer::isKeyInRange(const TileKey& key) const
 {    
-    if (!key.valid())
+    if ( !key.valid() )
+    {
         return false;
+    }
 
-    // Check to see if an explicity max LOD is set. Do NOT compare against the minLevel,
-    // because we still need to create empty tiles until we get to the data. The ImageLayer
-    // will deal with this.
-    if ( _runtimeOptions->maxLevel().isSet() && key.getLOD() > _runtimeOptions->maxLevel().value() ) 
+    // First check the key against the min/max level limits, it they are set.
+    if ((_runtimeOptions->maxLevel().isSet() && key.getLOD() > _runtimeOptions->maxLevel().value()) ||
+        (_runtimeOptions->minLevel().isSet() && key.getLOD() < _runtimeOptions->minLevel().value()))
     {
         return false;
     }
 
-    // Check to see if levels of detail based on resolution are set
-    const Profile* profile = getProfile();
-    if ( profile )
+    // Next, check against resolution limits (based on the source tile size).
+    if (_runtimeOptions->minResolution().isSet() ||
+        _runtimeOptions->maxResolution().isSet())
     {
-        if ( !profile->isEquivalentTo( key.getProfile() ) )
+        const Profile* profile = getProfile();
+        if ( profile )
         {
-            OE_DEBUG << LC
-                << "TerrainLayer::isKeyValid called with key of a different profile" << std::endl;
-        }
+            // calculate the resolution in the layer's profile, which can
+            // be different that the key's profile.
+            double resKey   = key.getExtent().width() / (double)getTileSize();
+            double resLayer = key.getProfile()->getSRS()->transformUnits(resKey, profile->getSRS());
 
-        if ( _runtimeOptions->maxResolution().isSet() )
-        {
-            double keyres = key.getExtent().width() / (double)getTileSize();
-            double keyresInLayerProfile = key.getProfile()->getSRS()->transformUnits(keyres, profile->getSRS());
+            if (_runtimeOptions->maxResolution().isSet() &&
+                _runtimeOptions->maxResolution().value() > resLayer)
+            {
+                return false;
+            }
 
-            if ( _runtimeOptions->maxResolution().isSet() && keyresInLayerProfile < _runtimeOptions->maxResolution().value() )
+            if (_runtimeOptions->minResolution().isSet() &&
+                _runtimeOptions->minResolution().value() < resLayer)
             {
                 return false;
             }
@@ -625,13 +712,18 @@ TerrainLayer::isKeyValid(const TileKey& key) const
 bool
 TerrainLayer::isCached(const TileKey& key) const
 {
+    // first consult the policy:
+    if ( getCachePolicy() == CachePolicy::NO_CACHE )
+        return false;
+    else if ( getCachePolicy() == CachePolicy::CACHE_ONLY )
+        return true;
+
+    // next check for a bin:
     CacheBin* bin = const_cast<TerrainLayer*>(this)->getCacheBin( key.getProfile() );
     if ( !bin )
         return false;
-
-    TimeStamp minTime = this->getCachePolicy().getMinAcceptTime();
-
-    return bin->getRecordStatus( key.str(), minTime ) == CacheBin::STATUS_OK;
+    
+    return bin->getRecordStatus( key.str() ) == CacheBin::STATUS_OK;
 }
 
 void
@@ -650,27 +742,21 @@ TerrainLayer::setDBOptions( const osgDB::Options* dbOptions )
 }
 
 void
-TerrainLayer::initializeCachePolicy( const osgDB::Options* options )
+TerrainLayer::initializeCachePolicy(const osgDB::Options* options)
 {
+    // Start with the cache policy passed in by the Map.
     optional<CachePolicy> cp;
+    CachePolicy::fromOptions(options, cp);
 
+    // if this layer specifies cache policy info, that will override 
+    // whatever the map passed in:
     if ( _initOptions.cachePolicy().isSet() )
-    {
-        // if the initialization options specify a cache policy, attempt to use it
-        osg::ref_ptr<osgDB::Options> temp = Registry::instance()->cloneOrCreateOptions(options);
-        _initOptions.cachePolicy()->apply( temp.get() );
+        cp->mergeAndOverride( _initOptions.cachePolicy() );
 
-        if ( ! Registry::instance()->getCachePolicy(cp, temp.get()) )
-            cp = CachePolicy::DEFAULT;
-    }
-    else 
-    {
-        // otherwise go the normal route.
-        if ( ! Registry::instance()->getCachePolicy(cp, options) )
-            cp = CachePolicy::DEFAULT;
-    }
+    // finally resolve with global overrides:
+    Registry::instance()->resolveCachePolicy( cp );
 
-    setCachePolicy( *cp );
+    setCachePolicy( cp.get() );
 }
 
 void
diff --git a/src/osgEarth/TerrainOptions b/src/osgEarth/TerrainOptions
index 737d836..679b512 100644
--- a/src/osgEarth/TerrainOptions
+++ b/src/osgEarth/TerrainOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -25,95 +25,7 @@
 #include <osg/Texture>
 
 namespace osgEarth
-{
-    /**
-     * @deprecated
-     * The LoadingPolicy configures how the terrain engine loads map data.
-     */
-    class OSGEARTH_EXPORT LoadingPolicy
-    {
-    public:
-        /** Tile loading modes. */
-        enum Mode {
-            /** Load tiles one LOD at a tile, serially */ 
-            MODE_SERIAL,
-
-            /** Load tiles one LOD at a tile, in parallel */
-            MODE_PARALLEL,
-
-            /** Load tiles using a task service thread pool, enforcing sequential display
-                of tile LODs. */
-            MODE_SEQUENTIAL,
-
-            /** Load tiles using a task service thread pool, but prioritize loading of the
-                highest visible LOD for imagery (elevation data is always sequential). */
-            MODE_PREEMPTIVE,
-
-            /** Load tiles using the standard OSG database pager mechanism. The default. */
-            MODE_STANDARD = MODE_SERIAL
-        };
-
-    public:
-        LoadingPolicy( const Config& conf =Config() );
-        virtual ~LoadingPolicy() { }
-
-    public: // Configrable
-        virtual Config getConfig() const;
-        virtual void fromConfig( const Config& conf );
-
-    public:
-        /**
-         * Gets or sets the tile loading mode.
-         */
-        optional<Mode>& mode() { return _mode; }
-        const optional<Mode>& mode() const { return _mode; }
-
-        /**
-         * Gets or sets the number of loading threads to use per CPU core.
-         *
-         * In STANDARD mode, this affects the number of OSG database pager threads;
-         * in SEQUENTIAL or PREEMPTIVE mode, this sets the number of task thread
-         * pool threads.
-         */
-        optional<float>& numLoadingThreadsPerCore() { return _numLoadingThreadsPerCore; }
-        const optional<float>& numLoadingThreadsPerCore() const { return _numLoadingThreadsPerCore; }
-
-        /**
-         * Gets or sets the total number of loading threads to use.
-         *
-         * In STANDARD mode, this affects the number of OSG database pager threads;
-         * in SEQUENTIAL or PREEMPTIVE mode, this sets the number of task thread
-         * pool threads.
-         */
-        const optional<int>& numLoadingThreads() const { return _numLoadingThreads; }
-        optional<int>& numLoadingThreads() { return _numLoadingThreads; }
-
-        /**
-         * Gets or sets the number of threads to allocate for regenerating terrain
-         * tiles in the background. This only applies in SEQUENTIAL or PREEMPTIVE
-         * mode.
-         */
-        const optional<int>& numCompileThreads() const { return _numCompileThreads; }
-        optional<int>& numCompileThreads() { return _numCompileThreads; }
-
-        /**
-         * Gets or sets the number of threads to allocate for regenerating terrain
-         * tiles in the background. This only applies in SEQUENTIAL or PREEMPTIVE
-         * mode.
-         */
-        const optional<float>& numCompileThreadsPerCore() const { return _numCompileThreadsPerCore; }
-        optional<float>& numCompileThreadsPerCore() { return _numCompileThreadsPerCore; }
-
-    protected:
-        optional<Mode> _mode;
-        optional<int>   _numLoadingThreads;
-        optional<float> _numLoadingThreadsPerCore;
-        optional<int>   _numCompileThreads;
-        optional<float> _numCompileThreadsPerCore;
-    };
-
-    extern OSGEARTH_EXPORT int computeLoadingThreads(const LoadingPolicy& policy);
-    
+{    
     /**
      * Base class for the configuration for a terrain engine driver.
      */
@@ -133,6 +45,14 @@ namespace osgEarth
         const optional<float>& verticalScale() const { return _verticalScale; }
 
         /**
+         * Sets or gets the offset for height-field values.
+         * Default = 0.0
+         */
+        optional<float>& verticalOffset() { return _verticalOffset; }
+        const optional<float>& verticalOffset() const { return _verticalOffset; }
+
+        /**
+         * @deprecated - Use MapOptions::elevationTileSize instead
          * The sample ratio for height fields. I.e., the terrain engine
          * will sample heightfield grids at this ratio
          * Default = 1.0
@@ -148,13 +68,6 @@ namespace osgEarth
         const optional<float>& minTileRangeFactor() const { return _minTileRangeFactor; }
 
         /**
-         * @deprecated
-         * Properties associated with the tile loading subsystem.
-         */
-        optional<LoadingPolicy>& loadingPolicy() { return _loadingPolicy; }
-        const optional<LoadingPolicy>& loadingPolicy() const { return _loadingPolicy; }
-
-        /**
          * The image-layer fading attenuation distance
          */
         optional<float>& attenuationDistance() { return _attenuationDistance; }
@@ -173,27 +86,6 @@ namespace osgEarth
         const optional<bool>& clusterCulling() const { return _clusterCulling; }
 
         /**
-         * @deprecated - will either go away or be moved into the Quadtree terrain engine
-         * Available techniques for compositing image layers at runtime.
-         */
-        enum CompositingTechnique
-        {
-            COMPOSITING_AUTO,
-            COMPOSITING_TEXTURE_ARRAY,
-            COMPOSITING_MULTITEXTURE_GPU,
-            COMPOSITING_MULTITEXTURE_FFP,
-            COMPOSITING_MULTIPASS,
-            COMPOSITING_USER
-        };
-
-        /**
-         * @deprecated - will either go away or be moved into the Quadtree terrain engine
-         * The texture composition technique
-         */
-        optional<CompositingTechnique>& compositingTechnique() { return _compositingTech; }
-        const optional<CompositingTechnique>& compositingTechnique() const { return _compositingTech; }
-
-        /**
          * The maximum level of detail to which the terrain should subdivide. If you leave this
          * unset, the terrain will subdivide until the map layers stop providing data (default
          * behavior). If you set a value, the terrain will stop subdividing at the specified LOD
@@ -288,11 +180,10 @@ namespace osgEarth
         void fromConfig( const Config& conf );
                     
         optional<float> _verticalScale;
+        optional<float> _verticalOffset;
         optional<float> _heightFieldSampleRatio;
         optional<float> _minTileRangeFactor;        
         optional<bool> _combineLayers;
-        optional<LoadingPolicy> _loadingPolicy;
-        optional<CompositingTechnique> _compositingTech;
         optional<unsigned> _minLOD;
         optional<unsigned> _maxLOD;
         optional<unsigned> _firstLOD;
diff --git a/src/osgEarth/TerrainOptions.cpp b/src/osgEarth/TerrainOptions.cpp
index dd4145e..ec06378 100644
--- a/src/osgEarth/TerrainOptions.cpp
+++ b/src/osgEarth/TerrainOptions.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -23,76 +23,15 @@
 
 using namespace osgEarth;
 
-//------------------------------------------------------------------------
-
-LoadingPolicy::LoadingPolicy( const Config& conf ) :
-_mode( MODE_STANDARD ),
-_numLoadingThreads( 4 ),
-_numLoadingThreadsPerCore( 2 ),
-_numCompileThreads( 2 ),
-_numCompileThreadsPerCore( 0.5 )
-{
-    fromConfig( conf );
-}
-
-void
-LoadingPolicy::fromConfig( const Config& conf )
-{
-    conf.getIfSet( "mode", "standard", _mode, MODE_SERIAL );
-    conf.getIfSet( "mode", "serial", _mode, MODE_SERIAL );
-    conf.getIfSet( "mode", "parallel", _mode, MODE_PARALLEL );
-    conf.getIfSet( "mode", "sequential", _mode, MODE_SEQUENTIAL );
-    conf.getIfSet( "mode", "preemptive", _mode, MODE_PREEMPTIVE );
-    conf.getIfSet( "loading_threads", _numLoadingThreads );
-    conf.getIfSet( "loading_threads_per_logical_processor", _numLoadingThreadsPerCore );
-    conf.getIfSet( "loading_threads_per_core", _numLoadingThreadsPerCore );
-    conf.getIfSet( "compile_threads", _numCompileThreads );
-    conf.getIfSet( "compile_threads_per_core", _numCompileThreadsPerCore );
-}
-
-Config
-LoadingPolicy::getConfig() const
-{
-    Config conf( "loading_policy" );
-    conf.addIfSet( "mode", "standard", _mode, MODE_STANDARD ); // aka MODE_SERIAL
-    conf.addIfSet( "mode", "parallel", _mode, MODE_PARALLEL );
-    conf.addIfSet( "mode", "sequential", _mode, MODE_SEQUENTIAL );
-    conf.addIfSet( "mode", "preemptive", _mode, MODE_PREEMPTIVE );
-    conf.addIfSet( "loading_threads", _numLoadingThreads );
-    conf.addIfSet( "loading_threads_per_core", _numLoadingThreadsPerCore );
-    conf.addIfSet( "compile_threads", _numCompileThreads );
-    conf.addIfSet( "compile_threads_per_core", _numCompileThreadsPerCore );
-    return conf;
-}
-
-int osgEarth::computeLoadingThreads(const LoadingPolicy& policy)
-{
-    const char* env_numTaskServiceThreads = getenv("OSGEARTH_NUM_PREEMPTIVE_LOADING_THREADS");
-    if ( env_numTaskServiceThreads )
-    {
-        return ::atoi( env_numTaskServiceThreads );
-    }
-    else if ( policy.numLoadingThreads().isSet() )
-    {
-        return osg::maximum( 1, policy.numLoadingThreads().get() );
-    }
-    else
-    {
-        return (int)osg::maximum( 1.0f, policy.numLoadingThreadsPerCore().get()
-                                  * (float)OpenThreads::GetNumberOfProcessors() );
-    }
-}
-
 //----------------------------------------------------------------------------
 
 TerrainOptions::TerrainOptions( const ConfigOptions& options ) :
 DriverConfigOptions( options ),
 _verticalScale( 1.0f ),
+_verticalOffset( 0.0f ),
 _heightFieldSampleRatio( 1.0f ),
 _minTileRangeFactor( 6.0 ),
 _combineLayers( true ),
-_loadingPolicy( LoadingPolicy() ),
-_compositingTech( COMPOSITING_AUTO ),
 _maxLOD( 23 ),
 _minLOD( 0 ),
 _firstLOD( 0 ),
@@ -122,8 +61,8 @@ TerrainOptions::getConfig() const
     else
         conf.updateIfSet( "sample_ratio", _heightFieldSampleRatio );
 
-    conf.updateObjIfSet( "loading_policy", _loadingPolicy );
     conf.updateIfSet( "vertical_scale", _verticalScale );
+    conf.updateIfSet( "vertical_offset", _verticalOffset );
     conf.updateIfSet( "min_tile_range_factor", _minTileRangeFactor );    
     conf.updateIfSet( "max_lod", _maxLOD );
     conf.updateIfSet( "min_lod", _minLOD );
@@ -138,12 +77,6 @@ TerrainOptions::getConfig() const
     conf.updateIfSet( "primary_traversal_mask", _primaryTraversalMask );
     conf.updateIfSet( "secondary_traversal_mask", _secondaryTraversalMask );
 
-    conf.updateIfSet( "compositor", "auto",             _compositingTech, COMPOSITING_AUTO );
-    conf.updateIfSet( "compositor", "texture_array",    _compositingTech, COMPOSITING_TEXTURE_ARRAY );
-    conf.updateIfSet( "compositor", "multitexture",     _compositingTech, COMPOSITING_MULTITEXTURE_GPU );
-    conf.updateIfSet( "compositor", "multitexture_ffp", _compositingTech, COMPOSITING_MULTITEXTURE_FFP );
-    conf.updateIfSet( "compositor", "multipass",        _compositingTech, COMPOSITING_MULTIPASS );
-
     //Save the filter settings
 	conf.updateIfSet("mag_filter","LINEAR",                _magFilter,osg::Texture::LINEAR);
     conf.updateIfSet("mag_filter","LINEAR_MIPMAP_LINEAR",  _magFilter,osg::Texture::LINEAR_MIPMAP_LINEAR);
@@ -169,12 +102,12 @@ TerrainOptions::fromConfig( const Config& conf )
     else
         conf.getIfSet( "sample_ratio", _heightFieldSampleRatio );
 
-    conf.getObjIfSet( "loading_policy", _loadingPolicy );
     conf.getIfSet( "vertical_scale", _verticalScale );
+    conf.getIfSet( "vertical_offset", _verticalOffset );
     conf.getIfSet( "min_tile_range_factor", _minTileRangeFactor );    
     conf.getIfSet( "max_lod", _maxLOD ); conf.getIfSet( "max_level", _maxLOD );
     conf.getIfSet( "min_lod", _minLOD ); conf.getIfSet( "min_level", _minLOD );
-    conf.getIfSet( "first_lod", _firstLOD );
+    conf.getIfSet( "first_lod", _firstLOD ); conf.getIfSet( "first_level", _firstLOD );
     conf.getIfSet( "lighting", _enableLighting );
     conf.getIfSet( "attenuation_distance", _attenuationDistance );
     conf.getIfSet( "lod_transition_time", _lodTransitionTimeSeconds );
@@ -185,13 +118,6 @@ TerrainOptions::fromConfig( const Config& conf )
     conf.getIfSet( "primary_traversal_mask", _primaryTraversalMask );
     conf.getIfSet( "secondary_traversal_mask", _secondaryTraversalMask );
 
-    conf.getIfSet( "compositor", "auto",             _compositingTech, COMPOSITING_AUTO );
-    conf.getIfSet( "compositor", "texture_array",    _compositingTech, COMPOSITING_TEXTURE_ARRAY );
-    conf.getIfSet( "compositor", "multitexture",     _compositingTech, COMPOSITING_MULTITEXTURE_GPU );
-    conf.getIfSet( "compositor", "multitexture_gpu", _compositingTech, COMPOSITING_MULTITEXTURE_GPU );
-    conf.getIfSet( "compositor", "multitexture_ffp", _compositingTech, COMPOSITING_MULTITEXTURE_FFP );
-    conf.getIfSet( "compositor", "multipass",        _compositingTech, COMPOSITING_MULTIPASS );
-
     //Load the filter settings
 	conf.getIfSet("mag_filter","LINEAR",                _magFilter,osg::Texture::LINEAR);
     conf.getIfSet("mag_filter","LINEAR_MIPMAP_LINEAR",  _magFilter,osg::Texture::LINEAR_MIPMAP_LINEAR);
diff --git a/src/osgEarth/Tessellator b/src/osgEarth/Tessellator
new file mode 100644
index 0000000..28f284a
--- /dev/null
+++ b/src/osgEarth/Tessellator
@@ -0,0 +1,45 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTH_TESSELLATOR_H
+#define OSGEARTH_TESSELLATOR_H 1
+
+#include <osgEarth/Common>
+
+#include <osg/Geometry>
+    
+namespace osgEarth {
+
+    /**
+     * Polygon tessellator using a modified ear clipping technique
+     */
+    class OSGEARTH_EXPORT Tessellator
+    {
+    public:
+        bool tessellateGeometry(osg::Geometry &geom);
+
+    protected:
+        osg::PrimitiveSet* tessellatePrimitive(osg::PrimitiveSet* primitive, osg::Vec3Array* vertices);
+        osg::PrimitiveSet* tessellatePrimitive(unsigned int first, unsigned int last, osg::Vec3Array* vertices);
+
+        bool isConvex(const osg::Vec3Array &vertices, const std::vector<unsigned int> &activeVerts, unsigned int cursor);
+        bool isEar(const osg::Vec3Array &vertices, const std::vector<unsigned int> &activeVerts, unsigned int cursor, bool &tradEar);
+    };
+} // namespace osgEarth
+
+#endif // OSGEARTH_TESSELLATOR_H
diff --git a/src/osgEarth/Tessellator.cpp b/src/osgEarth/Tessellator.cpp
new file mode 100644
index 0000000..8f0a2c8
--- /dev/null
+++ b/src/osgEarth/Tessellator.cpp
@@ -0,0 +1,401 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include <limits.h>
+
+#include <osgEarth/Tessellator>
+
+using namespace osgEarth;
+
+
+#define LC "[Tessellator] "
+
+/***************************************************/
+
+namespace
+{
+
+// Borrowed from osgUtil/DelaunayTriangulator.cpp
+// Compute the circumcircle of a triangle (only x and y coordinates are used),
+// return (Cx, Cy, r^2)
+inline osg::Vec3 compute_circumcircle(
+    const osg::Vec3 &a,
+    const osg::Vec3 &b,
+    const osg::Vec3 &c)
+{
+    float D =
+        (a.x() - c.x()) * (b.y() - c.y()) -
+        (b.x() - c.x()) * (a.y() - c.y());
+
+    float cx, cy, r2;
+
+    if(D==0.0)
+    {
+        // (Nearly) degenerate condition - either two of the points are equal (which we discount)
+        // or the three points are colinear. In this case we just determine the average of
+        // the three points as the centre for correctness, but squirt out a zero radius.
+        // This method will produce a triangulation with zero area, so we have to check later
+        cx = (a.x()+b.x()+c.x())/3.0;
+        cy = (a.y()+b.y()+c.y())/3.0;
+        r2 = 0.0;
+    }
+    else
+    {
+        cx =
+        (((a.x() - c.x()) * (a.x() + c.x()) +
+        (a.y() - c.y()) * (a.y() + c.y())) / 2 * (b.y() - c.y()) -
+        ((b.x() - c.x()) * (b.x() + c.x()) +
+        (b.y() - c.y()) * (b.y() + c.y())) / 2 * (a.y() - c.y())) / D;
+
+        cy =
+        (((b.x() - c.x()) * (b.x() + c.x()) +
+        (b.y() - c.y()) * (b.y() + c.y())) / 2 * (a.x() - c.x()) -
+        ((a.x() - c.x()) * (a.x() + c.x()) +
+        (a.y() - c.y()) * (a.y() + c.y())) / 2 * (b.x() - c.x())) / D;
+
+      //  r2 = (c.x() - cx) * (c.x() - cx) + (c.y() - cy) * (c.y() - cy);
+        // the return r square is compared with r*r many times in an inner loop
+        // so for efficiency use the inefficient sqrt once rather than 30* multiplies later.
+        r2 = sqrt((c.x() - cx) * (c.x() - cx) + (c.y() - cy) * (c.y() - cy));
+    }
+    return osg::Vec3(cx, cy, r2);
+}
+
+// Test whether a point (only the x and y coordinates are used) lies inside
+// a circle; the circle is passed as a vector: (Cx, Cy, r).
+
+inline bool point_in_circle(const osg::Vec3 &point, const osg::Vec3 &circle)
+{
+    float r2 =
+        (point.x() - circle.x()) * (point.x() - circle.x()) +
+        (point.y() - circle.y()) * (point.y() - circle.y());
+    return r2 <= circle.z()*circle.z();
+//    return r2 <= circle.z();
+}
+
+int checkCCW(double x1, double y1, double x2, double y2, double x3, double y3)
+{
+    double v = (x2 - x1) * (y3 - y1) - (y2 - y1) * (x3 - x1);
+    return (v == 0.0 ? 0 : (v > 0.0 ? 1 : -1));
+}
+
+//bool point_in_tri(const osg::Vec3 &p, const osg::Vec3 &t1, const osg::Vec3 &t2, const osg::Vec3 &t3)
+bool point_in_tri(double xp, double yp, double x1, double y1, double x2, double y2, double x3, double y3)
+{
+  int t1 = checkCCW(xp, yp, x1, y1, x2, y2);
+  int t2 = checkCCW(xp, yp, x2, y2, x3, y3);
+  int t3 = checkCCW(xp, yp, x3, y3, x1, y1);
+
+  return t1 == t2 && t2 == t3;
+}
+
+struct TriIndices
+{
+    unsigned int a;
+    unsigned int b;
+    unsigned int c;
+
+    TriIndices(unsigned int p1, unsigned int p2, unsigned int p3)
+      : a(p1), b(p2), c(p3)
+    {
+      //nop
+    }
+};
+
+typedef std::vector<TriIndices> TriList;
+
+}
+
+bool
+Tessellator::tessellateGeometry(osg::Geometry &geom)
+{
+    osg::Vec3Array* vertices = dynamic_cast<osg::Vec3Array*>(geom.getVertexArray());
+
+    if (!vertices || vertices->empty() || geom.getPrimitiveSetList().empty()) return false;
+
+    // copy the original primitive set list
+    osg::Geometry::PrimitiveSetList originalPrimitives = geom.getPrimitiveSetList();
+
+    // clear the primitive sets
+    unsigned int nprimsetoriginal= geom.getNumPrimitiveSets();
+    if (nprimsetoriginal) geom.removePrimitiveSet(0, nprimsetoriginal);
+
+    bool success = true;
+    for (unsigned int i=0; i < originalPrimitives.size(); i++)
+    {
+        osg::ref_ptr<osg::PrimitiveSet> primitive = originalPrimitives[i].get();
+
+        if (primitive->getMode()==osg::PrimitiveSet::POLYGON || primitive->getMode()==osg::PrimitiveSet::LINE_LOOP)
+        {
+            if (primitive->getType()==osg::PrimitiveSet::DrawArrayLengthsPrimitiveType)
+            {
+                osg::DrawArrayLengths* drawArrayLengths = static_cast<osg::DrawArrayLengths*>(primitive.get());
+                unsigned int first = drawArrayLengths->getFirst();
+                for(osg::DrawArrayLengths::iterator itr=drawArrayLengths->begin();
+                    itr!=drawArrayLengths->end();
+                    ++itr)
+                {
+                    unsigned int last = first + *itr;
+                    osg::PrimitiveSet* newPrimitive = tessellatePrimitive(first, last, vertices);
+                    if (newPrimitive)
+                    {
+                        geom.addPrimitiveSet(newPrimitive);
+                    }
+                    else
+                    {
+                        // tessellation failed, add old primitive set back
+                        geom.addPrimitiveSet(primitive);
+                        success = false;
+                    }
+
+                    first = last;
+                }
+            }
+            else
+            {
+                if (primitive->getNumIndices()>=3)
+                {
+                    osg::PrimitiveSet* newPrimitive = tessellatePrimitive(primitive.get(), vertices);
+                    if (newPrimitive)
+                    {
+                        geom.addPrimitiveSet(newPrimitive);
+                    }
+                    else
+                    {
+                        // tessellation failed, add old primitive set back
+                        geom.addPrimitiveSet(primitive);
+                        success = false;
+                    }
+                }
+            }
+        }
+        else
+        {
+            //
+            // TODO: handle more primitive modes
+            //
+        }
+    }
+
+    return success;
+}
+
+
+osg::PrimitiveSet*
+Tessellator::tessellatePrimitive(osg::PrimitiveSet* primitive, osg::Vec3Array* vertices)
+{
+    //
+    //TODO: Hnadle more primitive types
+    //
+
+    switch(primitive->getType())
+    {
+    case(osg::PrimitiveSet::DrawArraysPrimitiveType):
+        {
+            osg::DrawArrays* drawArray = static_cast<osg::DrawArrays*>(primitive);
+            unsigned int first = drawArray->getFirst();
+            unsigned int last = first+drawArray->getCount();
+            return tessellatePrimitive(first, last, vertices);
+         }
+    default:
+        OE_NOTICE << LC << "Primitive type " << primitive->getType()<< " not handled" << std::endl;
+        break;
+    }
+
+    return 0L;
+}
+
+osg::PrimitiveSet*
+Tessellator::tessellatePrimitive(unsigned int first, unsigned int last, osg::Vec3Array* vertices)
+{
+    std::vector<unsigned int> activeVerts;
+    activeVerts.reserve( last-first+1 );
+    for (unsigned int i=first; i < last; i++)
+    {
+        activeVerts.push_back(i);
+    }
+
+    TriList tris;
+    tris.reserve( activeVerts.size() );
+
+    bool success = true;
+    unsigned int cursor = 0;
+    unsigned int cursor_start = 0;
+    unsigned int tradCursor = UINT_MAX;
+    while (activeVerts.size() > 3)
+    {
+        if (isConvex(*vertices, activeVerts, cursor))
+        {
+            bool tradEar = tradCursor != UINT_MAX;
+            if (isEar(*vertices, activeVerts, cursor, tradEar))
+            {
+                unsigned int prev = cursor == 0 ? activeVerts.size() - 1 : cursor - 1;
+                unsigned int next = cursor == activeVerts.size() - 1 ? 0 : cursor + 1;
+
+                tris.push_back(TriIndices(activeVerts[prev], activeVerts[cursor], activeVerts[next]));
+              
+                activeVerts.erase(activeVerts.begin() + cursor);
+                if (cursor >= activeVerts.size())
+                    cursor = 0;
+
+                cursor_start = cursor;
+                tradCursor = UINT_MAX;
+
+                continue;
+            }
+            
+            if (tradEar && tradCursor == UINT_MAX)
+            {
+                tradCursor = cursor;
+            }
+        }
+
+        cursor++;
+        if (cursor >= activeVerts.size())
+            cursor = 0;
+
+        if (cursor == cursor_start)
+        {
+            if (tradCursor != UINT_MAX)
+            {
+                // No ear was found with circumcircle test, use first traditional ear found
+
+                cursor = tradCursor;
+
+                unsigned int prev = cursor == 0 ? activeVerts.size() - 1 : cursor - 1;
+                unsigned int next = cursor == activeVerts.size() - 1 ? 0 : cursor + 1;
+
+                tris.push_back(TriIndices(activeVerts[prev], activeVerts[cursor], activeVerts[next]));
+                  
+                activeVerts.erase(activeVerts.begin() + cursor);
+                if (cursor >= activeVerts.size())
+                    cursor = 0;
+
+                cursor_start = cursor;
+                tradCursor = UINT_MAX;
+
+                continue;
+            }
+            else
+            {
+                success = false;
+                break;
+            }
+        }
+    }
+
+    if (success)
+    {
+        if (activeVerts.size() == 3)
+        {
+            // add last tri
+            tris.push_back(TriIndices(activeVerts[0], activeVerts[1], activeVerts[2]));
+        }
+
+        osg::DrawElementsUInt* triElements = new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, 0);
+        triElements->reserve( tris.size() * 3 );
+        for (TriList::const_iterator it = tris.begin(); it != tris.end(); ++it)
+        {
+            triElements->push_back(it->a);
+            triElements->push_back(it->b);
+            triElements->push_back(it->c);
+        }
+
+        return triElements;
+    }
+    else
+    {
+        //TODO: handle?
+        OE_DEBUG << LC << "Tessellation failed!" << std::endl;
+    }
+
+    return 0L;
+}
+
+
+bool
+Tessellator::isConvex(const osg::Vec3Array &vertices, const std::vector<unsigned int> &activeVerts, unsigned int cursor)
+{
+    unsigned int prev = cursor == 0 ? activeVerts.size() - 1 : cursor - 1;
+    unsigned int next = cursor == activeVerts.size() - 1 ? 0 : cursor + 1;
+
+    unsigned int a = activeVerts[prev];
+    unsigned int b = activeVerts[cursor];
+    unsigned int c = activeVerts[next];
+
+    osg::Vec3d dataA;
+    dataA._v[0] = vertices[a][0];
+    dataA._v[1] = vertices[a][1];
+    dataA._v[2] = vertices[a][2];
+
+    osg::Vec3d dataB;
+    dataB._v[0] = vertices[b][0];
+    dataB._v[1] = vertices[b][1];
+    dataB._v[2] = vertices[b][2];
+
+    osg::Vec3d dataC;
+    dataC._v[0] = vertices[c][0];
+    dataC._v[1] = vertices[c][1];
+    dataC._v[2] = vertices[c][2];
+
+    //http://www.gamedev.net/topic/542870-determine-which-side-of-a-line-a-point-is/#entry4500667
+    //(Bx - Ax) * (Cy - Ay) - (By - Ay) * (Cx - Ax)
+
+    return (dataB.x() - dataA.x()) * (dataC.y() - dataA.y()) - (dataB.y() - dataA.y()) * (dataC.x() - dataA.x()) > 0.0;
+}
+
+bool
+Tessellator::isEar(const osg::Vec3Array &vertices, const std::vector<unsigned int> &activeVerts, unsigned int cursor, bool &tradEar)
+{
+    unsigned int prev = cursor == 0 ? activeVerts.size() - 1 : cursor - 1;
+    unsigned int next = cursor == activeVerts.size() - 1 ? 0 : cursor + 1;
+
+    osg::Vec3d cc(compute_circumcircle(vertices[activeVerts[prev]], vertices[activeVerts[cursor]], vertices[activeVerts[next]]));
+
+    unsigned int nextNext = next == activeVerts.size() - 1 ? 0 : next + 1;
+
+		// Check every point not part of the ear
+    bool circEar = true;
+		while( nextNext != prev )
+		{
+        unsigned int p = activeVerts[nextNext];
+
+        if (circEar && point_in_circle(vertices[p], cc))
+        {
+            circEar = false;
+
+            if (tradEar)
+              return false;
+        }
+
+        if (!tradEar &&
+            point_in_tri(vertices[p].x(), vertices[p].y(),
+                         vertices[activeVerts[prev]].x(), vertices[activeVerts[prev]].y(),
+                         vertices[activeVerts[cursor]].x(), vertices[activeVerts[cursor]].y(),
+                         vertices[activeVerts[next]].x(), vertices[activeVerts[next]].y()))
+			  {
+            return false;
+			  }
+
+			  nextNext = nextNext == activeVerts.size() - 1 ? 0 : nextNext + 1;
+		}
+
+    tradEar = true;
+
+		return circEar;
+}
\ No newline at end of file
diff --git a/src/osgEarth/TextureCompositor b/src/osgEarth/TextureCompositor
index c55d4d7..f03b99f 100644
--- a/src/osgEarth/TextureCompositor
+++ b/src/osgEarth/TextureCompositor
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -20,135 +20,12 @@
 #define OSGEARTH_TEXTURE_COMPOSITOR_H 1
 
 #include <osgEarth/Common>
-#include <osgEarth/GeoData>
 #include <osgEarth/ThreadingUtils>
-#include <osgEarth/TerrainOptions>
-#include <osg/StateSet>
-#include <osg/Program>
 
 namespace osgEarth
 {
-    class ImageLayer;
-    class TileKey;
-    struct MapModelChange;
-
-    /**
-     * Tracks the usage of texture slots and their rendering order for a map.
-     *
-     * A "slot" is a home for a texture layer. For example, in a multitexturing setup, a
-     * slot corresponds to a texture image unit in the hardware. In a texture_array setup,
-     * a slot is one "slice" of the texture array.
-     *
-     * A single layer can occupy more than one slot. (Specifically, this is the case when
-     * LOD Blending is enabled on that layer -- the layer will require 2 slots in order to
-     * blend the images together in the shader.) In this case, we refer to the first slot
-     * as the "primary" slot, and any additional slots belonging to the same layer as 
-     * "secondary" slots.
-     */
-    class OSGEARTH_EXPORT TextureLayout
-    {        
-    public:
-        // vector of texture slots, each slot containing the UID of the image layer in that slot
-        // or -1 if the slot is empty.
-        typedef std::vector<UID> TextureSlotVector;
-
-        // rendering order of texture slots- each element is an index into the TextureSlotVector.
-        typedef std::vector<int> RenderOrderVector;
-
-    public:
-        TextureLayout();
-
-        /** dtor */
-        virtual ~TextureLayout() { }
-
-        /**
-         * Gets a texture slot corresponding to the layer UID. There may be more than one,
-         * the "which" parameter allows you to select one in particular. You can also limit
-         * the number of slots to search.
-         */
-        int getSlot( UID layerUID, unsigned which =0, unsigned maxSlotsToSearch =~0 ) const;
-
-        /** Gets the render order index of the layer with the given UID */
-        int getOrder( UID layerUID ) const;
-
-        /** Gets the index of the highest populated slot. */
-        int getMaxUsedSlot() const;
-
-        /** Whether the indicated slot is available for use */
-        bool isSlotAvailable( int slot ) const;
-
-        /** Accesses a vector that maps layer UIDs to texture slots. */
-        const TextureSlotVector& getTextureSlots() const { return _slots; }
-
-        /** Access a vector that specifies the rendering order of texture slots. */
-        const RenderOrderVector& getRenderOrder() const { return _order; }
-
-        /** Returns "true" if the layout contains at least one secondary slot allocation. */
-        bool containsSecondarySlots( unsigned maxSlotsToSearch = ~0) const;
-
-        /** whether LOD blending is enabled on a layer */
-        bool isBlendingEnabled( UID layerUID ) const;
-
-    protected:
-        friend class TextureCompositor;
-
-        void applyMapModelChange( 
-          const MapModelChange& change, 
-          bool reserveSeconarySlotIfNecessary,
-          bool disableLodBlending );
-
-        void setReservedSlots( const std::set<int>& reservedSlots );
-
-    protected:
-        TextureSlotVector  _slots;
-        RenderOrderVector  _order;
-        bool               _textureImageUnitPerSlot;
-        std::set<int>      _reservedSlots;
-        std::map<UID,bool> _lodBlending;
-
-        void assignPrimarySlot( ImageLayer* layer, int index );
-        void assignSecondarySlot( ImageLayer* layer );
-    };
-
-    //-----------------------------------------------------------------------
-
     /**
-     * Base interface for a particular texture composition implementation
-     *
-     * TODO: document these methods
-     */
-    class OSGEARTH_EXPORT TextureCompositorTechnique : public osg::Referenced
-    {
-    public:
-        virtual bool requiresUnitTextureSpace() const =0;
-
-        virtual bool usesShaderComposition() const =0;
-
-        virtual bool blendingRequiresSecondarySlot() const { return false; }
-
-        virtual bool supportsLayerUpdate() const { return false; }
-
-        virtual void updateMasterStateSet( osg::StateSet* stateSet, const TextureLayout& layout ) const { }
-
-        virtual GeoImage prepareImage( const GeoImage& image, const GeoExtent& tileExtent ) const { return image; }
-
-        virtual GeoImage prepareSecondaryImage( const GeoImage& image, const GeoExtent& tileExtent ) const { return image; }
-
-        virtual void applyLayerUpdate( osg::StateSet* stateSet, UID layerUID, const GeoImage& preparedImage, const TileKey& tileKey, const TextureLayout& layout, osg::StateSet* parentStateSet ) const { }
-
-        virtual void applyLayerRemoval( osg::StateSet* stateSet, UID layerUID ) const { }
-
-        virtual osg::Shader* createSamplerFunction( 
-            UID layerUID, 
-            const std::string& functionName, 
-            osg::Shader::Type type, 
-            const TextureLayout& layout ) const { return 0L; }
-    };
-
-    //-----------------------------------------------------------------------
-
-    /**
-     * Utility class that composites texture data for use by a terrain engine.
+     * Utility class that tracks texture allocation units
      */
     class OSGEARTH_EXPORT TextureCompositor : public osg::Referenced
     {
@@ -156,141 +33,25 @@ namespace osgEarth
         /**
          * Constructs a new compositor.
          */
-        TextureCompositor( const TerrainOptions& options );
+        TextureCompositor();
 
         /** dtor */
         virtual ~TextureCompositor() { }
 
         /**
-         * Sets a custom technique to use.
-         */
-        void setTechnique( TextureCompositorTechnique* tech );
-
-        /**
-         * Gets the actual technique selected by the compositor. This might not be the same
-         * as the requested technique, since it will validate against system capabilities and
-         * automatically "fall back" on lesser techniques if necessary.
-         */
-        const TerrainOptions::CompositingTechnique& getTechnique() const { return _tech; }
-
-        /**
-         * Applies a map model change action to the texture layout contained within this compositor.
-         * You should call this any time the map model change happens.
-         */
-        void applyMapModelChange( const MapModelChange& change );
-
-        /**
-         * Returns true if the compositor implementation supports the ability to update an individual
-         * image layer (via prepareLayerUpdate and applyLayerUpdate).
-         */
-        bool supportsLayerUpdate() const;
-
-        /**
-         * Prepares a new image for incorporation into the texture composite. Usually you will call this 
-         * method when you are updating a single layer AFTER having originally created the stateset
-         * (via createStateSet). It is safe to call this method from any thread. You can thereafter take
-         * the result and pass it to updateLayer. These two methods are separated so that you can call
-         * this one from any thread (it is guaranteed to be thread-safe) and then use updateLayer to 
-         * updaet a live scene graph if necessary.
-         */
-        GeoImage prepareImage( const GeoImage& image, const GeoExtent& tileExtent ) const;
-
-        /**
-         * Like prepareImage, but prepares it for use as a secondary texture (for LOD blending).
-         */
-        GeoImage prepareSecondaryImage( const GeoImage& image, const GeoExtent& tileExtent ) const;
-
-        /**
-         * Updates a stateset's texture composition with an image. Typically this will be the image
-         * returned from prepareImage(), but it doesn't have to be. Note: if the stateset is live in
-         * the scene graph, be sure to only call this method from UPDATE trav.
-         */
-        void applyLayerUpdate(
-            osg::StateSet*  stateSet,
-            UID             layerUID,
-            const GeoImage& preparedImage,
-            const TileKey&  tileKey,
-            osg::StateSet*  parentStateSet ) const;
-
-        /**
-         * Updates a stateset's texture composition based on the information that a layer has been
-         * removed.
-         */
-        void applyLayerRemoval( osg::StateSet* stateSet, UID layerUID ) const;
-
-        /**
-         * Assigns a texture coordinate array to a geometry, putting it in the proper
-         * texture slot according to the compositor's layout.
-         */
-        void assignTexCoordArray(
-            osg::Geometry*  geom,
-            UID             layerUID,
-            osg::Vec2Array* texCoords ) const;
-
-        /**
-         * Whether the texture composition technique uses a single, unit (0..1) texture coordinate
-         * space (true), or a separate texture coordinate space per layer (false). This provides a
-         * hint to whomever is generating the texture coordinates as to what they need to do to 
-         * support the current technique.
-         */
-        bool requiresUnitTextureSpace() const;
-
-        /**
-         * Whether the composition technique uses GLSL shader composition.
-         */
-        bool usesShaderComposition() const;
-
-        /**
-         * Updates a state set with attributes required to support the technique.
-         * Call this whenever the composition of the image layer stack changes in order to update
-         * applicable global uniforms, texture attributes, and so forth.
-         */
-        void updateMasterStateSet( osg::StateSet* stateSet ) const;
-
-        /**
          * Requests a texture image unit that is not in use, and marks is as reserved. You can release
          * the reserved texture image unit by calling releaseTextureImageUnit().
          */
-        bool reserveTextureImageUnit( int& out_unit );
+        bool reserveTextureImageUnit(int& out_unit);
 
         /**
          * Releases a reserved texture image unit previously returned by reserveTextureImageUnit.
          */
-        void releaseTextureImageUnit( int unit );
-
-        /**
-         * Gets the rendering order of the layer with the specified UID.
-         */
-        int getRenderOrder( UID layerUID ) const;
-
-        /**
-         * Creates a shader component that implements texture sampling for the layer with 
-         * the specified layer UID. You can use this is a custom shader to sample texels from
-         * one of osgEarth's Map image layers.
-         *
-         *    vec4 function_name()
-         */
-        osg::Shader* createSamplerFunction( UID layerUID, const std::string& functionName, osg::Shader::Type type ) const;
-
-        /**
-         * Gets the terrain options for this TextureCompositor
-         */
-        TerrainOptions& getOptions() { return _options;}
+        void releaseTextureImageUnit(int unit);
 
     private:
-        void init();
-
-        OpenThreads::Mutex                       _initMutex;
-        TerrainOptions::CompositingTechnique     _tech;
-        TerrainOptions                           _options;
-        bool                                     _forceTech;
-        osg::ref_ptr<osg::Program>               _program;
-        osg::ref_ptr<TextureCompositorTechnique> _impl;
-
-        TextureLayout _layout;
-        Threading::ReadWriteMutex _layoutMutex;
-
-        std::set<int> _reservedUnits;
+        Threading::Mutex _reservedUnitsMutex;
+        std::set<int>    _reservedUnits;
     };
 }
 
diff --git a/src/osgEarth/TextureCompositor.cpp b/src/osgEarth/TextureCompositor.cpp
index eb98b87..872e65a 100755
--- a/src/osgEarth/TextureCompositor.cpp
+++ b/src/osgEarth/TextureCompositor.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -18,556 +18,41 @@
  */
 
 #include <osgEarth/TextureCompositor>
-#include <osgEarth/TextureCompositorTexArray>
-#include <osgEarth/TextureCompositorMulti>
-#include <osgEarth/Capabilities>
-#include <osgEarth/ImageUtils>
 #include <osgEarth/Registry>
-#include <osgEarth/Map>
-#include <osgEarth/MapModelChange>
-#include <osg/Texture2DArray>
-#include <osg/Texture2D>
-#include <osg/Texture3D>
-#include <vector>
+#include <osgEarth/Capabilities>
 
 using namespace osgEarth;
-using namespace OpenThreads;
 
 #define LC "[TextureCompositor] "
 
-//---------------------------------------------------------------------------
 
-TextureLayout::TextureLayout()
+TextureCompositor::TextureCompositor()
 {
     //nop
 }
 
-int
-TextureLayout::getSlot( UID layerUID, unsigned which, unsigned maxSlotsToSearch ) const
-{
-    for( unsigned slot = 0; slot < _slots.size() && slot < maxSlotsToSearch; ++slot )
-    {
-        if ( _slots[slot] == layerUID )
-        {
-            if ( which == 0 )
-                return slot;
-            else
-                --which;
-        }
-    }
-    return -1;
-}
-
-int 
-TextureLayout::getOrder( UID layerUID ) const
-{
-    int slot = getSlot( layerUID, 0 );
-    RenderOrderVector::const_iterator i = std::find( _order.begin(), _order.end(), slot );
-    return i != _order.end() ? (int)(i-_order.begin()) : -1;
-}
-
-int
-TextureLayout::getMaxUsedSlot() const
-{
-    for( int i = _slots.size()-1; i >= 0; --i )
-        if ( i >= 0 )
-            return i;
-    return -1;
-}
-
-void
-TextureLayout::assignPrimarySlot( ImageLayer* layer, int orderIndex )
-{
-    int slot = -1;
-
-    bool found = false;
-    for( TextureSlotVector::iterator i = _slots.begin(); i != _slots.end() && !found; ++i )
-    {
-        slot = (int)(i - _slots.begin());
-
-        // negative UID means the slot is empty.
-        bool slotAvailable = (*i < 0) && (_reservedSlots.find(slot) == _reservedSlots.end());
-        if ( slotAvailable )
-        {  
-            *i = layer->getUID();
-            found = true;
-            break;
-        }
-    }
-
-    if ( !found )
-    {
-        // put the UID in the next available slot (that's not reserved).
-        while( _reservedSlots.find(_slots.size()) != _reservedSlots.end() )
-            _slots.push_back( -1 );
-
-        slot = _slots.size();
-        _slots.push_back( layer->getUID() );     
-    }
-
-    // record the render order of this slot:
-    if ( orderIndex >= (int)_order.size() )
-    {
-        _order.resize( orderIndex + 1, -1 );
-        _order[orderIndex] = slot;
-    }
-    else
-    {
-        if (_order[orderIndex] == -1)
-            _order[orderIndex] = slot;
-        else
-            _order.insert(_order.begin() + orderIndex, slot);
-    }
-
-
-
-    OE_DEBUG << LC << "Allocated SLOT " << slot << "; primary slot for layer \"" << layer->getName() << "\"" << std::endl;
-}
-
-void
-TextureLayout::assignSecondarySlot( ImageLayer* layer )
-{
-    int slot = -1;
-    bool found = false;
-
-    for( TextureSlotVector::iterator i = _slots.begin(); i != _slots.end() && !found; ++i )
-    {
-        slot = (int)(i - _slots.begin());
-
-        // negative UID means the slot is empty.
-        bool slotAvailable = (*i < 0) && (_reservedSlots.find(slot) == _reservedSlots.end());
-        if ( slotAvailable )
-        {
-            // record this UID in the new slot:
-            *i = layer->getUID();
-            found = true;
-            break;
-        }
-    }
-
-    if ( !found )
-    {
-        // put the UID in the next available slot (that's not reserved).
-        while( _reservedSlots.find(_slots.size()) != _reservedSlots.end() )
-            _slots.push_back( -1 );
-
-        slot = _slots.size();
-        _slots.push_back( layer->getUID() );
-    }
-
-    OE_INFO << LC << "Allocated SLOT " << slot << "; secondary slot for layer \"" << layer->getName() << "\"" << std::endl;
-}
-
-void
-TextureLayout::applyMapModelChange(const MapModelChange& change, 
-                                   bool reserveSeconarySlotIfNecessary,
-                                   bool disableLODBlending )
-{
-    if ( change.getAction() == MapModelChange::ADD_IMAGE_LAYER )
-    {
-        assignPrimarySlot( change.getImageLayer(), change.getFirstIndex() );
-
-        // did the layer specify LOD blending (and is it supported?)
-        bool blendingOn = 
-          !disableLODBlending &&
-          change.getImageLayer()->getImageLayerOptions().lodBlending() == true;
-    
-        _lodBlending[ change.getImageLayer()->getUID() ] = blendingOn;
-
-        if ( blendingOn && reserveSeconarySlotIfNecessary )
-        {
-            assignSecondarySlot( change.getImageLayer() );
-        }
-    }
-
-    else if ( change.getAction() == MapModelChange::REMOVE_IMAGE_LAYER )
-    {
-        for( int which = 0; which <= 1; ++which )
-        {
-            int slot = getSlot( change.getLayer()->getUID(), which );
-            if ( slot < 0 )
-                break;
-
-            _slots[slot] = -1;
-
-            if ( which == 0 ) // primary slot; remove from render order:
-            {
-                for( RenderOrderVector::iterator j = _order.begin(); j != _order.end(); )
-                {
-                    if ( *j == slot )
-                        j = _order.erase( j );
-                    else
-                        ++j;
-                }
-            }
-        }
-    }
-
-    else if ( change.getAction() == MapModelChange::MOVE_IMAGE_LAYER )
-    {
-        int fromIndex = getOrder( change.getLayer()->getUID() );
-        int toIndex = change.getSecondIndex();
-
-        if ( fromIndex != toIndex )
-        {
-            int slot = _order[fromIndex];
-            _order.erase( _order.begin() + fromIndex );
-            _order.insert( _order.begin() + toIndex, slot );
-        }
-    }
-
-    //OE_INFO << LC << "Layout Slots: " << std::endl;
-    //for( int i=0; i<_slots.size(); ++i )
-    //    OE_INFO << LC << "  Slot " << i << ": uid=" << _slots[i] << ", order=" << getOrder(_slots[i]) << std::endl;
-    //OE_INFO << LC << "Layout Order: " << std::endl;
-    //for( int i=0; i<_order.size(); ++i )
-    //    OE_INFO << LC << "  Ordr " << i << ": slot=" << _order[i] << std::endl;
-}
-
-void
-TextureLayout::setReservedSlots( const std::set<int>& reservedSlots )
-{
-    _reservedSlots = reservedSlots;
-}
-
-bool
-TextureLayout::isSlotAvailable( int i ) const
-{
-    if ( (i < (int)_slots.size() && _slots[i] < 0) || i >= (int)_slots.size() )
-    {
-        if ( _reservedSlots.find(i) == _reservedSlots.end() )
-        {
-            return true;
-        }
-    }
-    return false;
-}
-
-bool
-TextureLayout::containsSecondarySlots( unsigned maxSlotsToSearch ) const
-{
-    for( int slot = 0; slot < (int)_slots.size() && slot < (int)maxSlotsToSearch; ++slot )
-    {
-        UID uid = _slots[slot];
-        if ( getSlot(uid, 0) != slot )
-            return true;
-    }
-    return false;
-}
-
 bool
-TextureLayout::isBlendingEnabled( UID layerUID ) const
+TextureCompositor::reserveTextureImageUnit(int& out_unit)
 {
-    std::map<UID,bool>::const_iterator i = _lodBlending.find(layerUID);
-    return i != _lodBlending.end() ? i->second : false;
-}
-
-//---------------------------------------------------------------------------
-
-TextureCompositor::TextureCompositor(const TerrainOptions& options) :
-osg::Referenced( true ),
-_tech( options.compositingTechnique().value() ),
-_options( options ),
-_forceTech( false )
-{
-    // for debugging:
-    if ( _tech == TerrainOptions::COMPOSITING_AUTO && ::getenv( "OSGEARTH_COMPOSITOR_TECH" ) )
-    {
-        TerrainOptions::CompositingTechnique oldTech = _tech;
-        std::string t( ::getenv( "OSGEARTH_COMPOSITOR_TECH" ) );
-        if      ( t == "TEXTURE_ARRAY" )    _tech = TerrainOptions::COMPOSITING_TEXTURE_ARRAY;
-        else if ( t == "MULTITEXTURE_GPU" ) _tech = TerrainOptions::COMPOSITING_MULTITEXTURE_GPU;
-        else if ( t == "MULTIPASS" )        _tech = TerrainOptions::COMPOSITING_MULTIPASS;
-        if ( oldTech != _tech )
-            _forceTech = true;
-    }
-
-    init();
-}
-
-bool
-TextureCompositor::reserveTextureImageUnit( int& out_unit )
-{
-    //todo: move this into the impls!!
-
     out_unit = -1;
-
-    //TODO: this only supports GPU texturing....
     unsigned maxUnits = osgEarth::Registry::instance()->getCapabilities().getMaxGPUTextureUnits();
-
-    if ( _tech == TerrainOptions::COMPOSITING_MULTITEXTURE_GPU )
-    {
-        Threading::ScopedWriteLock exclusiveLock( _layoutMutex );
-
-        for( unsigned i=0; i<maxUnits; ++i )
-        {
-            if ( _layout.isSlotAvailable(i) )
-            {
-                out_unit = i;
-                _reservedUnits.insert( i );
-                _layout.setReservedSlots( _reservedUnits ); // in multitexture, slots == units
-                return true;
-            }
-        }
-
-        // all taken, return false.
-        return false;
-    }
-
-    else if ( _tech == TerrainOptions::COMPOSITING_TEXTURE_ARRAY )
-    {
-        // texture array reserved slots 0 and 1 (for primary and blending)
-        for( unsigned i=2; i<maxUnits; ++i ) // 0 and 1 always reserved.
-        {
-            if ( _reservedUnits.find( i ) == _reservedUnits.end() )
-            {
-                out_unit = i;
-                _reservedUnits.insert( i );
-                return true;
-            }
-        }
-
-        // all taken, return false.
-        return false;
-    }
-
-    else // multipass or USER .. just simple reservations.
+    
+    Threading::ScopedMutexLock exclusiveLock( _reservedUnitsMutex );
+    for( unsigned i=0; i<maxUnits; ++i )
     {
-        // search for an unused unit.
-        for( unsigned i=0; i<maxUnits; ++i )
+        if (_reservedUnits.find(i) == _reservedUnits.end())
         {
-            if ( _reservedUnits.find( i ) == _reservedUnits.end() )
-            {
-                out_unit = i;
-                _reservedUnits.insert( i );
-                OE_INFO << LC << "Reserved image unit " << i << std::endl;
-                return true;
-            }
+            _reservedUnits.insert( i );
+            out_unit = i;
+            return true;
         }
-
-        // all taken, return false.
-        return false;
     }
+    return false;
 }
 
 void
-TextureCompositor::releaseTextureImageUnit( int unit )
+TextureCompositor::releaseTextureImageUnit(int unit)
 {
+    Threading::ScopedMutexLock exclusiveLock( _reservedUnitsMutex );
     _reservedUnits.erase( unit );
-    OE_INFO << LC << "Released image unit " << unit << std::endl;
-
-    if ( _tech == TerrainOptions::COMPOSITING_MULTITEXTURE_GPU )
-    {
-        Threading::ScopedWriteLock exclusiveLock( _layoutMutex );
-        _layout.setReservedSlots( _reservedUnits );
-    }
-}
-
-void
-TextureCompositor::applyMapModelChange( const MapModelChange& change )
-{
-    // verify it's actually an image layer
-    ImageLayer* layer = change.getImageLayer();
-    if ( !layer )
-        return;
-
-    Threading::ScopedWriteLock exclusiveLock( _layoutMutex );
-
-    // LOD blending does not work with mercator fast path texture mapping.
-    bool disableLODBlending =
-      layer->getProfile() &&
-      layer->getProfile()->getSRS()->isSphericalMercator() &&
-      _options.enableMercatorFastPath() == true;
-
-    // Let the use know why they aren't getting LOD blending!
-    if ( disableLODBlending && layer->getImageLayerOptions().lodBlending() == true )
-    {
-        OE_WARN << LC << "LOD blending disabled for layer \"" << layer->getName()
-            << "\" because it uses Mercator fast-path rendering" << std::endl;
-    }
-
-    _layout.applyMapModelChange(
-        change, 
-        _impl.valid() ? _impl->blendingRequiresSecondarySlot() : false,
-        disableLODBlending );
-}
-
-bool
-TextureCompositor::supportsLayerUpdate() const
-{
-    return _impl.valid() ? _impl->supportsLayerUpdate() : false;
-}
-
-GeoImage
-TextureCompositor::prepareImage( const GeoImage& image, const GeoExtent& tileExtent ) const
-{
-    return _impl.valid() ? _impl->prepareImage( image, tileExtent ) : GeoImage::INVALID;
-}
-
-GeoImage
-TextureCompositor::prepareSecondaryImage( const GeoImage& image, const GeoExtent& tileExtent ) const
-{
-    return _impl.valid() ? _impl->prepareSecondaryImage( image, tileExtent ) : GeoImage::INVALID;
-}
-
-void
-TextureCompositor::applyLayerUpdate(osg::StateSet* stateSet,
-                                    UID layerUID,
-                                    const GeoImage& preparedImage,
-                                    const TileKey& tileKey,
-                                    osg::StateSet* parentStateSet) const
-{
-    if ( _impl.valid() )
-    {
-        Threading::ScopedReadLock sharedLock( const_cast<TextureCompositor*>(this)->_layoutMutex );
-        _impl->applyLayerUpdate( stateSet, layerUID, preparedImage, tileKey,
-                                 _layout,  parentStateSet );
-    }
-}
-
-void
-TextureCompositor::applyLayerRemoval(osg::StateSet* stateSet,
-                                     UID layerUID ) const
-{
-    if ( _impl.valid() )
-        _impl->applyLayerRemoval( stateSet, layerUID );
-}
-
-bool
-TextureCompositor::requiresUnitTextureSpace() const
-{
-    return _impl.valid() ? _impl->requiresUnitTextureSpace() : false;
-}
-
-bool
-TextureCompositor::usesShaderComposition() const
-{
-    return _impl.valid() ? _impl->usesShaderComposition() : true;
-}
-
-void
-TextureCompositor::updateMasterStateSet( osg::StateSet* stateSet ) const
-{
-    if ( _impl.valid() )
-    {
-        Threading::ScopedReadLock sharedLock( const_cast<TextureCompositor*>(this)->_layoutMutex );
-        _impl->updateMasterStateSet( stateSet, _layout );
-    }
-}
-
-void
-TextureCompositor::assignTexCoordArray(osg::Geometry* geom,
-                                       UID layerUID,
-                                       osg::Vec2Array* texCoords ) const
-{
-    if ( geom && texCoords )
-    {
-        if ( _tech == TerrainOptions::COMPOSITING_MULTIPASS )
-        {
-            geom->setTexCoordArray( 0, texCoords );
-        }
-        else
-        {
-            int slot;
-            {            
-                Threading::ScopedReadLock sharedLock( const_cast<TextureCompositor*>(this)->_layoutMutex );
-                slot = _layout.getSlot( layerUID );
-            }
-            if ( slot >= 0 )
-                geom->setTexCoordArray( slot, texCoords );
-        }
-    }
-}
-
-int
-TextureCompositor::getRenderOrder( UID layerUID ) const
-{
-    Threading::ScopedReadLock sharedLock( const_cast<TextureCompositor*>(this)->_layoutMutex );
-    return _layout.getOrder( layerUID );
-}
-
-osg::Shader*
-TextureCompositor::createSamplerFunction(UID                layerUID, 
-                                         const std::string& functionName,
-                                         osg::Shader::Type  type ) const
-{
-    osg::Shader* result = 0L;
-    if ( _impl.valid() )
-    {
-        Threading::ScopedReadLock sharedLock( const_cast<TextureCompositor*>(this)->_layoutMutex );
-        result = _impl->createSamplerFunction( layerUID, functionName, type, _layout );
-    }
-
-    if ( !result )
-    {
-        std::string fname = !functionName.empty() ? functionName : "defaultSamplerFunction";
-        std::stringstream buf;
-        buf << "vec4 " << functionName << "() { \n return vec4(0,0,0,0); \n } \n";
-        std::string str;
-        str = buf.str();
-        result = new osg::Shader( type, str );
-    }
-
-    return result;
-}
-
-void
-TextureCompositor::setTechnique( TextureCompositorTechnique* tech )
-{
-    _tech = TerrainOptions::COMPOSITING_USER;
-    _impl = tech;
-    //OE_INFO << LC << "Custom texture compositing technique installed" << std::endl;
-}
-
-void
-TextureCompositor::init()
-{        
-    if ( _impl.valid() ) // double-check pattern
-    {
-        return; // already initialized
-    }
-
-    bool isAuto = _tech == TerrainOptions::COMPOSITING_AUTO;
-
-    const Capabilities& caps = Registry::instance()->getCapabilities();
-
-    // MULTITEXTURE_GPU is the current default.
-    
-    if (_tech == TerrainOptions::COMPOSITING_MULTITEXTURE_GPU ||
-        ( isAuto && TextureCompositorMultiTexture::isSupported(true) ) )
-    {
-        _tech = TerrainOptions::COMPOSITING_MULTITEXTURE_GPU;
-        _impl = new TextureCompositorMultiTexture( true, _options );
-        //OE_INFO << LC << "Compositing technique = MULTITEXTURE/GPU" << std::endl;
-    }
-
-#if OSG_VERSION_GREATER_OR_EQUAL( 2, 9, 8 )
-
-    else
-    if (_tech == TerrainOptions::COMPOSITING_TEXTURE_ARRAY || 
-        ( isAuto && TextureCompositorTexArray::isSupported() ) )
-    {
-        _tech = TerrainOptions::COMPOSITING_TEXTURE_ARRAY;
-        _impl = new TextureCompositorTexArray( _options );
-        //OE_INFO << LC << "Compositing technique = TEXTURE ARRAY" << std::endl;
-    }
-
-#endif // OSG_VERSION_GREATER_OR_EQUAL( 2, 9, 8 )
-
-    else
-    if ( _tech == TerrainOptions::COMPOSITING_MULTITEXTURE_FFP || 
-        (isAuto && TextureCompositorMultiTexture::isSupported(false) ) )
-    {
-        _tech = TerrainOptions::COMPOSITING_MULTITEXTURE_FFP;
-        _impl = new TextureCompositorMultiTexture( false, _options );
-        //OE_INFO << LC << "Compositing technique = MULTITEXTURE/FFP" << std::endl;
-    }
-
-    // Fallback of last resort. The implementation is actually a NO-OP for multipass mode.
-    else
-    {
-        _tech = TerrainOptions::COMPOSITING_MULTIPASS;
-        _impl = 0L;
-        //OE_INFO << LC << "Compositing technique = MULTIPASS" << std::endl;
-    }
 }
diff --git a/src/osgEarth/TextureCompositorMulti b/src/osgEarth/TextureCompositorMulti
deleted file mode 100644
index fb279c3..0000000
--- a/src/osgEarth/TextureCompositorMulti
+++ /dev/null
@@ -1,75 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#ifndef OSGEARTH_TEXTURE_COMPOSITOR_MULTI_TEXTURE_H
-#define OSGEARTH_TEXTURE_COMPOSITOR_MULTI_TEXTURE_H 1
-
-#include <osgEarth/TextureCompositor>
-
-namespace osgEarth
-{
-    /**
-     * Texture compositor that implements multitexturing. It supports either GPU multitexturing
-     * (using shaders and taking advantage of all the hardware's resources) or FFP (fixed-function
-     * pipeline) multitexturing, which limits you to the old ARB texture unit limit (usually 4).
-     */
-    class TextureCompositorMultiTexture : public TextureCompositorTechnique
-    {
-    public:
-        TextureCompositorMultiTexture( bool useGPU, const TerrainOptions& options );
-
-        /** dtor */
-        virtual ~TextureCompositorMultiTexture() { }
-
-        static bool isSupported( bool useGPU );
-
-    public:
-        bool requiresUnitTextureSpace() const { return false; }
-
-        bool usesShaderComposition() const { return _useGPU; }
-
-        bool blendingRequiresSecondarySlot() const { return true; }
-
-        void updateMasterStateSet( osg::StateSet* stateSet, const TextureLayout& layout ) const;
-
-        bool supportsLayerUpdate() const { return true; }
-
-        void applyLayerUpdate( 
-            osg::StateSet* stateSet, UID layerUID,
-            const GeoImage& preparedImage, const TileKey& tileExtent,
-            const TextureLayout& layout, osg::StateSet* parentStateSet) const;
-
-        osg::Shader* createSamplerFunction(
-            UID layerUID, 
-            const std::string& functionName,
-            osg::Shader::Type type,
-            const TextureLayout& layout ) const;
-
-
-    private:
-        float _lodTransitionTime;
-        bool _useGPU;
-        bool _enableMipmappingOnUpdatedTextures;
-        bool _enableMipmapping;
-
-        osg::Texture::FilterMode _minFilter;
-        osg::Texture::FilterMode _magFilter;
-    };
-}
-
-#endif // OSGEARTH_TEXTURE_COMPOSITOR_MULTI_TEXTURE_H
diff --git a/src/osgEarth/TextureCompositorMulti.cpp b/src/osgEarth/TextureCompositorMulti.cpp
deleted file mode 100755
index f49781c..0000000
--- a/src/osgEarth/TextureCompositorMulti.cpp
+++ /dev/null
@@ -1,560 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#include <osgEarth/TextureCompositorMulti>
-#include <osgEarth/ImageUtils>
-#include <osgEarth/Registry>
-#include <osgEarth/VirtualProgram>
-#include <osgEarth/ShaderUtils>
-#include <osgEarth/TileKey>
-#include <osgEarth/StringUtils>
-#include <osgEarth/Capabilities>
-#include <osg/Texture2D>
-#include <osg/TexEnv>
-#include <osg/TexEnvCombine>
-#include <vector>
-
-using namespace osgEarth;
-
-#define LC "[TextureCompositorMulti] "
-
-//------------------------------------------------------------------------
-
-namespace
-{
-    static std::string makeSamplerName(int slot)
-    {
-        return Stringify() << "osgearth_tex" << slot;
-    }
-
-    static std::string
-    s_createTextureVertexShader( const TextureLayout& layout, bool blending )
-    {
-        std::stringstream buf;
-
-        const TextureLayout::TextureSlotVector& slots = layout.getTextureSlots();
-
-        buf << "#version " << GLSL_VERSION_STR << "\n";
-
-        buf << "varying vec4 osg_FrontColor; \n"
-            << "varying vec4 osg_FrontSecondaryColor; \n";
-
-        // NOTE: always use the "max GPU coord sets" for texture coordinates 
-        // to support proper shader merging under GLES. -gw
-
-        if ( slots.size() > 0 )
-        {
-            buf << "varying vec4 osg_TexCoord[" << Registry::capabilities().getMaxGPUTextureCoordSets()  << "]; \n";
-        }
-
-        if ( blending )
-        {
-            buf << "uniform mat4 osgearth_TexBlendMatrix[" << Registry::capabilities().getMaxGPUTextureCoordSets() << "]; \n";
-        }
-
-        buf << "void oe_multicomp_vertex(inout vec4 VertexMODEL) \n"
-            << "{ \n"
-            << "    osg_FrontColor = gl_Color; \n"
-            << "    osg_FrontSecondaryColor = vec4(0.0); \n";
-
-        // Set up the texture coordinates for each active slot (primary and secondary).
-        // Primary slots are the actual image layer's texture image unit. A Secondary
-        // slot is what an image layer uses for LOD blending, and is set on a per-layer basis.
-        for( int slot = 0; slot < (int)slots.size(); ++slot )
-        {
-            if ( slots[slot] >= 0 )
-            {
-                UID uid = slots[slot];
-                int primarySlot = layout.getSlot(uid, 0);
-
-                if ( slot == primarySlot )
-                {
-                    // normal unit:
-                    buf << "    osg_TexCoord["<< slot <<"] = gl_MultiTexCoord" << slot << "; \n";
-                }
-                else
-                {
-                    // secondary (blending) unit:
-                    buf << "    osg_TexCoord["<< slot <<"] = osgearth_TexBlendMatrix["<< primarySlot << "] * gl_MultiTexCoord" << primarySlot << "; \n";
-                }
-            }
-        }
-
-        buf << "} \n";
-
-        std::string str;
-        str = buf.str();
-        return str;
-        //return new osg::Shader( osg::Shader::VERTEX, str );
-    }
-
-    static std::string
-    s_createTextureFragShaderFunction( const TextureLayout& layout, int maxSlots, bool blending, float fadeInDuration )
-    {
-        const TextureLayout::RenderOrderVector& order = layout.getRenderOrder();
-
-        std::stringstream buf;
-
-        buf << "#version " << GLSL_VERSION_STR << "\n";
-#ifdef OSG_GLES2_AVAILABLE
-        buf << "precision mediump float;\n";
-#endif
-
-        if ( blending )
-        {
-            buf << "#extension GL_ARB_shader_texture_lod : enable \n"
-                << "uniform float osgearth_SlotStamp[" << maxSlots << "]; \n"
-                << "uniform float osg_FrameTime; \n"
-                << "uniform float osgearth_LODRangeFactor; \n";
-        }
-
-        if ( maxSlots > 0 )
-        {
-            buf << "varying vec4 osg_TexCoord[" << Registry::capabilities().getMaxGPUTextureCoordSets() << "]; \n"
-                << "uniform float osgearth_ImageLayerOpacity[" << maxSlots << "]; \n"
-                << "uniform bool  osgearth_ImageLayerVisible[" << maxSlots << "]; \n"
-                << "uniform float osgearth_ImageLayerRange[" << 2 * maxSlots << "]; \n"
-                << "uniform float osgearth_ImageLayerAttenuation; \n"
-                << "uniform float osgearth_CameraElevation; \n";
-        }
-
-        const TextureLayout::TextureSlotVector& slots = layout.getTextureSlots();
-
-        for( int i = 0; i < maxSlots && i < (int)slots.size(); ++i )
-        {
-            if ( slots[i] >= 0 )
-            {
-                buf << "uniform sampler2D " << makeSamplerName(i) << ";\n";
-            }
-        }
-
-        // install the color filter chain prototypes:
-        for( int i=0; i<maxSlots && i <(int)slots.size(); ++i )
-        {
-            buf << "void osgearth_runColorFilters_" << i << "(inout vec4 color);\n";
-        }
-
-        // the main texturing function:
-        buf << "void oe_multicomp_fragment( inout vec4 color ) \n"
-            << "{ \n"
-            << "    vec3 color3 = color.rgb; \n"
-            << "    vec4 texel; \n"
-            << "    float maxOpacity = 0.0; \n"
-            << "    float dmin, dmax, atten_min, atten_max, age; \n";
-
-        for( unsigned int i=0; i < order.size(); ++i )
-        {
-            int slot = order[i];
-            int q = 2 * i;
-
-            // if this UID has a secondyar slot, LOD blending ON.
-            int secondarySlot = layout.getSlot( slots[slot], 1, maxSlots );
-
-            buf << "    if (osgearth_ImageLayerVisible["<< i << "]) { \n"
-                << "        dmin = osgearth_CameraElevation - osgearth_ImageLayerRange["<< q << "]; \n"
-                << "        dmax = osgearth_CameraElevation - osgearth_ImageLayerRange["<< q+1 <<"]; \n"
-
-                << "        if (dmin >= 0.0 && dmax <= 0.0) { \n"
-                << "            atten_max = -clamp( dmax, -osgearth_ImageLayerAttenuation, 0.0 ) / osgearth_ImageLayerAttenuation; \n"
-                << "            atten_min =  clamp( dmin, 0.0, osgearth_ImageLayerAttenuation ) / osgearth_ImageLayerAttenuation; \n";
-
-            if ( secondarySlot >= 0 ) // LOD blending enabled for this layer
-            {
-                float invFadeInDuration = 1.0f/fadeInDuration;
-
-                buf << std::fixed
-                    << std::setprecision(1)
-                    << "            age = "<< invFadeInDuration << " * min( "<< fadeInDuration << ", osg_FrameTime - osgearth_SlotStamp[" << slot << "] ); \n"
-                    << "            age = clamp(age, 0.0, 1.0); \n"
-                    << "            vec4 texel0 = texture2D(" << makeSamplerName(slot) << ", osg_TexCoord["<< slot << "].st);\n"
-                    << "            vec4 texel1 = texture2D(" << makeSamplerName(secondarySlot) << ", osg_TexCoord["<< secondarySlot << "].st);\n"
-                    << "            float mixval = age * osgearth_LODRangeFactor;\n"
-
-                    // pre-multiply alpha before mixing:
-                    << "            texel0.rgb *= texel0.a; \n"
-                    << "            texel1.rgb *= texel1.a; \n"
-                    << "            texel = mix(texel1, texel0, mixval); \n"
-
-                    // revert to non-pre-multiplies alpha (assumes openGL state uses non-pre-mult alpha)
-                    << "            if (texel.a > 0.0) { \n"
-                    << "                texel.rgb /= texel.a; \n"
-                    << "            } \n";
-            }
-            else
-            {
-                buf << "            texel = texture2D(" << makeSamplerName(slot) << ", osg_TexCoord["<< slot <<"].st); \n";
-            }
-
-            buf
-                // color filter:
-                << "            osgearth_runColorFilters_" << i << "(texel); \n"
-
-                // adjust for opacity
-                << "            float opacity =  texel.a * osgearth_ImageLayerOpacity[" << i << "];\n"
-                << "            color3 = mix(color3, texel.rgb, opacity * atten_max * atten_min); \n"
-                << "            if (opacity > maxOpacity) {\n"
-                << "              maxOpacity = opacity;\n"
-                << "            }\n"
-                << "        } \n"
-                << "    } \n";
-        }
-
-        buf << "    color = vec4(color3, maxOpacity);\n"
-            << "} \n";
-
-
-        std::string str;
-        str = buf.str();
-        return str;
-        //OE_INFO << std::endl << str;
-        //return new osg::Shader( osg::Shader::FRAGMENT, str );
-    }
-}
-
-//------------------------------------------------------------------------
-
-namespace
-{
-    static osg::Texture2D*
-        s_getTexture( osg::StateSet* stateSet, UID layerUID, const TextureLayout& layout, osg::StateSet* parentStateSet, osg::Texture::FilterMode minFilter, osg::Texture::FilterMode magFilter)
-    {
-        int slot = layout.getSlot( layerUID, 0 );
-        if ( slot < 0 )
-            return 0L;
-
-        osg::Texture2D* tex = static_cast<osg::Texture2D*>(
-            stateSet->getTextureAttribute( slot, osg::StateAttribute::TEXTURE ) );
-
-        if ( !tex )
-        {
-            tex = new osg::Texture2D();
-            tex->setUnRefImageDataAfterApply( true );
-
-            // configure the mipmapping
-
-            tex->setMaxAnisotropy( 16.0f );
-
-            tex->setResizeNonPowerOfTwoHint(false);
-            tex->setFilter( osg::Texture::MAG_FILTER, magFilter );
-            tex->setFilter( osg::Texture::MIN_FILTER, minFilter );
-
-            // configure the wrapping
-            tex->setWrap( osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE );
-            tex->setWrap( osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE );
-
-            stateSet->setTextureAttributeAndModes( slot, tex, osg::StateAttribute::ON );
-
-            // install the slot attribute
-            std::string name = makeSamplerName(slot);
-            stateSet->getOrCreateUniform( name.c_str(), osg::Uniform::SAMPLER_2D )->set( slot );
-        }
-
-        // see if we need an LOD blending secondary texture:
-        int secondarySlot = layout.getSlot( layerUID, 1 );
-        if ( secondarySlot >= 0 )
-        {
-            osg::Texture2D* parentTex = 0;
-
-            //int parentSlot = slot + layout.getRenderOrder().size();
-            std::string parentSampler = makeSamplerName( secondarySlot );
-            if (parentStateSet)
-            {
-                parentTex = static_cast<osg::Texture2D*>(
-                    parentStateSet->getTextureAttribute( slot, osg::StateAttribute::TEXTURE ) );
-
-                if (parentTex)
-                {
-                    stateSet->setTextureAttributeAndModes(secondarySlot, parentTex, osg::StateAttribute::ON );
-                    stateSet->getOrCreateUniform(parentSampler.c_str(),
-                                                 osg::Uniform::SAMPLER_2D )->set( secondarySlot );
-                }
-            }
-
-            if ( !parentTex )
-            {
-                // Bind the main texture as the secondary texture and
-                // set the scaling factors appropriately.
-                stateSet->getOrCreateUniform(
-                    parentSampler.c_str(), osg::Uniform::SAMPLER_2D)->set(slot);
-            }
-
-        }
-        return tex;
-    }
-}
-
-//------------------------------------------------------------------------
-
-bool
-TextureCompositorMultiTexture::isSupported( bool useGPU )
-{
-    const Capabilities& caps = osgEarth::Registry::capabilities();
-    if ( useGPU )
-        return caps.supportsGLSL() && caps.supportsMultiTexture();
-    else
-        return caps.supportsMultiTexture();
-}
-
-TextureCompositorMultiTexture::TextureCompositorMultiTexture( bool useGPU, const TerrainOptions& options ) :
-_lodTransitionTime( *options.lodTransitionTime() ),
-_enableMipmapping ( *options.enableMipmapping() ),
-_minFilter        ( *options.minFilter() ),
-_magFilter        ( *options.magFilter() ),
-_useGPU           ( useGPU )
-{
-    _enableMipmappingOnUpdatedTextures = Registry::capabilities().supportsMipmappedTextureUpdates();
-}
-
-void
-TextureCompositorMultiTexture::applyLayerUpdate(osg::StateSet*       stateSet,
-                                                UID                  layerUID,
-                                                const GeoImage&      preparedImage,
-                                                const TileKey&       tileKey,
-                                                const TextureLayout& layout,
-                                                osg::StateSet*       parentStateSet) const
-{
-    osg::Texture2D* tex = s_getTexture( stateSet, layerUID, layout, parentStateSet, _minFilter, _magFilter);
-    if ( tex )
-    {
-        osg::Image* image = preparedImage.getImage();
-        image->dirty(); // required for ensure the texture recognizes the image as new data
-        tex->setImage( image );
-
-        // set up proper mipmapping filters:
-        if (_enableMipmapping &&
-            _enableMipmappingOnUpdatedTextures &&
-            ImageUtils::isPowerOfTwo( image ) &&
-            !(!image->isMipmap() && ImageUtils::isCompressed(image)) )
-        {
-            if ( tex->getFilter(osg::Texture::MIN_FILTER) != _minFilter )
-                tex->setFilter( osg::Texture::MIN_FILTER, _minFilter );
-        }
-        else if ( tex->getFilter(osg::Texture::MIN_FILTER) != osg::Texture::LINEAR )
-        {
-            tex->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR );
-        }
-
-        bool lodBlending = layout.getSlot(layerUID, 1) >= 0;
-
-        if (_enableMipmapping &&
-            _enableMipmappingOnUpdatedTextures &&
-            lodBlending )
-        {
-            int slot = layout.getSlot(layerUID, 0);
-
-            // update the timestamp on the image layer to support blending.
-            float now = (float)osg::Timer::instance()->delta_s( osg::Timer::instance()->getStartTick(), osg::Timer::instance()->tick() );
-            ArrayUniform stampUniform( "osgearth_SlotStamp", osg::Uniform::FLOAT, stateSet, layout.getMaxUsedSlot() + 1 );
-            stampUniform.setElement( slot, now );
-
-            // set the texture matrix to properly position the blend (parent) texture
-            osg::Matrix mat;
-            if ( parentStateSet != 0L )
-            {
-                unsigned tileX, tileY;
-                tileKey.getTileXY(tileX, tileY);
-
-                mat(0,0) = 0.5f;
-                mat(1,1) = 0.5f;
-                mat(3,0) = (float)(tileX % 2) * 0.5f;
-                mat(3,1) = (float)(1 - tileY % 2) * 0.5f;
-            }
-
-            ArrayUniform texMatUniform( "osgearth_TexBlendMatrix", osg::Uniform::FLOAT_MAT4, stateSet, layout.getMaxUsedSlot() + 1 );
-            texMatUniform.setElement( slot, mat );
-        }
-    }
-}
-
-void
-TextureCompositorMultiTexture::updateMasterStateSet(osg::StateSet*       stateSet,
-                                                    const TextureLayout& layout    ) const
-{
-    int numSlots = layout.getMaxUsedSlot() + 1;
-    int maxUnits = numSlots;
-
-    if ( _useGPU )
-    {
-        // Validate against the max number of GPU texture units:
-        if ( maxUnits > Registry::instance()->getCapabilities().getMaxGPUTextureUnits() )
-        {
-            maxUnits = Registry::instance()->getCapabilities().getMaxGPUTextureUnits();
-
-            OE_WARN << LC
-                << "Warning! You have exceeded the number of texture units available on your GPU ("
-                << maxUnits << "). Consider using another compositing mode."
-                << std::endl;
-        }
-
-        VirtualProgram* vp = static_cast<VirtualProgram*>( stateSet->getAttribute(VirtualProgram::SA_TYPE) );
-        // see if we have any blended layers:
-        bool hasBlending = layout.containsSecondarySlots( maxUnits );
-
-        // Why are these marked as PROTECTED? See the comments in MapNode.cpp for the answer.
-        // (Where it sets up the top-level VirtualProgram)
-
-        vp->setFunction(
-            "oe_multicomp_vertex",
-            s_createTextureVertexShader(layout, hasBlending),
-            ShaderComp::LOCATION_VERTEX_MODEL,
-            0.0 );
-
-        vp->setFunction(
-            "oe_multicomp_fragment",
-            s_createTextureFragShaderFunction(layout, maxUnits, hasBlending, _lodTransitionTime),
-            ShaderComp::LOCATION_FRAGMENT_COLORING,
-            0.0 );
-
-        //vp->setShader(
-        //    "osgearth_vert_setupColoring",
-        //    s_createTextureVertexShader(layout, hasBlending),
-        //    osg::StateAttribute::ON | osg::StateAttribute::PROTECTED );
-
-        //vp->setShader(
-        //    "osgearth_frag_applyColoring",
-        //    s_createTextureFragShaderFunction(layout, maxUnits, hasBlending, _lodTransitionTime),
-        //    osg::StateAttribute::ON | osg::StateAttribute::PROTECTED );
-    }
-
-    else
-    {
-        // Forcably disable shaders
-        stateSet->setAttributeAndModes( new osg::Program(), osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED );
-
-        // Validate against the maximum number of textures available in FFP mode.
-        if ( maxUnits > Registry::instance()->getCapabilities().getMaxFFPTextureUnits() )
-        {
-            maxUnits = Registry::instance()->getCapabilities().getMaxFFPTextureUnits();
-            OE_WARN << LC <<
-                "Warning! You have exceeded the number of texture units available in fixed-function pipeline "
-                "mode on your graphics hardware (" << maxUnits << "). Consider using another "
-                "compositing mode." << std::endl;
-        }
-
-        // FFP multitexturing requires that we set up a series of TexCombine attributes:
-        if (maxUnits == 1)
-        {
-            osg::TexEnv* texenv = new osg::TexEnv(osg::TexEnv::MODULATE);
-            stateSet->setTextureAttributeAndModes(0, texenv, osg::StateAttribute::ON);
-        }
-        else if (maxUnits >= 2)
-        {
-            //Blend together the colors and accumulate the alpha values of textures 0 and 1 on unit 0
-            {
-                osg::TexEnvCombine* texenv = new osg::TexEnvCombine;
-                texenv->setCombine_RGB(osg::TexEnvCombine::INTERPOLATE);
-                texenv->setCombine_Alpha(osg::TexEnvCombine::ADD);
-
-                texenv->setSource0_RGB(osg::TexEnvCombine::TEXTURE0+1);
-                texenv->setOperand0_RGB(osg::TexEnvCombine::SRC_COLOR);
-                texenv->setSource0_Alpha(osg::TexEnvCombine::TEXTURE0+1);
-                texenv->setOperand0_Alpha(osg::TexEnvCombine::SRC_ALPHA);
-
-                texenv->setSource1_RGB(osg::TexEnvCombine::TEXTURE0+0);
-                texenv->setOperand1_RGB(osg::TexEnvCombine::SRC_COLOR);
-                texenv->setSource1_Alpha(osg::TexEnvCombine::TEXTURE0+0);
-                texenv->setOperand1_Alpha(osg::TexEnvCombine::SRC_ALPHA);
-
-                texenv->setSource2_RGB(osg::TexEnvCombine::TEXTURE0+1);
-                texenv->setOperand2_RGB(osg::TexEnvCombine::SRC_ALPHA);
-
-                stateSet->setTextureAttributeAndModes(0, texenv, osg::StateAttribute::ON);
-            }
-
-
-            //For textures 2 and beyond, blend them together with the previous
-            //Add the alpha values of this unit and the previous unit
-            for (int unit = 1; unit < maxUnits-1; ++unit)
-            {
-                osg::TexEnvCombine* texenv = new osg::TexEnvCombine;
-                texenv->setCombine_RGB(osg::TexEnvCombine::INTERPOLATE);
-                texenv->setCombine_Alpha(osg::TexEnvCombine::ADD);
-
-                texenv->setSource0_RGB(osg::TexEnvCombine::TEXTURE0+unit+1);
-                texenv->setOperand0_RGB(osg::TexEnvCombine::SRC_COLOR);
-                texenv->setSource0_Alpha(osg::TexEnvCombine::TEXTURE0+unit+1);
-                texenv->setOperand0_Alpha(osg::TexEnvCombine::SRC_ALPHA);
-
-                texenv->setSource1_RGB(osg::TexEnvCombine::PREVIOUS);
-                texenv->setOperand1_RGB(osg::TexEnvCombine::SRC_COLOR);
-                texenv->setSource1_Alpha(osg::TexEnvCombine::PREVIOUS);
-                texenv->setOperand1_Alpha(osg::TexEnvCombine::SRC_ALPHA);
-
-                texenv->setSource2_RGB(osg::TexEnvCombine::TEXTURE0+unit+1);
-                texenv->setOperand2_RGB(osg::TexEnvCombine::SRC_ALPHA);
-
-                stateSet->setTextureAttributeAndModes(unit, texenv, osg::StateAttribute::ON);
-            }
-
-            //Modulate the colors to get proper lighting on the last unit
-            //Keep the alpha results from the previous stage
-            {
-                osg::TexEnvCombine* texenv = new osg::TexEnvCombine;
-                texenv->setCombine_RGB(osg::TexEnvCombine::MODULATE);
-                texenv->setCombine_Alpha(osg::TexEnvCombine::REPLACE);
-
-                texenv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS);
-                texenv->setOperand0_RGB(osg::TexEnvCombine::SRC_COLOR);
-                texenv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS);
-                texenv->setOperand0_Alpha(osg::TexEnvCombine::SRC_ALPHA);
-
-                texenv->setSource1_RGB(osg::TexEnvCombine::PRIMARY_COLOR);
-                texenv->setOperand1_RGB(osg::TexEnvCombine::SRC_COLOR);
-                stateSet->setTextureAttributeAndModes(maxUnits-1, texenv, osg::StateAttribute::ON);
-            }
-        }
-    }
-}
-
-osg::Shader*
-TextureCompositorMultiTexture::createSamplerFunction(UID layerUID,
-                                                     const std::string& functionName,
-                                                     osg::Shader::Type type,
-                                                     const TextureLayout& layout ) const
-{
-    osg::Shader* result = 0L;
-
-    int slot = layout.getSlot( layerUID );
-    if ( slot >= 0 )
-    {
-        std::string src;
-
-        if ( type == osg::Shader::VERTEX )
-        {
-            src = Stringify()
-                << "uniform sampler2D "<< makeSamplerName(slot) << "; \n"
-                << "vec4 " << functionName << "() \n"
-                << "{ \n"
-                << "    return texture2D("<< makeSamplerName(slot) << ", gl_MultiTexCoord"<< slot <<".st); \n"
-                << "} \n";
-        }
-        else // if ( type != osg::Shader::FRAGMENT )
-        {
-            src = Stringify()
-                << "uniform sampler2D "<< makeSamplerName(slot) << "; \n"
-                << "varying vec4 osg_TexCoord[" << Registry::capabilities().getMaxGPUTextureCoordSets()  << "]; \n"
-                << "vec4 " << functionName << "() \n"
-                << "{ \n"
-                << "    return texture2D("<< makeSamplerName(slot) << ", osg_TexCoord["<< slot << "].st); \n"
-                << "} \n";
-        }
-
-        result = new osg::Shader( type, src );
-    }
-    return result;
-}
diff --git a/src/osgEarth/TextureCompositorTexArray b/src/osgEarth/TextureCompositorTexArray
deleted file mode 100644
index 81f14f9..0000000
--- a/src/osgEarth/TextureCompositorTexArray
+++ /dev/null
@@ -1,86 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#ifndef OSGEARTH_TEXTURE_COMPOSITOR_TEX_ARRAY_H
-#define OSGEARTH_TEXTURE_COMPOSITOR_TEX_ARRAY_H 1
-
-#include <osgEarth/TextureCompositor>
-#include <osg/Version>
-
-// only in newer OSG versions.
-#if OSG_VERSION_GREATER_OR_EQUAL( 2, 9, 8 )
-
-namespace osgEarth
-{
-    /**
-     * Texture compositor that stacks image layers into a Texture Array.
-     * Note: not compatible with GLES, or with GLSL Version < 1.3
-     */
-    class TextureCompositorTexArray : public TextureCompositorTechnique
-    {
-    public:
-        TextureCompositorTexArray( const TerrainOptions& options );
-
-        /** dtor */
-        virtual ~TextureCompositorTexArray() { }
-        
-        static bool isSupported();
-
-    public:
-        void updateMasterStateSet( osg::StateSet* stateSet, const TextureLayout& layout ) const;
-
-        bool requiresUnitTextureSpace() const { return true; }
-
-        bool usesShaderComposition() const { return true; }
-
-        bool supportsLayerUpdate() const { return true; }
-
-        GeoImage prepareImage( const GeoImage& layerImage, const GeoExtent& tileExtent ) const
-        {
-            return prepareImage(layerImage, tileExtent, textureSize() );
-        }
-
-        GeoImage prepareSecondaryImage( const GeoImage& layerImage, const GeoExtent& tileExtent ) const
-        {
-            return prepareImage(layerImage, tileExtent, textureSize() / 2);
-        }
-
-    protected:
-        GeoImage prepareImage( const GeoImage& layerImage, const GeoExtent& tileExtent, unsigned size ) const;
-    public:
-        void applyLayerUpdate( 
-            osg::StateSet* stateSet,
-            UID layerUID, 
-            const GeoImage& preparedImage,
-            const TileKey& tileKey,
-            const TextureLayout& layout,
-            osg::StateSet* parentStateSet ) const;
-
-        osg::Shader* createSamplerFunction( UID layerUID, const std::string& functionName, osg::Shader::Type type, const TextureLayout& layout ) const;
-        static inline unsigned textureSize() { return 256; }
-
-    private:
-        float _lodTransitionTime;
-        osg::Texture::FilterMode _minFilter;
-        osg::Texture::FilterMode _magFilter;
-    };
-}
-
-#endif // OSG_VERSION_GREATER_OR_EQUAL( 2, 9, 8 )
-
-#endif // OSGEARTH_TEXTURE_COMPOSITOR_TEX_ARRAY_H
diff --git a/src/osgEarth/TextureCompositorTexArray.cpp b/src/osgEarth/TextureCompositorTexArray.cpp
deleted file mode 100644
index 589abaa..0000000
--- a/src/osgEarth/TextureCompositorTexArray.cpp
+++ /dev/null
@@ -1,476 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#include <osgEarth/TextureCompositorTexArray>
-
-// only in newer OSG versions.
-#if OSG_VERSION_GREATER_OR_EQUAL( 2, 9, 8 )
-
-#include <sstream>
-
-#include <osgEarth/ImageUtils>
-#include <osgEarth/Registry>
-#include <osgEarth/VirtualProgram>
-#include <osgEarth/SparseTexture2DArray>
-#include <osgEarth/ShaderUtils>
-#include <osgEarth/TileKey>
-#include <osgEarth/Capabilities>
-
-using namespace osgEarth;
-
-#define LC "[TextureCompositorTexArray] "
-
-
-//------------------------------------------------------------------------
-
-namespace
-{
-    static osg::Shader*
-    s_createTextureVertSetupShaderFunction( const TextureLayout& layout )
-    {
-        std::stringstream buf;
-       
-        const TextureLayout::TextureSlotVector& slots = layout.getTextureSlots();
-
-        buf << "#version 130 \n"
-            << "varying vec4 osg_FrontColor; \n"
-            << "varying vec4 osg_FrontSecondaryColor; \n"
-
-            << "void osgearth_vert_setupColoring() \n"
-            << "{ \n"
-            << "    gl_TexCoord[0] = gl_MultiTexCoord0; \n"
-            << "    osg_FrontColor = gl_Color; \n"
-            << "    osg_FrontSecondaryColor = vec4(0.0); \n"
-            << "} \n";
-
-        std::string str;
-        str = buf.str();
-        return new osg::Shader( osg::Shader::VERTEX, str );
-    }
-
-    static osg::Shader*
-    s_createTextureFragShaderFunction( const TextureLayout& layout, bool blending, float blendTime )
-    {
-        int numSlots = layout.getMaxUsedSlot() + 1;
-
-        std::stringstream buf;
-
-        buf << "#version 130 \n"
-            << "#extension GL_EXT_gpu_shader4 : enable \n"
-            << "varying vec4 osg_FrontColor; \n"
-            << "varying vec4 osg_FrontSecondaryColor; \n";
-            
-
-        if ( numSlots <= 0 )
-        {
-            // No textures : create a no-op shader
-            buf << "void osgearth_frag_applyColoring( inout vec4 color ) \n"
-                << "{ \n"
-                << "    color = osg_FrontColor; \n"
-                << "} \n";
-        }
-        else
-        {
-            if ( blending )
-            {
-                buf << "#extension GL_ARB_shader_texture_lod : enable \n"
-                    << "uniform float osgearth_SlotStamp[ " << numSlots << "]; \n"
-                    << "uniform float osg_FrameTime;\n"
-                    << "uniform float osgearth_LODRangeFactor;\n";
-            }
-
-            buf << "uniform sampler2DArray tex0; \n";
-            
-            if ( blending )
-                buf << "uniform sampler2DArray tex1;\n";
-
-            buf << "uniform float region[ " << 8*numSlots << "]; \n"
-                << "uniform float osgearth_ImageLayerOpacity[" << numSlots << "]; \n"
-                << "uniform bool  osgearth_ImageLayerVisible[" << numSlots << "]; \n"
-                << "uniform float osgearth_ImageLayerRange[" << 2*numSlots << "]; \n"
-                << "uniform float osgearth_ImageLayerAttenuation; \n"
-                << "uniform float osgearth_CameraElevation; \n"
-
-                << "void osgearth_frag_applyColoring( inout vec4 color ) \n"
-                << "{ \n"
-                << "    vec3 color3 = color.rgb; \n"
-                << "    float u, v, dmin, dmax, atten_min, atten_max, age; \n"
-                << "    vec4 texel; \n";
-
-            const TextureLayout::TextureSlotVector& slots = layout.getTextureSlots();
-            const TextureLayout::RenderOrderVector& order = layout.getRenderOrder();
-
-            for( unsigned int i = 0; i < order.size(); ++i )
-            {
-                int slot = order[i];
-                int q = 2 * i;
-                int r = 8 * slot;
-                UID uid = slots[slot];
-
-                buf << "    if (osgearth_ImageLayerVisible["<< i << "]) \n"
-                    << "    { \n"
-                    << "        u = region["<< r <<"] + (region["<< r+2 <<"] * gl_TexCoord[0].s); \n"
-                    << "        v = region["<< r+1 <<"] + (region["<< r+3 <<"] * gl_TexCoord[0].t); \n"
-                    << "        dmin = osgearth_CameraElevation - osgearth_ImageLayerRange["<< q << "]; \n"
-                    << "        dmax = osgearth_CameraElevation - osgearth_ImageLayerRange["<< q+1 <<"]; \n"
-                    << "        if (dmin >= 0 && dmax <= 0.0) \n"
-                    << "        { \n"
-                    << "            atten_max = -clamp( dmax, -osgearth_ImageLayerAttenuation, 0 ) / osgearth_ImageLayerAttenuation; \n"
-                    << "            atten_min =  clamp( dmin, 0, osgearth_ImageLayerAttenuation ) / osgearth_ImageLayerAttenuation; \n";
-
-                if ( layout.isBlendingEnabled(uid) )
-                {
-                    float invBlendTime = 1.0f/blendTime;
-
-                    buf << "            age = "<< invBlendTime << " * min( "<< blendTime << ", osg_FrameTime - osgearth_SlotStamp[" << slot << "] ); \n"
-                        << "            age = clamp(age, 0.0, 1.0);\n"
-                        << "            float pu, pv;\n"
-                        << "            pu = region["<< r+4 <<"] + (region["<< r+6 <<"] * gl_TexCoord[0].s); \n"
-                        << "            pv = region["<< r+5 <<"] + (region["<< r+7 <<"] * gl_TexCoord[0].t); \n"
-
-                        << "            vec3 texCoord = vec3(pu, pv, " << slot <<");\n;\n"
-                        << "            vec4 texel0 = texture2DArray( tex0, vec3(u, v, " << slot << ") );\n"
-                        << "            vec4 texel1 = texture2DArray( tex1, vec3(pu, pv, " << slot << ") );\n"
-                        << "            float mixval = age * osgearth_LODRangeFactor;\n"
-                        
-                        // pre-multiply alpha before mixing:
-                        << "            texel0.rgb *= texel0.a; \n"
-                        << "            texel1.rgb *= texel1.a; \n"
-                        << "            texel = mix(texel1, texel0, mixval); \n"
-
-                        // revert to non-pre-multiplies alpha (assumes openGL state uses non-pre-mult alpha)
-                        << "            if (texel.a > 0.0) { \n"
-                        << "                texel.rgb /= texel.a; \n"
-                        << "            } \n";
-                }
-                else
-                {
-                    buf << "            texel = texture2DArray( tex0, vec3(u,v,"<< slot <<") ); \n";
-                }
-
-                buf << "            color3 = mix(color3, texel.rgb, texel.a * osgearth_ImageLayerOpacity["<< i <<"] * atten_max * atten_min); \n"
-                    << "        } \n"
-                    << "    } \n";
-            }
-
-            buf << "    color = vec4(color3.rgb, color.a); \n"
-                << "} \n";
-        }
-
-        std::string str;
-        str = buf.str();
-        return new osg::Shader( osg::Shader::FRAGMENT, str );
-    }
-}
-
-//------------------------------------------------------------------------
-
-namespace
-{
-    osg::Texture2DArray*
-    s_getTexture( osg::StateSet* stateSet, const TextureLayout& layout,
-                  int unit, unsigned textureSize, osg::Texture::FilterMode minFilter, osg::Texture::FilterMode magFilter )
-    {
-        osg::Texture2DArray* tex = static_cast<osg::Texture2DArray*>(
-            stateSet->getTextureAttribute( unit, osg::StateAttribute::TEXTURE ) );
-
-        // if the texture array doesn't exist, create it anew.
-        if ( !tex )
-        {
-            tex = new SparseTexture2DArray();
-            tex->setSourceFormat( GL_RGBA );
-            tex->setInternalFormat( GL_RGBA8 );
-            tex->setTextureWidth( textureSize );
-            tex->setTextureHeight( textureSize );
-
-            // configure the mipmapping
-            tex->setMaxAnisotropy(16.0f);
-            tex->setResizeNonPowerOfTwoHint(false);
-            tex->setFilter( osg::Texture::MAG_FILTER, magFilter );
-            tex->setFilter( osg::Texture::MIN_FILTER, minFilter );
-
-            // configure the wrapping
-            tex->setWrap(osg::Texture::WRAP_S,osg::Texture::CLAMP_TO_EDGE);
-            tex->setWrap(osg::Texture::WRAP_T,osg::Texture::CLAMP_TO_EDGE);
-
-            stateSet->setTextureAttribute( unit, tex, osg::StateAttribute::ON );
-        }
-
-        // grow the texture array if necessary.
-        int requiredDepth = layout.getMaxUsedSlot() + 1;
-        if ( tex->getTextureDepth() < requiredDepth )
-            tex->setTextureDepth( requiredDepth );
-
-        const TextureLayout::TextureSlotVector& slots = layout.getTextureSlots();
-
-        // null out any empty slots (to save memory, i guess)
-        for( int i=0; i < tex->getTextureDepth(); ++i )
-        {
-            if ( i < (int)slots.size() && slots[i] < 0 )
-                tex->setImage( i, 0L );
-        }
-
-        return tex;
-    }
-    
-    osg::Uniform* ensureSampler(osg::StateSet* ss, int unit)
-    {
-        std::stringstream sstream;
-        sstream << "tex" << unit;
-        std::string str;
-        str = sstream.str();
-        osg::ref_ptr<osg::Uniform> sampler = ss->getUniform(str);
-        int samplerUnit = -1;
-        if (sampler.valid() && sampler->getType() == osg::Uniform::SAMPLER_2D_ARRAY)
-            sampler->get(samplerUnit);
-        if (samplerUnit == -1 || samplerUnit != unit)
-        {
-            sampler = new osg::Uniform(osg::Uniform::SAMPLER_2D_ARRAY, str);
-            sampler->set(unit);
-            ss->addUniform(sampler.get());
-        }
-        return sampler.get();
-    }
-
-    void assignImage(osg::Texture2DArray* texture, int slot, osg::Image* image)
-    {
-        // We have to dirty() the image because otherwise the texture2d
-        // array implementation will not recognize it as new data.
-        image->dirty();
-        texture->setImage( slot, image );
-
-        if (ImageUtils::isPowerOfTwo( image ) && !(!image->isMipmap() && ImageUtils::isCompressed(image)))
-        {
-            if ( texture->getFilter(osg::Texture::MIN_FILTER) != osg::Texture::LINEAR_MIPMAP_LINEAR )
-                texture->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR );
-        }
-        else if ( texture->getFilter(osg::Texture::MIN_FILTER) != osg::Texture::LINEAR )
-        {
-            texture->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR );
-        }
-    }
-
-    void getImageTransform(
-        const GeoExtent& tileExtent,
-        const GeoExtent& imageExtent,
-        osg::Vec4&       transform)
-    {
-        if (!tileExtent.isValid() || !imageExtent.isValid())
-            return;
-
-        transform[0] = (tileExtent.xMin() - imageExtent.xMin()) / imageExtent.width();
-        transform[1] = (tileExtent.yMin() - imageExtent.yMin()) / imageExtent.height();
-        transform[2]  = tileExtent.width() / imageExtent.width();
-        transform[3]  = tileExtent.height() / imageExtent.height();
-    }
-}
-
-//------------------------------------------------------------------------
-
-bool
-TextureCompositorTexArray::isSupported()
-{
-    const Capabilities& caps = osgEarth::Registry::instance()->getCapabilities();
-    return caps.supportsGLSL(1.30f) && caps.supportsTextureArrays();
-}
-
-TextureCompositorTexArray::TextureCompositorTexArray( const TerrainOptions& options ) :
-_lodTransitionTime( *options.lodTransitionTime() ),
-_minFilter        ( *options.minFilter() ),
-_magFilter        ( *options.magFilter() )
-{
-    //nop
-}
-
-GeoImage
-TextureCompositorTexArray::prepareImage( const GeoImage& layerImage, const GeoExtent& tileExtent, unsigned textureSize ) const
-{
-    const osg::Image* image = layerImage.getImage();
-    if (!image)
-        return GeoImage::INVALID;
-
-    if (image->getPixelFormat() != GL_RGBA ||
-        image->getInternalTextureFormat() != GL_RGBA8 ||
-        image->s() != textureSize ||
-        image->t() != textureSize )
-    {
-        // Because all tex2darray layers must be identical in format, let's use RGBA.
-        osg::ref_ptr<osg::Image> newImage = ImageUtils::convertToRGBA8( image );
-        
-        // TODO: revisit. For now let's just settle on 256 (again, all layers must be the same size)
-        if ( image->s() != textureSize || image->t() != textureSize )
-        {
-            osg::ref_ptr<osg::Image> resizedImage;
-            if ( ImageUtils::resizeImage( newImage.get(), textureSize, textureSize, resizedImage ) )
-                newImage = resizedImage.get();
-        }
-
-        return GeoImage( newImage.get(), layerImage.getExtent() );
-    }
-    else
-    {
-        return layerImage;
-    }
-}
-
-void
-TextureCompositorTexArray::applyLayerUpdate(osg::StateSet*       stateSet,
-                                            UID                  layerUID,
-                                            const GeoImage&      preparedImage,
-                                            const TileKey&       tileKey,
-                                            const TextureLayout& layout,
-                                            osg::StateSet*       parentStateSet) const
-{
-    GeoExtent tileExtent(tileKey.getExtent());
-    int slot = layout.getSlot( layerUID );
-    if ( slot < 0 )
-        return; // means the layer no longer exists
-
-    // access the texture array, creating or growing it if necessary:
-    osg::Texture2DArray* texture = s_getTexture( stateSet, layout, 0,
-                                                 textureSize(), _minFilter, _magFilter);
-    ensureSampler( stateSet, 0 );
-    // assign the new image at the proper position in the texture array.
-    osg::Image* image = preparedImage.getImage();
-    assignImage(texture, slot, image);
-    
-    // update the region uniform to reflect the geo extent of the image:
-    const GeoExtent& imageExtent = preparedImage.getExtent();
-    osg::Vec4 tileTransform;
-    getImageTransform(tileExtent, imageExtent, tileTransform);
-
-    // access the region uniform, creating or growing it if necessary:
-    ArrayUniform regionUni( "region", osg::Uniform::FLOAT, stateSet, layout.getMaxUsedSlot()+1 );
-    if ( regionUni.isValid() )
-    {
-        int layerOffset = slot * 8;
-        for (int i = 0; i < 4; ++i)
-            regionUni.setElement( layerOffset + i, tileTransform[i]);
-        //region->dirty();
-    }
-    
-    if ( layout.isBlendingEnabled( layerUID ) && regionUni.isValid() )
-    {
-        osg::Uniform* secondarySampler = ensureSampler( stateSet, 1 );
-        osg::Texture2DArray* parentTexture = 0;
-        const unsigned parentLayerOffset = slot * 8 + 4;
-        if ( parentStateSet )
-        {
-            ArrayUniform parentRegion( "region", osg::Uniform::FLOAT, parentStateSet, layout.getMaxUsedSlot()+1 );
-
-            //osg::Uniform* parentRegion = s_getRegionUniform( parentStateSet,
-            //                                                 layout );
-            GeoExtent parentExtent(tileKey.createParentKey().getExtent());
-            float widthRatio, heightRatio;
-            parentRegion.getElement(slot * 8 + 2, widthRatio);
-            parentRegion.getElement(slot * 8 + 3, heightRatio);
-            float parentImageWidth =  parentExtent.width() / widthRatio;
-            float parentImageHeight = parentExtent.height() / heightRatio;
-            float xRatio, yRatio;
-            parentRegion.getElement(slot * 8, xRatio);
-            parentRegion.getElement(slot * 8 + 1, yRatio);
-            float ParentImageXmin = parentExtent.xMin() - xRatio * parentImageWidth;
-            float ParentImageYmin = parentExtent.yMin() - yRatio * parentImageHeight;
-            regionUni.setElement(parentLayerOffset,
-                               static_cast<float>((tileExtent.xMin() - ParentImageXmin) / parentImageWidth));
-            regionUni.setElement(parentLayerOffset + 1,
-                               static_cast<float>((tileExtent.yMin() - ParentImageYmin) / parentImageHeight));
-            regionUni.setElement(parentLayerOffset + 2,
-                               static_cast<float>(tileExtent.width() / parentImageWidth));
-            regionUni.setElement(parentLayerOffset + 3,
-                               static_cast<float>(tileExtent.height() / parentImageHeight));
-            //regionUni.dirty();
-            parentTexture = static_cast<osg::Texture2DArray*>(parentStateSet->getTextureAttribute(0, osg::StateAttribute::TEXTURE));
-        }
-        else
-        {
-            // setting the parent transform values to -1 disabled blending for this layer. #hack -gw
-            for (int i = 0; i < 4; ++i)
-                regionUni.setElement(parentLayerOffset + i, tileTransform[i]);
-        }
-
-        if (parentTexture)
-            stateSet->setTextureAttribute(1, parentTexture, osg::StateAttribute::ON);
-        else
-            secondarySampler->set(0);
-
-        // update the timestamp on the image layer to support fade-in blending.
-        float now = (float)osg::Timer::instance()->delta_s( osg::Timer::instance()->getStartTick(), osg::Timer::instance()->tick() );
-        ArrayUniform stampUniform( "osgearth_SlotStamp", osg::Uniform::FLOAT, stateSet, layout.getMaxUsedSlot()+1 );
-        stampUniform.setElement( slot, now );
-    }
-}
-
-void
-TextureCompositorTexArray::updateMasterStateSet( osg::StateSet* stateSet, const TextureLayout& layout ) const
-{
-    VirtualProgram* vp = static_cast<VirtualProgram*>( stateSet->getAttribute(VirtualProgram::SA_TYPE) );
-
-    vp->setShader(
-        "osgearth_vert_setupColoring",
-        s_createTextureVertSetupShaderFunction(layout),
-        osg::StateAttribute::ON | osg::StateAttribute::PROTECTED );
-
-    vp->setShader( 
-        "osgearth_frag_applyColoring", 
-        s_createTextureFragShaderFunction(layout, true, _lodTransitionTime ),
-        osg::StateAttribute::ON | osg::StateAttribute::PROTECTED );
-}
-
-osg::Shader*
-TextureCompositorTexArray::createSamplerFunction(UID layerUID,
-                                                 const std::string& functionName,
-                                                 osg::Shader::Type type,
-                                                 const TextureLayout& layout ) const
-{
-    osg::Shader* result = 0L;
-
-    int slot = layout.getSlot( layerUID );
-    if ( slot >= 0 )
-    {
-        int r = 8 * slot;
-
-        std::string texCoord = 
-            type == osg::Shader::VERTEX ? "gl_MultiTexCoord0" : 
-            "gl_TexCoord[0]";
-
-        std::stringstream buf;
-
-        buf << "#version 130 \n"
-            << "#extension GL_EXT_gpu_shader4 : enable \n"
-
-            << "uniform sampler2DArray tex0; \n"
-            << "uniform float[] region; \n"
-
-            << "vec4 " << functionName << "() \n"
-            << "{ \n"
-            << "    float u = region["<< r <<"] + (region["<< r+2 <<"] * "<< texCoord <<".s); \n"
-            << "    float v = region["<< r+1 <<"] + (region["<< r+3 <<"] * "<< texCoord <<".t); \n"
-            << "    return texture2DArray( tex0, vec3(u,v,"<< slot <<") ); \n"
-            << "} \n";
-
-        std::string str;
-        str = buf.str();
-        result = new osg::Shader( type, str );
-    }
-    return result;
-}
-
-//------------------------------------------------------------------------
-
-#endif // OSG_VERSION_GREATER_OR_EQUAL( 2, 9, 8 )
diff --git a/src/osgEarth/ThreadingUtils b/src/osgEarth/ThreadingUtils
index 2a62f4a..9f63fd8 100644
--- a/src/osgEarth/ThreadingUtils
+++ b/src/osgEarth/ThreadingUtils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -22,7 +22,7 @@
 #include <osgEarth/Common>
 #include <OpenThreads/Condition>
 #include <OpenThreads/Mutex>
-#include <OpenThreads/ReentrantMutex>
+#include <OpenThreads/Thread>
 #include <osg/ref_ptr>
 #include <set>
 #include <map>
diff --git a/src/osgEarth/ThreadingUtils.cpp b/src/osgEarth/ThreadingUtils.cpp
index ae323d0..a291663 100644
--- a/src/osgEarth/ThreadingUtils.cpp
+++ b/src/osgEarth/ThreadingUtils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/TileHandler b/src/osgEarth/TileHandler
new file mode 100644
index 0000000..7fe49b4
--- /dev/null
+++ b/src/osgEarth/TileHandler
@@ -0,0 +1,60 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTH_TILEHANDLER_H
+#define OSGEARTH_TILEHANDLER_H 1
+
+#include <osgEarth/Common>
+#include <osgEarth/TileKey>
+#include <osgEarth/Map>
+
+#include <osgEarth/TerrainLayer>
+
+namespace osgEarth
+{
+    class TileVisitor;
+
+    /**
+    * TileHandler is an interface for operations on a TileKey
+    */
+    class OSGEARTH_EXPORT TileHandler : public osg::Referenced
+    {
+    public:        
+        /**
+         * Process a tile - also provides a reference to the calling TileVisitor
+         */
+        virtual bool handleTile(const TileKey& key, const TileVisitor& tv);
+
+        /**
+         * Callback that tells a TileVisitor if it should attempt to process this key.
+         * If this function returns false no further processing is done on child keys.
+         */
+        virtual bool hasData( const TileKey& key ) const;
+
+        /**
+         * Returns the process to run when executing in a MultiProcessTileVisitor.
+         * 
+         * When running in a MultiProcessTileVisitor the actual data processing is done by an external program
+         * that takes a --tiles argument.  This function lets you tie that process to the TileHandler
+         */
+        virtual std::string getProcessString() const;
+    };    
+
+} // namespace osgEarth
+
+#endif // OSGEARTH_TRAVERSAL_DATA_H
diff --git a/src/osgEarthDrivers/engine_mp/KeyNodeFactory.cpp b/src/osgEarth/TileHandler.cpp
similarity index 69%
copy from src/osgEarthDrivers/engine_mp/KeyNodeFactory.cpp
copy to src/osgEarth/TileHandler.cpp
index 559d695..918a2dc 100644
--- a/src/osgEarthDrivers/engine_mp/KeyNodeFactory.cpp
+++ b/src/osgEarth/TileHandler.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -16,16 +16,23 @@
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
-#include "KeyNodeFactory"
+#include <osgEarth/TileHandler>
+#include <osgEarth/TileVisitor>
 
-using namespace osgEarth_engine_mp;
-using namespace osgEarth;
 
-#define LC "[KeyNodeFactory] "
+using namespace osgEarth;
 
-//--------------------------------------------------------------------------
+bool TileHandler::handleTile(const TileKey& key, const TileVisitor& tv)
+{
+    return true;    
+}
 
-KeyNodeFactory::KeyNodeFactory()
+bool TileHandler::hasData( const TileKey& key ) const
+{
+    return true;
+}
+        
+std::string TileHandler::getProcessString() const
 {
-    //NOP
+    return "";
 }
diff --git a/src/osgEarth/TileKey b/src/osgEarth/TileKey
index c3f7aa8..4221d59 100644
--- a/src/osgEarth/TileKey
+++ b/src/osgEarth/TileKey
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -24,7 +24,6 @@
 #include <osgEarth/Profile>
 #include <osg/ref_ptr>
 #include <osg/Version>
-#include <osgTerrain/TerrainTile>
 #include <string>
 
 namespace osgEarth
@@ -59,17 +58,26 @@ namespace osgEarth
             unsigned int tile_y,
             const Profile* profile );
 
+        /** Copy constructor. */
         TileKey( const TileKey& rhs );
 
         /** dtor */
         virtual ~TileKey() { }
 
+        /** Compare two tilekeys for equality. */
         bool operator == (const TileKey& rhs) const {
-            return valid() && rhs.valid() && _lod==rhs._lod && _x==rhs._x && _y==rhs._y;
+            return
+                valid() && rhs.valid() && 
+                _lod==rhs._lod && _x==rhs._x && _y==rhs._y && 
+                _profile->isHorizEquivalentTo(rhs._profile);
         }
+
+        /** Compare two tilekeys for inequality */
         bool operator != (const TileKey& rhs) const {
             return !(*this == rhs);
         }
+
+        /** Sorts tilekeys, ignoring profiles */
         bool operator < (const TileKey& rhs) const {
             if (_lod < rhs._lod) return true;
             if (_lod > rhs._lod) return false;
@@ -90,11 +98,6 @@ namespace osgEarth
         const std::string& str() const { return _key; }
 
         /**
-         * Gets a TileID corresponding to this key.
-         */
-        osgTerrain::TileID getTileId() const;
-
-        /**
          * Gets the profile within which this key is interpreted.
          */
         const osgEarth::Profile* getProfile() const;
@@ -102,7 +105,7 @@ namespace osgEarth
         /**
          * Whether this is a valid key.
          */
-        const bool valid() const { return _profile.valid(); }
+        bool valid() const { return _profile.valid(); }
 
         /**
          * Get the quadrant relative to this key's parent.
@@ -127,7 +130,6 @@ namespace osgEarth
          */
         TileKey createAncestorKey( int ancestorLod ) const;
 
-
         /**
          * Creates a key that represents this tile's neighbor at the same LOD. Wraps
          * around in X and Y automatically. For example, xoffset=-1 yoffset=1 will
@@ -167,10 +169,30 @@ namespace osgEarth
         unsigned int getTileX() const { return _x; }
         unsigned int getTileY() const { return _y; }
 
-        static inline int getLOD(const osgTerrain::TileID& id)
-        {
-            return id.level;
-        }
+        //static inline int getLOD(const osgTerrain::TileID& id)
+        //{
+        //    return id.level;
+        //}
+
+        /**
+         * Maps this tile key to another tile key in order to account in
+         * a resolution difference. For example: we are requesting data for
+         * a TileKey at LOD 4, at a tile size of 32. The source data's tile
+         * size if 256. That means the source data at LOD 1 will contain the
+         * data necessary to build the tile.
+         *
+         *   usage: TileKey tk = mapResolution(31, 256);
+         *
+         * We want a 31x31 tile. The source data is 256x256. This will return
+         * a TileKey that access the appropriate source data, which we will then
+         * need to crop to populate our tile.
+         *
+         * You can set "minimumLOD" if you don't want a key below a certain LOD.
+         */
+        TileKey mapResolution(
+            unsigned targetSize,
+            unsigned sourceDataSize,
+            unsigned minimumLOD =0) const;
 
     protected:
         std::string _key;
diff --git a/src/osgEarth/TileKey.cpp b/src/osgEarth/TileKey.cpp
index e9410dc..f8f3483 100644
--- a/src/osgEarth/TileKey.cpp
+++ b/src/osgEarth/TileKey.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -28,7 +28,7 @@ TileKey TileKey::INVALID( 0, 0, 0, 0L );
 
 //------------------------------------------------------------------------
 
-TileKey::TileKey( unsigned int lod, unsigned int tile_x, unsigned int tile_y, const Profile* profile)
+TileKey::TileKey(unsigned int lod, unsigned int tile_x, unsigned int tile_y, const Profile* profile)
 {
     _x = tile_x;
     _y = tile_y;
@@ -94,14 +94,6 @@ TileKey::getQuadrant() const
         yeven          ? 1 : 3;
 }
 
-osgTerrain::TileID
-TileKey::getTileId() const
-{
-    //TODO: will this be an issue with multi-face? perhaps not since each face will
-    // exist within its own scene graph.. ?
-    return osgTerrain::TileID(_lod, _x, _y);
-}
-
 void
 TileKey::getPixelExtents(unsigned int& xmin,
                          unsigned int& ymin,
@@ -186,3 +178,49 @@ TileKey::createNeighborKey( int xoffset, int yoffset ) const
 
     return TileKey( _lod, x, y, _profile.get() );
 }
+
+namespace
+{
+    int nextPowerOf2(int x) {
+        --x;
+        x |= x >> 1;
+        x |= x >> 2;
+        x |= x >> 4;
+        x |= x >> 8;
+        x |= x >> 16;
+        return x+1;
+    }
+}
+
+TileKey
+TileKey::mapResolution(unsigned targetSize,
+                       unsigned sourceSize,
+                       unsigned minimumLOD) const
+{
+    // This only works when falling back; i.e. targetSize is smaller than sourceSize.
+    if ( getLOD() == 0 || targetSize >= sourceSize )
+        return *this;
+
+    // Minimum target tile size.
+    if ( targetSize < 2 )
+        targetSize = 2;
+
+    int lod = (int)getLOD();
+    int targetSizePOT = nextPowerOf2((int)targetSize);
+
+    while(true)
+    {
+        if (targetSizePOT >= (int)sourceSize)
+        {
+            return createAncestorKey(lod);
+        }
+
+        if ( lod == (int)minimumLOD )
+        {
+            return createAncestorKey(lod);
+        }
+
+        lod--;
+        targetSizePOT *= 2;        
+    }
+}
diff --git a/src/osgEarth/TileSource b/src/osgEarth/TileSource
index b964044..7299309 100644
--- a/src/osgEarth/TileSource
+++ b/src/osgEarth/TileSource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -31,16 +31,14 @@
 #include <osgEarth/CachePolicy>
 #include <osgEarth/TileKey>
 #include <osgEarth/Profile>
-#include <osgEarth/MemCache>
 #include <osgEarth/ThreadingUtils>
+#include <osgEarth/MemCache>
 
 #include <osg/Referenced>
 #include <osg/Object>
 #include <osg/Image>
 #include <osg/Shape>
-#if OSG_MIN_VERSION_REQUIRED(2,9,5)
 #include <osgDB/Options>
-#endif
 #include <osgDB/ReadFile>
 #include <string>
 
@@ -89,6 +87,10 @@ namespace osgEarth
         optional<bool>& bilinearReprojection() { return _bilinearReprojection; }
         const optional<bool>& bilinearReprojection() const { return _bilinearReprojection; }
 
+        /** Force the tilesource to report this as the maximum available LOD */
+        optional<unsigned>& maxDataLevel() { return _maxDataLevel; }
+        const optional<unsigned>& maxDataLevel() const { return _maxDataLevel; }
+
     public:
         /** For backwards-compatibility; use minValidValue() instead 
          *  @deprecated */
@@ -121,6 +123,7 @@ namespace osgEarth
         optional<std::string>    _blacklistFilename;
         optional<int>            _L2CacheSize;
         optional<bool>           _bilinearReprojection;
+        optional<unsigned>       _maxDataLevel;
     };
 
 
@@ -141,12 +144,12 @@ namespace osgEarth
         /**
          *Adds the given tile to the blacklist
          */
-        void add(const osgTerrain::TileID &tile);
+        void add(const TileKey& key);
 
         /**
          *Removes the given tile from the blacklist
          */
-        void remove(const osgTerrain::TileID& tile);
+        void remove(const TileKey& key);
 
         /**
          *Removes all tiles from the blacklist
@@ -156,7 +159,7 @@ namespace osgEarth
         /**
          *Returns whether the given tile is in the blacklist
          */
-        bool contains(const osgTerrain::TileID &tile) const;
+        bool contains(const TileKey& key) const;
 
         /**
          *Returns the size of the blacklist
@@ -184,9 +187,9 @@ namespace osgEarth
         void write(const std::string &filename) const;
 
     private:
-        typedef std::set< osgTerrain::TileID > BlacklistedTiles;
+        typedef std::set<TileKey> BlacklistedTiles;
         BlacklistedTiles _tiles;
-        osgEarth::Threading::ReadWriteMutex _mutex;
+        mutable osgEarth::Threading::ReadWriteMutex _mutex;
     };
 
     /**
@@ -197,6 +200,19 @@ namespace osgEarth
     class OSGEARTH_EXPORT TileSource : public virtual osg::Object
     {
     public:
+        /** Modes: OR these together when you call open(). */
+        typedef unsigned Mode;
+
+        /** Open for reading only */
+        static const Mode MODE_READ;  
+        
+        /** Open for writing */
+        static const Mode MODE_WRITE;
+
+        /** When using MODE_WRITE, create the data store if necessary */
+        static const Mode MODE_CREATE;
+
+
         /** Initialization status */
         struct Status
         {
@@ -216,6 +232,9 @@ namespace osgEarth
 
         static Status STATUS_OK;
 
+        /** interface name, used by the plugin system. */
+        static const char* INTERFACE_NAME;
+
     public:
         struct ImageOperation : public osg::Referenced {
             virtual void operator()( osg::ref_ptr<osg::Image>& in_out_image ) =0;
@@ -226,7 +245,8 @@ namespace osgEarth
         };
 
     public:
-        TileSource( const TileSourceOptions& options =TileSourceOptions() );
+        /** Constructor */
+        TileSource(const TileSourceOptions& options);
 
         /**
          * Gets the status of this tile source.
@@ -262,6 +282,24 @@ namespace osgEarth
             HeightFieldOperation* op        =0L,
             ProgressCallback*     progress  =0L );
 
+        /**
+         * Stores an image in the tile source for the given TileKey.
+         * The driver must support writing or this method will return false.
+         */
+        virtual bool storeImage(const TileKey&    key,
+                                osg::Image*       image,
+                                ProgressCallback* progress) { return false; }
+
+        /**
+         * Stores a heightfield in the tile source for the given TileKey.
+         * The driver must support writing or this method will return false.
+         * (Note: The default implementation will convert the heightfield to an
+         * image with one 32-bit channel and call storeImage.)
+         */
+        virtual bool storeHeightField(const TileKey&    key,
+                                      osg::HeightField* hf,
+                                      ProgressCallback* progress);
+
     public:
 
         /**
@@ -353,10 +391,18 @@ namespace osgEarth
         /**
          * Starts up the tile source.
          */
-        const Status& startup( const osgDB::Options* dbOptions );
+        const Status& open(
+            const Mode&           openMode  =MODE_READ,
+            const osgDB::Options* dbOptions =0L);
 
+        /**
+         * The open mode (passed to startup).
+         */
+        const Mode& getMode() const { return _mode; }
 
-        /** The options used to construct this tile source. */
+        /**
+         * The options used to construct this tile source.
+         */
         const TileSourceOptions& getOptions() const { return _options; }
 
     public:
@@ -370,7 +416,6 @@ namespace osgEarth
 
     protected:
 
-        virtual ~TileSource();
 
         /**
          * Initializes the tile source (called by startup)
@@ -385,7 +430,7 @@ namespace osgEarth
          * The Subclass should override this to report a correct initialization status.
          * The default implementation reports STATUS_OK (for compatibility).
          */
-        virtual Status initialize( const osgDB::Options* dbOptions );
+        virtual Status initialize(const osgDB::Options* dbOptions);
 
         /**
          * Creates an image for the given TileKey.
@@ -393,7 +438,7 @@ namespace osgEarth
          */
         virtual osg::Image* createImage(
             const TileKey&        key,
-            ProgressCallback*     progress ) = 0;
+            ProgressCallback*     progress );
 
         /**
          * Creates a heightfield for the given TileKey
@@ -403,14 +448,20 @@ namespace osgEarth
             const TileKey&        key,
             ProgressCallback*     progress );
 
+    protected:
+        
+        virtual ~TileSource();
+
         /**
-         * Called by subclasses to initialize their profile
+         * Subclass can call this to set a Profile after opening the 
+         * data store.
          */
         void setProfile( const Profile* profile );
 
         /**
-         * Sets the status of this tile source. Called by a subclass to
-         * set the status
+         * Subclass can call this to set the status. Normally you set the
+         * status by returning it from initialize(), but you can call this
+         * later, e.g. in the event of an unrecoverable error.
          */
         void setStatus( Status status );
 
@@ -445,6 +496,7 @@ namespace osgEarth
 
         DataExtentList _dataExtents;
         Status         _status;
+        Mode           _mode;
     };
 
 
@@ -455,19 +507,26 @@ namespace osgEarth
     class OSGEARTH_EXPORT TileSourceDriver : public osgDB::ReaderWriter
     {
     protected:
-        const TileSourceOptions& getTileSourceOptions( const osgDB::ReaderWriter::Options* rwOpt ) const;
+        const TileSourceOptions& getTileSourceOptions(const osgDB::Options* options) const;
+
+        const std::string getInterfaceName(const osgDB::Options* options) const;
+
+        TileSource::Mode getOpenMode(const osgDB::Options* options) const;
     };
 
     //--------------------------------------------------------------------
 
     /**
-     * Creates TileSource instances and chains them together to create
-     * tile source "pipelines" for data access and processing.
+     * Creates TileSource instances.
      */
     class OSGEARTH_EXPORT TileSourceFactory
     {
     public:
-        static TileSource* create( const TileSourceOptions& options );
+        /**
+         * Creates a TileSource instance by loading the driver plugin
+         * specified in the options.
+         */
+        static TileSource* create(const TileSourceOptions& options);
     };
 }
 
diff --git a/src/osgEarth/TileSource.cpp b/src/osgEarth/TileSource.cpp
index 9790e9c..0fe36b0 100644
--- a/src/osgEarth/TileSource.cpp
+++ b/src/osgEarth/TileSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -24,6 +24,7 @@
 #include <osgEarth/FileUtils>
 #include <osgEarth/Registry>
 #include <osgEarth/ThreadingUtils>
+#include <osgEarth/MemCache>
 #include <osgDB/FileUtils>
 #include <osgDB/FileNameUtils>
 #include <osgDB/ReadFile>
@@ -44,19 +45,19 @@ TileBlacklist::TileBlacklist()
 }
 
 void
-TileBlacklist::add(const osgTerrain::TileID &tile)
+TileBlacklist::add(const TileKey& key)
 {
     Threading::ScopedWriteLock lock(_mutex);
-    _tiles.insert(tile);
-    OE_DEBUG << "Added " << tile.level << " (" << tile.x << ", " << tile.y << ") to blacklist" << std::endl;
+    _tiles.insert(key);
+    OE_DEBUG << "Added " << key.str() << " to blacklist" << std::endl;
 }
 
 void
-TileBlacklist::remove(const osgTerrain::TileID &tile)
+TileBlacklist::remove(const TileKey& key)
 {
     Threading::ScopedWriteLock lock(_mutex);
-    _tiles.erase(tile);
-    OE_DEBUG << "Removed " << tile.level << " (" << tile.x << ", " << tile.y << ") from blacklist" << std::endl;
+    _tiles.erase(key);
+    OE_DEBUG << "Removed " << key.str() << " from blacklist" << std::endl;
 }
 
 void
@@ -68,16 +69,16 @@ TileBlacklist::clear()
 }
 
 bool
-TileBlacklist::contains(const osgTerrain::TileID &tile) const
+TileBlacklist::contains(const TileKey& key) const
 {
-    Threading::ScopedReadLock lock(const_cast<TileBlacklist*>(this)->_mutex);
-    return _tiles.find(tile) != _tiles.end();
+    Threading::ScopedReadLock lock(_mutex);
+    return _tiles.find(key) != _tiles.end();
 }
 
 unsigned int
 TileBlacklist::size() const
 {
-    Threading::ScopedReadLock lock(const_cast<TileBlacklist*>(this)->_mutex);
+    Threading::ScopedReadLock lock(_mutex);
     return _tiles.size();
 }
 
@@ -86,7 +87,6 @@ TileBlacklist::read(std::istream &in)
 {
     osg::ref_ptr< TileBlacklist > result = new TileBlacklist();
 
-
     while (!in.eof())
     {
         std::string line;
@@ -96,7 +96,7 @@ TileBlacklist::read(std::istream &in)
             int z, x, y;
             if (sscanf(line.c_str(), "%d %d %d", &z, &x, &y) == 3)
             {
-                result->add(osgTerrain::TileID(z, x, y ));
+                result->add(TileKey(z, x, y, 0L));
             }
 
         }
@@ -135,7 +135,7 @@ TileBlacklist::write(std::ostream &output) const
     Threading::ScopedReadLock lock(const_cast<TileBlacklist*>(this)->_mutex);
     for (BlacklistedTiles::const_iterator itr = _tiles.begin(); itr != _tiles.end(); ++itr)
     {
-        output << itr->level << " " << itr->x << " " << itr->y << std::endl;
+        output << itr->getLOD() << " " << itr->getTileX() << " " << itr->getTileY() << std::endl;
     }
 }
 
@@ -169,6 +169,7 @@ TileSourceOptions::getConfig() const
     conf.updateIfSet( "blacklist_filename", _blacklistFilename);
     conf.updateIfSet( "l2_cache_size", _L2CacheSize );
     conf.updateIfSet( "bilinear_reprojection", _bilinearReprojection );
+    conf.updateIfSet( "max_data_level", _maxDataLevel );
     conf.updateObjIfSet( "profile", _profileOptions );
     return conf;
 }
@@ -192,32 +193,27 @@ TileSourceOptions::fromConfig( const Config& conf )
     conf.getIfSet( "blacklist_filename", _blacklistFilename);
     conf.getIfSet( "l2_cache_size", _L2CacheSize );
     conf.getIfSet( "bilinear_reprojection", _bilinearReprojection );
+    conf.getIfSet( "max_data_level", _maxDataLevel );
     conf.getObjIfSet( "profile", _profileOptions );
-
-    // special handling of default tile size:
-    if ( !tileSize().isSet() )
-    {
-        optional<int> defaultTileSize;
-        conf.getIfSet( "default_tile_size", defaultTileSize );
-        if ( defaultTileSize.isSet() )
-        {
-            _tileSize.init(*defaultTileSize);
-        }
-    }
-
-    // remove it now so it does not get serialized
-    _conf.remove( "default_tile_size" );
 }
 
 
 //------------------------------------------------------------------------
 
+// statics
 TileSource::Status TileSource::STATUS_OK = TileSource::Status();
 
+const char* TileSource::INTERFACE_NAME = "osgEarth::TileSource";
 
-TileSource::TileSource( const TileSourceOptions& options ) :
+const TileSource::Mode TileSource::MODE_READ   = 0x01;
+const TileSource::Mode TileSource::MODE_WRITE  = 0x02;
+const TileSource::Mode TileSource::MODE_CREATE = 0x04;
+
+
+TileSource::TileSource(const TileSourceOptions& options) :
 _options( options ),
-_status ( Status::Error("Not initialized") )
+_status ( Status::Error("Not initialized") ),
+_mode   ( 0 )
 {
     this->setThreadSafeRefUnref( true );
 
@@ -276,9 +272,16 @@ TileSource::initialize(const osgDB::Options* options)
 }
 
 const TileSource::Status&
-TileSource::startup(const osgDB::Options* options)
+TileSource::open(const Mode&           openMode,
+                 const osgDB::Options* options)
 {
+    _mode = openMode;
+
+    // Initialize the underlying data store
     Status status = initialize(options);
+
+    // Check the return status. The TileSource MUST have a valid
+    // Profile after initialization.
     if ( status == STATUS_OK )
     {
         if ( getProfile() != 0L )
@@ -296,7 +299,9 @@ TileSource::startup(const osgDB::Options* options)
     }
 
     if ( _status.isError() )
-        OE_WARN << LC << "Startup failed: " << _status.message() << std::endl;
+    {
+        OE_WARN << LC << "Open failed: " << _status.message() << std::endl;
+    }
 
     return _status;
 }
@@ -318,7 +323,7 @@ TileSource::createImage(const TileKey&        key,
     // Try to get it from the memcache fist
     if (_memCache.valid())
     {
-        ReadResult r = _memCache->getOrCreateDefaultBin()->readImage( key.str(), 0 );
+        ReadResult r = _memCache->getOrCreateDefaultBin()->readImage( key.str() );
         if ( r.succeeded() )
             return r.releaseImage();
     }
@@ -348,7 +353,7 @@ TileSource::createHeightField(const TileKey&        key,
     // Try to get it from the memcache first:
     if (_memCache.valid())
     {
-        ReadResult r = _memCache->getOrCreateDefaultBin()->readObject( key.str(), 0 );
+        ReadResult r = _memCache->getOrCreateDefaultBin()->readObject( key.str() );
         if ( r.succeeded() )
             return r.release<osg::HeightField>();
     }
@@ -367,6 +372,13 @@ TileSource::createHeightField(const TileKey&        key,
     return newHF.valid() ? new osg::HeightField( *newHF.get() ) : 0L;
 }
 
+osg::Image*
+TileSource::createImage(const TileKey&    key,
+                        ProgressCallback* progress)
+{
+    return 0L;
+}
+
 osg::HeightField*
 TileSource::createHeightField(const TileKey&        key,
                               ProgressCallback*     progress)
@@ -385,6 +397,23 @@ TileSource::createHeightField(const TileKey&        key,
 }
 
 bool
+TileSource::storeHeightField(const TileKey&     key,
+                             osg::HeightField*  hf,
+                              ProgressCallback* progress)
+{
+    if ( _status != STATUS_OK || hf == 0L )
+        return 0L;
+
+    ImageToHeightFieldConverter conv;
+    osg::ref_ptr<osg::Image> image = conv.convert(hf, 32);
+    if (image.valid())
+    {
+        return storeImage(key, image.get(), progress);
+    }
+    return false;
+}
+
+bool
 TileSource::isOK() const 
 {
     return _status == STATUS_OK;
@@ -407,6 +436,10 @@ TileSource::hasDataAtLOD( unsigned lod ) const
 {
     // the sematics here are really "MIGHT have data at LOD".
 
+    // Explicit max data level?
+    if ( _options.maxDataLevel().isSet() && lod > _options.maxDataLevel().value() )
+        return false;
+
     // If no data extents are provided, just return true
     if ( _dataExtents.size() == 0 )
         return true;
@@ -458,14 +491,27 @@ TileSource::hasData(const osgEarth::TileKey& key) const
     if (_dataExtents.size() == 0) 
         return true;
 
-    const osgEarth::GeoExtent& keyExtent = key.getExtent();
-    bool intersectsData = false;
+    unsigned int lod = key.getLevelOfDetail();
+
+    // Remap the lod to an appropriate lod if it's not in the same SRS        
+    if (!key.getProfile()->isHorizEquivalentTo( getProfile() ) )
+    {        
+        lod = getProfile()->getEquivalentLOD( key.getProfile(), key.getLevelOfDetail() );        
+    }
 
+    // Check the explicit max data override:
+    if (_options.maxDataLevel().isSet() && lod > _options.maxDataLevel().value())
+        return false;
+
+
+    bool intersectsData = false;
+    const osgEarth::GeoExtent& keyExtent = key.getExtent();
+    
     for (DataExtentList::const_iterator itr = _dataExtents.begin(); itr != _dataExtents.end(); ++itr)
     {
         if ((keyExtent.intersects( *itr )) && 
-            (!itr->minLevel().isSet() || itr->minLevel() <= key.getLOD()) &&
-            (!itr->maxLevel().isSet() || itr->maxLevel() >= key.getLOD()))
+            (!itr->minLevel().isSet() || itr->minLevel() <= lod ) &&
+            (!itr->maxLevel().isSet() || itr->maxLevel() >= lod ))
         {
             intersectsData = true;
             break;
@@ -516,28 +562,30 @@ TileSource::getBlacklist() const
 
 #undef  LC
 #define LC "[TileSourceFactory] "
-#define TILESOURCEOPTIONS_TAG "__osgEarth::TileSourceOptions"
+#define TILESOURCE_OPTIONS_TAG   "__osgEarth::TileSourceOptions"
+#define TILESOURCE_INTERFACE_TAG "__osgEarth::Interface"
 
 TileSource*
-TileSourceFactory::create( const TileSourceOptions& options )
+TileSourceFactory::create(const TileSourceOptions& options)
 {
     TileSource* result = 0L;
 
     std::string driver = options.getDriver();
     if ( driver.empty() )
     {
-        OE_WARN << "ILLEGAL- no driver set for tile source" << std::endl;
+        OE_WARN << LC << "ILLEGAL- no driver set for tile source" << std::endl;
         return 0L;
     }
 
-    osg::ref_ptr<osgDB::Options> rwopt = Registry::instance()->cloneOrCreateOptions();
-    rwopt->setPluginData( TILESOURCEOPTIONS_TAG, (void*)&options );
+    osg::ref_ptr<osgDB::Options> dbopt = Registry::instance()->cloneOrCreateOptions();
+    dbopt->setPluginData      ( TILESOURCE_OPTIONS_TAG,   (void*)&options );
+    dbopt->setPluginStringData( TILESOURCE_INTERFACE_TAG, TileSource::INTERFACE_NAME );
 
     std::string driverExt = std::string( ".osgearth_" ) + driver;
-    result = dynamic_cast<TileSource*>( osgDB::readObjectFile( driverExt, rwopt.get() ) );
+    result = dynamic_cast<TileSource*>( osgDB::readObjectFile( driverExt, dbopt.get() ) );
     if ( !result )
     {
-        OE_WARN << "WARNING: Failed to load TileSource driver for \"" << driver << "\"" << std::endl;
+        OE_WARN << LC << "Failed to load TileSource driver \"" << driver << "\"" << std::endl;
     }
 
     // apply an Override Profile if provided.
@@ -553,10 +601,54 @@ TileSourceFactory::create( const TileSourceOptions& options )
     return result;
 }
 
+#if 0
+ReadWriteTileSource*
+TileSourceFactory::openReadWrite(const TileSourceOptions& options)
+{
+    ReadWriteTileSource* result = 0L;
+
+    std::string driver = options.getDriver();
+    if ( driver.empty() )
+    {
+        OE_WARN << LC << "ILLEGAL- no driver set for tile source" << std::endl;
+        return 0L;
+    }
+
+    osg::ref_ptr<osgDB::Options> dbopt = Registry::instance()->cloneOrCreateOptions();
+    dbopt->setPluginData      ( TILESOURCEOPTIONS_TAG,   (void*)&options );
+    dbopt->setPluginStringData( TILESOURCEINTERFACE_TAG, ReadWriteTileSource::INTERFACE_NAME );
+
+    std::string driverExt = std::string( ".osgearth_" ) + driver;
+    result = dynamic_cast<ReadWriteTileSource*>( osgDB::readObjectFile( driverExt, dbopt.get() ) );
+    if ( !result )
+    {
+        OE_WARN << LC << "Failed to load ReadWriteTileSource driver \"" << driver << "\"" << std::endl;
+    }
+
+    // apply an Override Profile if provided.
+    if ( result && options.profile().isSet() )
+    {
+        const Profile* profile = Profile::create(*options.profile());
+        if ( profile )
+        {
+            result->setProfile( profile );
+        }
+    }
+
+    return result;
+}
+#endif
+
 //------------------------------------------------------------------------
 
 const TileSourceOptions&
-TileSourceDriver::getTileSourceOptions( const osgDB::ReaderWriter::Options* rwopt ) const
+TileSourceDriver::getTileSourceOptions(const osgDB::Options* dbopt ) const
+{
+    return *static_cast<const TileSourceOptions*>( dbopt->getPluginData( TILESOURCE_OPTIONS_TAG ) );
+}
+
+const std::string
+TileSourceDriver::getInterfaceName(const osgDB::Options* dbopt) const
 {
-    return *static_cast<const TileSourceOptions*>( rwopt->getPluginData( TILESOURCEOPTIONS_TAG ) );
+    return dbopt->getPluginStringData(TILESOURCE_INTERFACE_TAG);
 }
diff --git a/src/osgEarth/TileVisitor b/src/osgEarth/TileVisitor
new file mode 100644
index 0000000..20ea8b6
--- /dev/null
+++ b/src/osgEarth/TileVisitor
@@ -0,0 +1,228 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTH_TILEVISITOR_H
+#define OSGEARTH_TILEVISITOR_H 1
+
+#include <osgEarth/Common>
+#include <osgEarth/TileHandler>
+#include <osgEarth/Profile>
+#include <osgEarth/TaskService>
+
+namespace osgEarth
+{
+    /**
+    * Utility class that traverses a Profile and emits TileKey's based on a collection of extents and min/max levels
+    */
+    class OSGEARTH_EXPORT TileVisitor : public osg::Referenced
+    {
+    public:
+
+        TileVisitor();
+
+        TileVisitor(TileHandler* handler);
+
+        /**
+        * Sets the minimum level to visit
+        */
+        void setMinLevel(const unsigned int& minLevel) {_minLevel = minLevel;}
+
+        /**
+        * Gets the minimum level to visit
+        */
+        const unsigned int getMinLevel() const {return _minLevel;}
+
+        /**
+        * Sets the maximum level to visit
+        */
+        void setMaxLevel(const unsigned int& maxLevel) {_maxLevel = maxLevel;}
+
+        /**
+        * Gets the maximum level to visit
+        */
+        const unsigned int getMaxLevel() const {return _maxLevel;}
+  
+
+        /**
+        * Extents to visit
+        */
+        void addExtent( const GeoExtent& extent ); 
+        const std::vector< GeoExtent >& getExtents() const { return _extents; }  
+
+        virtual void run(const Profile* mapProfile);
+
+        bool intersects( const GeoExtent& extent );    
+
+        void setTileHandler( TileHandler* handler );
+
+        void setProgressCallback( ProgressCallback* progress );
+
+        void incrementProgress( unsigned int progress );
+
+        void resetProgress();
+        
+
+    protected:        
+
+        void estimate();
+
+        virtual bool handleTile( const TileKey& key );
+
+        void processKey( const TileKey& key );
+
+        unsigned int _minLevel;
+        unsigned int _maxLevel;
+
+        std::vector< GeoExtent > _extents;
+
+        osg::ref_ptr< TileHandler > _tileHandler;
+
+        osg::ref_ptr< ProgressCallback > _progress;
+
+        osg::ref_ptr< const Profile > _profile;
+
+        OpenThreads::Mutex _progressMutex;
+
+        unsigned int _total;
+        unsigned int _processed;        
+    };
+
+
+    /**
+    * A TileVisitor that pushes all of it's generated keys onto a TaskService queue and handles them in background threads.
+    */
+    class OSGEARTH_EXPORT MultithreadedTileVisitor: public TileVisitor
+    {
+    public:
+        MultithreadedTileVisitor();
+
+        MultithreadedTileVisitor( TileHandler* handler );
+
+        unsigned int getNumThreads() const;
+        void setNumThreads( unsigned int numThreads);
+
+        virtual void run(const Profile* mapProfile);
+
+    protected:
+
+        virtual bool handleTile( const TileKey& key );
+
+        unsigned int _numThreads;
+
+        // The work queue to pass seed operations to
+        osg::ref_ptr<osgEarth::TaskService> _taskService;
+    };
+
+
+    typedef std::vector< TileKey > TileKeyList;
+
+    
+    /**
+     * A list of TileKeys that you can serialize to a file
+     */
+    class OSGEARTH_EXPORT TaskList
+    {    
+    public:
+        TaskList(const Profile* profile);
+
+        /**
+         * Loads the tiles from the given file.
+         */
+        bool load( const std::string &filename);          
+
+        /**
+         * Saves the tiles to the given file.
+         */
+        void save( const std::string& filename);
+
+        /**
+         * Gets the list of keys
+         */
+        TileKeyList& getKeys();
+
+        const TileKeyList& getKeys() const;
+
+    protected:
+        TileKeyList _keys;
+        osg::ref_ptr< const Profile > _profile;
+    };
+
+
+
+
+    /**
+    * A TileVisitor that launches an external process to process tiles.
+    */
+    class OSGEARTH_EXPORT MultiprocessTileVisitor: public TileVisitor
+    {
+    public:
+        MultiprocessTileVisitor();
+
+        MultiprocessTileVisitor( TileHandler* handler );
+
+        unsigned int getNumProcesses() const;
+        void setNumProcesses( unsigned int numProcesses);
+
+        unsigned int getBatchSize() const;
+        void setBatchSize( unsigned int batchSize );
+
+        virtual void run(const Profile* mapProfile);          
+
+        const std::string& getEarthFile() const;
+        void setEarthFile( const std::string& earthFile );
+
+    protected:
+
+        virtual bool handleTile( const TileKey& key );
+
+        void processBatch();
+
+        TileKeyList _batch;
+
+        unsigned int _batchSize;
+        unsigned int _numProcesses;    
+
+        std::string _earthFile;
+
+        // The work queue to pass seed operations to
+        osg::ref_ptr<osgEarth::TaskService> _taskService;        
+    };
+
+    
+    /**
+    * A TileVisitor that simply emits keys from a list.  Useful for running a list of tasks.
+    */
+    class OSGEARTH_EXPORT TileKeyListVisitor : public TileVisitor
+    {
+    public:
+        TileKeyListVisitor();
+
+        void setKeys(const TileKeyList& keys);
+        
+        virtual void run(const Profile* mapProfile);
+
+    protected:
+        
+        TileKeyList _keys;
+    };
+
+
+
+} // namespace osgEarth
+
+#endif // OSGEARTH_TRAVERSAL_DATA_H
diff --git a/src/osgEarth/TileVisitor.cpp b/src/osgEarth/TileVisitor.cpp
new file mode 100644
index 0000000..45837d6
--- /dev/null
+++ b/src/osgEarth/TileVisitor.cpp
@@ -0,0 +1,497 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osgEarth/TileVisitor>
+#include <osgEarth/CacheEstimator>
+#include <osgEarth/FileUtils>
+
+using namespace osgEarth;
+
+TileVisitor::TileVisitor():
+_total(0),
+_processed(0),
+_minLevel(0),
+_maxLevel(5)
+{
+}
+
+
+TileVisitor::TileVisitor(TileHandler* handler):
+_tileHandler( handler ),
+_total(0),
+_processed(0),
+_minLevel(0),
+_maxLevel(5)
+{
+}
+
+void TileVisitor::resetProgress()
+{
+    _total = 0;
+    _processed = 0;
+}
+
+void TileVisitor::addExtent( const GeoExtent& extent )
+{
+    _extents.push_back( extent );
+}
+
+bool TileVisitor::intersects( const GeoExtent& extent )
+{    
+    if ( _extents.empty()) return true;
+    else
+    {
+        for (unsigned int i = 0; i < _extents.size(); ++i)
+        {
+            if (_extents[i].intersects( extent ))                
+            {
+                return true;
+            }
+
+        }
+    }
+    return false;
+}
+
+void TileVisitor::setTileHandler( TileHandler* handler )
+{
+    _tileHandler = handler;
+}
+
+void TileVisitor::setProgressCallback( ProgressCallback* progress )
+{
+    _progress = progress;
+}
+
+void TileVisitor::run( const Profile* mapProfile )
+{
+    _profile = mapProfile;
+    
+    // Reset the progress in case this visitor has been ran before.
+    resetProgress();
+    
+    estimate();
+
+    // Get all the root keys and process them.
+    std::vector<TileKey> keys;
+    mapProfile->getRootKeys(keys);
+
+    for (unsigned int i = 0; i < keys.size(); ++i)
+    {
+        processKey( keys[i] );
+    }
+}
+
+void TileVisitor::estimate()
+{
+    //Estimate the number of tiles    
+    CacheEstimator est;
+    est.setMinLevel( _minLevel );
+    est.setMaxLevel( _maxLevel );
+    est.setProfile( _profile ); 
+    for (unsigned int i = 0; i < _extents.size(); i++)
+    {                
+        est.addExtent( _extents[ i ] );
+    } 
+    _total = est.getNumTiles();
+}
+
+void TileVisitor::processKey( const TileKey& key )
+{        
+    // If we've been cancelled then just return.
+    if (_progress && _progress->isCanceled())
+    {        
+        return;
+    }    
+
+    unsigned int x, y, lod;
+    key.getTileXY(x, y);
+    lod = key.getLevelOfDetail();    
+
+    // Only process this key if it has a chance of succeeding.
+    if (_tileHandler && !_tileHandler->hasData(key))
+    {                
+        return;
+    }    
+
+    bool traverseChildren = false;
+
+    // If the key intersects the extent attempt to traverse
+    if (intersects( key.getExtent() ))
+    {
+        // If the lod is less than the min level don't do anything but do traverse the children.
+        if (lod < _minLevel)
+        {
+            traverseChildren = true;
+        }
+        else
+        {         
+            // Process the key
+            traverseChildren = handleTile( key );        
+        }
+    }
+
+    // Traverse the children
+    if (traverseChildren && lod < _maxLevel)
+    {
+        for (unsigned int i = 0; i < 4; i++)
+        {
+            TileKey k = key.createChildKey(i);
+            processKey( k );
+        }                                
+    }       
+}
+
+void TileVisitor::incrementProgress(unsigned int amount)
+{
+    {
+        OpenThreads::ScopedLock< OpenThreads::Mutex > lk(_progressMutex );
+        _processed += amount;
+    }
+    if (_progress.valid())
+    {
+        // If report progress returns true then mark the task as being cancelled.
+        if (_progress->reportProgress( _processed, _total ))
+        {
+            _progress->cancel();
+        }
+    }    
+}
+
+bool TileVisitor::handleTile( const TileKey& key )
+{    
+    bool result = false;
+    if (_tileHandler.valid() )
+    {
+        result = _tileHandler->handleTile( key, *this );
+    }
+
+    incrementProgress(1);    
+    
+    return result;
+}
+
+
+
+/*****************************************************************************************/
+/**
+ * A TaskRequest that runs a TileHandler in a background thread.
+ */
+class HandleTileTask : public TaskRequest
+{
+public:
+    HandleTileTask( TileHandler* handler, TileVisitor* visitor, const TileKey& key ):      
+      _handler( handler ),
+          _visitor(visitor),
+          _key( key )
+      {
+
+      }
+
+      virtual void operator()(ProgressCallback* progress )
+      {         
+          if (_handler.valid())
+          {                           
+              _handler->handleTile( _key, *_visitor.get() );
+              _visitor->incrementProgress(1);
+          }
+      }
+
+      osg::ref_ptr<TileHandler> _handler;
+      TileKey _key;
+      osg::ref_ptr<TileVisitor> _visitor;
+};
+
+MultithreadedTileVisitor::MultithreadedTileVisitor():
+_numThreads( OpenThreads::GetNumberOfProcessors() )
+{
+    // We must do this to avoid an error message in OpenSceneGraph b/c the findWrapper method doesn't appear to be threadsafe.
+    // This really isn't a big deal b/c this only effects data that is already cached.
+    osgDB::ObjectWrapper* wrapper = osgDB::Registry::instance()->getObjectWrapperManager()->findWrapper( "osg::Image" );
+}
+
+MultithreadedTileVisitor::MultithreadedTileVisitor( TileHandler* handler ):
+TileVisitor( handler ),
+    _numThreads( OpenThreads::GetNumberOfProcessors() )
+{
+}
+
+unsigned int MultithreadedTileVisitor::getNumThreads() const
+{
+    return _numThreads; 
+}
+
+void MultithreadedTileVisitor::setNumThreads( unsigned int numThreads)
+{
+    _numThreads = numThreads; 
+}
+
+void MultithreadedTileVisitor::run(const Profile* mapProfile)
+{                   
+    // Start up the task service
+    OE_INFO << "Starting " << _numThreads << std::endl;
+    _taskService = new TaskService( "MTTileHandler", _numThreads, 100000 );
+
+    // Produce the tiles
+    TileVisitor::run( mapProfile );
+
+    // Send a poison pill to kill all the threads
+    _taskService->add( new PoisonPill() );
+
+    OE_INFO << "Waiting on threads to complete" << _taskService->getNumRequests() << " tasks remaining" << std::endl;
+
+    // Wait for everything to finish, checking for cancellation while we wait so we can kill all the existing tasks.
+    while (_taskService->areThreadsRunning())
+    {
+        OpenThreads::Thread::microSleep(10000);
+        if (_progress && _progress->isCanceled())
+        {            
+            _taskService->cancelAll();
+        }
+    }
+    OE_INFO << "All threads have completed" << std::endl;
+}
+
+bool MultithreadedTileVisitor::handleTile( const TileKey& key )        
+{    
+    // Add the tile to the task queue.
+    _taskService->add( new HandleTileTask(_tileHandler, this, key ) );
+    return true;
+}
+
+/*****************************************************************************************/
+
+TaskList::TaskList(const Profile* profile):
+_profile( profile )
+{
+}
+
+bool TaskList::load( const std::string &filename)
+{          
+    std::ifstream in( filename.c_str(), std::ios::in );
+
+    std::string line;
+    while( getline(in, line) )
+    {            
+        std::vector< std::string > parts;
+        StringTokenizer(line, parts, "," );
+
+
+        _keys.push_back( TileKey(as<unsigned int>(parts[0], 0), 
+            as<unsigned int>(parts[1], 0), 
+            as<unsigned int>(parts[2], 0),
+            _profile ) );
+    }
+
+
+    return true;
+}
+
+void TaskList::save( const std::string& filename )
+{        
+    std::ofstream out( filename.c_str() );
+    for (TileKeyList::iterator itr = _keys.begin(); itr != _keys.end(); ++itr)
+    {
+        out << (*itr).getLevelOfDetail() << ", " << (*itr).getTileX() << ", " << (*itr).getTileY() << std::endl;
+    }
+}
+
+TileKeyList& TaskList::getKeys()
+{
+    return _keys;
+}
+
+const TileKeyList& TaskList::getKeys() const
+{
+    return _keys;
+}
+
+/*****************************************************************************************/
+MultiprocessTileVisitor::MultiprocessTileVisitor():
+    _numProcesses( OpenThreads::GetNumberOfProcessors() ),
+    _batchSize(100)
+{
+    osgDB::ObjectWrapper* wrapper = osgDB::Registry::instance()->getObjectWrapperManager()->findWrapper( "osg::Image" );
+}
+
+MultiprocessTileVisitor::MultiprocessTileVisitor( TileHandler* handler ):
+TileVisitor( handler ),
+    _numProcesses( OpenThreads::GetNumberOfProcessors() ),
+    _batchSize(100)
+{
+}
+
+unsigned int MultiprocessTileVisitor::getNumProcesses() const
+{
+    return _numProcesses; 
+}
+
+void MultiprocessTileVisitor::setNumProcesses( unsigned int numProcesses)
+{
+    _numProcesses = numProcesses; 
+}
+
+unsigned int MultiprocessTileVisitor::getBatchSize() const
+{
+    return _batchSize;
+}
+
+void MultiprocessTileVisitor::setBatchSize( unsigned int batchSize )
+{
+    _batchSize = batchSize;
+}
+
+
+void MultiprocessTileVisitor::run(const Profile* mapProfile)
+{                             
+    // Start up the task service          
+    _taskService = new TaskService( "MPTileHandler", _numProcesses, 100000 );
+    
+    // Produce the tiles
+    TileVisitor::run( mapProfile );
+
+    // Process any remaining tasks in the final batch
+    processBatch();
+
+    // Send a poison pill to kill all the threads
+    _taskService->add( new PoisonPill() );
+   
+    OE_INFO << "Waiting on threads to complete" << _taskService->getNumRequests() << " tasks remaining" << std::endl;
+
+    // Wait for everything to finish, checking for cancellation while we wait so we can kill all the existing tasks.
+    while (_taskService->areThreadsRunning())
+    {
+        OpenThreads::Thread::microSleep(10000);
+        if (_progress && _progress->isCanceled())
+        {            
+            _taskService->cancelAll();
+        }
+    }
+    OE_INFO << "All threads have completed" << std::endl;
+}
+
+bool MultiprocessTileVisitor::handleTile( const TileKey& key )        
+{        
+    _batch.push_back( key );
+
+    if (_batch.size() == _batchSize)
+    {
+        processBatch();
+    }         
+    return true;
+}
+
+const std::string& MultiprocessTileVisitor::getEarthFile() const
+{
+    return _earthFile;
+}
+
+void MultiprocessTileVisitor::setEarthFile( const std::string& earthFile )
+{
+    _earthFile = earthFile;
+}
+
+/**
+* Executes a command in an external process
+*/
+class ExecuteTask : public TaskRequest
+{
+public:
+    ExecuteTask(const std::string& command, TileVisitor* visitor, unsigned int count):            
+      _command( command ),
+      _visitor( visitor ),
+      _count( count )
+      {
+      }
+
+      virtual void operator()(ProgressCallback* progress )
+      {         
+          system(_command.c_str());     
+
+          // Cleanup the temp files and increment the progress on the visitor.
+          cleanupTempFiles();
+          _visitor->incrementProgress( _count );
+      }
+
+      void addTempFile( const std::string& filename )
+      {
+          _tempFiles.push_back(filename);
+      }
+
+      void cleanupTempFiles()
+      {
+          for (unsigned int i = 0; i < _tempFiles.size(); i++)
+          {
+              remove( _tempFiles[i].c_str() );
+          }
+      }
+
+
+      std::vector< std::string > _tempFiles;
+      std::string _command;
+      TileVisitor* _visitor;
+      unsigned int _count;
+};
+
+void MultiprocessTileVisitor::processBatch()
+{       
+    TaskList tasks( 0 );
+    for (unsigned int i = 0; i < _batch.size(); i++)
+    {
+        tasks.getKeys().push_back( _batch[i] );
+    }
+    // Save the task file out.
+    std::string tmpPath = getTempPath();
+    std::string filename = getTempName(tmpPath, "batch.tiles");        
+    tasks.save( filename );        
+
+    std::stringstream command;        
+    command << _tileHandler->getProcessString() << " --tiles " << filename << " " << _earthFile;
+    OE_INFO << "Running command " << command.str() << std::endl;
+    osg::ref_ptr< ExecuteTask > task = new ExecuteTask( command.str(), this, tasks.getKeys().size() );
+    // Add the task file as a temp file to the task to make sure it gets deleted
+    task->addTempFile( filename );
+
+    _taskService->add(task);
+    _batch.clear();
+}
+
+
+/*****************************************************************************************/
+TileKeyListVisitor::TileKeyListVisitor()
+{
+}
+
+void TileKeyListVisitor::setKeys(const TileKeyList& keys)
+{
+    _keys = keys;
+}
+
+void TileKeyListVisitor::run(const Profile* mapProfile)
+{
+    resetProgress();        
+
+    for (TileKeyList::iterator itr = _keys.begin(); itr != _keys.end(); ++itr)
+    {
+        if (_tileHandler)
+        {
+            _tileHandler->handleTile( *itr, *this );
+            incrementProgress(1);
+        }
+    }
+}
diff --git a/src/osgEarth/TimeControl b/src/osgEarth/TimeControl
index 8a5c0fa..180a65e 100644
--- a/src/osgEarth/TimeControl
+++ b/src/osgEarth/TimeControl
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/TimeControl.cpp b/src/osgEarth/TimeControl.cpp
index 17daa32..d5af577 100644
--- a/src/osgEarth/TimeControl.cpp
+++ b/src/osgEarth/TimeControl.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/TraversalData b/src/osgEarth/TraversalData
index eeaad25..fce9b75 100644
--- a/src/osgEarth/TraversalData
+++ b/src/osgEarth/TraversalData
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -67,7 +67,7 @@ namespace osgEarth
     public:
         osg::observer_ptr<class MapNode> _mapNode;
         osg::ref_ptr<osg::StateSet>      _stateSet;
-        osg::ref_ptr<osg::Uniform>       _windowScaleMatrixUniform;
+        osg::ref_ptr<osg::Uniform>       _windowMatrixUniform;
         double                           _cameraAltitude;
 
     protected:
diff --git a/src/osgEarth/TraversalData.cpp b/src/osgEarth/TraversalData.cpp
index 25f0fb1..9a4528b 100644
--- a/src/osgEarth/TraversalData.cpp
+++ b/src/osgEarth/TraversalData.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -24,11 +24,9 @@ MapNodeCullData::MapNodeCullData()
 {
     _stateSet = new osg::StateSet();
 
-    _windowScaleMatrixUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT3, "oe_WindowScaleMatrix");
-    osg::Matrix3 identity;
-    identity.makeIdentity();
-    _windowScaleMatrixUniform->set( identity );
-    _stateSet->addUniform( _windowScaleMatrixUniform.get() );
+    _windowMatrixUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "oe_WindowMatrix");
+    _windowMatrixUniform->set( osg::Matrix::identity() );
+    _stateSet->addUniform( _windowMatrixUniform.get() );
 
     _cameraAltitude = 0.0;
 }
diff --git a/src/osgEarth/URI b/src/osgEarth/URI
index 5ce1f50..0e1c887 100644
--- a/src/osgEarth/URI
+++ b/src/osgEarth/URI
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -120,6 +120,23 @@ namespace osgEarth
     /**
      * Represents the location of a resource, providing the raw (original, possibly
      * relative) and absolute forms.
+     *
+     * URI is serializable and may be used in an earth file, like in the following
+     * example. Note that in earth files, the URI is actually called "url"; this is
+     * simply because of an old convention and we wish to avoid breaking backwards
+     * compatibility.
+     *
+     *   <url>../path/relative/to/earth/file</url>
+     *
+     * Note also that a relative URI will be relative to the location of the 
+     * parent resource (usually the earth file itself). 
+     *
+     * You can also specify osgDB plugin options; for example:
+     *
+     *   <url options_string="JPEG_QUALITY 60">../path/to/image.jpg</url>
+     *
+     * Of course, options are particular to OSG plugins, so please consult the
+     * code for your plugin for more information.
      */
     class OSGEARTH_EXPORT URI
     {
@@ -173,6 +190,9 @@ namespace osgEarth
         /** String used for keying the cache */
         const std::string& cacheKey() const { return !_cacheKey.empty() ? _cacheKey : _fullURI; }
 
+        /** osgDB::Options option string (plugin options) */
+        optional<std::string>& optionString() { return _optionString; }
+        const optional<std::string>& optionString() const { return _optionString; }
 
     public:
 
@@ -228,14 +248,27 @@ namespace osgEarth
         /** Copier */
         URI( const URI& rhs ) : _baseURI(rhs._baseURI), _fullURI(rhs._fullURI), _context(rhs._context) { }
 
-    public:
-        //TODO: methods to load data from the URI.
+
+    public: // config methods
+
+        Config getConfig() const
+        {
+            Config conf("uri", base());
+            conf.addIfSet("option_string", _optionString);
+            return conf;
+        }
+
+        void mergeConfig(const Config& conf)
+        {
+            conf.getIfSet("option_string", _optionString);
+        }
 
     protected:
         std::string _baseURI;
         std::string _fullURI;
         std::string _cacheKey;
         URIContext  _context;
+        optional<std::string> _optionString;
     };
     
 
@@ -373,19 +406,21 @@ namespace osgEarth
     template <> inline
     void Config::addIfSet<URI>( const std::string& key, const optional<URI>& opt ) {
         if ( opt.isSet() ) {
-            Config conf(key, opt->base());
-            conf.setReferrer(opt->context().referrer());
-            add( conf );
+            add(key, opt->getConfig());
+            //Config conf(key, opt->base());
+            //conf.setReferrer(opt->context().referrer());
+            //add( conf );
         }
     }
 
     template<> inline
     void Config::updateIfSet<URI>( const std::string& key, const optional<URI>& opt ) {
         if ( opt.isSet() ) {
-            remove(key);
-            Config conf(key, opt->base());
-            conf.setReferrer(opt->context().referrer());
-            add( conf );
+            //remove(key);
+            update(key, opt->getConfig());
+            //Config conf(key, opt->base());
+            //conf.setReferrer(opt->context().referrer());
+            //add( conf );
         }
     }
 
@@ -393,6 +428,7 @@ namespace osgEarth
     bool Config::getIfSet<URI>( const std::string& key, optional<URI>& output ) const {
         if ( hasValue(key) ) {
             output = URI( value(key), referrer(key) );
+            output->mergeConfig(*this);
             return true;
         }
         else
diff --git a/src/osgEarth/URI.cpp b/src/osgEarth/URI.cpp
index ba70221..fdb2ef7 100644
--- a/src/osgEarth/URI.cpp
+++ b/src/osgEarth/URI.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -121,8 +121,11 @@ URIContext::apply( osgDB::Options* options )
 {
     if ( options )
     {
-        options->setDatabasePath( _referrer );
-        options->setPluginStringData( "osgEarth::URIContext::referrer", _referrer );
+        if (_referrer.empty() == false)
+        {
+            options->setDatabasePath( _referrer );
+            options->setPluginStringData( "osgEarth::URIContext::referrer", _referrer );
+        }
     }
 }
 
@@ -288,8 +291,16 @@ namespace
     {
         bool callbackRequestsCaching( URIReadCallback* cb ) const { return !cb || ((cb->cachingSupport() & URIReadCallback::CACHE_OBJECTS) != 0); }
         ReadResult fromCallback( URIReadCallback* cb, const std::string& uri, const osgDB::Options* opt ) { return cb->readObject(uri, opt); }
-        ReadResult fromCache( CacheBin* bin, const std::string& key, TimeStamp minTime) { return bin->readObject(key, minTime); }
-        ReadResult fromHTTP( const std::string& uri, const osgDB::Options* opt, ProgressCallback* p ) { return HTTPClient::readObject(uri, opt, p); }
+        ReadResult fromCache( CacheBin* bin, const std::string& key) { return bin->readObject(key); }
+        ReadResult fromHTTP( const std::string& uri, const osgDB::Options* opt, ProgressCallback* p, TimeStamp lastModified )
+        {
+            HTTPRequest req(uri);            
+            if (lastModified > 0)
+            {
+                req.setLastModified(lastModified);
+            }
+            return HTTPClient::readObject(req, opt, p);
+        }
         ReadResult fromFile( const std::string& uri, const osgDB::Options* opt ) { return ReadResult(osgDB::readObjectFile(uri, opt)); }
     };
 
@@ -297,8 +308,16 @@ namespace
     {
         bool callbackRequestsCaching( URIReadCallback* cb ) const { return !cb || ((cb->cachingSupport() & URIReadCallback::CACHE_NODES) != 0); }
         ReadResult fromCallback( URIReadCallback* cb, const std::string& uri, const osgDB::Options* opt ) { return cb->readNode(uri, opt); }
-        ReadResult fromCache( CacheBin* bin, const std::string& key, TimeStamp minTime) { return bin->readObject(key, minTime); }
-        ReadResult fromHTTP( const std::string& uri, const osgDB::Options* opt, ProgressCallback* p ) { return HTTPClient::readNode(uri, opt, p); }
+        ReadResult fromCache( CacheBin* bin, const std::string& key ) { return bin->readObject(key); }
+        ReadResult fromHTTP( const std::string& uri, const osgDB::Options* opt, ProgressCallback* p, TimeStamp lastModified )
+        {
+            HTTPRequest req(uri);            
+            if (lastModified > 0)
+            {
+                req.setLastModified(lastModified);
+            }
+            return HTTPClient::readNode(req, opt, p);
+        }
         ReadResult fromFile( const std::string& uri, const osgDB::Options* opt ) { return ReadResult(osgDB::readNodeFile(uri, opt)); }
     };
 
@@ -312,13 +331,18 @@ namespace
             if ( r.getImage() ) r.getImage()->setFileName(uri);
             return r;
         }                
-        ReadResult fromCache( CacheBin* bin, const std::string& key, TimeStamp minTime ) { 
-            ReadResult r = bin->readImage(key, minTime);
+        ReadResult fromCache( CacheBin* bin, const std::string& key) { 
+            ReadResult r = bin->readImage(key);
             if ( r.getImage() ) r.getImage()->setFileName( key );
             return r;
         }
-        ReadResult fromHTTP( const std::string& uri, const osgDB::Options* opt, ProgressCallback* p ) { 
-            ReadResult r = HTTPClient::readImage(uri, opt, p);
+        ReadResult fromHTTP( const std::string& uri, const osgDB::Options* opt, ProgressCallback* p, TimeStamp lastModified ) { 
+            HTTPRequest req(uri);            
+            if (lastModified > 0)
+            {
+                req.setLastModified(lastModified);
+            }
+            ReadResult r = HTTPClient::readImage(req, opt, p);
             if ( r.getImage() ) r.getImage()->setFileName( uri );
             return r;
         }
@@ -333,8 +357,16 @@ namespace
     {
         bool callbackRequestsCaching( URIReadCallback* cb ) const { return !cb || ((cb->cachingSupport() & URIReadCallback::CACHE_STRINGS) != 0); }
         ReadResult fromCallback( URIReadCallback* cb, const std::string& uri, const osgDB::Options* opt ) { return cb->readString(uri, opt); }
-        ReadResult fromCache( CacheBin* bin, const std::string& key, TimeStamp minTime) { return bin->readString(key, minTime); }
-        ReadResult fromHTTP( const std::string& uri, const osgDB::Options* opt, ProgressCallback* p ) { return HTTPClient::readString(uri, opt, p); }
+        ReadResult fromCache( CacheBin* bin, const std::string& key) { return bin->readString(key); }
+        ReadResult fromHTTP( const std::string& uri, const osgDB::Options* opt, ProgressCallback* p, TimeStamp lastModified )
+        {
+            HTTPRequest req(uri);            
+            if (lastModified > 0)
+            {
+                req.setLastModified(lastModified);
+            }
+            return HTTPClient::readString(req, opt, p);
+        }
         ReadResult fromFile( const std::string& uri, const osgDB::Options* opt ) { return readStringFile(uri, opt); }
     };
 
@@ -355,7 +387,16 @@ namespace
         if ( !inputURI.empty() )
         {
             // establish our IO options:
-            const osgDB::Options* localOptions = dbOptions ? dbOptions : Registry::instance()->getDefaultOptions();
+            osg::ref_ptr<const osgDB::Options> localOptions = dbOptions ? dbOptions : Registry::instance()->getDefaultOptions();
+
+            // if we have an option string, incorporate it.
+            if ( inputURI.optionString().isSet() )
+            {
+                osgDB::Options* newLocalOptions = osg::clone(localOptions.get());
+                newLocalOptions->setOptionString(
+                    inputURI.optionString().get() + " " + localOptions->getOptionString());
+                localOptions = newLocalOptions;
+            }
 
             READ_FUNCTOR reader;
 
@@ -416,32 +457,40 @@ namespace
 
                     // establish the caching policy.
                     optional<CachePolicy> cp;
-                    if ( !Registry::instance()->getCachePolicy( cp, localOptions ) )
-                        cp = CachePolicy::DEFAULT;
+                    CachePolicy::fromOptions(localOptions, cp);
+                    Registry::instance()->resolveCachePolicy( cp );                    
 
                     // get a cache bin if we need it:
                     CacheBin* bin = 0L;
                     if ( (cp->usage() != CachePolicy::USAGE_NO_CACHE) && callbackCachingOK )
                     {
                         bin = s_getCacheBin( dbOptions );
-                    }
+                    }                    
 
+
+                    bool expired = false;
                     // first try to go to the cache if there is one:
                     if ( bin && cp->isCacheReadable() )
-                    {
-                        result = reader.fromCache( bin, uri.cacheKey(), cp->getMinAcceptTime() );
+                    {                                                
+                        result = reader.fromCache( bin, uri.cacheKey() );                        
                         if ( result.succeeded() )
+                        {                                        
+                            expired = cp->isExpired(result.lastModifiedTime());
                             result.setIsFromCache(true);
+                        }
                     }
 
-                    // not in the cache, so proceed to read it from the network.
-                    if ( result.empty() )
-                    {
+                    // If it's not cached, or it is cached but is expired then try to hit the server.                    
+                    if ( result.empty() || expired )
+                    {                        
                         // Need to do this to support nested PLODs and Proxynodes.
                         osg::ref_ptr<osgDB::Options> remoteOptions =
                             Registry::instance()->cloneOrCreateOptions( localOptions );
                         remoteOptions->getDatabasePathList().push_front( osgDB::getFilePath(uri.full()) );
 
+                        // Store the existing object from the cache if there is one.
+                        osg::ref_ptr< osg::Object > object = result.getObject();
+
                         // try to use the callback if it's set. Callback ignores the caching policy.
                         if ( cb )
                         {                
@@ -455,16 +504,28 @@ namespace
                         }
 
                         if ( !gotResultFromCallback )
-                        {
+                        {                            
                             // still no data, go to the source:
-                            if ( result.empty() && cp->usage() != CachePolicy::USAGE_CACHE_ONLY )
-                            {
-                                result = reader.fromHTTP( uri.full(), remoteOptions.get(), progress );
+                            if ( (result.empty() || expired) && cp->usage() != CachePolicy::USAGE_CACHE_ONLY )
+                            {                                
+                                ReadResult remoteResult = reader.fromHTTP( uri.full(), remoteOptions.get(), progress, result.lastModifiedTime() );
+                                if (remoteResult.code() == ReadResult::RESULT_NOT_MODIFIED)
+                                {                                    
+                                    OE_DEBUG << uri.full() << " not modified, using cached result" << std::endl;
+                                    // Touch the cached item to update it's last modified timestamp so it doesn't expire again immediately.
+                                    bin->touch( uri.cacheKey() );
+                                }
+                                else
+                                {
+                                    OE_DEBUG << "Got remote result for " << uri.full() << std::endl;
+                                    result = remoteResult;                                    
+                                }
                             }
 
                             // write the result to the cache if possible:
-                            if ( result.succeeded() && bin && cp->isCacheWriteable() )
+                            if ( result.succeeded() && !result.isFromCache() && bin && cp->isCacheWriteable() )
                             {
+                                OE_DEBUG << "Writing " << uri.cacheKey() << " to cache" << std::endl;
                                 bin->write( uri.cacheKey(), result.getObject(), result.metadata() );
                             }
                         }
@@ -498,21 +559,6 @@ namespace
             (*post)(result);
         }
 
-        /*
-        osg::Timer_t endTime = osg::Timer::instance()->tick();
-
-        double time = osg::Timer::instance()->delta_s( startTime, endTime );
-        {
-            OpenThreads::ScopedLock< OpenThreads::Mutex > lock( s_statsLock );            
-            totalTime += time;
-            totalRequests += 1;
-            double avg = (double)totalRequests / totalTime;
-            OE_NOTICE << "total req = " << totalRequests << " totalTime = " << totalTime << " " << avg << " req/s" << std::endl;            
-        }
-        */
-
-        
-
         return result;
     }
 }
diff --git a/src/osgEarth/Units b/src/osgEarth/Units
index b01b9c3..cea9f47 100644
--- a/src/osgEarth/Units
+++ b/src/osgEarth/Units
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Units.cpp b/src/osgEarth/Units.cpp
index e528ff4..733e7d7 100644
--- a/src/osgEarth/Units.cpp
+++ b/src/osgEarth/Units.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -173,6 +173,7 @@ Units::registerAll(Registry* r)
 }
 
 
+// Factor converts unit into METERS:
 const Units Units::CENTIMETERS       ( "centimeters",    "cm",  Units::TYPE_LINEAR, 0.01 ); 
 const Units Units::FEET              ( "feet",           "ft",  Units::TYPE_LINEAR, 0.3048 );
 const Units Units::FEET_US_SURVEY    ( "feet(us)",       "ft",  Units::TYPE_LINEAR, 12.0/39.37 );
@@ -188,24 +189,26 @@ const Units Units::FATHOMS           ( "fathoms",        "fm",  Units::TYPE_LINE
 const Units Units::KILOFEET          ( "kilofeet",       "kf",  Units::TYPE_LINEAR, 304.8 );
 const Units Units::KILOYARDS         ( "kiloyards",      "kyd", Units::TYPE_LINEAR, 914.4 );
 
+// Factor converts unit into RADIANS:
 const Units Units::DEGREES           ( "degrees",        "\xb0",Units::TYPE_ANGULAR, 0.017453292519943295 );
 const Units Units::RADIANS           ( "radians",        "rad", Units::TYPE_ANGULAR, 1.0 );
 const Units Units::BAM               ( "BAM",            "bam", Units::TYPE_ANGULAR, 6.283185307179586476925286766559 );
 const Units Units::NATO_MILS         ( "mils",           "mil", Units::TYPE_ANGULAR, 9.8174770424681038701957605727484e-4 );
 
-const Units Units::DAYS              ( "days",           "d",   Units::TYPE_TEMPORAL, 1.0/86400.0 );
-const Units Units::HOURS             ( "hours",          "hr",  Units::TYPE_TEMPORAL, 1.0/3600.0 );
-const Units Units::MICROSECONDS      ( "microseconds",   "us",  Units::TYPE_TEMPORAL, 1000000.0 );
-const Units Units::MILLISECONDS      ( "milliseconds",   "ms",  Units::TYPE_TEMPORAL, 1000.0 );
-const Units Units::MINUTES           ( "minutes",        "min", Units::TYPE_TEMPORAL, 1.0/60.0 );
+// Factor convert unit into SECONDS:
+const Units Units::DAYS              ( "days",           "d",   Units::TYPE_TEMPORAL, 86400.0 );
+const Units Units::HOURS             ( "hours",          "hr",  Units::TYPE_TEMPORAL, 3600.0 );
+const Units Units::MICROSECONDS      ( "microseconds",   "us",  Units::TYPE_TEMPORAL, 0.000001 );
+const Units Units::MILLISECONDS      ( "milliseconds",   "ms",  Units::TYPE_TEMPORAL, 0.001 );
+const Units Units::MINUTES           ( "minutes",        "min", Units::TYPE_TEMPORAL, 60.0 );
 const Units Units::SECONDS           ( "seconds",        "s",   Units::TYPE_TEMPORAL, 1.0 );
-const Units Units::WEEKS             ( "weeks",          "wk",  Units::TYPE_TEMPORAL, 1.0/604800.0 );
+const Units Units::WEEKS             ( "weeks",          "wk",  Units::TYPE_TEMPORAL, 604800.0 );
 
 const Units Units::FEET_PER_SECOND      ( "feet per second",         "ft/s", Units::FEET,           Units::SECONDS );
 const Units Units::YARDS_PER_SECOND     ( "yards per second",        "yd/s", Units::YARDS,          Units::SECONDS );
 const Units Units::METERS_PER_SECOND    ( "meters per second",       "m/s",  Units::METERS,         Units::SECONDS );
 const Units Units::KILOMETERS_PER_SECOND( "kilometers per second",   "km/s", Units::KILOMETERS,     Units::SECONDS );
-const Units Units::KILOMETERS_PER_HOUR  ( "kilometers per hour",     "kmh",  Units::KILOMETERS,     Units::SECONDS );
+const Units Units::KILOMETERS_PER_HOUR  ( "kilometers per hour",     "kmh",  Units::KILOMETERS,     Units::HOURS );
 const Units Units::MILES_PER_HOUR       ( "miles per hour",          "mph",  Units::MILES,          Units::HOURS );
 const Units Units::DATA_MILES_PER_HOUR  ( "data miles per hour",     "dm/h", Units::DATA_MILES,     Units::HOURS );
 const Units Units::KNOTS                ( "nautical miles per hour", "kts",  Units::NAUTICAL_MILES, Units::HOURS );
diff --git a/src/osgEarth/Utils b/src/osgEarth/Utils
index 1dc0589..870fa2f 100644
--- a/src/osgEarth/Utils
+++ b/src/osgEarth/Utils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -52,6 +52,16 @@ namespace osgEarth
             float vr = (osg::clampBetween(v, vmin, vmax)-vmin)/(vmax-vmin);
             return r0 + vr * (r1-r0);
         }
+        
+
+        // Polyfill
+        static const osg::BoundingBox& getBoundingBox(const osg::Drawable* d) {
+#if OSG_VERSION_GREATER_THAN(3,3,1)
+            return d->getBoundingBox();
+#else
+            return d->getBound();
+#endif
+        }
     };
 
     /**
@@ -88,13 +98,6 @@ namespace osgEarth
         }
     };
 
-    // a cull callback that prevents objects from being included in the near/fear clip
-    // plane calculates that OSG does.
-    struct DoNotComputeNearFarCullCallback : public osg::NodeCallback
-    {
-        void operator()(osg::Node* node, osg::NodeVisitor* nv);
-    };
-
     /**
      * A pixel-based AutoTransform variant.
      */
@@ -121,12 +124,28 @@ namespace osgEarth
          */
         void dirty();
 
+        /**
+         * Set up the transform to orient the node based on a 2D screen-space
+         * rotation.
+         */
+        void setRotateInScreenSpace(bool value) { _rotateInScreenSpace = value; }
+        bool getRotateInScreenSpace() const { return _rotateInScreenSpace; }
+        
+        /**
+         * If setRotateInScreenSpace is true, this is the value of the 2D rotation
+         * in radians.
+         */
+        void setScreenSpaceRotation(double radians) { _screenSpaceRotationRadians = radians; }
+        double getScreenSpaceRotation() const { return _screenSpaceRotationRadians; }
+
     public: // override
-        void accept( osg::NodeVisitor& nv );
+        virtual void accept( osg::NodeVisitor& nv );
 
     protected:
         double _minPixels;
         bool   _dirty;
+        bool   _rotateInScreenSpace;
+        double _screenSpaceRotationRadians;
         osg::observer_ptr<osg::Node> _sizingNode;
     };
 
@@ -174,6 +193,16 @@ namespace osgEarth
         void apply( osg::Geode& node );
         osg::Object::DataVariance _value;
     };
+
+    /**
+     * Scans geometry and validates that it's set up properly.
+     */    
+    struct OSGEARTH_EXPORT GeometryValidator : public osg::NodeVisitor
+    {
+        GeometryValidator();
+        void apply(osg::Geode& geode);
+        void apply(osg::Geometry& geom);
+    };
 }
 
 #endif // OSGEARTH_UTILS_H
diff --git a/src/osgEarth/Utils.cpp b/src/osgEarth/Utils.cpp
index 7769312..66fe815 100644
--- a/src/osgEarth/Utils.cpp
+++ b/src/osgEarth/Utils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -17,6 +17,8 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarth/Utils>
+#include <osgEarth/ECEF>
+#include <osgEarth/CullingUtils>
 #include <osg/Version>
 #include <osg/CoordinateSystemNode>
 #include <osg/MatrixTransform>
@@ -37,30 +39,13 @@ void osgEarth::removeEventHandler(osgViewer::View* view, osgGA::GUIEventHandler*
 
 //------------------------------------------------------------------------
 
-void
-DoNotComputeNearFarCullCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
-{
-    osgUtil::CullVisitor* cv = static_cast< osgUtil::CullVisitor*>( nv );
-    osg::CullSettings::ComputeNearFarMode oldMode;
-    if( cv )
-    {
-        oldMode = cv->getComputeNearFarMode();
-        cv->setComputeNearFarMode( osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR );
-    }
-    traverse(node, nv);
-    if( cv )
-    {
-        cv->setComputeNearFarMode(oldMode);
-    }
-}
-
-//------------------------------------------------------------------------
-
 #undef LC
 #define LC "[PixelAutoTransform] "
 
 PixelAutoTransform::PixelAutoTransform() :
-osg::AutoTransform()
+osg::AutoTransform         (),
+_rotateInScreenSpace       ( false ),
+_screenSpaceRotationRadians( 0.0 )
 {
     // deactivate culling for the first traversal. We will reactivate it later.
     setCullingActive( false );
@@ -75,24 +60,24 @@ PixelAutoTransform::accept( osg::NodeVisitor& nv )
     {
         // re-activate culling now that the first cull traversal has taken place.
         this->setCullingActive( true );
-        osg::CullStack* cs = dynamic_cast<osg::CullStack*>(&nv);
-        if ( cs )
+        osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
+        if ( cv )
         {
             osg::Viewport::value_type width  = _previousWidth;
             osg::Viewport::value_type height = _previousHeight;
 
-            osg::Viewport* viewport = cs->getViewport();
+            osg::Viewport* viewport = cv->getViewport();
             if (viewport)
             {
                 width = viewport->width();
                 height = viewport->height();
             }
 
-            osg::Vec3d eyePoint = cs->getEyeLocal(); 
-            osg::Vec3d localUp = cs->getUpLocal(); 
+            osg::Vec3d eyePoint = cv->getEyeLocal(); 
+            osg::Vec3d localUp = cv->getUpLocal(); 
             osg::Vec3d position = getPosition();
 
-            const osg::Matrix& projection = *(cs->getProjectionMatrix());
+            const osg::Matrix& projection = *(cv->getProjectionMatrix());
 
             bool doUpdate = _firstTimeToInitEyePoint || _dirty;
             if ( !_firstTimeToInitEyePoint )
@@ -128,7 +113,7 @@ PixelAutoTransform::accept( osg::NodeVisitor& nv )
                         getNumChildren() > 0 ? getChild(0)->getBound().radius() : 
                         0.48;
 
-                    double pixels = cs->pixelSize( getPosition(), radius );
+                    double pixels = cv->pixelSize( getPosition(), radius );
 
                     double scaledMinPixels = _minPixels * _minimumScale;
                     double scale = pixels < scaledMinPixels ? scaledMinPixels / pixels : 1.0;
@@ -148,14 +133,69 @@ PixelAutoTransform::accept( osg::NodeVisitor& nv )
                 _matrixDirty = true;
             }
 
-            if (_autoRotateMode==ROTATE_TO_SCREEN)
+            if (_rotateInScreenSpace==true)
+            {
+                osg::Vec3d translation, scale;
+                osg::Quat  rotation, so;
+                osg::RefMatrix& mvm = *(cv->getModelViewMatrix());
+
+                mvm.decompose( translation, rotation, scale, so );
+
+                // this will rotate the object into screen space.
+                osg::Quat toScreen( rotation.inverse() );
+
+                // we need to compensate for the "heading" of the camera, so compute that.
+                // From (http://goo.gl/9bjM4t).
+                // GEOCENTRIC ONLY!
+
+                const osg::Matrixd& view = cv->getCurrentCamera()->getViewMatrix();
+                osg::Matrixd viewInverse;
+                viewInverse.invert(view);
+
+                osg::Vec3d N(0, 0, 6356752); // north pole, more or less
+                osg::Vec3d b( -view(0,2), -view(1,2), -view(2,2) ); // look vector
+                osg::Vec3d E = osg::Vec3d(0,0,0)*viewInverse;
+                osg::Vec3d u = E; u.normalize();
+
+                // account for looking straight downish
+                if ( osg::equivalent(b*u, -1.0, 1e-4) )
+                {
+                    // up vec becomes the look vec.
+                    b = osg::Matrixd::transform3x3(view, osg::Vec3f(0.0,1.0,0.0));
+                    b.normalize();
+                }
+
+                osg::Vec3d proj_d = b - u*(b*u);
+                osg::Vec3d n = N - E;
+                osg::Vec3d proj_n = n - u*(n*u);
+                osg::Vec3d proj_e = proj_n^u;
+
+                double cameraHeading = atan2(proj_e*proj_d, proj_n*proj_d);
+
+                //OE_NOTICE << "h=" << osg::RadiansToDegrees(cameraHeading) << std::endl;
+
+                while (cameraHeading < 0.0)
+                    cameraHeading += osg::PI*2.0;
+                double objHeading = _screenSpaceRotationRadians;
+                while ( objHeading < 0.0 )
+                    objHeading += osg::PI*2.0;
+                double finalRot = cameraHeading - objHeading;
+                while( finalRot > osg::PI )
+                    finalRot -= osg::PI*2.0;
+
+                osg::Quat toRotation( finalRot, osg::Vec3(0,0,1) );
+
+                setRotation( toRotation * toScreen );
+            }
+
+            else if (_autoRotateMode==ROTATE_TO_SCREEN)
             {
                 osg::Vec3d translation;
                 osg::Quat rotation;
                 osg::Vec3d scale;
                 osg::Quat so;
 
-                cs->getModelViewMatrix()->decompose( translation, rotation, scale, so );
+                cv->getModelViewMatrix()->decompose( translation, rotation, scale, so );
 
                 setRotation(rotation.inverse());
             }
@@ -169,7 +209,6 @@ PixelAutoTransform::accept( osg::NodeVisitor& nv )
                 setRotation(q);
             }
 
-#if OSG_MIN_VERSION_REQUIRED(3,0,0)
             else if (_autoRotateMode==ROTATE_TO_AXIS)
             {
                 osg::Matrix matrix;
@@ -244,15 +283,17 @@ PixelAutoTransform::accept( osg::NodeVisitor& nv )
                 q.set(matrix);
                 setRotation(q);
             }
-#endif
 
             _dirty = false;
-        }
-    }
+
+            // update the LOD Scale based on the auto-scale.
+            cv->setLODScale( 1.0/getScale().x() );
+
+        } // if (cv)
+    } // if is cull visitor
 
     // finally, skip AT's accept and do Transform.
     Transform::accept(nv);
-
 }
 
 void
@@ -348,3 +389,108 @@ SetDataVarianceVisitor::apply(osg::Geode& geode)
 
     traverse(geode);
 }
+
+//-----------------------------------------------------------------------------
+
+namespace
+{
+    template<typename DE>
+    void validateDE( DE* de, unsigned maxIndex, unsigned numVerts )
+    {
+        for( unsigned i=0; i<de->getNumIndices(); ++i )
+        {
+            typename DE::value_type index = de->getElement(i);
+            if ( index > maxIndex )
+            {
+                OE_WARN << "MAXIMUM Index exceeded in DrawElements" << std::endl;
+                break;
+            }
+            else if ( index > numVerts-1 )
+            {
+                OE_WARN << "INDEX OUT OF Range in DrawElements" << std::endl;
+            }
+        }
+    }
+}
+
+
+GeometryValidator::GeometryValidator()
+{
+    setVisitorType(this->NODE_VISITOR);
+    setTraversalMode(this->TRAVERSE_ALL_CHILDREN);
+    setNodeMaskOverride(~0);
+}
+
+void
+GeometryValidator::apply(osg::Geometry& geom)
+{
+    unsigned numVerts = geom.getVertexArray()->getNumElements();
+
+    if ( geom.getColorArray() )
+    {
+        if ( geom.getColorBinding() == osg::Geometry::BIND_OVERALL && geom.getColorArray()->getNumElements() != 1 )
+        {
+            OE_WARN << "Color: BIND_OVERALL with wrong number of elements" << std::endl;
+        }
+        else if ( geom.getColorBinding() == osg::Geometry::BIND_PER_VERTEX && geom.getColorArray()->getNumElements() != numVerts )
+        {
+            OE_WARN << "Color: BIND_PER_VERTEX with color.size != verts.size" << std::endl;
+        }
+    }
+
+    if ( geom.getNormalArray() )
+    {
+        if ( geom.getNormalBinding() == osg::Geometry::BIND_OVERALL && geom.getNormalArray()->getNumElements() != 1 )
+        {
+            OE_WARN << "Normal: BIND_OVERALL with wrong number of elements" << std::endl;
+        }
+        else if ( geom.getNormalBinding() == osg::Geometry::BIND_PER_VERTEX && geom.getNormalArray()->getNumElements() != numVerts )
+        {
+            OE_WARN << "Normal: BIND_PER_VERTEX with color.size != verts.size" << std::endl;
+        }
+    }
+
+    const osg::Geometry::PrimitiveSetList& plist = geom.getPrimitiveSetList();
+
+    for( osg::Geometry::PrimitiveSetList::const_iterator p = plist.begin(); p != plist.end(); ++p )
+    {
+        osg::PrimitiveSet* pset = p->get();
+
+        osg::DrawElementsUByte* de_byte = dynamic_cast<osg::DrawElementsUByte*>(pset);
+        if ( de_byte )
+        {
+            if ( numVerts > 0xFF )
+            {
+                OE_WARN << "DrawElementsUByte used when numVerts > 255 (" << numVerts << ")" << std::endl;
+            }
+            validateDE(de_byte, 0xFF, numVerts );
+        }
+
+        osg::DrawElementsUShort* de_short = dynamic_cast<osg::DrawElementsUShort*>(pset);
+        if ( de_short )
+        {
+            if ( numVerts > 0xFFFF )
+            {
+                OE_WARN << "DrawElementsUShort used when numVerts > 65535 (" << numVerts << ")" << std::endl;
+            }
+            validateDE(de_short, 0xFFFF, numVerts );
+        }
+
+        osg::DrawElementsUInt* de_int = dynamic_cast<osg::DrawElementsUInt*>(pset);
+        if ( de_int )
+        {
+            validateDE(de_int, 0xFFFFFFFF, numVerts );
+        }
+    }
+}
+
+void
+GeometryValidator::apply(osg::Geode& geode)
+{
+    for(unsigned i=0; i<geode.getNumDrawables(); ++i)
+    {
+        osg::Geometry* geom = geode.getDrawable(i)->asGeometry();
+        if ( geom )
+            apply( *geom );
+    }
+}
\ No newline at end of file
diff --git a/src/osgEarth/Version b/src/osgEarth/Version
index ef9303d..c7f4465 100644
--- a/src/osgEarth/Version
+++ b/src/osgEarth/Version
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -25,7 +25,7 @@
 extern "C" {
 
 #define OSGEARTH_MAJOR_VERSION    2
-#define OSGEARTH_MINOR_VERSION    5
+#define OSGEARTH_MINOR_VERSION    6
 #define OSGEARTH_PATCH_VERSION    0
 #define OSGEARTH_SOVERSION        0
 #define OSGEARTH_RC_VERSION       0
@@ -42,7 +42,9 @@ extern "C" {
 #define OSGEARTH_VERSION_GREATER_OR_EQUAL(MAJOR, MINOR, PATCH) ((OSGEARTH_MAJOR_VERSION>MAJOR) || (OSGEARTH_MAJOR_VERSION==MAJOR && (OSGEARTH_MINOR_VERSION>MINOR || (OSGEARTH_MINOR_VERSION==MINOR && OSGEARTH_PATCH_VERSION>=PATCH))))
 
 /** embedded GIT SHA1 */
-extern OSGEARTH_EXPORT const char* osgEarthGitSHA1();
+#ifdef OSGEARTH_EMBED_GIT_SHA
+    extern OSGEARTH_EXPORT const char* osgEarthGitSHA1();
+#endif
 
 /**
   * osgEarthGetVersion() returns the library version number.
diff --git a/src/osgEarth/Version.cpp b/src/osgEarth/Version.cpp
index 55c953f..2eac968 100644
--- a/src/osgEarth/Version.cpp
+++ b/src/osgEarth/Version.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -22,6 +22,12 @@
 #include <string>
 #include <stdio.h>
 
+#ifdef OSGEARTH_EMBED_GIT_SHA
+#    define GET_SHA osgEarthGitSHA1
+#else
+#    define GET_SHA ""
+#endif
+
 extern "C" {
 
 const char* osgEarthGetVersion()
@@ -36,7 +42,7 @@ const char* osgEarthGetVersion()
                 OSGEARTH_MAJOR_VERSION,
                 OSGEARTH_MINOR_VERSION,
                 OSGEARTH_PATCH_VERSION,
-                osgEarthGitSHA1() );
+                GET_SHA );
         }
         else
         {
@@ -45,7 +51,7 @@ const char* osgEarthGetVersion()
                 OSGEARTH_MINOR_VERSION,
                 OSGEARTH_PATCH_VERSION,
                 OSGEARTH_RC_VERSION,
-                osgEarthGitSHA1() );
+                GET_SHA );
         }
 
         osgearth_version_init = 0;
diff --git a/src/osgEarth/VersionGit.cpp.in b/src/osgEarth/VersionGit.cpp.in
index b0f86ac..b21c5cf 100644
--- a/src/osgEarth/VersionGit.cpp.in
+++ b/src/osgEarth/VersionGit.cpp.in
@@ -1,3 +1,4 @@
+// ***DO NOT EDIT THIS FILE - IT IS AUTOMATICALLY GENERATED BY CMAKE***
 #include <osgEarth/Version>
 #define OSGEARTH_GIT_SHA1_STRING "@OSGEARTH_GIT_SHA1@"
 extern "C" {
diff --git a/src/osgEarth/VerticalDatum b/src/osgEarth/VerticalDatum
index 19ecb22..158aa63 100644
--- a/src/osgEarth/VerticalDatum
+++ b/src/osgEarth/VerticalDatum
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/VerticalDatum.cpp b/src/osgEarth/VerticalDatum.cpp
index 2519dfb..3a916e4 100644
--- a/src/osgEarth/VerticalDatum.cpp
+++ b/src/osgEarth/VerticalDatum.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -98,7 +98,7 @@ VerticalDatum::transform(const VerticalDatum* from,
 
     if ( from )
     {
-        in_out_z = from->msl2hae( lat_deg, lon_deg, INTERP_BILINEAR );
+        in_out_z = from->msl2hae( lat_deg, lon_deg, in_out_z );
     }
 
     Units fromUnits = from ? from->getUnits() : Units::METERS;
@@ -108,7 +108,7 @@ VerticalDatum::transform(const VerticalDatum* from,
 
     if ( to )
     {
-        in_out_z = to->hae2msl( lat_deg, lon_deg, INTERP_BILINEAR );
+        in_out_z = to->hae2msl( lat_deg, lon_deg, in_out_z );
     }
 
     return true;
@@ -138,20 +138,20 @@ VerticalDatum::transform(const VerticalDatum* from,
 
     unsigned cols = hf->getNumColumns();
     unsigned rows = hf->getNumRows();
-    osg::Vec3d sw = hf->getOrigin();
-    osg::Vec3d ne;
-    ne.x() = sw.x() + hf->getXInterval()*double(rows);
-    ne.y() = sw.y() + hf->getYInterval()*double(cols);
-    double xstep = hf->getXInterval();
-    double ystep = hf->getYInterval();
+    
+    osg::Vec3d sw(extent.west(), extent.south(), 0.0);
+    osg::Vec3d ne(extent.east(), extent.north(), 0.0);
+    
+    double xstep = abs(extent.east() - extent.west()) / double(cols-1);
+    double ystep = abs(extent.north() - extent.south()) / double(rows-1);
 
     if ( !extent.getSRS()->isGeographic() )
     {
         const SpatialReference* geoSRS = extent.getSRS()->getGeographicSRS();
         extent.getSRS()->transform(sw, geoSRS, sw);
         extent.getSRS()->transform(ne, geoSRS, ne);
-        xstep = (ne.x()-sw.x()) / double(cols);
-        ystep = (ne.y()-sw.y()) / double(rows);
+        xstep = (ne.x()-sw.x()) / double(cols-1);
+        ystep = (ne.y()-sw.y()) / double(rows-1);
     }
 
     for( unsigned c=0; c<cols; ++c)
@@ -160,7 +160,7 @@ VerticalDatum::transform(const VerticalDatum* from,
         for( unsigned r=0; r<rows; ++r)
         {
             double lat = sw.y() + ystep*double(r);
-            float& h = hf->getHeight(r, c);
+            float& h = hf->getHeight(c, r);
             VerticalDatum::transform( from, to, lat, lon, h );
         }
     }
@@ -186,6 +186,12 @@ VerticalDatum::isEquivalentTo( const VerticalDatum* rhs ) const
     if ( this == rhs )
         return true;
 
+    if ( rhs == 0L && !_geoid.valid() )
+        return true;
+
+    if ( rhs == 0L )
+        return false;
+
     if ( _units != rhs->_units )
         return false;
 
diff --git a/src/osgEarth/Viewpoint b/src/osgEarth/Viewpoint
index 3bd9b1c..f59107f 100644
--- a/src/osgEarth/Viewpoint
+++ b/src/osgEarth/Viewpoint
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/Viewpoint.cpp b/src/osgEarth/Viewpoint.cpp
index 2eca790..4999ca1 100644
--- a/src/osgEarth/Viewpoint.cpp
+++ b/src/osgEarth/Viewpoint.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/VirtualProgram b/src/osgEarth/VirtualProgram
index 7883a2e..4efcbbc 100644
--- a/src/osgEarth/VirtualProgram
+++ b/src/osgEarth/VirtualProgram
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -23,6 +23,7 @@
 #include <osgEarth/Revisioning>
 #include <osgEarth/ThreadingUtils>
 #include <osgEarth/ColorFilter>
+#include <osgEarth/Containers>
 #include <osg/Shader>
 #include <osg/Program>
 #include <osg/StateAttribute>
@@ -58,11 +59,42 @@ namespace osgEarth
             LOCATION_FRAGMENT_COLORING = 3,
 
             // fragment is being lit.
-            LOCATION_FRAGMENT_LIGHTING = 4
+            LOCATION_FRAGMENT_LIGHTING = 4,
+
+            // fragment output is being assigned.
+            LOCATION_FRAGMENT_OUTPUT = 5
+        };
+
+        /**
+         * Callback that accepts a user-injected shader function (set with
+         * setFunction) for inclusing in the program at render time.
+         */
+        class AcceptCallback : public osg::Referenced
+        {
+        public:
+            // implement this to accept or reject based on the state
+            virtual bool operator()(const osg::State& state) =0;
+        protected:
+            virtual ~AcceptCallback() { }
+        };
+
+        // User function (used internally)
+        struct Function
+        {
+            std::string                  _name;
+            osg::ref_ptr<AcceptCallback> _accept;
+            optional<float>              _minRange;
+            optional<float>              _maxRange;
+            
+            bool accept(const osg::State& state) const {
+                return (!_accept.valid()) || (_accept->operator()(state) == true);
+            }
         };
 
-        // set of user functions, ordered by priority.
-        typedef std::multimap<float, std::string> OrderedFunctionMap; // duplicate keys allowed
+        typedef std::pair<float, Function> OrderedFunction;
+
+        // ordered set of user functions.
+        typedef std::multimap<float, Function> OrderedFunctionMap; // duplicate keys allowed
 
         // user function sets, categorized by function location.
         typedef std::map<FunctionLocation, OrderedFunctionMap> FunctionLocationMap;
@@ -104,10 +136,15 @@ namespace osgEarth
         static const VirtualProgram* get(const osg::StateSet* on);
 
         /**
-        * Clones the VP on a stateset, or creates a new one
+        * Creates a new VP on the stateset, cloning an existing one if found.
         */
-        static VirtualProgram* cloneOrCreate(const osg::StateSet* src, osg::StateSet* dest);
+        static VirtualProgram* cloneOrCreate(osg::StateSet* stateset);
 
+        /**
+        * Creates a new VP on the "dest" stateset, either by cloning one found
+        * on the "src" stateset, or otherwise just constructing a new one.
+        */
+        static VirtualProgram* cloneOrCreate(const osg::StateSet* src, osg::StateSet* dest);
 
     public:
         /**
@@ -116,19 +153,36 @@ namespace osgEarth
          * Call this method (rather than setShader directly) to inject "user" functions into the
          * shader program.
          *
-         * name:     Name of the function. This should be the actual function name in the shader source.
-         * source:   The shader source code.
-         * location: Function location relative to the built-ins.
-         * priority: Lets you control the order of functions that you inject at the same location.
-         *           The default priority is 1.0. Note that many of osgEarth's built-in shaders (like
-         *           those that render the terrain) use priority 0.0 so that by default they run before
-         *           user-injected functions.
+         * name:      Name of the function. This should be the actual function name in the shader source.
+         * source:    The shader source code.
+         * location:  Function location relative to the built-ins.
+         * order:     Lets you control the order of functions that you inject at the same location.
+         *            The default order is 1.0. Note that many of osgEarth's built-in shaders (like
+         *            those that render the terrain) use order=0.0 so that by default they run before
+         *            user-injected functions by default.
          */
         void setFunction( 
             const std::string&           name,
             const std::string&           source, 
-            ShaderComp::FunctionLocation loc,
-            float                        priority =1.0f );
+            ShaderComp::FunctionLocation location,
+            float                        order =1.0f );
+
+        void setFunction( 
+            const std::string&           name,
+            const std::string&           source, 
+            ShaderComp::FunctionLocation location,
+            ShaderComp::AcceptCallback*  acceptCallback,
+            float                        order =1.0f );
+
+        /**
+         * Sets the minimum active range for a named function. The function must already
+         * exist in the VirtualProgram. For this to work, there must to a uniform in the
+         * scene graph that conveys the current range; use the VirtualProgramRangeCallback
+         * to activate this.
+         */
+        void setFunctionMinRange(const std::string& name, float minRange);
+
+        void setFunctionMaxRange(const std::string& name, float maxRange);
 
         /**
          * Whether this VP should inherit shaders from parent state sets. This is
@@ -215,14 +269,39 @@ namespace osgEarth
         typedef std::vector< osg::ref_ptr<osg::Shader> > ShaderVector;
 
     public:
-        typedef std::pair< osg::ref_ptr<osg::Shader>, osg::StateAttribute::OverrideValue > ShaderEntry;
+        struct ShaderEntry
+        {
+            ShaderEntry();
+            bool accept(const osg::State& state) const;
+            osg::ref_ptr<osg::Shader>                _shader;
+            osg::StateAttribute::OverrideValue       _overrideValue;
+            osg::ref_ptr<ShaderComp::AcceptCallback> _accept;
+            bool operator < (const ShaderEntry& rhs) const;
+        };
+
+        struct ProgramEntry
+        {
+            osg::ref_ptr<osg::Program> _program;
+            unsigned                   _frameLastUsed;
+        };
+
         typedef std::map< std::string, ShaderEntry > ShaderMap;
-        typedef std::map< ShaderVector, osg::ref_ptr<osg::Program> > ProgramMap;
         typedef std::map< std::string, std::string > AttribAliasMap;
         typedef std::pair< std::string, std::string > AttribAlias;
         typedef std::vector< AttribAlias > AttribAliasVector;
+        //typedef std::map< ShaderVector, osg::ref_ptr<osg::Program> > ProgramMap;
+        typedef osgEarth::fast_map< ShaderVector, ProgramEntry > ProgramMap;
 
     public:
+        /**
+         * Populates the output collection with all the osg::Shader objects that
+         * are applicable for the given State.
+         */
+        static void getShaders(
+            const osg::State&                        state,
+            std::vector<osg::ref_ptr<osg::Shader> >& output);
+
+    protected:
         // thread-safe functions map getter
         void getFunctions( ShaderComp::FunctionLocationMap& out ) const;
 
@@ -255,12 +334,33 @@ namespace osgEarth
         mutable ProgramMap                _programCache;
         mutable Threading::ReadWriteMutex _programCacheMutex;
 
+        mutable optional<bool> _active;
         bool _inherit;
         bool _inheritSet;
 
         bool hasLocalFunctions() const;
-        void accumulateFunctions( const osg::State& state, ShaderComp::FunctionLocationMap& result ) const;
+
+        void accumulateFunctions(            const osg::State&                state,
+            ShaderComp::FunctionLocationMap& result ) const;
+
         const AttribAliasMap& getAttribAliases() const { return _attribAliases; }
+
+        // utility function
+        static void accumulateShaders(
+            const osg::State&  state,
+            unsigned           mask,
+            ShaderMap&         accumShaderMap,
+            AttribBindingList& accumAttribBindings,
+            AttribAliasMap&    accumAttribAliases);
+        
+        bool readProgramCache(
+            const ShaderVector& vec,
+            unsigned frameNumber,
+            osg::ref_ptr<osg::Program>& program);
+
+        void removeExpiredProgramsFromCache(
+            osg::State& state,
+            unsigned frameNumber);
     };
 
 } // namespace osgEarth
diff --git a/src/osgEarth/VirtualProgram.cpp b/src/osgEarth/VirtualProgram.cpp
index 07a2f91..3f7c885 100644
--- a/src/osgEarth/VirtualProgram.cpp
+++ b/src/osgEarth/VirtualProgram.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -22,6 +22,7 @@
 #include <osgEarth/Capabilities>
 #include <osgEarth/ShaderFactory>
 #include <osgEarth/ShaderUtils>
+#include <osgEarth/Containers>
 #include <osg/Shader>
 #include <osg/Program>
 #include <osg/State>
@@ -36,8 +37,34 @@ using namespace osgEarth::ShaderComp;
 
 #define OE_TEST OE_NULL
 //#define OE_TEST OE_NOTICE
-
 //#define USE_ATTRIB_ALIASES
+//#define DEBUG_APPLY_COUNTS
+//#define DEBUG_ACCUMULATION
+
+//------------------------------------------------------------------------
+
+namespace
+{
+    /** Locate a function by name in the location map. */
+    bool findFunction(const std::string&               name, 
+                      ShaderComp::FunctionLocationMap& flm, 
+                      ShaderComp::Function**           output)
+    {        
+        for(ShaderComp::FunctionLocationMap::iterator i = flm.begin(); i != flm.end(); ++i )
+        {
+            ShaderComp::OrderedFunctionMap& ofm = i->second;
+            for( ShaderComp::OrderedFunctionMap::iterator j = ofm.begin(); j != ofm.end(); ++j )
+            {
+                if ( j->second._name.compare(name) == 0 )
+                {
+                    (*output) = &j->second;
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+}
 
 //------------------------------------------------------------------------
 
@@ -77,6 +104,13 @@ namespace
             const StateHack* sh = reinterpret_cast< const StateHack* >( &state );
             return sh->getAttributeVec( attribute );
         }
+
+        static const AttributeVec* GetAttributeVec(const osg::State& state, osg::StateAttribute::Type type)
+        {
+            const StateHack* sh = reinterpret_cast< const StateHack* >( &state );
+            osg::State::AttributeMap::const_iterator i = sh->_attributeMap.find( std::make_pair(type, 0) );
+            return i != sh->_attributeMap.end() ? &(i->second.attributeVec) : 0L;            
+        }
     };
     typedef std::map<std::string, std::string> HeaderMap;
 
@@ -189,11 +223,10 @@ namespace
     void addToAccumulatedMap(VirtualProgram::ShaderMap&         accumShaderMap,
                              const std::string&                 shaderID,
                              const VirtualProgram::ShaderEntry& newEntry)
-    {
-        const osg::StateAttribute::OverrideValue& ov = newEntry.second;
+    {        
 
         // see if we're trying to disable a previous entry:
-        if ((ov & osg::StateAttribute::ON) == 0 ) //TODO: check for higher override
+        if ((newEntry._overrideValue & osg::StateAttribute::ON) == 0 ) //TODO: check for higher override
         {
             // yes? remove it!
             accumShaderMap.erase( shaderID );
@@ -205,16 +238,15 @@ namespace
             VirtualProgram::ShaderEntry& accumEntry = accumShaderMap[ shaderID ]; 
 
             // make sure we can add the new one:
-            if ((accumEntry.first.get() == 0L ) ||                           // empty slot, fill it
-                ((ov & osg::StateAttribute::PROTECTED) != 0) ||              // new entry is protected
-                ((accumEntry.second & osg::StateAttribute::OVERRIDE) == 0) ) // old entry does NOT override
+            if ((accumEntry._shader.get() == 0L ) ||                                   // empty slot, fill it
+                ((accumEntry._overrideValue & osg::StateAttribute::PROTECTED) != 0) || // new entry is protected
+                ((accumEntry._overrideValue & osg::StateAttribute::OVERRIDE) == 0) )   // old entry does NOT override
             {
                 accumEntry = newEntry;
             }
         }
     }
 
-
     /**
     * Apply the data binding information from a template program to the
     * target program.
@@ -317,7 +349,11 @@ namespace
             {
                 program->addShader( i->get() );
                 if ( s_dumpShaders )
-                    OE_NOTICE << LC << "SHADER " << i->get()->getName() << ":\n" << i->get()->getShaderSource() << "\n" << std::endl;
+                {
+                    OE_NOTIFY(osg::NOTICE,"")
+                        << "----------\n"
+                        << i->get()->getShaderSource() << std::endl;
+                }
             }
         }
 
@@ -343,6 +379,41 @@ namespace
                                VirtualProgram::ShaderVector&       outputKeyVector)
     {
 
+#ifdef DEBUG_ACCUMULATION
+
+        // test dump .. function map and shader map should always include identical data.
+        OE_INFO << LC << "====PROGRAM: " << programName << "\n";
+
+        // debug:
+        OE_INFO << LC << "====FUNCTIONS:\n";
+
+        for(ShaderComp::FunctionLocationMap::iterator i = accumFunctions.begin();
+            i != accumFunctions.end();
+            ++i)
+        {
+            ShaderComp::OrderedFunctionMap& ofm = i->second;
+            for(ShaderComp::OrderedFunctionMap::iterator j = ofm.begin(); j != ofm.end(); ++j)
+            {
+                OE_INFO << LC 
+                    << j->second._name << ", " << (j->second.accept(state)?"accepted":"rejected")
+                    << std::endl;
+            }
+        }
+
+        OE_INFO << LC << "====SHADERS:\n";
+        for(VirtualProgram::ShaderMap::iterator i = accumShaderMap.begin();
+            i != accumShaderMap.end();
+            ++i)
+        {
+            OE_INFO << LC
+                << i->first << ", "
+                << (i->second.accept(state)?"accepted":"rejected") << ", "
+                << i->second._overrideValue
+                << std::endl;
+        }
+        OE_INFO << LC << "\n\n";
+#endif
+
         // create new MAINs for this function stack.
         osg::Shader* vertMain = Registry::shaderFactory()->createVertexShaderMain( accumFunctions );
         osg::Shader* fragMain = Registry::shaderFactory()->createFragmentShaderMain( accumFunctions );
@@ -352,7 +423,7 @@ namespace
         // based on its accumlated function set.
         for( VirtualProgram::ShaderMap::iterator i = accumShaderMap.begin(); i != accumShaderMap.end(); ++i )
         {
-            outputKeyVector.push_back( i->second.first.get() );
+            outputKeyVector.push_back( i->second._shader.get() );
         }
 
         // finally, add the mains (AFTER building the key vector .. we don't want or
@@ -363,7 +434,9 @@ namespace
         buildVector.push_back( fragMain );
 
         if ( s_dumpShaders )
-            OE_NOTICE << LC << "---------PROGRAM: " << programName << " ---------------\n" << std::endl;
+        {
+            OE_NOTICE << LC << "\nPROGRAM: " << programName << " =============================\n" << std::endl;
+        }
 
         // Create the new program.
         osg::Program* program = new osg::Program();
@@ -377,6 +450,29 @@ namespace
 
 //------------------------------------------------------------------------
 
+VirtualProgram::ShaderEntry::ShaderEntry() :
+_overrideValue(0)
+{
+    //nop
+}
+
+bool
+VirtualProgram::ShaderEntry::accept(const osg::State& state) const
+{
+    return (!_accept.valid()) || (_accept->operator()(state) == true);
+}
+
+bool
+VirtualProgram::ShaderEntry::operator < (const VirtualProgram::ShaderEntry& rhs) const
+{
+    if ( _shader->compare(*rhs._shader.get()) < 0 ) return true;
+    if ( _overrideValue < rhs._overrideValue ) return true;
+    if ( _accept.valid() && !rhs._accept.valid() ) return true;
+    return false;
+}
+
+//------------------------------------------------------------------------
+
 // same type as PROGRAM (for proper state sorting)
 const osg::StateAttribute::Type VirtualProgram::SA_TYPE = osg::StateAttribute::PROGRAM;
 
@@ -441,19 +537,28 @@ VirtualProgram::cloneOrCreate(const osg::StateSet* src, osg::StateSet* dest)
     }
 }
 
+VirtualProgram*
+VirtualProgram::cloneOrCreate(osg::StateSet* stateset)
+{
+    return cloneOrCreate(stateset, stateset);
+}
+
 //------------------------------------------------------------------------
 
 
 VirtualProgram::VirtualProgram( unsigned mask ) : 
 _mask              ( mask ),
+_active            ( true ),
 _inherit           ( true ),
 _inheritSet        ( false )
 {
+    // Note: we cannot set _active here. Wait until apply().
+    // It will cause a conflict in the Registry.
+
     // check the the dump env var
     if ( ::getenv(OSGEARTH_DUMP_SHADERS) != 0L )
     {
         s_dumpShaders = true;
-        s_mergeShaders = true;
     }
 
     // check the merge env var
@@ -508,11 +613,9 @@ VirtualProgram::compare(const osg::StateAttribute& sa) const
 
             const ShaderEntry& lhsEntry = lhsIter->second;
             const ShaderEntry& rhsEntry = rhsIter->second;
-            int shaderComp = lhsEntry.first->compare( *rhsEntry.first.get() );
-            if ( shaderComp != 0 ) return shaderComp;
 
-            if ( lhsEntry.second < rhsEntry.second ) return -1;
-            if ( lhsEntry.second > rhsEntry.second ) return 1;
+            if ( lhsEntry < rhsEntry ) return -1;
+            if ( rhsEntry < lhsEntry ) return  1;
 
             lhsIter++;
             rhsIter++;
@@ -556,35 +659,36 @@ VirtualProgram::removeBindAttribLocation( const std::string& name )
 void
 VirtualProgram::compileGLObjects(osg::State& state) const
 {
-    //nop - precompilation not required
+    this->apply(state);
 }
 
 void
 VirtualProgram::resizeGLObjectBuffers(unsigned maxSize)
 {
-  Threading::ScopedWriteLock exclusive( _programCacheMutex );
+    Threading::ScopedWriteLock exclusive( _programCacheMutex );
 
-//  OE_WARN << LC << "Resize VP " << getName() << std::endl;
+    //  OE_WARN << LC << "Resize VP " << getName() << std::endl;
 
-  for (ProgramMap::iterator i = _programCache.begin();
-    i != _programCache.end(); ++i)
-  {
-    i->second->resizeGLObjectBuffers(maxSize);
-  }
+    for (ProgramMap::iterator i = _programCache.begin(); i != _programCache.end(); ++i)
+    {
+        i->second._program->resizeGLObjectBuffers(maxSize);
+    }
 }
 
 void
 VirtualProgram::releaseGLObjects(osg::State* state) const
 {
-  Threading::ScopedWriteLock exclusive( _programCacheMutex );
+    Threading::ScopedWriteLock exclusive( _programCacheMutex );
+
+    //  OE_WARN << LC << "Release VP " << getName() << std::endl;
 
-//  OE_WARN << LC << "Release VP " << getName() << std::endl;
+    for (ProgramMap::const_iterator i = _programCache.begin(); i != _programCache.end(); ++i)
+    {
+        //if ( i->second->referenceCount() == 1 )
+            i->second._program->releaseGLObjects(state);
+    }
 
-  for (ProgramMap::const_iterator i = _programCache.begin();
-    i != _programCache.end(); ++i)
-  {
-    i->second->releaseGLObjects(state);
-  }
+    _programCache.clear();
 }
 
 osg::Shader*
@@ -593,7 +697,7 @@ VirtualProgram::getShader( const std::string& shaderID ) const
     Threading::ScopedReadLock readonly( _dataModelMutex );
 
     ShaderMap::const_iterator i = _shaderMap.find(shaderID);
-    return i != _shaderMap.end() ? i->second.first.get() : 0L;
+    return i != _shaderMap.end() ? i->second._shader.get() : 0L;
 }
 
 
@@ -621,7 +725,11 @@ VirtualProgram::setShader(const std::string&                 shaderID,
     // lock the data model and insert the new shader.
     {
         Threading::ScopedWriteLock exclusive( _dataModelMutex );
-        _shaderMap[shaderID] = ShaderEntry(shader, ov);
+
+        ShaderEntry& entry = _shaderMap[shaderID];
+        entry._shader        = shader;
+        entry._overrideValue = ov;
+        entry._accept        = 0L;
     }
 
     return shader;
@@ -654,7 +762,11 @@ VirtualProgram::setShader(osg::Shader*                       shader,
     // lock the data model while changing it.
     {
         Threading::ScopedWriteLock exclusive( _dataModelMutex );
-        _shaderMap[shader->getName()] = ShaderEntry(shader, ov);
+        
+        ShaderEntry& entry = _shaderMap[shader->getName()];
+        entry._shader        = shader;
+        entry._overrideValue = ov;
+        entry._accept        = 0L;
     }
 
     return shader;
@@ -662,10 +774,20 @@ VirtualProgram::setShader(osg::Shader*                       shader,
 
 
 void
-VirtualProgram::setFunction(const std::string& functionName,
-                            const std::string& shaderSource,
-                            FunctionLocation   location,
-                            float              priority)
+VirtualProgram::setFunction(const std::string&           functionName,
+                            const std::string&           shaderSource,
+                            ShaderComp::FunctionLocation location,
+                            float                        ordering)
+{
+    setFunction(functionName, shaderSource, location, 0L, ordering);
+}
+
+void
+VirtualProgram::setFunction(const std::string&           functionName,
+                            const std::string&           shaderSource,
+                            ShaderComp::FunctionLocation location,
+                            ShaderComp::AcceptCallback*  accept,
+                            float                        ordering)
 {
     // set the inherit flag if it's not initialized
     if ( !_inheritSet )
@@ -682,7 +804,8 @@ VirtualProgram::setFunction(const std::string& functionName,
         // if there's already a function by this name, remove it
         for( OrderedFunctionMap::iterator i = ofm.begin(); i != ofm.end(); )
         {
-            if ( i->second.compare(functionName) == 0 )
+            ShaderComp::Function& f = i->second;
+            if ( f._name.compare(functionName) == 0 )
             {
                 OrderedFunctionMap::iterator j = i;
                 ++j;
@@ -695,7 +818,10 @@ VirtualProgram::setFunction(const std::string& functionName,
             }
         }
         
-        ofm.insert( std::pair<float,std::string>( priority, functionName ) );
+        ShaderComp::Function function;
+        function._name   = functionName;
+        function._accept = accept;
+        ofm.insert( OrderedFunction(ordering, function) ); //std::make_pair(ordering, function) );
 
         // create and add the new shader function.
         osg::Shader::Type type = (int)location <= (int)LOCATION_VERTEX_CLIP ?
@@ -707,11 +833,40 @@ VirtualProgram::setFunction(const std::string& functionName,
         // pre-processes the shader's source to include GLES uniforms as necessary
         ShaderPreProcessor::run( shader );
 
-        _shaderMap[functionName] = ShaderEntry(shader, osg::StateAttribute::ON);
+        ShaderEntry& entry = _shaderMap[functionName];
+        entry._shader        = shader;
+        entry._overrideValue = osg::StateAttribute::ON;
+        entry._accept        = accept;
 
     } // release lock
 }
 
+void 
+VirtualProgram::setFunctionMinRange(const std::string& name, float minRange)
+{
+    // lock the functions map while making changes:
+    Threading::ScopedWriteLock exclusive( _dataModelMutex );
+
+    ShaderComp::Function* function;
+    if ( findFunction(name, _functions, &function) )
+    {
+        function->_minRange = minRange;
+    }
+}
+
+void 
+VirtualProgram::setFunctionMaxRange(const std::string& name, float maxRange)
+{
+    // lock the functions map while making changes:
+    Threading::ScopedWriteLock exclusive( _dataModelMutex );
+
+    ShaderComp::Function* function;
+    if ( findFunction(name, _functions, &function) )
+    {
+        function->_maxRange = maxRange;
+    }
+}
+
 void
 VirtualProgram::removeShader( const std::string& shaderID )
 {
@@ -725,7 +880,7 @@ VirtualProgram::removeShader( const std::string& shaderID )
         OrderedFunctionMap& ofm = i->second;
         for( OrderedFunctionMap::iterator j = ofm.begin(); j != ofm.end(); ++j )
         {
-            if ( j->second == shaderID )
+            if ( j->second._name.compare(shaderID) == 0 )
             {
                 ofm.erase( j );
 
@@ -760,13 +915,18 @@ VirtualProgram::setInheritShaders( bool value )
 }
 
 
-namespace
-{
-}
-
 void
 VirtualProgram::apply( osg::State& state ) const
 {
+    if (_active.isSetTo(false))
+    {
+        return;
+    }
+    else if ( !_active.isSet() )
+    {
+        _active = Registry::capabilities().supportsGLSL();
+    }
+
     if (_shaderMap.empty() && !_inheritSet)
     {
         // If there's no data in the VP, and never has been, unload any existing program.
@@ -788,44 +948,10 @@ VirtualProgram::apply( osg::State& state ) const
     AttribBindingList accumAttribBindings;
     AttribAliasMap    accumAttribAliases;
     
+    // Build the active shader map up to this point:
     if ( _inherit )
     {
-        const StateHack::AttributeVec* av = StateHack::GetAttributeVec( state, this );
-        if ( av && av->size() > 0 )
-        {
-            // find the deepest VP that doesn't inherit:
-            unsigned start = 0;
-            for( start = (int)av->size()-1; start > 0; --start )
-            {
-                const VirtualProgram* vp = dynamic_cast<const VirtualProgram*>( (*av)[start].first );
-                if ( vp && (vp->_mask & _mask) && vp->_inherit == false )
-                    break;
-            }
-            
-            // collect shaders from there to here:
-            for( unsigned i=start; i<av->size(); ++i )
-            {
-                const VirtualProgram* vp = dynamic_cast<const VirtualProgram*>( (*av)[i].first );
-                if ( vp && (vp->_mask && _mask) )
-                {
-                    ShaderMap vpShaderMap;
-                    vp->getShaderMap( vpShaderMap );
-
-                    for( ShaderMap::const_iterator i = vpShaderMap.begin(); i != vpShaderMap.end(); ++i )
-                    {
-                        addToAccumulatedMap( accumShaderMap, i->first, i->second );
-                    }
-
-                    const AttribBindingList& abl = vp->getAttribBindingList();
-                    accumAttribBindings.insert( abl.begin(), abl.end() );
-
-#ifdef USE_ATTRIB_ALIASES
-                    const AttribAliasMap& aliases = vp->getAttribAliases();
-                    accumAttribAliases.insert( aliases.begin(), aliases.end() );
-#endif
-                }
-            }
-        }
+        accumulateShaders(state, _mask, accumShaderMap, accumAttribBindings, accumAttribAliases);
     }
 
     // next add the local shader components to the map, respecting the override values:
@@ -834,7 +960,10 @@ VirtualProgram::apply( osg::State& state ) const
 
         for( ShaderMap::const_iterator i = _shaderMap.begin(); i != _shaderMap.end(); ++i )
         {
-            addToAccumulatedMap( accumShaderMap, i->first, i->second );
+            if ( i->second.accept(state) )
+            {
+                addToAccumulatedMap( accumShaderMap, i->first, i->second );
+            }
         }
 
         const AttribBindingList& abl = this->getAttribBindingList();
@@ -847,89 +976,196 @@ VirtualProgram::apply( osg::State& state ) const
     }
 
 
-    if ( true ) //even with nothing in the map, we still want mains! -gw  //accumShaderMap.size() )
+    // next, assemble a list of the shaders in the map so we can use it as our
+    // program cache key.
+    // (Note: at present, the "cache key" does not include any information on the vertex
+    // attribute bindings. Technically it should, but in practice this might not be an
+    // issue; it is unlikely one would have two identical shader programs with different
+    // bindings.)
+    ShaderVector vec;
+    vec.reserve( accumShaderMap.size() );
+    for( ShaderMap::iterator i = accumShaderMap.begin(); i != accumShaderMap.end(); ++i )
     {
-        // next, assemble a list of the shaders in the map so we can use it as our
-        // program cache key.
-        // (Note: at present, the "cache key" does not include any information on the vertex
-        // attribute bindings. Technically it should, but in practice this might not be an
-        // issue; it is unlikely one would have two identical shader programs with different
-        // bindings.)
-        ShaderVector vec;
-        vec.reserve( accumShaderMap.size() );
-        for( ShaderMap::iterator i = accumShaderMap.begin(); i != accumShaderMap.end(); ++i )
+        ShaderEntry& entry = i->second;
+        if ( i->second.accept(state) )
         {
-            ShaderEntry& entry = i->second;
-            vec.push_back( entry.first.get() );
+            vec.push_back( entry._shader.get() );
         }
-        
-        // see if there's already a program associated with this list:
-        osg::ref_ptr<osg::Program> program;
-        
-        // look up the program:
+    }
+
+    // current frame number, for shader program expiry.
+    unsigned frameNumber = state.getFrameStamp() ? state.getFrameStamp()->getFrameNumber() : 0;
+
+    // see if there's already a program associated with this list:
+    osg::ref_ptr<osg::Program> program;
+
+    // look up the program:
+    {
+        Threading::ScopedReadLock shared( _programCacheMutex );
+        const_cast<VirtualProgram*>(this)->readProgramCache(vec, frameNumber, program);
+    }
+
+    // if not found, lock and build it:
+    if ( !program.valid() )
+    {
+        // build a new set of accumulated functions, to support the creation of main()
+        ShaderComp::FunctionLocationMap accumFunctions;
+        accumulateFunctions( state, accumFunctions );
+
+        // now double-check the program cache, and failing that, build the
+        // new shader Program.
         {
-            Threading::ScopedReadLock shared( _programCacheMutex );
-            
-            ProgramMap::const_iterator p = _programCache.find( vec );
-            if ( p != _programCache.end() )
+            Threading::ScopedWriteLock exclusive( _programCacheMutex );
+
+            // double-check: look again to negate race conditions
+            const_cast<VirtualProgram*>(this)->readProgramCache(vec, frameNumber, program);
+            if ( !program.valid() )
             {
-                program = p->second.get();
+                ShaderVector keyVector;
+
+                //OE_NOTICE << LC << "Building new Program for VP " << getName() << std::endl;
+
+                program = buildProgram(
+                    getName(),
+                    state,
+                    accumFunctions,
+                    accumShaderMap, 
+                    accumAttribBindings, 
+                    accumAttribAliases, 
+                    _template.get(),
+                    keyVector);
+
+                // global sharing.
+                Registry::programSharedRepo()->share( program );
+
+                // finally, put own new program in the cache.
+                ProgramEntry& pe = _programCache[keyVector];
+                pe._program = program.get();
+                pe._frameLastUsed = frameNumber;
+
+                // purge expired programs.
+                const_cast<VirtualProgram*>(this)->removeExpiredProgramsFromCache(state, frameNumber);
             }
         }
-        
-        // if not found, lock and build it:
-        if ( !program.valid() )
+    }
+
+    // finally, apply the program attribute.
+    if ( program.valid() )
+    {
+        const unsigned int contextID = state.getContextID();
+        const osg::GL2Extensions* extensions = osg::GL2Extensions::Get(contextID,true);
+
+        osg::Program::PerContextProgram* pcp = program->getPCP( contextID );
+        bool useProgram = state.getLastAppliedProgramObject() != pcp;
+
+#ifdef DEBUG_APPLY_COUNTS
         {
-            // build a new set of accumulated functions, to support the creation of main()
-            ShaderComp::FunctionLocationMap accumFunctions;
-            accumulateFunctions( state, accumFunctions );
+            // debugging
+
+            static int s_framenum = 0;
+            static Threading::Mutex s_mutex;
+            static std::map< const VirtualProgram*, std::pair<int,int> > s_counts;
 
-            // now double-check the program cache, and failing that, build the
-            // new shader Program.
+            Threading::ScopedMutexLock lock(s_mutex);
+
+            int framenum = state.getFrameStamp()->getFrameNumber();
+            if ( framenum > s_framenum )
             {
-                Threading::ScopedWriteLock exclusive( _programCacheMutex );
-                
-                // double-check: look again ito negate race conditions
-                ProgramMap::const_iterator p = _programCache.find( vec );
-                if ( p != _programCache.end() )
-                {
-                    program = p->second.get();
-                }
-                else
+                OE_NOTICE << LC << "Applies in last frame: " << std::endl;
+                for(std::map<const VirtualProgram*,std::pair<int,int> >::iterator i = s_counts.begin(); i != s_counts.end(); ++i)
                 {
-                    ShaderVector keyVector;
-
-                    //OE_NOTICE << LC << "Building new Program for VP " << getName() << std::endl;
-
-                    program = buildProgram(
-                        getName(),
-                        state,
-                        accumFunctions,
-                        accumShaderMap, 
-                        accumAttribBindings, 
-                        accumAttribAliases, 
-                        _template.get(),
-                        keyVector);
-
-                    // finally, put own new program in the cache.
-                    _programCache[ keyVector ] = program;
+                    std::pair<int,int>& counts = i->second;
+                    OE_NOTICE << LC << "  " 
+                        << i->first->getName() << " : " << counts.second << "/" << counts.first << std::endl;
                 }
+                s_framenum = framenum;
+                s_counts.clear();
             }
+            s_counts[this].first++;
+            if ( useProgram )
+                s_counts[this].second++;
         }
-        
-        // finally, apply the program attribute.
-        if ( program.valid() )
+#endif
+
+        if ( useProgram )
         {
-            program->apply( state );
+            if( pcp->needsLink() )
+                program->compileGLObjects( state );
 
-#if 0 // test code for detecting race conditions
-            for(int i=0; i<10000; ++i) {
-                state.setLastAppliedProgramObject(0L);
-                program->apply( state );
+            if( pcp->isLinked() )
+            {
+                if( osg::isNotifyEnabled(osg::INFO) )
+                    pcp->validateProgram();
+
+                pcp->useProgram();
+                state.setLastAppliedProgramObject( pcp );
+            }
+            else
+            {
+                // program not usable, fallback to fixed function.
+                extensions->glUseProgram( 0 );
+                state.setLastAppliedProgramObject(0);
+                OE_WARN << LC << "Program link failure!" << std::endl;
             }
+        }
+
+        //program->apply( state );
+
+#if 0 // test code for detecting race conditions
+        for(int i=0; i<10000; ++i) {
+            state.setLastAppliedProgramObject(0L);
+            program->apply( state );
+        }
 #endif
+    }
+}
+
+void
+VirtualProgram::removeExpiredProgramsFromCache(osg::State& state, unsigned frameNumber)
+{
+    if ( frameNumber > 0 )
+    {
+        // ASSUME a mutex lock on the cache.
+        for(ProgramMap::iterator k=_programCache.begin(); k!=_programCache.end(); )
+        {
+            if ( frameNumber - k->second._frameLastUsed > 2 )
+            {
+                if ( k->second._program->referenceCount() == 1 )
+                {
+                    k->second._program->releaseGLObjects(&state);
+                }
+                k = _programCache.erase(k);
+            }
+            else
+            {
+                ++k;
+            }
+        }
+    }
+}
+
+bool
+VirtualProgram::readProgramCache(const ShaderVector& vec, unsigned frameNumber, osg::ref_ptr<osg::Program>& program)
+{
+    ProgramMap::iterator p = _programCache.find( vec );
+    if ( p != _programCache.end() )
+    {
+        //OE_NOTICE << "found. fn=" << frameNumber << ", flu=" << p->second._frameLastUsed << std::endl;
+
+        // check for expiry..
+        if ( frameNumber == 0 || (frameNumber - p->second._frameLastUsed <= 2) )
+        {
+            // update as current..
+            p->second._frameLastUsed = frameNumber;
+            program = p->second._program.get();
+        }
+        else
+        {
+            // remove it; it's too old.
+            _programCache.erase( p );
         }
     }
+    return program.valid();
 }
 
 void
@@ -984,16 +1220,19 @@ VirtualProgram::accumulateFunctions(const osg::State&                state,
 
                         for( OrderedFunctionMap::const_iterator k = source.begin(); k != source.end(); ++k )
                         {
-                            // remove/override an existing function with the same name
-                            for( OrderedFunctionMap::iterator exists = dest.begin(); exists != dest.end(); ++exists )
+                            if ( k->second.accept(state) )
                             {
-                                if ( exists->second.compare( k->second ) == 0 )
+                                // remove/override an existing function with the same name
+                                for( OrderedFunctionMap::iterator exists = dest.begin(); exists != dest.end(); ++exists )
                                 {
-                                    dest.erase(exists);
-                                    break;
+                                    if ( exists->second._name.compare( k->second._name ) == 0 )
+                                    {
+                                        dest.erase(exists);
+                                        break;
+                                    }
                                 }
+                                dest.insert( *k );
                             }
-                            dest.insert( *k );
                         }
                     }
                 }
@@ -1012,17 +1251,92 @@ VirtualProgram::accumulateFunctions(const osg::State&                state,
 
             for( OrderedFunctionMap::const_iterator k = source.begin(); k != source.end(); ++k )
             {
-                // remove/override an existing function with the same name
-                for( OrderedFunctionMap::iterator exists = dest.begin(); exists != dest.end(); ++exists )
+                if ( k->second.accept(state) )
                 {
-                    if ( exists->second.compare( k->second ) == 0 )
+                    // remove/override an existing function with the same name
+                    for( OrderedFunctionMap::iterator exists = dest.begin(); exists != dest.end(); ++exists )
                     {
-                        dest.erase(exists);
-                        break;
+                        if ( exists->second._name.compare( k->second._name ) == 0 )
+                        {
+                            dest.erase(exists);
+                            break;
+                        }
                     }
+                    dest.insert( *k );
                 }
-                dest.insert( *k );
             }
         }
     }
 }
+
+
+
+void
+VirtualProgram::accumulateShaders(const osg::State&  state, 
+                                  unsigned           mask,
+                                  ShaderMap&         accumShaderMap,
+                                  AttribBindingList& accumAttribBindings,
+                                  AttribAliasMap&    accumAttribAliases)
+{
+    const StateHack::AttributeVec* av = StateHack::GetAttributeVec(state, VirtualProgram::SA_TYPE);
+    if ( av && av->size() > 0 )
+    {
+        // find the deepest VP that doesn't inherit:
+        unsigned start = 0;
+        for( start = (int)av->size()-1; start > 0; --start )
+        {
+            const VirtualProgram* vp = dynamic_cast<const VirtualProgram*>( (*av)[start].first );
+            if ( vp && (vp->_mask & mask) && vp->_inherit == false )
+                break;
+        }
+
+        // collect shaders from there to here:
+        for( unsigned i=start; i<av->size(); ++i )
+        {
+            const VirtualProgram* vp = dynamic_cast<const VirtualProgram*>( (*av)[i].first );
+            if ( vp && (vp->_mask && mask) )
+            {
+                ShaderMap vpShaderMap;
+                vp->getShaderMap( vpShaderMap );
+
+                for( ShaderMap::const_iterator i = vpShaderMap.begin(); i != vpShaderMap.end(); ++i )
+                {
+                    if ( i->second.accept(state) )
+                    {
+                        addToAccumulatedMap( accumShaderMap, i->first, i->second );
+                    }
+                }
+
+                const AttribBindingList& abl = vp->getAttribBindingList();
+                accumAttribBindings.insert( abl.begin(), abl.end() );
+
+#ifdef USE_ATTRIB_ALIASES
+                const AttribAliasMap& aliases = vp->getAttribAliases();
+                accumAttribAliases.insert( aliases.begin(), aliases.end() );
+#endif
+            }
+        }
+    }
+}
+
+
+void
+VirtualProgram::getShaders(const osg::State&                        state,
+                           std::vector<osg::ref_ptr<osg::Shader> >& output)
+{
+    ShaderMap         shaders;
+    AttribBindingList bindings;
+    AttribAliasMap    aliases;
+
+    // build the collection:
+    accumulateShaders(state, ~0, shaders, bindings, aliases);
+
+    // pre-allocate space:
+    output.reserve( shaders.size() );
+
+    // copy to output.
+    for(ShaderMap::iterator i = shaders.begin(); i != shaders.end(); ++i)
+    {
+        output.push_back( i->second._shader.get() );
+    }
+}
diff --git a/src/osgEarth/XmlUtils b/src/osgEarth/XmlUtils
index d05377e..6175df1 100644
--- a/src/osgEarth/XmlUtils
+++ b/src/osgEarth/XmlUtils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/XmlUtils.cpp b/src/osgEarth/XmlUtils.cpp
index 0c212b8..a1f507c 100644
--- a/src/osgEarth/XmlUtils.cpp
+++ b/src/osgEarth/XmlUtils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -115,17 +115,12 @@ XmlElement::getChildren() const
 XmlElement*
 XmlElement::getSubElement( const std::string& name ) const
 {
-    std::string name_lower = name;
-    std::transform( name_lower.begin(), name_lower.end(), name_lower.begin(), tolower );
-
     for( XmlNodeList::const_iterator i = getChildren().begin(); i != getChildren().end(); i++ )
     {
         if ( i->get()->isElement() )
         {
             XmlElement* e = (XmlElement*)i->get();
-            std::string name = e->getName();
-            std::transform( name.begin(), name.end(), name.begin(), tolower );
-            if ( name == name_lower )
+            if (osgEarth::ciEquals(name, e->getName()))
                 return e;
         }
     }
@@ -166,17 +161,12 @@ XmlElement::getSubElements( const std::string& name ) const
 {
     XmlNodeList results;
 
-    std::string name_lower = name;
-    std::transform( name_lower.begin(), name_lower.end(), name_lower.begin(), tolower );
-
     for( XmlNodeList::const_iterator i = getChildren().begin(); i != getChildren().end(); i++ )
     {
         if ( i->get()->isElement() )
         {
             XmlElement* e = (XmlElement*)i->get();
-            std::string name = e->getName();
-            std::transform( name.begin(), name.end(), name.begin(), tolower );
-            if ( name == name_lower )
+            if ( osgEarth::ciEquals(name, e->getName()) )
                 results.push_back( e );
         }
     }
@@ -263,9 +253,8 @@ namespace
         while( *ptr != NULL )
         {
             std::string name = *ptr++;
-            std::string value = *ptr++;
-            std::transform( name.begin(), name.end(), name.begin(), tolower );
-            map[name] = value;
+            std::string value = *ptr++;            
+            map[osgEarth::toLower(name)] = value;
         }
         return map;
     }
@@ -278,17 +267,15 @@ namespace
         case TiXmlNode::TINYXML_ELEMENT:
             {
                 TiXmlElement* element = node->ToElement();
-                std::string tag = element->Value();
-                std::transform( tag.begin(), tag.end(), tag.begin(), tolower);
+                std::string tag = osgEarth::toLower(element->Value());
 
                 //Get all the attributes
                 XmlAttributes attrs;
                 TiXmlAttribute* attr = element->FirstAttribute();
                 while (attr)
                 {
-                    std::string name  = attr->Name();
+                    std::string name  = osgEarth::toLower(attr->Name());
                     std::string value = attr->Value();
-                    std::transform( name.begin(), name.end(), name.begin(), tolower);
                     attrs[name] = value;
                     attr = attr->Next();
                 }
diff --git a/src/osgEarth/optional b/src/osgEarth/optional
index c93e070..97a9b63 100644
--- a/src/osgEarth/optional
+++ b/src/osgEarth/optional
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarth/tinyxml.cpp b/src/osgEarth/tinyxml.cpp
index 9be6c6a..f981d7a 100644
--- a/src/osgEarth/tinyxml.cpp
+++ b/src/osgEarth/tinyxml.cpp
@@ -23,6 +23,7 @@ distribution.
 */
 
 #include <ctype.h>
+#include <sys/stat.h>
 
 #ifdef TIXML_USE_STL
 #include <sstream>
@@ -57,7 +58,7 @@ void TiXmlBase::EncodeString( const TIXML_STRING& str, TIXML_STRING* outString )
 	{
 		unsigned char c = (unsigned char) str[i];
 
-		if (    c == '&' 
+		if (    c == '&'
 		     && i < ( (int)str.length() - 2 )
 			 && str[i+1] == '#'
 			 && str[i+2] == 'x' )
@@ -110,12 +111,12 @@ void TiXmlBase::EncodeString( const TIXML_STRING& str, TIXML_STRING* outString )
 			// Easy pass at non-alpha/numeric/symbol
 			// Below 32 is symbolic.
 			char buf[ 32 ];
-			
-			#if defined(TIXML_SNPRINTF)		
+
+			#if defined(TIXML_SNPRINTF)
 				TIXML_SNPRINTF( buf, sizeof(buf), "&#x%02X;", (unsigned) ( c & 0xff ) );
 			#else
 				sprintf( buf, "&#x%02X;", (unsigned) ( c & 0xff ) );
-			#endif		
+			#endif
 
 			//*ME:	warning C4267: convert 'size_t' to 'int'
 			//*ME:	Int-Cast to make compiler happy ...
@@ -154,14 +155,14 @@ TiXmlNode::~TiXmlNode()
 		temp = node;
 		node = node->next;
 		delete temp;
-	}	
+	}
 }
 
 
 void TiXmlNode::CopyTo( TiXmlNode* target ) const
 {
 	target->SetValue (value.c_str() );
-	target->userData = userData; 
+	target->userData = userData;
 	target->location = location;
 }
 
@@ -176,7 +177,7 @@ void TiXmlNode::Clear()
 		temp = node;
 		node = node->next;
 		delete temp;
-	}	
+	}
 
 	firstChild = 0;
 	lastChild = 0;
@@ -226,7 +227,7 @@ TiXmlNode* TiXmlNode::InsertEndChild( const TiXmlNode& addThis )
 
 
 TiXmlNode* TiXmlNode::InsertBeforeChild( TiXmlNode* beforeThis, const TiXmlNode& addThis )
-{	
+{
 	if ( !beforeThis || beforeThis->parent != this ) {
 		return 0;
 	}
@@ -300,7 +301,7 @@ TiXmlNode* TiXmlNode::ReplaceChild( TiXmlNode* replaceThis, const TiXmlNode& wit
 	if ( withThis.ToDocument() ) {
 		// A document can never be a child.	Thanks to Noam.
 		TiXmlDocument* document = GetDocument();
-		if ( document ) 
+		if ( document )
 			document->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN );
 		return 0;
 	}
@@ -335,7 +336,7 @@ bool TiXmlNode::RemoveChild( TiXmlNode* removeThis )
 	}
 
 	if ( removeThis->parent != this )
-	{	
+	{
 		assert( 0 );
 		return false;
 	}
@@ -406,7 +407,7 @@ const TiXmlNode* TiXmlNode::IterateChildren( const char * val, const TiXmlNode*
 }
 
 
-const TiXmlNode* TiXmlNode::NextSibling( const char * _value ) const 
+const TiXmlNode* TiXmlNode::NextSibling( const char * _value ) const
 {
 	const TiXmlNode* node;
 	for ( node = next; node; node = node->next )
@@ -527,7 +528,7 @@ TiXmlElement::TiXmlElement (const char * _value)
 
 
 #ifdef TIXML_USE_STL
-TiXmlElement::TiXmlElement( const std::string& _value ) 
+TiXmlElement::TiXmlElement( const std::string& _value )
 	: TiXmlNode( TiXmlNode::TINYXML_ELEMENT )
 {
 	firstChild = lastChild = 0;
@@ -540,7 +541,7 @@ TiXmlElement::TiXmlElement( const TiXmlElement& copy)
 	: TiXmlNode( TiXmlNode::TINYXML_ELEMENT )
 {
 	firstChild = lastChild = 0;
-	copy.CopyTo( this );	
+	copy.CopyTo( this );
 }
 
 
@@ -694,7 +695,7 @@ int TiXmlElement::QueryDoubleAttribute( const std::string& name, double* dval )
 
 
 void TiXmlElement::SetAttribute( const char * name, int val )
-{	
+{
 	TiXmlAttribute* attrib = attributeSet.FindOrCreate( name );
 	if ( attrib ) {
 		attrib->SetIntValue( val );
@@ -704,7 +705,7 @@ void TiXmlElement::SetAttribute( const char * name, int val )
 
 #ifdef TIXML_USE_STL
 void TiXmlElement::SetAttribute( const std::string& name, int val )
-{	
+{
 	TiXmlAttribute* attrib = attributeSet.FindOrCreate( name );
 	if ( attrib ) {
 		attrib->SetIntValue( val );
@@ -714,7 +715,7 @@ void TiXmlElement::SetAttribute( const std::string& name, int val )
 
 
 void TiXmlElement::SetDoubleAttribute( const char * name, double val )
-{	
+{
 	TiXmlAttribute* attrib = attributeSet.FindOrCreate( name );
 	if ( attrib ) {
 		attrib->SetDoubleValue( val );
@@ -724,13 +725,13 @@ void TiXmlElement::SetDoubleAttribute( const char * name, double val )
 
 #ifdef TIXML_USE_STL
 void TiXmlElement::SetDoubleAttribute( const std::string& name, double val )
-{	
+{
 	TiXmlAttribute* attrib = attributeSet.FindOrCreate( name );
 	if ( attrib ) {
 		attrib->SetDoubleValue( val );
 	}
 }
-#endif 
+#endif
 
 
 void TiXmlElement::SetAttribute( const char * cname, const char * cvalue )
@@ -811,7 +812,7 @@ void TiXmlElement::CopyTo( TiXmlElement* target ) const
 	// superclass:
 	TiXmlNode::CopyTo( target );
 
-	// Element class: 
+	// Element class:
 	// Clone the attributes, then clone the children.
 	const TiXmlAttribute* attribute = 0;
 	for(	attribute = attributeSet.First();
@@ -830,7 +831,7 @@ void TiXmlElement::CopyTo( TiXmlElement* target ) const
 
 bool TiXmlElement::Accept( TiXmlVisitor* visitor ) const
 {
-	if ( visitor->VisitEnter( *this, attributeSet.First() ) ) 
+	if ( visitor->VisitEnter( *this, attributeSet.First() ) )
 	{
 		for ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() )
 		{
@@ -923,7 +924,7 @@ bool TiXmlDocument::LoadFile( const char* _filename, TiXmlEncoding encoding )
 	value = filename;
 
 	// reading in binary mode so that tinyxml can normalize the EOL
-	FILE* file = TiXmlFOpen( value.c_str (), "rb" );	
+	FILE* file = TiXmlFOpen( value.c_str (), "rb" );
 
 	if ( file )
 	{
@@ -940,7 +941,7 @@ bool TiXmlDocument::LoadFile( const char* _filename, TiXmlEncoding encoding )
 
 bool TiXmlDocument::LoadFile( FILE* file, TiXmlEncoding encoding )
 {
-	if ( !file ) 
+	if ( !file )
 	{
 		SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN );
 		return false;
@@ -952,9 +953,14 @@ bool TiXmlDocument::LoadFile( FILE* file, TiXmlEncoding encoding )
 
 	// Get the file size, so we can pre-allocate the string. HUGE speed impact.
 	long length = 0;
-	fseek( file, 0, SEEK_END );
-	length = ftell( file );
-	fseek( file, 0, SEEK_SET );
+	int f_d = fileno(file);
+	struct stat st;
+	fstat(f_d, &st);
+	length = st.st_size;
+
+//	fseek( file, 0, SEEK_END );
+//	length = ftell( file );
+//	fseek( file, 0, SEEK_SET );
 
 	// Strange case, but good to handle up front.
 	if ( length <= 0 )
@@ -967,13 +973,13 @@ bool TiXmlDocument::LoadFile( FILE* file, TiXmlEncoding encoding )
 	// 2.11 End-of-Line Handling
 	// <snip>
 	// <quote>
-	// ...the XML processor MUST behave as if it normalized all line breaks in external 
-	// parsed entities (including the document entity) on input, before parsing, by translating 
-	// both the two-character sequence #xD #xA and any #xD that is not followed by #xA to 
+	// ...the XML processor MUST behave as if it normalized all line breaks in external
+	// parsed entities (including the document entity) on input, before parsing, by translating
+	// both the two-character sequence #xD #xA and any #xD that is not followed by #xA to
 	// a single #xA character.
 	// </quote>
 	//
-	// It is not clear fgets does that, and certainly isn't clear it works cross platform. 
+	// It is not clear fgets does that, and certainly isn't clear it works cross platform.
 	// Generally, you expect fgets to translate from the convention of the OS to the c/unix
 	// convention, and not work generally.
 
@@ -998,7 +1004,7 @@ bool TiXmlDocument::LoadFile( FILE* file, TiXmlEncoding encoding )
 	// a newline-carriage return is hit.
 	//
 	// Wikipedia:
-	// Systems based on ASCII or a compatible character set use either LF  (Line feed, '\n', 0x0A, 10 in decimal) or 
+	// Systems based on ASCII or a compatible character set use either LF  (Line feed, '\n', 0x0A, 10 in decimal) or
 	// CR (Carriage return, '\r', 0x0D, 13 in decimal) individually, or CR followed by LF (CR+LF, 0x0D 0x0A)...
 	//		* LF:    Multics, Unix and Unix-like systems (GNU/Linux, AIX, Xenix, Mac OS X, FreeBSD, etc.), BeOS, Amiga, RISC OS, and others
     //		* CR+LF: DEC RT-11 and most other early non-Unix, non-IBM OSes, CP/M, MP/M, DOS, OS/2, Microsoft Windows, Symbian OS
@@ -1052,7 +1058,7 @@ bool TiXmlDocument::SaveFile( const char * filename ) const
 
 bool TiXmlDocument::SaveFile( FILE* fp ) const
 {
-	if ( useMicrosoftBOM ) 
+	if ( useMicrosoftBOM )
 	{
 		const unsigned char TIXML_UTF_LEAD_0 = 0xefU;
 		const unsigned char TIXML_UTF_LEAD_1 = 0xbbU;
@@ -1082,7 +1088,7 @@ void TiXmlDocument::CopyTo( TiXmlDocument* target ) const
 	for ( node = firstChild; node; node = node->NextSibling() )
 	{
 		target->LinkEndChild( node->Clone() );
-	}	
+	}
 }
 
 
@@ -1205,7 +1211,7 @@ int TiXmlAttribute::QueryDoubleValue( double* dval ) const
 void TiXmlAttribute::SetIntValue( int _value )
 {
 	char buf [64];
-	#if defined(TIXML_SNPRINTF)		
+	#if defined(TIXML_SNPRINTF)
 		TIXML_SNPRINTF(buf, sizeof(buf), "%d", _value);
 	#else
 		sprintf (buf, "%d", _value);
@@ -1216,7 +1222,7 @@ void TiXmlAttribute::SetIntValue( int _value )
 void TiXmlAttribute::SetDoubleValue( double _value )
 {
 	char buf [256];
-	#if defined(TIXML_SNPRINTF)		
+	#if defined(TIXML_SNPRINTF)
 		TIXML_SNPRINTF( buf, sizeof(buf), "%g", _value);
 	#else
 		sprintf (buf, "%g", _value);
@@ -1318,7 +1324,7 @@ bool TiXmlText::Accept( TiXmlVisitor* visitor ) const
 
 
 TiXmlNode* TiXmlText::Clone() const
-{	
+{
 	TiXmlText* clone = 0;
 	clone = new TiXmlText( "" );
 
@@ -1357,7 +1363,7 @@ TiXmlDeclaration::TiXmlDeclaration(	const std::string& _version,
 TiXmlDeclaration::TiXmlDeclaration( const TiXmlDeclaration& copy )
 	: TiXmlNode( TiXmlNode::TINYXML_DECLARATION )
 {
-	copy.CopyTo( this );	
+	copy.CopyTo( this );
 }
 
 
@@ -1407,7 +1413,7 @@ bool TiXmlDeclaration::Accept( TiXmlVisitor* visitor ) const
 
 
 TiXmlNode* TiXmlDeclaration::Clone() const
-{	
+{
 	TiXmlDeclaration* clone = new TiXmlDeclaration();
 
 	if ( !clone )
@@ -1545,7 +1551,7 @@ TiXmlAttribute* TiXmlAttributeSet::FindOrCreate( const char* _name )
 }
 
 
-#ifdef TIXML_USE_STL	
+#ifdef TIXML_USE_STL
 std::istream& operator>> (std::istream & in, TiXmlNode & base)
 {
 	TIXML_STRING tag;
@@ -1558,7 +1564,7 @@ std::istream& operator>> (std::istream & in, TiXmlNode & base)
 #endif
 
 
-#ifdef TIXML_USE_STL	
+#ifdef TIXML_USE_STL
 std::ostream& operator<< (std::ostream & out, const TiXmlNode & base)
 {
 	TiXmlPrinter printer;
@@ -1728,12 +1734,12 @@ bool TiXmlPrinter::VisitEnter( const TiXmlElement& element, const TiXmlAttribute
 		attrib->Print( 0, 0, &buffer );
 	}
 
-	if ( !element.FirstChild() ) 
+	if ( !element.FirstChild() )
 	{
 		buffer += " />";
 		DoLineBreak();
 	}
-	else 
+	else
 	{
 		buffer += ">";
 		if (    element.FirstChild()->ToText()
@@ -1748,7 +1754,7 @@ bool TiXmlPrinter::VisitEnter( const TiXmlElement& element, const TiXmlAttribute
 			DoLineBreak();
 		}
 	}
-	++depth;	
+	++depth;
 	return true;
 }
 
@@ -1756,11 +1762,11 @@ bool TiXmlPrinter::VisitEnter( const TiXmlElement& element, const TiXmlAttribute
 bool TiXmlPrinter::VisitExit( const TiXmlElement& element )
 {
 	--depth;
-	if ( !element.FirstChild() ) 
+	if ( !element.FirstChild() )
 	{
 		// nothing.
 	}
-	else 
+	else
 	{
 		if ( simpleTextPrint )
 		{
diff --git a/src/osgEarthAnnotation/AnnotationData b/src/osgEarthAnnotation/AnnotationData
index 025ec5b..98394f6 100644
--- a/src/osgEarthAnnotation/AnnotationData
+++ b/src/osgEarthAnnotation/AnnotationData
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/AnnotationData.cpp b/src/osgEarthAnnotation/AnnotationData.cpp
index 72f7532..63c2dcf 100644
--- a/src/osgEarthAnnotation/AnnotationData.cpp
+++ b/src/osgEarthAnnotation/AnnotationData.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/AnnotationEditing b/src/osgEarthAnnotation/AnnotationEditing
index 96cab29..ff6deef 100644
--- a/src/osgEarthAnnotation/AnnotationEditing
+++ b/src/osgEarthAnnotation/AnnotationEditing
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/AnnotationEditing.cpp b/src/osgEarthAnnotation/AnnotationEditing.cpp
index 0e43770..f2a70f8 100644
--- a/src/osgEarthAnnotation/AnnotationEditing.cpp
+++ b/src/osgEarthAnnotation/AnnotationEditing.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -18,7 +18,7 @@
 */
 
 #include <osgEarthAnnotation/AnnotationEditing>
-
+#include <osgEarth/GeoMath>
 #include <osg/io_utils>
 
 using namespace osgEarth::Annotation;
diff --git a/src/osgEarthAnnotation/AnnotationNode b/src/osgEarthAnnotation/AnnotationNode
index f8251ad..4f21995 100644
--- a/src/osgEarthAnnotation/AnnotationNode
+++ b/src/osgEarthAnnotation/AnnotationNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -192,7 +192,7 @@ namespace osgEarth { namespace Annotation
         AnnotationNode( MapNode* mapNode, const Config& conf );
 
         // hidden copy ctor
-        AnnotationNode(const AnnotationNode& rhs, const osg::CopyOp& op=osg::CopyOp::DEEP_COPY_ALL) { }
+        AnnotationNode(const AnnotationNode& rhs, const osg::CopyOp& op=osg::CopyOp::DEEP_COPY_ALL) : osg::Group(rhs, op) { }
 
         osg::ref_ptr< TerrainCallback > _autoClampCallback;
 
@@ -238,6 +238,7 @@ namespace osgEarth { namespace Annotation
         PositionedAnnotationNode(MapNode* mapNode, const Config& conf)
             : AnnotationNode( mapNode, conf ) { }
         
+        PositionedAnnotationNode(const PositionedAnnotationNode& rhs, const osg::CopyOp& op=osg::CopyOp::DEEP_COPY_ALL) : AnnotationNode(rhs, op) { }
     };
 
 } } // namespace osgEarth::Annotation
diff --git a/src/osgEarthAnnotation/AnnotationNode.cpp b/src/osgEarthAnnotation/AnnotationNode.cpp
index e83698e..055158d 100644
--- a/src/osgEarthAnnotation/AnnotationNode.cpp
+++ b/src/osgEarthAnnotation/AnnotationNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -418,5 +418,16 @@ AnnotationNode::applyGeneralSymbology(const Style& style)
                 GL_CULL_FACE,
                 (render->backfaceCulling() == true? osg::StateAttribute::ON : osg::StateAttribute::OFF) | osg::StateAttribute::OVERRIDE );
         }
+
+        if ( render->clipPlane().isSet() )
+        {
+            GLenum mode = GL_CLIP_PLANE0 + render->clipPlane().value();
+            getOrCreateStateSet()->setMode(mode, 1);
+        }
+
+        if ( render->minAlpha().isSet() )
+        {
+            DiscardAlphaFragments().install( getOrCreateStateSet(), render->minAlpha().value() );
+        }
     }
 }
diff --git a/src/osgEarthAnnotation/AnnotationRegistry b/src/osgEarthAnnotation/AnnotationRegistry
index 10b34ee..a48ba08 100644
--- a/src/osgEarthAnnotation/AnnotationRegistry
+++ b/src/osgEarthAnnotation/AnnotationRegistry
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/AnnotationRegistry.cpp b/src/osgEarthAnnotation/AnnotationRegistry.cpp
index 50a9b10..b04b6ad 100644
--- a/src/osgEarthAnnotation/AnnotationRegistry.cpp
+++ b/src/osgEarthAnnotation/AnnotationRegistry.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/AnnotationSettings b/src/osgEarthAnnotation/AnnotationSettings
index fa23d8d..1c5d5ad 100644
--- a/src/osgEarthAnnotation/AnnotationSettings
+++ b/src/osgEarthAnnotation/AnnotationSettings
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/AnnotationSettings.cpp b/src/osgEarthAnnotation/AnnotationSettings.cpp
index 1f98099..5245763 100644
--- a/src/osgEarthAnnotation/AnnotationSettings.cpp
+++ b/src/osgEarthAnnotation/AnnotationSettings.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/AnnotationUtils b/src/osgEarthAnnotation/AnnotationUtils
index 178ab5c..e1a3c35 100644
--- a/src/osgEarthAnnotation/AnnotationUtils
+++ b/src/osgEarthAnnotation/AnnotationUtils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -23,6 +23,7 @@
 #include <osgEarthSymbology/TextSymbol>
 #include <osgEarthSymbology/Style>
 #include <osg/AutoTransform>
+#include <osg/CullStack>
 #include <osg/Drawable>
 #include <osg/Geometry>
 #include <osgText/TextBase>
diff --git a/src/osgEarthAnnotation/AnnotationUtils.cpp b/src/osgEarthAnnotation/AnnotationUtils.cpp
index ace9978..213fcff 100755
--- a/src/osgEarthAnnotation/AnnotationUtils.cpp
+++ b/src/osgEarthAnnotation/AnnotationUtils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -32,6 +32,7 @@
 #include <osg/CullFace>
 #include <osg/MatrixTransform>
 #include <osg/LightModel>
+#include <osg/Projection>
 
 using namespace osgEarth;
 using namespace osgEarth::Annotation;
@@ -97,7 +98,7 @@ AnnotationUtils::createTextDrawable(const std::string& text,
 
     t->setText( text, text_encoding );
 
-    // osgText::Text turns on depth writing by default, even if you turned it off..
+    // osgText::Text turns on depth writing by default, even if you turned it off.
     t->setEnableDepthWrites( false );
 
     if ( symbol && symbol->layout().isSet() )
@@ -129,12 +130,17 @@ AnnotationUtils::createTextDrawable(const std::string& text,
 
     t->setAutoRotateToScreen( false );
     t->setCharacterSizeMode( osgText::Text::OBJECT_COORDS );
-    t->setCharacterSize( symbol && symbol->size().isSet() ? *symbol->size() : 16.0f );
+    t->setCharacterSize( symbol && symbol->size().isSet() ? (float)(symbol->size()->eval()) : 16.0f );
     t->setColor( symbol && symbol->fill().isSet() ? symbol->fill()->color() : Color::White );
 
     osgText::Font* font = 0L;
     if ( symbol && symbol->font().isSet() )
+    {
         font = osgText::readFontFile( *symbol->font() );
+        // mitigates mipmapping issues that cause rendering artifacts for some fonts/placement
+        if ( font )
+          font->setGlyphImageMargin( 2 );
+    }
     if ( !font )
         font = Registry::instance()->getDefaultFont();
     if ( font )
@@ -432,7 +438,11 @@ AnnotationUtils::createSphere( float r, const osg::Vec4& color, float maxAngle )
     osg::Geode* geode = new osg::Geode();
     geode->addDrawable( geom );
 
-    return geode;
+    // need 2-pass alpha so you can view it properly from below.
+    if ( color.a() < 1.0f )
+      return installTwoPassAlpha( geode );
+    else
+      return geode;
 }
 
 osg::Node* 
@@ -453,7 +463,7 @@ AnnotationUtils::createHemisphere( float r, const osg::Vec4& color, float maxAng
        v->getVertexBufferObject()->setUsage(GL_STATIC_DRAW_ARB);
 
     osg::DrawElementsUByte* b = new osg::DrawElementsUByte(GL_TRIANGLES);
-    b->reserve(24);
+    b->reserve(12);
     b->push_back(0); b->push_back(2); b->push_back(3);
     b->push_back(0); b->push_back(3); b->push_back(1);
     b->push_back(0); b->push_back(1); b->push_back(4);
@@ -482,10 +492,13 @@ AnnotationUtils::createHemisphere( float r, const osg::Vec4& color, float maxAng
     geode->addDrawable( geom );
 
     // need 2-pass alpha so you can view it properly from below.
-    return installTwoPassAlpha( geode );
+    if ( color.a() < 1.0f )
+      return installTwoPassAlpha( geode );
+    else
+      return geode;
 }
 
-// constucts an ellipsoidal mesh
+// constructs an ellipsoidal mesh
 osg::Geometry*
 AnnotationUtils::createEllipsoidGeometry(float xRadius, 
                                          float yRadius,
@@ -544,9 +557,9 @@ AnnotationUtils::createEllipsoidGeometry(float xRadius,
             float sin_v = sinf(v);
             
             verts->push_back(osg::Vec3(
-                xRadius * cos_u * sin_v,
-                yRadius * sin_u * sin_v,
-                zRadius * cos_v ));
+                xRadius * cos_u * cos_v,
+                yRadius * sin_u * cos_v,
+                zRadius * sin_v ));
 
             if (genTexCoords)
             {
diff --git a/src/osgEarthAnnotation/CMakeLists.txt b/src/osgEarthAnnotation/CMakeLists.txt
index 5bcb39a..533f639 100644
--- a/src/osgEarthAnnotation/CMakeLists.txt
+++ b/src/osgEarthAnnotation/CMakeLists.txt
@@ -21,6 +21,7 @@ set(LIB_PUBLIC_HEADERS
     EllipseNode
     Export
     FeatureNode
+    FeatureEditing
     LocalGeometryNode
     HighlightDecoration
     ImageOverlay
@@ -46,6 +47,7 @@ set(LIB_COMMON_FILES
     Decoration.cpp
     EllipseNode.cpp
     FeatureNode.cpp
+    FeatureEditing.cpp
     LocalGeometryNode.cpp
     HighlightDecoration.cpp
     ImageOverlay.cpp
@@ -59,22 +61,6 @@ set(LIB_COMMON_FILES
     TrackNode.cpp
 )
 
-if( NOT ${OPENSCENEGRAPH_VERSION} VERSION_LESS "2.9.6" )
-
-    set(LIB_PUBLIC_HEADERS ${LIB_PUBLIC_HEADERS}
-        AnnotationEditing
-        FeatureEditing
-        ImageOverlayEditor
-    )
-        
-    set(LIB_COMMON_FILES ${LIB_COMMON_FILES} 
-        AnnotationEditing.cpp
-        FeatureEditing.cpp
-        ImageOverlayEditor.cpp
-    )
-        
-endif()
-
 
 ADD_LIBRARY( ${LIB_NAME} ${OSGEARTH_USER_DEFINED_DYNAMIC_OR_STATIC}
     ${LIB_PUBLIC_HEADERS}  
diff --git a/src/osgEarthAnnotation/CircleNode b/src/osgEarthAnnotation/CircleNode
index 020105e..5613516 100644
--- a/src/osgEarthAnnotation/CircleNode
+++ b/src/osgEarthAnnotation/CircleNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/CircleNode.cpp b/src/osgEarthAnnotation/CircleNode.cpp
index a176953..8487c79 100644
--- a/src/osgEarthAnnotation/CircleNode.cpp
+++ b/src/osgEarthAnnotation/CircleNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -25,6 +25,7 @@
 #include <osgEarth/MapNode>
 #include <osgEarth/DrapeableNode>
 #include <osg/MatrixTransform>
+#include <cmath>
 
 using namespace osgEarth;
 using namespace osgEarth::Annotation;
@@ -151,7 +152,7 @@ CircleNode::rebuild()
     // construct a local-origin circle.
     GeometryFactory factory;
     Geometry* geom = NULL;
-    if (abs(_arcEnd.as(Units::DEGREES) - _arcStart.as(Units::DEGREES)) >= 360.0)
+    if (std::abs(_arcEnd.as(Units::DEGREES) - _arcStart.as(Units::DEGREES)) >= 360.0)
     {
         geom = factory.createCircle(osg::Vec3d(0,0,0), _radius, _numSegments);
     }
diff --git a/src/osgEarthAnnotation/Common b/src/osgEarthAnnotation/Common
index 14aae38..43f7e5c 100644
--- a/src/osgEarthAnnotation/Common
+++ b/src/osgEarthAnnotation/Common
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/Decoration b/src/osgEarthAnnotation/Decoration
index 2a28730..ef10947 100644
--- a/src/osgEarthAnnotation/Decoration
+++ b/src/osgEarthAnnotation/Decoration
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/Decoration.cpp b/src/osgEarthAnnotation/Decoration.cpp
index e485649..c1ce9ff 100644
--- a/src/osgEarthAnnotation/Decoration.cpp
+++ b/src/osgEarthAnnotation/Decoration.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/EllipseNode b/src/osgEarthAnnotation/EllipseNode
index 41a91cb..fce1e8e 100644
--- a/src/osgEarthAnnotation/EllipseNode
+++ b/src/osgEarthAnnotation/EllipseNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/EllipseNode.cpp b/src/osgEarthAnnotation/EllipseNode.cpp
index dff1c44..035f38b 100644
--- a/src/osgEarthAnnotation/EllipseNode.cpp
+++ b/src/osgEarthAnnotation/EllipseNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -22,6 +22,7 @@
 #include <osgEarthSymbology/GeometryFactory>
 #include <osgEarth/DrapeableNode>
 #include <osgEarth/MapNode>
+#include <cmath>
 
 using namespace osgEarth;
 using namespace osgEarth::Annotation;
@@ -187,7 +188,7 @@ EllipseNode::rebuild()
     GeometryFactory factory;
     Geometry* geom = NULL;
 
-    if (abs(_arcEnd.as(Units::DEGREES) - _arcStart.as(Units::DEGREES)) >= 360.0)
+    if (std::abs(_arcEnd.as(Units::DEGREES) - _arcStart.as(Units::DEGREES)) >= 360.0)
     {
         geom = factory.createEllipse(osg::Vec3d(0,0,0), _radiusMajor, _radiusMinor, _rotationAngle, _numSegments);
     }
diff --git a/src/osgEarthAnnotation/Export b/src/osgEarthAnnotation/Export
index d793d3c..53b9d98 100644
--- a/src/osgEarthAnnotation/Export
+++ b/src/osgEarthAnnotation/Export
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/FeatureEditing b/src/osgEarthAnnotation/FeatureEditing
index bd4b02a..ad15f61 100644
--- a/src/osgEarthAnnotation/FeatureEditing
+++ b/src/osgEarthAnnotation/FeatureEditing
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/FeatureEditing.cpp b/src/osgEarthAnnotation/FeatureEditing.cpp
index a773abf..9e74df4 100644
--- a/src/osgEarthAnnotation/FeatureEditing.cpp
+++ b/src/osgEarthAnnotation/FeatureEditing.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/FeatureNode b/src/osgEarthAnnotation/FeatureNode
index fe2e6f6..73c0144 100644
--- a/src/osgEarthAnnotation/FeatureNode
+++ b/src/osgEarthAnnotation/FeatureNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/FeatureNode.cpp b/src/osgEarthAnnotation/FeatureNode.cpp
index 69d6a21..c0c993b 100644
--- a/src/osgEarthAnnotation/FeatureNode.cpp
+++ b/src/osgEarthAnnotation/FeatureNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/HighlightDecoration b/src/osgEarthAnnotation/HighlightDecoration
index 2a3f64c..de6e7c3 100644
--- a/src/osgEarthAnnotation/HighlightDecoration
+++ b/src/osgEarthAnnotation/HighlightDecoration
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -20,15 +20,16 @@
 #define OSGEARTH_ANNOTATION_HIGHLIGHT_DECORATION_H 1
 
 #include <osgEarthAnnotation/Decoration>
+#include <osg/Uniform>
 
 namespace osgEarth { namespace Annotation
 {	
     using namespace osgEarth;
 
     /**
-     * Decoration technique that highlights the geometry
+     * Decoration technique that highlights the geometry.
      */
-    class OSGEARTHANNO_EXPORT HighlightDecoration : public InjectionDecoration
+    class OSGEARTHANNO_EXPORT HighlightDecoration : public Decoration
     {
     public:
         HighlightDecoration(const osg::Vec4f& color =osg::Vec4f(1,1,0,0.5));
@@ -36,14 +37,19 @@ namespace osgEarth { namespace Annotation
         virtual ~HighlightDecoration() { }
 
         virtual Decoration* clone() const { return new HighlightDecoration(_color); }
+
+        /** Hightlight color (mixes with alpha) */
+        void setColor(const osg::Vec4f& color);
+        const osg::Vec4f& getColor() const { return _color; }
         
-        virtual bool apply(class OrthoNode& node, bool enable);
+    public: // Decoration interface
 
-    protected:
-        virtual bool apply(osg::Group* attachPoint, bool enable);
+        virtual bool apply(class AnnotationNode& node, bool enable);
 
-        osg::Vec4f _color;
-        osg::ref_ptr<osg::StateSet> _passes[2];
+    private:
+        bool                         _supported;
+        osg::Vec4f                   _color;
+        osg::ref_ptr<osg::Uniform>   _colorUniform;
     };
 
 } } // namespace osgEarth::Annotation
diff --git a/src/osgEarthAnnotation/HighlightDecoration.cpp b/src/osgEarthAnnotation/HighlightDecoration.cpp
index 8267196..787f85b 100644
--- a/src/osgEarthAnnotation/HighlightDecoration.cpp
+++ b/src/osgEarthAnnotation/HighlightDecoration.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -18,134 +18,73 @@
 */
 
 #include <osgEarthAnnotation/HighlightDecoration>
+#include <osgEarthAnnotation/AnnotationNode>
 #include <osgEarthAnnotation/AnnotationUtils>
-#include <osgEarthAnnotation/OrthoNode>
-#include <osgEarth/NodeUtils>
-#include <osg/Stencil>
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/Registry>
+#include <osgEarth/Capabilities>
 
 #undef  LC
 #define LC "[HighlightDecoration] "
 
 using namespace osgEarth::Annotation;
 
+#define FRAG_FUNCTION "oe_anno_highlight_frag"
+
 namespace
 {
-    struct HighlightGroup : public osg::Group
-    {
-        osg::ref_ptr<osg::StateSet> _pass1, _pass2;
-        osg::ref_ptr<osg::Node>     _fillNode;
-
-        void traverse(osg::NodeVisitor& nv)
-        {
-            osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
-            if ( cv && _fillNode.valid() && _pass1.valid() )
-            {
-                const osg::GraphicsContext* gc = cv->getCurrentCamera()->getGraphicsContext();
-                if ( gc && gc->getTraits() && gc->getTraits()->stencil < 1 )
-                {
-                    OE_WARN << LC << "Insufficient stencil buffer bits available; disabling highlighting." << std::endl;
-                    OE_WARN << LC << "Please call osg::DisplaySettings::instance()->setMinimumNumStencilBits()" << std::endl;
-                    _pass1 = 0L;
-                }
-                else
-                {
-                    // first render the geometry to the stencil buffer:
-                    cv->pushStateSet(_pass1);
-                    osg::Group::traverse( nv );
-                    cv->popStateSet();
-
-                    // the render the coverage quad
-                    cv->pushStateSet(_pass2);
-                    _fillNode->accept( nv );
-                    cv->popStateSet();
-                }
-            }
-            else
-            {
-                osg::Group::traverse(nv);
-            }
-        }
-    };
+    const char* fragSource =
+        "#version " GLSL_VERSION_STR "\n"
+        "uniform vec4 oe_anno_highlight_color; \n"
+        "void " FRAG_FUNCTION "(inout vec4 color) {\n"
+        "    color.rgb = mix(color.rgb, oe_anno_highlight_color.rgb, oe_anno_highlight_color.a); \n"
+        "}\n";
 }
 
+
 HighlightDecoration::HighlightDecoration(const osg::Vec4f& color) :
-InjectionDecoration( new HighlightGroup() ),
-_color( color )
+Decoration(),
+_color    (color)
 {
-    HighlightGroup* hg = dynamic_cast<HighlightGroup*>( _injectionGroup.get() );
-
-    hg->_pass1 = new osg::StateSet();
+    _supported = Registry::capabilities().supportsGLSL();
+    if ( _supported )
     {
-        osg::Stencil* stencil  = new osg::Stencil();
-        stencil->setFunction(osg::Stencil::ALWAYS, 1, ~0u);
-        stencil->setOperation(osg::Stencil::KEEP, osg::Stencil::KEEP, osg::Stencil::REPLACE);
-        hg->_pass1->setAttributeAndModes(stencil, 1);
-        hg->_pass1->setBinNumber(0);
+        _colorUniform = new osg::Uniform(osg::Uniform::FLOAT_VEC4, "oe_anno_highlight_color");
+        _colorUniform->set(_color);
     }
+}
 
-    hg->_pass2 = new osg::StateSet();
+void
+HighlightDecoration::setColor(const osg::Vec4f& color)
+{
+    _color = color;
+    if ( _colorUniform.valid() )
     {
-        osg::Stencil* stencil  = new osg::Stencil();
-        stencil->setFunction(osg::Stencil::NOTEQUAL, 0, ~0u);
-        stencil->setOperation(osg::Stencil::REPLACE, osg::Stencil::REPLACE, osg::Stencil::REPLACE);
-        hg->_pass2->setAttributeAndModes(stencil, 1);
-        hg->_pass2->setBinNumber(1);
+        _colorUniform->set(_color);
     }
 }
 
 bool
-HighlightDecoration::apply(OrthoNode& node, bool enable)
+HighlightDecoration::apply(AnnotationNode& node, bool enable)
 {
-    if ( node.getAttachPoint() )
+    if ( _supported )
     {
-        node.setDynamic( true );
-        FindNodesVisitor<osg::Geode> fnv;
-        node.getAttachPoint()->accept( fnv );
+        osg::StateSet* ss = node.getOrCreateStateSet();
         if ( enable )
         {
-            osg::BoundingBox box;
-            for( std::vector<osg::Geode*>::iterator i = fnv._results.begin(); i != fnv._results.end(); ++i )
+            VirtualProgram* vp = VirtualProgram::getOrCreate( ss );
+            if ( vp->getShader(FRAG_FUNCTION) == 0L )
             {
-                osg::Geode* geode = *i;
-                box.expandBy( geode->getBoundingBox() );
-            }
-            
-            if ( fnv._results.size() > 0 )
-            {
-                osg::Drawable* geom = AnnotationUtils::create2DOutline( box, 3.0f, _color );  
-                geom->setUserData( node.getAnnotationData() );
-                for( std::vector<osg::Geode*>::iterator i = fnv._results.begin(); i != fnv._results.end(); ++i )
-                {
-                    (*i)->addDrawable(geom);
-                }
+                vp->setFunction(FRAG_FUNCTION, fragSource, ShaderComp::LOCATION_FRAGMENT_COLORING);
+                ss->addUniform( _colorUniform.get() );
             }
+            _colorUniform->set(_color);
         }
         else
         {
-            for( std::vector<osg::Geode*>::iterator i = fnv._results.begin(); i != fnv._results.end(); ++i )
-            {
-                osg::Geode* geode = *i;
-                geode->removeDrawable( geode->getDrawable(geode->getNumDrawables()-1) );
-            }
+            // sets alpha=0 to disable highlighting
+            _colorUniform->set(osg::Vec4f(1,1,1,0));
         }
-
-        return true;
     }
-    return false;
-}
-
-bool
-HighlightDecoration::apply(osg::Group* ap, bool enable)
-{
-    HighlightGroup* hg = dynamic_cast<HighlightGroup*>( _injectionGroup.get() );
-    if ( !hg->_fillNode.valid() && ap != 0L )
-    {
-        const osg::BoundingSphere& bs = ap->getBound();
-
-        osg::Node* quad = AnnotationUtils::createFullScreenQuad( _color );
-        quad->setCullingActive( false );
-        hg->_fillNode = quad;
-    }
-
-    return InjectionDecoration::apply(ap, enable);
+    return _supported;
 }
diff --git a/src/osgEarthAnnotation/ImageOverlay b/src/osgEarthAnnotation/ImageOverlay
index 53fae48..acdf514 100644
--- a/src/osgEarthAnnotation/ImageOverlay
+++ b/src/osgEarthAnnotation/ImageOverlay
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/ImageOverlay.cpp b/src/osgEarthAnnotation/ImageOverlay.cpp
index 4dde087..ff4c7e0 100644
--- a/src/osgEarthAnnotation/ImageOverlay.cpp
+++ b/src/osgEarthAnnotation/ImageOverlay.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -305,9 +305,7 @@ ImageOverlay::init()
         if ( Registry::capabilities().supportsGLSL() )
         {
             //OE_WARN << LC << "ShaderGen RUNNING" << std::endl;
-            ShaderGenerator gen;
-            gen.setProgramName( "osgEarth.ImageOverlay" );
-            gen.run( _geode );
+            Registry::shaderGenerator().run( _geode, "osgEarth.ImageOverlay" );
         }
     }
 }
diff --git a/src/osgEarthAnnotation/ImageOverlayEditor b/src/osgEarthAnnotation/ImageOverlayEditor
index 99c3586..c878a9b 100644
--- a/src/osgEarthAnnotation/ImageOverlayEditor
+++ b/src/osgEarthAnnotation/ImageOverlayEditor
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/ImageOverlayEditor.cpp b/src/osgEarthAnnotation/ImageOverlayEditor.cpp
index 7853c50..eb5b76e 100644
--- a/src/osgEarthAnnotation/ImageOverlayEditor.cpp
+++ b/src/osgEarthAnnotation/ImageOverlayEditor.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/LabelNode b/src/osgEarthAnnotation/LabelNode
index 82d375d..fa32e2b 100644
--- a/src/osgEarthAnnotation/LabelNode
+++ b/src/osgEarthAnnotation/LabelNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -123,7 +123,7 @@ namespace osgEarth { namespace Annotation
         osg::ref_ptr<osg::Geode> _geode;
 
         /** Copy constructor */
-        LabelNode( const LabelNode& rhs, const osg::CopyOp& op =osg::CopyOp::DEEP_COPY_ALL ) { }
+        LabelNode( const LabelNode& rhs, const osg::CopyOp& op =osg::CopyOp::DEEP_COPY_ALL ) : OrthoNode(rhs, op) { }
     };
 
 } } // namespace osgEarth::Annotation
diff --git a/src/osgEarthAnnotation/LabelNode.cpp b/src/osgEarthAnnotation/LabelNode.cpp
index 78b2bf7..ae8baa6 100644
--- a/src/osgEarthAnnotation/LabelNode.cpp
+++ b/src/osgEarthAnnotation/LabelNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -141,9 +141,10 @@ LabelNode::setStyle( const Style& style )
 
     setLightingIfNotSet( false );
 
-    ShaderGenerator gen;
-    gen.setProgramName( "osgEarth.LabelNode" );
-    gen.run( this, Registry::stateSetCache() );
+    Registry::shaderGenerator().run(
+        this,
+        "osgEarth.LabelNode",
+        Registry::stateSetCache() );
 }
 
 void
@@ -152,10 +153,9 @@ LabelNode::setAnnotationData( AnnotationData* data )
     OrthoNode::setAnnotationData( data );
 
     // override this method so we can attach the anno data to the drawables.
-    const osg::Geode::DrawableList& list = _geode->getDrawableList();
-    for( osg::Geode::DrawableList::const_iterator i = list.begin(); i != list.end(); ++i )
+    for(unsigned i=0; i<_geode->getNumDrawables(); ++i)
     {
-        i->get()->setUserData( data );
+        _geode->getDrawable(i)->setUserData( data );
     }
 }
 
diff --git a/src/osgEarthAnnotation/LocalGeometryNode b/src/osgEarthAnnotation/LocalGeometryNode
index 459317f..7b2b5c1 100644
--- a/src/osgEarthAnnotation/LocalGeometryNode
+++ b/src/osgEarthAnnotation/LocalGeometryNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/LocalGeometryNode.cpp b/src/osgEarthAnnotation/LocalGeometryNode.cpp
index 440edff..d2814b2 100644
--- a/src/osgEarthAnnotation/LocalGeometryNode.cpp
+++ b/src/osgEarthAnnotation/LocalGeometryNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/LocalizedNode b/src/osgEarthAnnotation/LocalizedNode
index 7b50148..119fbe6 100644
--- a/src/osgEarthAnnotation/LocalizedNode
+++ b/src/osgEarthAnnotation/LocalizedNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -141,7 +141,7 @@ namespace osgEarth { namespace Annotation
 
         // hidden copy constructor
         LocalizedNode() { }
-        LocalizedNode(const LocalizedNode& rhs, const osg::CopyOp& op=osg::CopyOp::DEEP_COPY_ALL) { }
+        LocalizedNode(const LocalizedNode& rhs, const osg::CopyOp& op=osg::CopyOp::DEEP_COPY_ALL) : PositionedAnnotationNode(rhs, op) { }
         virtual ~LocalizedNode() { }
 
 
diff --git a/src/osgEarthAnnotation/LocalizedNode.cpp b/src/osgEarthAnnotation/LocalizedNode.cpp
index f92c661..ebe77eb 100644
--- a/src/osgEarthAnnotation/LocalizedNode.cpp
+++ b/src/osgEarthAnnotation/LocalizedNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/ModelNode b/src/osgEarthAnnotation/ModelNode
index c5f7305..dceba6a 100644
--- a/src/osgEarthAnnotation/ModelNode
+++ b/src/osgEarthAnnotation/ModelNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/ModelNode.cpp b/src/osgEarthAnnotation/ModelNode.cpp
index b592211..27c53ee 100644
--- a/src/osgEarthAnnotation/ModelNode.cpp
+++ b/src/osgEarthAnnotation/ModelNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -112,15 +112,10 @@ ModelNode::init()
                 if ( Registry::capabilities().supportsGLSL() )
                 {
                     // generate shader code for the loaded model:
-                    ShaderGenerator gen;
-                    gen.setProgramName( "osgEarth.ModelNode" );
-                    gen.run( node, Registry::stateSetCache() );
-
-                    // do we really need this? perhaps
-#if 0
-                    // better: put your modelnode under the mapnode?
-                    node->addCullCallback( new UpdateLightingUniformsHelper() );
-#endif
+                    Registry::shaderGenerator().run(
+                        node.get(),
+                        "osgEarth.ModelNode",
+                        Registry::stateSetCache() );
                 }
 
                 // attach to the transform:
diff --git a/src/osgEarthAnnotation/OrthoNode b/src/osgEarthAnnotation/OrthoNode
index 3ab2656..8895041 100644
--- a/src/osgEarthAnnotation/OrthoNode
+++ b/src/osgEarthAnnotation/OrthoNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -138,9 +138,11 @@ namespace osgEarth { namespace Annotation
 
         osg::Vec3d adjustOcclusionCullingPoint( const osg::Vec3d& world );
 
+    protected:
         // required by META_Node, but this object is not cloneable
-        OrthoNode( const OrthoNode& rhs, const osg::CopyOp& op =osg::CopyOp::DEEP_COPY_ALL ) { }
+        OrthoNode( const OrthoNode& rhs, const osg::CopyOp& op =osg::CopyOp::DEEP_COPY_ALL ) : PositionedAnnotationNode(rhs, op) { }
 
+    private:
         // autoclamping.
         virtual void reclamp( const TileKey& key, osg::Node* tile, const Terrain* );
 
diff --git a/src/osgEarthAnnotation/OrthoNode.cpp b/src/osgEarthAnnotation/OrthoNode.cpp
index a2deb45..6a36c69 100644
--- a/src/osgEarthAnnotation/OrthoNode.cpp
+++ b/src/osgEarthAnnotation/OrthoNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -30,6 +30,7 @@
 #include <osg/OcclusionQueryNode>
 #include <osg/Point>
 #include <osg/Depth>
+#include <osg/Switch>
 
 #define LC "[OrthoNode] "
 
diff --git a/src/osgEarthAnnotation/PlaceNode b/src/osgEarthAnnotation/PlaceNode
index 5511be0..6732ed8 100644
--- a/src/osgEarthAnnotation/PlaceNode
+++ b/src/osgEarthAnnotation/PlaceNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -131,7 +131,7 @@ namespace osgEarth { namespace Annotation
 
         // required by META_Node, but this object is not cloneable
         PlaceNode() { }
-        PlaceNode(const PlaceNode& rhs, const osg::CopyOp& op =osg::CopyOp::DEEP_COPY_ALL) { }
+        PlaceNode(const PlaceNode& rhs, const osg::CopyOp& op =osg::CopyOp::DEEP_COPY_ALL) : OrthoNode(rhs, op) { }
     };
 
 } } // namespace osgEarth::Annotation
diff --git a/src/osgEarthAnnotation/PlaceNode.cpp b/src/osgEarthAnnotation/PlaceNode.cpp
index f0071f3..b47dd48 100644
--- a/src/osgEarthAnnotation/PlaceNode.cpp
+++ b/src/osgEarthAnnotation/PlaceNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -216,10 +216,12 @@ PlaceNode::init()
     applyStyle( _style );
 
     setLightingIfNotSet( false );
-
-    ShaderGenerator gen;
-    gen.setProgramName( "osgEarth.PlaceNode" );
-    gen.run( this, Registry::stateSetCache() );
+    
+    // generate shaders:
+    Registry::shaderGenerator().run(
+        this,
+        "osgEarth.PlaceNode",
+        Registry::stateSetCache() );
 
     // re-apply annotation drawable-level stuff as neccesary.
     AnnotationData* ad = getAnnotationData();
@@ -242,10 +244,9 @@ PlaceNode::setText( const std::string& text )
 
     _text = text;
 
-    const osg::Geode::DrawableList& list = _geode->getDrawableList();
-    for( osg::Geode::DrawableList::const_iterator i = list.begin(); i != list.end(); ++i )
+    for(unsigned i=0; i<_geode->getNumDrawables(); ++i)
     {
-        osgText::Text* d = dynamic_cast<osgText::Text*>( i->get() );
+        osgText::Text* d = dynamic_cast<osgText::Text*>( _geode->getDrawable(i) );
         if ( d )
         {
 			TextSymbol* symbol =  _style.getOrCreate<TextSymbol>();
@@ -286,10 +287,9 @@ PlaceNode::setAnnotationData( AnnotationData* data )
     OrthoNode::setAnnotationData( data );
 
     // override this method so we can attach the anno data to the drawables.
-    const osg::Geode::DrawableList& list = _geode->getDrawableList();
-    for( osg::Geode::DrawableList::const_iterator i = list.begin(); i != list.end(); ++i )
+    for(unsigned i=0; i<_geode->getNumDrawables(); ++i)
     {
-        i->get()->setUserData( data );
+        _geode->getDrawable(i)->setUserData( data );
     }
 }
 
@@ -298,11 +298,11 @@ void
 PlaceNode::setDynamic( bool value )
 {
     OrthoNode::setDynamic( value );
-
-    const osg::Geode::DrawableList& list = _geode->getDrawableList();
-    for( osg::Geode::DrawableList::const_iterator i = list.begin(); i != list.end(); ++i )
+    
+    for(unsigned i=0; i<_geode->getNumDrawables(); ++i)
     {
-        i->get()->setDataVariance( value ? osg::Object::DYNAMIC : osg::Object::STATIC );
+        _geode->getDrawable(i)->setDataVariance( 
+            value ? osg::Object::DYNAMIC : osg::Object::STATIC );
     }
 }
 
diff --git a/src/osgEarthAnnotation/RectangleNode b/src/osgEarthAnnotation/RectangleNode
index bd3f593..3e98423 100644
--- a/src/osgEarthAnnotation/RectangleNode
+++ b/src/osgEarthAnnotation/RectangleNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/RectangleNode.cpp b/src/osgEarthAnnotation/RectangleNode.cpp
index 5b012d8..f0a1b70 100644
--- a/src/osgEarthAnnotation/RectangleNode.cpp
+++ b/src/osgEarthAnnotation/RectangleNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/ScaleDecoration b/src/osgEarthAnnotation/ScaleDecoration
index c814fbf..b789335 100644
--- a/src/osgEarthAnnotation/ScaleDecoration
+++ b/src/osgEarthAnnotation/ScaleDecoration
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/TrackNode b/src/osgEarthAnnotation/TrackNode
index 0b16e9a..59b8995 100644
--- a/src/osgEarthAnnotation/TrackNode
+++ b/src/osgEarthAnnotation/TrackNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthAnnotation/TrackNode.cpp b/src/osgEarthAnnotation/TrackNode.cpp
index 1a5bb27..958e59a 100644
--- a/src/osgEarthAnnotation/TrackNode.cpp
+++ b/src/osgEarthAnnotation/TrackNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -122,10 +122,12 @@ TrackNode::init( const TrackNodeFieldSchema& schema )
     setLightingIfNotSet( false );
 
     getAttachPoint()->addChild( _geode );
-
-    ShaderGenerator gen;
-    gen.setProgramName( "osgEarth.TrackNode" );
-    gen.run( this, Registry::stateSetCache() );
+    
+    // generate shaders:
+    Registry::shaderGenerator().run(
+        this,
+        "osgEarth.TrackNode",
+        Registry::stateSetCache() );
 }
 
 void
@@ -179,9 +181,8 @@ TrackNode::setAnnotationData( AnnotationData* data )
     OrthoNode::setAnnotationData( data );
 
     // override this method so we can attach the anno data to the drawables.
-    const osg::Geode::DrawableList& list = _geode->getDrawableList();
-    for( osg::Geode::DrawableList::const_iterator i = list.begin(); i != list.end(); ++i )
+    for(unsigned i=0; i<_geode->getNumDrawables(); ++i)
     {
-        i->get()->setUserData( data );
+        _geode->getDrawable(i)->setUserData( data );
     }
 }
diff --git a/src/osgEarthDrivers/CMakeLists.txt b/src/osgEarthDrivers/CMakeLists.txt
index f41aaf4..7cabbc4 100644
--- a/src/osgEarthDrivers/CMakeLists.txt
+++ b/src/osgEarthDrivers/CMakeLists.txt
@@ -48,15 +48,14 @@ ADD_SUBDIRECTORY(agglite)
 ADD_SUBDIRECTORY(model_simple)
 ADD_SUBDIRECTORY(debug)
 ADD_SUBDIRECTORY(cache_filesystem)
-ADD_SUBDIRECTORY(ocean_surface)
 ADD_SUBDIRECTORY(refresh)
 ADD_SUBDIRECTORY(xyz)
 ADD_SUBDIRECTORY(bing)
 ADD_SUBDIRECTORY(tileindex)
 
-IF(LIBNOISE_FOUND)
-    ADD_SUBDIRECTORY(noise)
-ENDIF(LIBNOISE_FOUND)
+ADD_SUBDIRECTORY(noise)
+ADD_SUBDIRECTORY(splat_mask)
+ADD_SUBDIRECTORY(colorramp)
 
 IF(GDAL_FOUND)
   ADD_SUBDIRECTORY(gdal)
@@ -71,17 +70,20 @@ IF(GDAL_FOUND)
 ENDIF(GDAL_FOUND)
 
 IF(SQLITE3_FOUND)
-#  ADD_SUBDIRECTORY(cache_sqlite3)
   ADD_SUBDIRECTORY(mbtiles)
 ENDIF(SQLITE3_FOUND)
 
-ADD_SUBDIRECTORY(engine_osgterrain)
-ADD_SUBDIRECTORY(engine_quadtree)
+IF(LEVELDB_FOUND)
+  ADD_SUBDIRECTORY(cache_leveldb)
+ENDIF(LEVELDB_FOUND)
+
 ADD_SUBDIRECTORY(engine_mp)
 ADD_SUBDIRECTORY(engine_byo)
 
 IF(V8_FOUND)
   ADD_SUBDIRECTORY(script_engine_v8)
+ELSEIF(NOT WITH_EXTERNAL_DUKTAPE OR DUKTAPE_FOUND)
+  ADD_SUBDIRECTORY(script_engine_duktape)
 ENDIF(V8_FOUND)
 
 IF(JAVASCRIPTCORE_FOUND)
@@ -91,3 +93,17 @@ ENDIF(JAVASCRIPTCORE_FOUND)
 ADD_SUBDIRECTORY(vdatum_egm84)
 ADD_SUBDIRECTORY(vdatum_egm96)
 ADD_SUBDIRECTORY(vdatum_egm2008)
+
+ADD_SUBDIRECTORY(sky_gl)
+ADD_SUBDIRECTORY(sky_simple)
+IF(SILVERLINING_FOUND)
+  ADD_SUBDIRECTORY(sky_silverlining)
+ENDIF(SILVERLINING_FOUND)
+
+ADD_SUBDIRECTORY(ocean_simple)
+IF(TRITON_FOUND)
+  ADD_SUBDIRECTORY(ocean_triton)
+ENDIF(TRITON_FOUND)
+
+# temporary
+ADD_SUBDIRECTORY(template_matclass)
diff --git a/src/osgEarthDrivers/agglite/AGGLiteOptions b/src/osgEarthDrivers/agglite/AGGLiteOptions
index 0b50da8..4db521e 100644
--- a/src/osgEarthDrivers/agglite/AGGLiteOptions
+++ b/src/osgEarthDrivers/agglite/AGGLiteOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -39,10 +39,18 @@ namespace osgEarth { namespace Drivers
         optional<bool>& optimizeLineSampling() { return _optimizeLineSampling; }
         const optional<bool>& optimizeLineSampling() const { return _optimizeLineSampling; }
 
+        /** 
+         * Set the gamma parameter applied on the AggLite rasterizer : allow to control geometry antialiasing
+         * (Default = 1.3)
+         */
+        optional<double>& gamma() { return _gamma; }
+        const optional<double>& gamma() const { return _gamma; }
+
     public:
         AGGLiteOptions( const TileSourceOptions& options =TileSourceOptions() )
             : FeatureTileSourceOptions( options ),
-              _optimizeLineSampling   ( true )
+              _optimizeLineSampling   ( true ),
+              _gamma                  ( 1.3 )
         {
             setDriver( "agglite" );
             fromConfig( _conf );
@@ -55,6 +63,7 @@ namespace osgEarth { namespace Drivers
         Config getConfig() const {
             Config conf = FeatureTileSourceOptions::getConfig();
             conf.updateIfSet("optimize_line_sampling", _optimizeLineSampling);
+            conf.updateIfSet("gamma", _gamma );
             return conf;
         }
 
@@ -67,9 +76,11 @@ namespace osgEarth { namespace Drivers
     private:
         void fromConfig( const Config& conf ) {
             conf.getIfSet( "optimize_line_sampling", _optimizeLineSampling );
+            conf.getIfSet( "gamma", _gamma );
         }
 
         optional<bool> _optimizeLineSampling;
+        optional<double> _gamma;
     };
 
 } } // namespace osgEarth::Drivers
diff --git a/src/osgEarthDrivers/agglite/AGGLiteRasterizerTileSource.cpp b/src/osgEarthDrivers/agglite/AGGLiteRasterizerTileSource.cpp
index 4bb5f91..4c56f6a 100644
--- a/src/osgEarthDrivers/agglite/AGGLiteRasterizerTileSource.cpp
+++ b/src/osgEarthDrivers/agglite/AGGLiteRasterizerTileSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -84,7 +84,7 @@ public:
     {
         // A processing context to use with the filters:
         FilterContext context;
-        context.profile() = getFeatureSource()->getFeatureProfile();
+        context.setProfile( getFeatureSource()->getFeatureProfile() );
 
         const LineSymbol*    masterLine = style.getSymbol<LineSymbol>();
         const PolygonSymbol* masterPoly = style.getSymbol<PolygonSymbol>();
@@ -218,7 +218,7 @@ public:
         agg::rasterizer ras;
 
         // Setup the rasterizer
-        ras.gamma(1.3);
+        ras.gamma(_options.gamma().get());
         ras.filling_rule(agg::fill_even_odd);
 
         // construct an extent for cropping the geometry to our tile.
diff --git a/src/osgEarthDrivers/arcgis/ArcGISOptions b/src/osgEarthDrivers/arcgis/ArcGISOptions
index 4f82e03..660c76c 100644
--- a/src/osgEarthDrivers/arcgis/ArcGISOptions
+++ b/src/osgEarthDrivers/arcgis/ArcGISOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -41,6 +41,10 @@ namespace osgEarth { namespace Drivers
         /** Override the image format read from the server metadata */
         optional<std::string>& format() { return _format; }
         const optional<std::string>& format() const { return _format; }
+        
+        /** Override the layers read from the server metadata */
+        optional<std::string>& layers() { return _layers; }
+        const optional<std::string>& layers() const { return _layers; }
 
     public:
         ArcGISOptions( const TileSourceOptions& opt =TileSourceOptions() ) : TileSourceOptions( opt )
@@ -58,6 +62,7 @@ namespace osgEarth { namespace Drivers
             conf.updateIfSet("url", _url );
             conf.updateIfSet("token", _token );
             conf.updateIfSet("format", _format );
+            conf.updateIfSet("layers", _layers );
             return conf;
         }
 
@@ -70,12 +75,14 @@ namespace osgEarth { namespace Drivers
             conf.getIfSet( "url", _url );
             conf.getIfSet( "token", _token);
             conf.getIfSet( "format", _format);
+            conf.getIfSet( "layers", _layers);
         }
 
     private:
         optional<URI>         _url;
         optional<std::string> _token;
         optional<std::string> _format;
+        optional<std::string> _layers;
     };
 
 } } // namespace osgEarth::Drivers
diff --git a/src/osgEarthDrivers/arcgis/Extent.h b/src/osgEarthDrivers/arcgis/Extent.h
index a162717..d96ec02 100644
--- a/src/osgEarthDrivers/arcgis/Extent.h
+++ b/src/osgEarthDrivers/arcgis/Extent.h
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/arcgis/MapService.cpp b/src/osgEarthDrivers/arcgis/MapService.cpp
index 78e22da..2d7bea0 100644
--- a/src/osgEarthDrivers/arcgis/MapService.cpp
+++ b/src/osgEarthDrivers/arcgis/MapService.cpp
@@ -7,6 +7,8 @@
 
 using namespace osgEarth;
 
+#define LC "[MapService] "
+
 
 MapServiceLayer::MapServiceLayer(int in_id, 
                                  const std::string& in_name) :
@@ -139,29 +141,80 @@ MapService::init( const URI& _uri, const osgDB::ReaderWriter::Options* options )
     Json::Reader reader;
 //    if ( !reader.parse( response.getPartStream(0), doc ) )
     if ( !reader.parse( r.getString(), doc ) )
+	{
         return setError( "Unable to parse metadata; invalid JSON" );
+	}
 
     // Read the profile. We are using "fullExtent"; perhaps an option to use "initialExtent" instead?
-    double xmin = doc["fullExtent"].get("xmin", 0).asDouble();
-    double ymin = doc["fullExtent"].get("ymin", 0).asDouble();
-    double xmax = doc["fullExtent"].get("xmax", 0).asDouble();
-    double ymax = doc["fullExtent"].get("ymax", 0).asDouble();
-    int srs = doc["fullExtent"].get("spatialReference", osgEarth::Json::Value::null).get("wkid", 0).asInt();
+	double xmin = 0.0;
+	double ymin = 0.0; 
+	double xmax = 0.0;
+	double ymax = 0.0;
+	int srs = 0;
+
+    Json::Value fullExtentValue = doc["fullExtent"];
+	Json::Value extentValue = doc["extent"];
+	std::string srsValue;
+	
+	// added a case for "extent" which can be removed if we want to fall back on initialExtent if fullExtent fails
+    if ( !fullExtentValue.empty() )
+	{
+		// if "fullExtent" exists .. use that
+		xmin = doc["fullExtent"].get("xmin", 0).asDouble();
+		ymin = doc["fullExtent"].get("ymin", 0).asDouble();
+		xmax = doc["fullExtent"].get("xmax", 0).asDouble();
+		ymax = doc["fullExtent"].get("ymax", 0).asDouble();
+		srs = doc["fullExtent"].get("spatialReference", osgEarth::Json::Value::null).get("wkid", 0).asInt();
+
+		srsValue = doc["fullExtent"].get("spatialReference", osgEarth::Json::Value::null).get("wkt", "null").asString();
+	
+		OE_DEBUG << LC << "fullExtent discovered: xmin: " << xmin << ", ymin: " << ymin << ", xmax: " << xmax << ", ymax: " << ymax << ", srs: " << srs << std::endl;
+	}
+	else if( !extentValue.empty() )
+	{
+		// else if "extent" exists .. use that
+		xmin = doc["extent"].get("xmin", 0).asDouble();
+		ymin = doc["extent"].get("ymin", 0).asDouble();
+		xmax = doc["extent"].get("xmax", 0).asDouble();
+		ymax = doc["extent"].get("ymax", 0).asDouble();
+		srs = doc["extent"].get("spatialReference", osgEarth::Json::Value::null).get("wkid", 0).asInt();
+
+		srsValue = doc["extent"].get("spatialReference", osgEarth::Json::Value::null).get("wkt", "null").asString();
+
+		OE_DEBUG << LC << "extent discovered: xmin: " << xmin << ", ymin: " << ymin << ", xmax: " << xmax << ", ymax: " << ymax << ", srs: " << srs << std::endl;
+	}
+	else
+	{
+		// else "initialExtent" must exist ..
+		xmin = doc["initialExtent"].get("xmin", 0).asDouble();
+		ymin = doc["initialExtent"].get("ymin", 0).asDouble();
+		xmax = doc["initialExtent"].get("xmax", 0).asDouble();
+		ymax = doc["initialExtent"].get("ymax", 0).asDouble();
+		srs = doc["initialExtent"].get("spatialReference", osgEarth::Json::Value::null).get("wkid", 0).asInt();
+
+		srsValue = doc["initialExtent"].get("spatialReference", osgEarth::Json::Value::null).get("wkt", "null").asString();
+
+		OE_DEBUG << LC << "initialExtent discovered: xmin: " << xmin << ", ymin: " << ymin << ", xmax: " << xmax << ", ymax: " << ymax << ", srs: " << srs << std::endl;
+	}
     
     //Assumes the SRS is going to be an EPSG code
     std::stringstream ss;
     ss << "epsg:" << srs;
     
-    if ( ! (xmax > xmin && ymax > ymin && srs != 0 ) )
+	// we can create a valid spatial reference from the WKT/proj4 string .. here just check if x/y/min/max values are set & correct
+    if ( ! (xmax > xmin && ymax > ymin /*&& srs != 0*/ ) )
     {
         return setError( "Map service does not define a full extent" );
     }
 
-    // Read the layers list
+    // Check that the layers list is not empty
     Json::Value j_layers = doc["layers"];
     if ( j_layers.empty() )
+	{
         return setError( "Map service contains no layers" );
+	}
 
+	// not required to initialise a layer .. in any case this code does nothing
     for( unsigned int i=0; i<j_layers.size(); i++ )
     {
         Json::Value layer = j_layers[i];
@@ -195,15 +248,21 @@ MapService::init( const URI& _uri, const osgDB::ReaderWriter::Options* options )
         tile_rows = j_tileinfo.get( "rows", 0 ).asInt();
         tile_cols = j_tileinfo.get( "cols", 0 ).asInt();
         if ( tile_rows <= 0 && tile_cols <= 0 )
+		{
             return setError( "Map service tile size not specified" );
+		}
 
         format = j_tileinfo.get( "format", "" ).asString();
         if ( format.empty() )
+		{
             return setError( "Map service tile schema does not specify an image format" );
+		}
 
         Json::Value j_levels = j_tileinfo["lods"];
         if ( j_levels.empty() )
+		{
             return setError( "Map service tile schema contains no LODs" );
+		}
         
         min_level = INT_MAX;
         max_level = 0;
@@ -211,9 +270,13 @@ MapService::init( const URI& _uri, const osgDB::ReaderWriter::Options* options )
         {
             int level = j_levels[i].get( "level", -1 ).asInt();
             if ( level >= 0 && level < min_level )
+			{
                 min_level = level;
+			}
             if ( level >= 0 && level > max_level )
+			{
                 max_level = level;
+			}
         }
 
         if (j_levels.size() > 0)
@@ -235,31 +298,58 @@ MapService::init( const URI& _uri, const osgDB::ReaderWriter::Options* options )
         }
     }
 
-	 std::string ssStr;
-	 ssStr = ss.str();
-
-    osg::ref_ptr< SpatialReference > spatialReference = SpatialReference::create( ssStr );
-    if (spatialReference->isGeographic())
-    {
-        //If we have a geographic SRS, just use the geodetic profile
-        profile = Registry::instance()->getGlobalGeodeticProfile();
-    }
-    else if (spatialReference->isMercator())
-    {
-        //If we have a mercator SRS, just use the mercator profile
-        profile = Registry::instance()->getGlobalMercatorProfile();
-    }
-    else
-    {
-        //It's not geodetic or mercator, so try to use the full extent
-        profile = Profile::create(
-            spatialReference.get(),
-            xmin, ymin, xmax, ymax,
-            num_tiles_wide,
-            num_tiles_high);
-    }    
-
-
+	std::string ssStr;
+	ssStr = ss.str();
+
+	osg::ref_ptr< SpatialReference > spatialReference;
+
+	 // if srs is in a non integer form .. find out what form it's in & create ..
+	if(srs == 0)
+	{
+		OE_DEBUG << LC << "srsString: " << srsValue << std::endl;
+
+		// create spatial reference from WKT string
+		spatialReference = SpatialReference::create( srsValue );
+	}
+	else
+	{
+		// create spatial reference from epsg string (WKID)
+		spatialReference = SpatialReference::create( ssStr );
+	}
+
+	// if spatial reference is valid .. create a profile
+	if( spatialReference.valid() )
+	{
+		// create profile from spatial reference
+		if ( spatialReference->isGeographic() )
+		{
+			// If we have a geographic SRS, just use the geodetic profile
+			profile = Registry::instance()->getGlobalGeodeticProfile();
+		}
+		else if ( spatialReference->isMercator() )
+		{
+			// If we have a mercator SRS, just use the mercator profile
+			profile = Registry::instance()->getGlobalMercatorProfile();
+		}
+		else
+		{
+			//It's not geodetic or mercator, so try to use the full extent
+			profile = Profile::create(
+				spatialReference.get(),
+				xmin, ymin, xmax, ymax,
+				num_tiles_wide,
+				num_tiles_high);
+		}
+	}
+	else
+	{
+		return setError( "Map service Spatial Reference INVALID" );
+	}
+
+	if( !profile.valid() )
+	{
+		return setError( "Map service could not create a valid profile" );
+	}
 
     // now we're good.
     tile_info = TileInfo( tile_rows, format, min_level, max_level, num_tiles_wide, num_tiles_high);
diff --git a/src/osgEarthDrivers/arcgis/MapService.h b/src/osgEarthDrivers/arcgis/MapService.h
index 002053d..a37d133 100644
--- a/src/osgEarthDrivers/arcgis/MapService.h
+++ b/src/osgEarthDrivers/arcgis/MapService.h
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/arcgis/ReaderWriterArcGIS.cpp b/src/osgEarthDrivers/arcgis/ReaderWriterArcGIS.cpp
index 19b4069..460c7fe 100644
--- a/src/osgEarthDrivers/arcgis/ReaderWriterArcGIS.cpp
+++ b/src/osgEarthDrivers/arcgis/ReaderWriterArcGIS.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -38,6 +38,8 @@
 using namespace osgEarth;
 using namespace osgEarth::Drivers;
 
+#define LC "[ReaderWriterArcGIS] "
+
 //#define PROPERTY_URL        "url"
 //#define PROPERTY_PROFILE    "profile"
 
@@ -51,21 +53,38 @@ public:
     {
         //TODO: allow single layers vs. "fused view"
         if ( _layer.empty() )
+		{
             _layer = "_alllayers"; // default to the AGS "fused view"
-
-        if ( _options.format().isSet() ) 
+		}
+		else
+		{
+			_layer = *_options.layers();
+		}
+
+        if ( _options.format().isSet() )
+		{
             _format = *_options.format();
+		}
         else
+		{
             _format = _map_service.getTileInfo().getFormat();
+		}
+
+        _format = osgEarth::toLower(_format);
 
-        std::transform( _format.begin(), _format.end(), _format.begin(), tolower );
         if ( _format.length() > 3 && _format.substr( 0, 3 ) == "png" )
+		{
             _format = "png";
+		}
 
         if ( _format == "mixed" )
+		{
             _format = "";
+		}
         if ( !_format.empty() )
+		{
             _dot_format = "." + _format;
+		}
     }
 
     // override
@@ -73,6 +92,10 @@ public:
     {
         // add the security token to the URL if necessary:
         URI url = _options.url().value();
+
+		OE_DEBUG << LC << "Initial URL: " << url.full() << std::endl;
+
+		// append token to url in query string is set
         if (_options.token().isSet())
         {
             std::string token = _options.token().value();
@@ -82,19 +105,40 @@ public:
                 url = url.append( sep + std::string("token=") + token );
             }
         }
+		else
+		{
+			OE_DEBUG << LC << "Token not set" << std::endl;
+		}
+
+		// append layers to url in query string is set .. format is show:1,2,3
+		if (_options.layers().isSet())
+        {
+			std::string layers = _options.layers().value();
+			OE_DEBUG << LC << "_Layers: " << layers << std::endl;
+			if (!layers.empty())
+			{
+				std::string sep = url.full().find( "?" ) == std::string::npos ? "?" : "&";
+				url = url.append( sep + std::string("layers=show:") + layers );
+			}
+		}
+		else
+		{
+			OE_DEBUG << LC << "Layer options not set" << std::endl;
+		}
+
+		OE_DEBUG << LC << "_map_service URL: " << url.full() << std::endl;
 
         // read map service metadata from the server
         if ( !_map_service.init(url, dbOptions) )
         {
+			OE_INFO << LC << "_map_service.init failed: " << _map_service.getError() << std::endl;
+
             return Status::Error( Stringify()
                 << "[osgearth] [ArcGIS] map service initialization failed: "
                 << _map_service.getError() );
         }
 
-        // create a local i/o options with caching disabled (since the TerrainLayer
-        // will implement the caching for us)
-        _dbOptions = Registry::instance()->cloneOrCreateOptions( dbOptions );
-        CachePolicy::NO_CACHE.apply( _dbOptions.get() );
+        _dbOptions = Registry::instance()->cloneOrCreateOptions( dbOptions );        
 
         // establish a profile if we don't already have one:
         if ( !getProfile() )
@@ -170,6 +214,19 @@ public:
             }
         }
 
+		//Add the layers if necessary
+        if (_options.layers().isSet())
+        {
+            std::string layers = _options.layers().value();
+            if (!layers.empty())
+            {
+                std::string str;
+                str = buf.str();
+                std::string sep = str.find( "?" ) == std::string::npos ? "?" : "&";
+				buf << sep << "layers=show:" << layers;
+            }
+        }
+
         std::string bufStr;
         bufStr = buf.str();
         return URI(bufStr).getImage( _dbOptions.get(), progress );
diff --git a/src/osgEarthDrivers/arcgis_map_cache/ReaderWriterArcGISMapCache.cpp b/src/osgEarthDrivers/arcgis_map_cache/ReaderWriterArcGISMapCache.cpp
index d358cf8..c3aa81f 100644
--- a/src/osgEarthDrivers/arcgis_map_cache/ReaderWriterArcGISMapCache.cpp
+++ b/src/osgEarthDrivers/arcgis_map_cache/ReaderWriterArcGISMapCache.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -69,8 +69,7 @@ public:
 
     Status initialize( const osgDB::Options* dbOptions )
     {
-        _dbOptions = Registry::instance()->cloneOrCreateOptions( dbOptions );
-        CachePolicy::NO_CACHE.apply( _dbOptions.get() );
+        _dbOptions = Registry::instance()->cloneOrCreateOptions( dbOptions );        
 
         //Set the profile to global geodetic.
         setProfile(osgEarth::Registry::instance()->getGlobalGeodeticProfile());
diff --git a/src/osgEarthDrivers/bing/BingOptions b/src/osgEarthDrivers/bing/BingOptions
index b353268..93cdea2 100644
--- a/src/osgEarthDrivers/bing/BingOptions
+++ b/src/osgEarthDrivers/bing/BingOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/bing/BingTileSource.cpp b/src/osgEarthDrivers/bing/BingTileSource.cpp
index b74e387..3ef5340 100644
--- a/src/osgEarthDrivers/bing/BingTileSource.cpp
+++ b/src/osgEarthDrivers/bing/BingTileSource.cpp
@@ -82,7 +82,7 @@ public:
      */
     Status initialize(const osgDB::Options* dbOptions)
     {
-        // Always apply the NO CACHE policy.
+        // Always apply the NO CACHE policy.  Bing doesn't allow caching of their data.
         _dbOptions = Registry::instance()->cloneOrCreateOptions(dbOptions);
         CachePolicy::NO_CACHE.apply( _dbOptions.get() );
 
@@ -123,117 +123,115 @@ public:
      */
     osg::Image* createImage( const TileKey& key, ProgressCallback* progress )
     {
+        osg::Image* image = 0L;
+
         if (_debugDirect)
         {
-            //osg::Image* image = new osg::Image;
-            //image->allocateImage(256,256,1, GL_RGB, GL_UNSIGNED_BYTE);
-            //return image;
-
-            //return osgDB::readImageFile( getDirectURI(key) );
-            return URI(getDirectURI(key)).getImage(_dbOptions.get(), progress);
+            image = URI(getDirectURI(key)).getImage(_dbOptions.get(), progress);
         }
 
-        // center point of the tile (will be in spherical mercator)
-        double x, y;
-        key.getExtent().getCentroid(x, y);
-
-        // transform it to lat/long:
-        GeoPoint geo;
-
-        GeoPoint( getProfile()->getSRS(), x, y ).transform(
-            getProfile()->getSRS()->getGeographicSRS(),
-            geo );
-
-        // contact the REST API. Docs are here:
-        // http://msdn.microsoft.com/en-us/library/ff701716.aspx
-
-        // construct the request URI:
-        std::string request = Stringify()
-            << std::setprecision(12)
-            << _options.imageryMetadataAPI().get()     // base REST API
-            << "/"    << _options.imagerySet().get()   // imagery set to use
-            << "/"    << geo.y() << "," << geo.x()     // center point in lat/long
-            << "?zl=" << key.getLOD() + 1              // zoom level
-            << "&o=json"                               // response format
-            << "&key=" << _options.key().get();        // API key
-
-        // check the URI cache.
-        URI                  location;
-        TileURICache::Record rec;
-
-        if ( _tileURICache.get(request, rec) )
-        {
-            location = URI(rec.value());
-            //CacheStats stats = _tileURICache.getStats();
-            //OE_INFO << "Ratio = " << (stats._hitRatio*100) << "%" << std::endl;
-        }
         else
         {
-            unsigned c = ++_apiCount;
-            if ( c % 25 == 0 )
-                OE_INFO << LC << "API calls = " << c << std::endl;
+            // center point of the tile (will be in spherical mercator)
+            double x, y;
+            key.getExtent().getCentroid(x, y);
+
+            // transform it to lat/long:
+            GeoPoint geo;
+
+            GeoPoint( getProfile()->getSRS(), x, y ).transform(
+                getProfile()->getSRS()->getGeographicSRS(),
+                geo );
+
+            // contact the REST API. Docs are here:
+            // http://msdn.microsoft.com/en-us/library/ff701716.aspx
+
+            // construct the request URI:
+            std::string request = Stringify()
+                << std::setprecision(12)
+                << _options.imageryMetadataAPI().get()     // base REST API
+                << "/"    << _options.imagerySet().get()   // imagery set to use
+                << "/"    << geo.y() << "," << geo.x()     // center point in lat/long
+                << "?zl=" << key.getLOD() + 1              // zoom level
+                << "&o=json"                               // response format
+                << "&key=" << _options.key().get();        // API key
+
+            // check the URI cache.
+            URI                  location;
+            TileURICache::Record rec;
+
+            if ( _tileURICache.get(request, rec) )
+            {
+                location = URI(rec.value());
+                //CacheStats stats = _tileURICache.getStats();
+                //OE_INFO << "Ratio = " << (stats._hitRatio*100) << "%" << std::endl;
+            }
+            else
+            {
+                unsigned c = ++_apiCount;
+                if ( c % 25 == 0 )
+                    OE_INFO << LC << "API calls = " << c << std::endl;
             
-            // fetch it:
-            ReadResult metadataResult = URI(request).readString(_dbOptions, progress);
+                // fetch it:
+                ReadResult metadataResult = URI(request).readString(_dbOptions, progress);
 
-            if ( metadataResult.failed() )
-            {
-                // check for a REST error:
-                if ( metadataResult.code() == ReadResult::RESULT_SERVER_ERROR )
+                if ( metadataResult.failed() )
                 {
-                    OE_WARN << LC << "REST API request error!" << std::endl;
-
-                    Config metadata;
-                    std::string content = metadataResult.getString();
-                    metadata.fromJSON( content );
-                    ConfigSet errors = metadata.child("errorDetails").children();
-                    for(ConfigSet::const_iterator i = errors.begin(); i != errors.end(); ++i )
+                    // check for a REST error:
+                    if ( metadataResult.code() == ReadResult::RESULT_SERVER_ERROR )
+                    {
+                        OE_WARN << LC << "REST API request error!" << std::endl;
+
+                        Config metadata;
+                        std::string content = metadataResult.getString();
+                        metadata.fromJSON( content );
+                        ConfigSet errors = metadata.child("errorDetails").children();
+                        for(ConfigSet::const_iterator i = errors.begin(); i != errors.end(); ++i )
+                        {
+                            OE_WARN << LC << "REST API: " << i->value() << std::endl;
+                        }
+                        return 0L;
+                    }
+                    else
                     {
-                        OE_WARN << LC << "REST API: " << i->value() << std::endl;
+                        OE_WARN << LC << "Request error: " << metadataResult.getResultCodeString() << std::endl;
                     }
                     return 0L;
                 }
-                else
+
+                // decode it:
+                Config metadata;
+                if ( !metadata.fromJSON(metadataResult.getString()) )
                 {
-                    OE_WARN << LC << "Request error: " << metadataResult.getResultCodeString() << std::endl;
+                    OE_WARN << LC << "Error decoding REST API response" << std::endl;
+                    return 0L;
                 }
-                return 0L;
-            }
 
-            // decode it:
-            Config metadata;
-            if ( !metadata.fromJSON(metadataResult.getString()) )
-            {
-                OE_WARN << LC << "Error decoding REST API response" << std::endl;
-                return 0L;
-            }
+                // check the vintage field. If it's empty, that means we got a "no data" tile.
+                Config* vintageEnd = metadata.find("vintageEnd");
+                if ( !vintageEnd || vintageEnd->value().empty() )
+                {
+                    OE_DEBUG << LC << "NO data image encountered." << std::endl;
+                    return 0L;
+                }
 
-            // check the vintage field. If it's empty, that means we got a "no data" tile.
-            Config* vintageEnd = metadata.find("vintageEnd");
-            if ( !vintageEnd || vintageEnd->value().empty() )
-            {
-                OE_DEBUG << LC << "NO data image encountered." << std::endl;
-                return 0L;
-            }
+                // find the tile URI:
+                Config* locationConf= metadata.find("imageUrl");
+                if ( !locationConf )
+                {
+                    OE_WARN << LC << "REST API JSON parsing error (imageUrl not found)" << std::endl;
+                    return 0L;
+                }
 
-            // find the tile URI:
-            Config* locationConf= metadata.find("imageUrl");
-            if ( !locationConf )
-            {
-                OE_WARN << LC << "REST API JSON parsing error (imageUrl not found)" << std::endl;
-                return 0L;
+                location = URI( locationConf->value() );
+                _tileURICache.insert( request, location.full() );
             }
 
-            location = URI( locationConf->value() );
-            _tileURICache.insert( request, location.full() );
+            // request the actual tile
+            //OE_INFO << "key = " << key.str() << ", URL = " << location->value() << std::endl;
+            image = osgDB::readImageFile( location.full() );
         }
 
-        // request the actual tile
-        //OE_INFO << "key = " << key.str() << ", URL = " << location->value() << std::endl;
-
-        //osg::Image* image = location.getImage(_dbOptions.get(), progress);
-        osg::Image* image = osgDB::readImageFile( location.full() );
-
         if ( image &&  _geom.valid() )
         {
             GeometryRasterizer rasterizer( image->s(), image->t() );
diff --git a/src/osgEarthDrivers/cache_filesystem/FileSystemCache b/src/osgEarthDrivers/cache_filesystem/FileSystemCache
index 9f389a9..d87d873 100644
--- a/src/osgEarthDrivers/cache_filesystem/FileSystemCache
+++ b/src/osgEarthDrivers/cache_filesystem/FileSystemCache
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/cache_filesystem/FileSystemCache.cpp b/src/osgEarthDrivers/cache_filesystem/FileSystemCache.cpp
index c40b298..0ad470e 100644
--- a/src/osgEarthDrivers/cache_filesystem/FileSystemCache.cpp
+++ b/src/osgEarthDrivers/cache_filesystem/FileSystemCache.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -23,6 +23,7 @@
 #include <osgEarth/XmlUtils>
 #include <osgEarth/URI>
 #include <osgEarth/FileUtils>
+#include <osgEarth/StringUtils>
 #include <osgEarth/Registry>
 #include <osgDB/FileUtils>
 #include <osgDB/FileNameUtils>
@@ -80,13 +81,13 @@ namespace
 
     public: // CacheBin interface
 
-        ReadResult readObject(const std::string& key, TimeStamp minTime);
+        ReadResult readObject(const std::string& key);
 
-        ReadResult readImage(const std::string& key, TimeStamp minTime);
+        ReadResult readImage(const std::string& key);
 
-        ReadResult readNode(const std::string& key, TimeStamp minTime);
+        ReadResult readNode(const std::string& key);
 
-        ReadResult readString(const std::string& key, TimeStamp minTime);
+        ReadResult readString(const std::string& key);
 
         bool write(const std::string& key, const osg::Object* object, const Config& meta);
 
@@ -94,9 +95,9 @@ namespace
 
         bool touch(const std::string& key);
 
-        RecordStatus getRecordStatus(const std::string& key, TimeStamp minTime);
+        RecordStatus getRecordStatus(const std::string& key);
 
-        bool purge();
+        bool clear();
 
         Config readMetadata();
 
@@ -105,9 +106,11 @@ namespace
     protected:
         bool purgeDirectory( const std::string& dir );
 
-        bool binValidForReading();
+        bool binValidForReading(bool silent =true);
+
+        bool binValidForWriting(bool silent =false);
 
-        bool binValidForWriting();
+        std::string getValidKey(const std::string&);
 
         bool                              _ok;
         bool                              _binPathExists;
@@ -159,6 +162,15 @@ namespace
     Cache( options )
     {
         FileSystemCacheOptions fsco( options );
+
+        // read the root path from ENV is necessary:
+        if ( !fsco.rootPath().isSet())
+        {           
+            const char* cachePath = ::getenv(OSGEARTH_ENV_CACHE_PATH);
+            if ( cachePath )
+                fsco.rootPath() = cachePath;
+        }
+
         _rootPath = URI( *fsco.rootPath(), options.referrer() ).full();
         init();
     }
@@ -192,8 +204,17 @@ namespace
 
     //------------------------------------------------------------------------
 
+    std::string
+    FileSystemCacheBin::getValidKey(const std::string& key)
+    {
+        if ( getHashKeys() )
+            return Stringify() << std::hex << osgEarth::hashString(key);
+        else
+            return osgEarth::toLegalFileName(key);
+    }
+
     bool
-    FileSystemCacheBin::binValidForReading()
+    FileSystemCacheBin::binValidForReading(bool silent)
     {
         if ( !_binPathExists )
         {
@@ -206,7 +227,10 @@ namespace
             else if ( _ok )
             {
                 // one-time error.
-                OE_WARN << LC << "Failed to locate cache bin at [" << _binPath << "]" << std::endl;
+                if ( !silent )
+                {
+                    OE_WARN << LC << "Failed to locate cache bin at [" << _binPath << "]" << std::endl;
+                }
                 _ok = false;
             }
         }
@@ -215,11 +239,11 @@ namespace
     }
 
     bool
-    FileSystemCacheBin::binValidForWriting()
+    FileSystemCacheBin::binValidForWriting(bool silent)
     {
         if ( !_binPathExists )
         {
-            osgDB::makeDirectoryForFile( _metaPath );
+            osgEarth::makeDirectoryForFile( _metaPath );
 
             if ( osgDB::fileExists(_binPath) )
             {
@@ -230,7 +254,10 @@ namespace
             else
             {
                 // one-time error.
-                OE_WARN << LC << "FAILED to find or create cache bin at [" << _metaPath << "]" << std::endl;
+                if ( !silent )
+                {
+                    OE_WARN << LC << "FAILED to find or create cache bin at [" << _metaPath << "]" << std::endl;
+                }
                 _ok = false;
             }
         }
@@ -250,25 +277,23 @@ namespace
 #ifdef OSGEARTH_HAVE_ZLIB
         _rwOptions = Registry::instance()->cloneOrCreateOptions();
         _rwOptions->setOptionString( "Compressor=zlib" );
-#endif
-        CachePolicy::NO_CACHE.apply(_rwOptions.get());
+#endif        
     }
 
     ReadResult
-    FileSystemCacheBin::readImage(const std::string& key, TimeStamp minTime)
+    FileSystemCacheBin::readImage(const std::string& key)
     {
         if ( !binValidForReading() ) 
             return ReadResult(ReadResult::RESULT_NOT_FOUND);
 
         // mangle "key" into a legal path name
-        URI fileURI( toLegalFileName(key), _metaPath );
+        URI fileURI( getValidKey(key), _metaPath );
         std::string path = fileURI.full() + ".osgb";
 
         if ( !osgDB::fileExists(path) )
             return ReadResult( ReadResult::RESULT_NOT_FOUND );
 
-        if ( osgEarth::getLastModifiedTime(path) < minTime )
-            return ReadResult( ReadResult::RESULT_EXPIRED );
+        osgEarth::TimeStamp timeStamp = osgEarth::getLastModifiedTime(path);        
 
         osgDB::ReaderWriter::ReadResult r;
         {
@@ -283,25 +308,26 @@ namespace
             if ( osgDB::fileExists(metafile) )
                 readMeta( metafile, meta );
 
-            return ReadResult( r.getImage(), meta );
+            ReadResult rr( r.getImage(), meta );
+            rr.setLastModifiedTime(timeStamp);
+            return rr;            
         }
     }
 
     ReadResult
-    FileSystemCacheBin::readObject(const std::string& key, TimeStamp minTime)
+    FileSystemCacheBin::readObject(const std::string& key)
     {
         if ( !binValidForReading() ) 
             return ReadResult(ReadResult::RESULT_NOT_FOUND);
 
         // mangle "key" into a legal path name
-        URI fileURI( toLegalFileName(key), _metaPath );
+        URI fileURI( getValidKey(key), _metaPath );
         std::string path = fileURI.full() + ".osgb";
 
         if ( !osgDB::fileExists(path) )
             return ReadResult( ReadResult::RESULT_NOT_FOUND );
 
-        if ( osgEarth::getLastModifiedTime(path) < minTime )
-            return ReadResult( ReadResult::RESULT_EXPIRED );
+        osgEarth::TimeStamp timeStamp = osgEarth::getLastModifiedTime(path);
 
         osgDB::ReaderWriter::ReadResult r;
         {
@@ -316,25 +342,26 @@ namespace
             if ( osgDB::fileExists(metafile) )
                 readMeta( metafile, meta );
 
-             return ReadResult( r.getObject(), meta );
+            ReadResult rr( r.getObject(), meta );
+            rr.setLastModifiedTime(timeStamp);
+            return rr;            
         }
     }
 
     ReadResult
-    FileSystemCacheBin::readNode(const std::string& key, TimeStamp minTime)
+    FileSystemCacheBin::readNode(const std::string& key)
     {
         if ( !binValidForReading() ) 
             return ReadResult(ReadResult::RESULT_NOT_FOUND);
 
         // mangle "key" into a legal path name
-        URI fileURI( toLegalFileName(key), _metaPath );
+        URI fileURI( getValidKey(key), _metaPath );
         std::string path = fileURI.full() + ".osgb";
 
         if ( !osgDB::fileExists(path) )
             return ReadResult( ReadResult::RESULT_NOT_FOUND );
 
-        if ( osgEarth::getLastModifiedTime(path) < minTime )
-            return ReadResult( ReadResult::RESULT_EXPIRED );
+        osgEarth::TimeStamp timeStamp = osgEarth::getLastModifiedTime(path);
 
         osgDB::ReaderWriter::ReadResult r;
         {
@@ -349,14 +376,16 @@ namespace
             if ( osgDB::fileExists(metafile) )
                 readMeta( metafile, meta );
 
-            return ReadResult( r.getNode(), meta );
+            ReadResult rr( r.getNode(), meta );
+            rr.setLastModifiedTime(timeStamp);
+            return rr;            
         }
     }
 
     ReadResult
-    FileSystemCacheBin::readString(const std::string& key, TimeStamp minTime)
+    FileSystemCacheBin::readString(const std::string& key)
     {
-        ReadResult r = readObject(key, minTime);
+        ReadResult r = readObject(key);
         if ( r.succeeded() )
         {
             if ( r.get<StringObject>() )
@@ -377,7 +406,9 @@ namespace
             return false;
 
         // convert the key into a legal filename:
-        URI fileURI( toLegalFileName(key), _metaPath );
+        URI fileURI( getValidKey(key), _metaPath );
+        
+        osgDB::ReaderWriter::WriteResult r;
 
         bool objWriteOK = false;
         {
@@ -386,10 +417,8 @@ namespace
 
             // make a home for it..
             if ( !osgDB::fileExists( osgDB::getFilePath(fileURI.full()) ) )
-                osgDB::makeDirectoryForFile( fileURI.full() );
+                osgEarth::makeDirectoryForFile( fileURI.full() );
 
-            // write it.  
-            osgDB::ReaderWriter::WriteResult r;
 
             if ( dynamic_cast<const osg::Image*>(object) )
             {
@@ -424,33 +453,32 @@ namespace
         }
         else
         {
-            OE_WARN << LC << "FAILED to write \"" << key << "\" to cache bin " << getID() << std::endl;
+            OE_WARN << LC << "FAILED to write \"" << key << "\" to cache bin " << getID()
+                << "; msg = \"" << r.message() << "\"" << std::endl;
         }
 
         return objWriteOK;
     }
 
     CacheBin::RecordStatus
-    FileSystemCacheBin::getRecordStatus(const std::string& key, TimeStamp minTime)
+    FileSystemCacheBin::getRecordStatus(const std::string& key)
     {
         if ( !binValidForReading() ) 
             return STATUS_NOT_FOUND;
 
-        URI fileURI( toLegalFileName(key), _metaPath );
+        URI fileURI( getValidKey(key), _metaPath );
         std::string path( fileURI.full() + ".osgb" );
         if ( !osgDB::fileExists(path) )
             return STATUS_NOT_FOUND;
 
-        struct stat s;
-        ::stat( path.c_str(), &s );
-        return s.st_mtime >= minTime ? STATUS_OK : STATUS_EXPIRED;
+        return STATUS_OK;
     }
 
     bool
     FileSystemCacheBin::remove(const std::string& key)
     {
         if ( !binValidForReading() ) return false;
-        URI fileURI( toLegalFileName(key), _metaPath );
+        URI fileURI( getValidKey(key), _metaPath );
         std::string path( fileURI.full() + ".osgb" );
         return ::unlink( path.c_str() ) == 0;
     }
@@ -459,7 +487,7 @@ namespace
     FileSystemCacheBin::touch(const std::string& key)
     {
         if ( !binValidForReading() ) return false;
-        URI fileURI( toLegalFileName(key), _metaPath );
+        URI fileURI( getValidKey(key), _metaPath );
         std::string path( fileURI.full() + ".osgb" );
         return osgEarth::touchFile( path );
     }
@@ -506,14 +534,14 @@ namespace
     }
 
     bool
-    FileSystemCacheBin::purge()
+    FileSystemCacheBin::clear()
     {
-        if ( !binValidForReading() ) return false;
-        {
-            ScopedWriteLock exclusiveLock( _rwmutex );
-            std::string binDir = osgDB::getFilePath( _metaPath );
-            return purgeDirectory( binDir );
-        }
+        if ( !binValidForReading() )
+            return false;
+
+        ScopedWriteLock exclusiveLock( _rwmutex );
+        std::string binDir = osgDB::getFilePath( _metaPath );
+        return purgeDirectory( binDir );
     }
 
     Config
diff --git a/src/osgEarthDrivers/cache_leveldb/CMakeLists.txt b/src/osgEarthDrivers/cache_leveldb/CMakeLists.txt
new file mode 100644
index 0000000..4ee4733
--- /dev/null
+++ b/src/osgEarthDrivers/cache_leveldb/CMakeLists.txt
@@ -0,0 +1,25 @@
+
+INCLUDE_DIRECTORIES( ${LEVELDB_INCLUDE_DIR} )
+
+SET(TARGET_H
+    LevelDBCacheOptions
+    LevelDBCache
+    LevelDBCacheBin
+	Tracker
+)
+SET(TARGET_SRC 
+    LevelDBCache.cpp
+    LevelDBCacheBin.cpp
+    LevelDBCacheDriver.cpp
+)
+
+SET(TARGET_LIBRARIES_VARS LEVELDB_LIBRARY)
+
+SETUP_PLUGIN(osgearth_cache_leveldb)
+
+
+# to install public driver includes:
+SET(LIB_NAME cache_leveldb)
+SET(LIB_PUBLIC_HEADERS LevelDBCacheOptions)
+INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
+
diff --git a/src/osgEarthDrivers/cache_leveldb/LevelDBCache b/src/osgEarthDrivers/cache_leveldb/LevelDBCache
new file mode 100644
index 0000000..09cf10c
--- /dev/null
+++ b/src/osgEarthDrivers/cache_leveldb/LevelDBCache
@@ -0,0 +1,77 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_DRIVER_CACHE_LEVELDB
+#define OSGEARTH_DRIVER_CACHE_LEVELDB 1
+
+#include "LevelDBCacheOptions"
+#include "Tracker"
+#include <osgEarth/Common>
+#include <osgEarth/Cache>
+#include <leveldb/db.h>
+
+namespace osgEarth { namespace Drivers { namespace LevelDBCache
+{    
+    /** 
+     * Cache that stores data in a LEVELDB database in the local filesystem.
+     */
+    class LevelDBCacheImpl : public osgEarth::Cache
+    {
+    public:
+        META_Object( osgEarth, LevelDBCacheImpl );
+        virtual ~LevelDBCacheImpl();
+        LevelDBCacheImpl() { } // unused
+        LevelDBCacheImpl( const LevelDBCacheImpl& rhs, const osg::CopyOp& op ) { } // unused
+
+        /**
+         * Constructs a new leveldb cache object.
+         * @param options Options structure that comes from a serialized description of 
+         *        the object (see LevelDBCacheOptions)
+         */
+        LevelDBCacheImpl( const osgEarth::CacheOptions& options );
+
+    public: // Cache interface
+
+        osgEarth::CacheBin* addBin( const std::string& binID );
+
+        osgEarth::CacheBin* getOrCreateDefaultBin();
+
+        off_t getApproximateSize() const;
+
+        // Compact the cache, reclaiming space fragmented by removing records
+        bool compact();
+
+        // Clear all records from the cache
+        bool clear();
+
+    protected:
+
+        void init();
+        void open();
+
+        std::string  _rootPath;
+        bool         _active;
+        leveldb::DB* _db;
+        osg::ref_ptr<Tracker> _tracker;
+        LevelDBCacheOptions _options;
+    };
+
+
+} } } // namespace osgEarth::Drivers::LevelDBCache
+
+#endif // OSGEARTH_DRIVER_CACHE_LEVELDB
diff --git a/src/osgEarthDrivers/cache_leveldb/LevelDBCache.cpp b/src/osgEarthDrivers/cache_leveldb/LevelDBCache.cpp
new file mode 100644
index 0000000..42e99af
--- /dev/null
+++ b/src/osgEarthDrivers/cache_leveldb/LevelDBCache.cpp
@@ -0,0 +1,227 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include "LevelDBCache"
+#include "LevelDBCacheBin"
+#include <osgEarth/URI>
+#include <osgEarth/ThreadingUtils>
+#include <osgDB/Registry>
+#include <osgDB/ReaderWriter>
+#include <osgDB/FileUtils>
+#include <osgDB/FileNameUtils>
+
+#include <sys/stat.h>
+#ifndef _WIN32
+#   include <unistd.h>
+#endif
+
+#define LC "[LevelDBCache] "
+
+#define OSGEARTH_ENV_CACHE_MAX_SIZE_MB "OSGEARTH_CACHE_MAX_SIZE_MB"
+
+#define LEVELDB_CACHE_VERSION 1
+
+using namespace osgEarth;
+using namespace osgEarth::Drivers::LevelDBCache;
+
+
+LevelDBCacheImpl::LevelDBCacheImpl( const CacheOptions& options ) :
+osgEarth::Cache( options ),
+_options       ( options ),
+_active        ( true )
+{
+    if ( _options.rootPath().isSet() )
+    {
+        _rootPath = URI( *_options.rootPath(), options.referrer() ).full();
+    }
+    else
+    {
+        // read the root path from ENV is necessary:
+        const char* cachePath = ::getenv(OSGEARTH_ENV_CACHE_PATH);
+        if ( cachePath )
+        {
+            _rootPath = cachePath;           
+            OE_INFO << LC << "Cache location set from environment: \"" 
+                << cachePath << "\"" << std::endl;
+        }
+    }
+
+    const char* maxsize = ::getenv(OSGEARTH_ENV_CACHE_MAX_SIZE_MB);
+    if ( maxsize )
+    {
+        unsigned mb = as<unsigned>(std::string(maxsize), 0u);
+        if ( mb > 0 )
+        {
+            _options.maxSizeMB() = mb;
+
+            OE_INFO << LC << "Set max cache size from environment: "
+                << (_options.maxSizeMB().value()) << " MB"
+                << std::endl;
+        }
+        else
+        {
+            OE_WARN << LC 
+                << "Env var \"" OSGEARTH_ENV_CACHE_MAX_SIZE_MB "\" set to an invalid value"
+                << std::endl;
+        }
+    }
+
+    _tracker = new Tracker(_options, _rootPath);
+    
+    if ( !_rootPath.empty() )
+    {
+        init();
+    }
+    else
+    {
+        _active = false;
+        OE_WARN << LC << "Illegal: no root path set for cache!" << std::endl;
+    }
+}
+
+LevelDBCacheImpl::~LevelDBCacheImpl()
+{
+    if ( _db )
+    {
+        delete _db;
+        _db = 0L;
+    }
+}
+
+void
+LevelDBCacheImpl::init()
+{
+    // ensure there's a folder for the cache.
+    if ( !osgDB::fileExists(_rootPath) )
+    {
+        if (osgDB::makeDirectory(_rootPath) == false)
+        {
+            OE_WARN << LC << "Oh no, failed to create root cache folder \"" << _rootPath << "\""
+                << std::endl;
+            return;
+        }
+    }
+
+    open();
+
+    // Do an initial size check.
+    if ( _db )
+    {
+        _tracker->calcSize();
+    }
+
+    if ( _active )
+    {
+        OE_INFO << LC << "Opened DB at " << _rootPath << std::endl;
+    }
+}
+
+void
+LevelDBCacheImpl::open()
+{
+    leveldb::Options options;
+    options.create_if_missing = true;
+    options.block_size        = _options.blockSize().value();
+
+    leveldb::Status status;
+        
+    status = leveldb::DB::Open(options, _rootPath, &_db);
+    if ( status.ok() )
+        return;
+
+    OE_WARN << LC << "Database problem...attempting to repair..." << std::endl;
+    status = leveldb::RepairDB(_rootPath, options);
+    if ( status.ok() )
+    {
+        status = leveldb::DB::Open(options, _rootPath, &_db);
+        if ( status.ok() )
+        {
+            OE_WARN << LC << "...repair complete!" << std::endl;
+            return;
+        }
+    }
+
+    OE_WARN << LC << "Failed to open or create cache bin at " << _rootPath << std::endl;
+    if ( _db )
+    {
+        delete _db;
+        _db = 0L;
+        _active = false;
+    }
+}
+
+CacheBin*
+LevelDBCacheImpl::addBin( const std::string& name )
+{
+    return _db ?
+        _bins.getOrCreate(name, new LevelDBCacheBin(name, _db, _tracker.get())) :
+        0L;
+}
+
+CacheBin*
+LevelDBCacheImpl::getOrCreateDefaultBin()
+{    
+    if ( !_db )
+        return 0L;
+
+    static Threading::Mutex s_defaultBinMutex;
+    if ( !_defaultBin.valid() )
+    {
+        Threading::ScopedMutexLock lock( s_defaultBinMutex );
+        if ( !_defaultBin.valid() ) // double-check
+        {
+            _defaultBin = new LevelDBCacheBin("_default", _db, _tracker.get());
+        }
+    }
+    return _defaultBin.get();
+}
+
+off_t
+LevelDBCacheImpl::getApproximateSize() const
+{
+    return _tracker->calcSize();
+}
+
+bool
+LevelDBCacheImpl::compact()
+{
+    if ( !_db )
+        return false;
+
+    _db->CompactRange(0L, 0L);
+
+    return true;
+}
+
+bool
+LevelDBCacheImpl::clear()
+{
+    if ( !_db )
+        return false;
+
+    // No WriteBatch because it doesn't seem to allow compaction to occur
+    // -- need to figure out why someday.
+
+    leveldb::Iterator* it = _db->NewIterator(leveldb::ReadOptions());
+    for(it->SeekToFirst(); it->Valid(); it->Next())
+    {
+        _db->Delete(leveldb::WriteOptions(), it->key());
+    }
+
+    return true;
+}
diff --git a/src/osgEarthDrivers/cache_leveldb/LevelDBCacheBin b/src/osgEarthDrivers/cache_leveldb/LevelDBCacheBin
new file mode 100644
index 0000000..2269b92
--- /dev/null
+++ b/src/osgEarthDrivers/cache_leveldb/LevelDBCacheBin
@@ -0,0 +1,138 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_DRIVER_CACHE_LEVELDB_BIN
+#define OSGEARTH_DRIVER_CACHE_LEVELDB_BIN 1
+
+#include "Tracker"
+#include <osgEarth/Common>
+#include <osgEarth/Cache>
+#include <string>
+#include <leveldb/db.h>
+
+#define LEVELDB_CACHE_VERSION 1
+
+namespace osgEarth { namespace Drivers { namespace LevelDBCache
+{
+    using namespace osgEarth;
+
+    /** 
+     * Cache bin implementation for a LevelDBCache.
+    */
+    class LevelDBCacheBin : public osgEarth::CacheBin
+    {
+    public:
+        LevelDBCacheBin(const std::string& name, leveldb::DB* db, Tracker* tracker);
+
+        virtual ~LevelDBCacheBin();
+
+    public: // CacheBin interface
+
+        ReadResult readObject(const std::string& key);
+
+        ReadResult readImage(const std::string& key);
+
+        ReadResult readNode(const std::string& key);
+
+        ReadResult readString(const std::string& key);
+
+        bool write(const std::string& key, const osg::Object* object, const Config& meta);
+
+        bool remove(const std::string& key);
+
+        bool touch(const std::string& key);
+
+        RecordStatus getRecordStatus(const std::string& key);
+
+        bool clear();
+
+        bool compact();
+        
+        unsigned getStorageSize();
+
+        Config readMetadata();
+
+        bool writeMetadata( const Config& meta );
+
+        bool purgeOldest(unsigned maxnum);
+        
+    protected:
+
+        bool binValidForReading(bool silent =true);
+
+        bool binValidForWriting(bool silent =false);
+
+        bool                              _ok;
+        bool                              _binPathExists;
+        std::string                       _metaPath;       // full path to the bin's metadata file
+        std::string                       _binPath;        // full path to the bin's root folder
+        osg::ref_ptr<osgDB::ReaderWriter> _rw;
+        osg::ref_ptr<osgDB::Options>      _rwOptions;
+        Threading::Mutex                  _rwMutex;
+        leveldb::DB*                      _db;
+        osg::ref_ptr<Tracker>             _tracker;
+        bool                              _debug;
+        
+        // adapter base for all the osg read functions...
+        struct Reader {
+            osgDB::ReaderWriter* _rw;
+            osgDB::Options*      _op;
+            Reader(osgDB::ReaderWriter* rw, osgDB::Options* op) : _rw(rw), _op(op) { }
+            virtual osgDB::ReaderWriter::ReadResult read(std::istream& in) const = 0;
+        };
+
+        struct ImageReader : public Reader {
+            ImageReader(osgDB::ReaderWriter* rw, osgDB::Options* op) : Reader(rw, op) { }
+            osgDB::ReaderWriter::ReadResult read(std::istream& in) const { return _rw->readImage(in, _op); }
+        };
+        struct NodeReader : public Reader {
+            NodeReader(osgDB::ReaderWriter* rw, osgDB::Options* op) : Reader(rw, op) { }
+            osgDB::ReaderWriter::ReadResult read(std::istream& in) const { return _rw->readNode(in, _op); }
+        };
+        struct ObjectReader : public Reader {
+            ObjectReader(osgDB::ReaderWriter* rw, osgDB::Options* op) : Reader(rw, op) { }
+            osgDB::ReaderWriter::ReadResult read(std::istream& in) const { return _rw->readObject(in, _op); }
+        };
+
+        ReadResult read(const std::string& key, const Reader& reader);
+
+        void postWrite();
+
+        // key generators
+        std::string binDataKeyTuple(const std::string& key);
+        std::string binPhrase();
+        std::string dataKey(const std::string& key);
+        std::string dataKeyFromTuple(const std::string& tuple);
+        std::string dataBegin();
+        std::string dataEnd();
+        std::string metaKey(const std::string& key);
+        std::string metaKeyFromTuple(const std::string& tuple);
+        std::string metaBegin();
+        std::string metaEnd();
+        std::string timeKey(const DateTime& t, const std::string& key);
+        std::string timeBegin();
+        std::string timeEnd();
+        std::string binKey();
+        std::string timeBeginGlobal();
+        std::string timeEndGlobal();
+    };
+
+
+} } } // namespace osgEarth::Drivers::LevelDBCache
+
+#endif // OSGEARTH_DRIVER_CACHE_LEVELDB_BIN
diff --git a/src/osgEarthDrivers/cache_leveldb/LevelDBCacheBin.cpp b/src/osgEarthDrivers/cache_leveldb/LevelDBCacheBin.cpp
new file mode 100644
index 0000000..17de068
--- /dev/null
+++ b/src/osgEarthDrivers/cache_leveldb/LevelDBCacheBin.cpp
@@ -0,0 +1,668 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include "LevelDBCacheBin"
+#include <osgEarth/Cache>
+#include <osgEarth/Registry>
+#include <osgEarth/Random>
+#include <osgDB/Registry>
+#include <leveldb/write_batch.h>
+#include <string>
+
+using namespace osgEarth;
+using namespace osgEarth::Threading;
+using namespace osgEarth::Drivers::LevelDBCache;
+
+//------------------------------------------------------------------------
+
+namespace
+{
+    void encodeMeta(const Config& meta, std::string& out)
+    {
+        out = Stringify() << meta.toJSON(false);
+    }
+
+    void decodeMeta(const std::string& in, Config& meta)
+    {
+        std::istringstream inmeta(in);
+        inmeta >> std::noskipws;
+        std::stringstream buf;
+        buf << inmeta.rdbuf();
+        std::string bufStr;
+        bufStr = buf.str();
+        meta.fromJSON( bufStr );
+    }
+
+    void blend(std::string& data, unsigned seed)
+    {
+        osgEarth::Random prng(seed, osgEarth::Random::METHOD_FAST);
+        unsigned paddedSize = data.size();
+        if ( data.size() % 4 > 0 ) paddedSize += 4 - (data.size() % 4);
+        char* buf = new char[paddedSize];
+        memcpy(buf, data.c_str(), data.size());
+        unsigned* ptr = (unsigned*)buf;
+        for(unsigned i=0; i<paddedSize/4; ++i, ++ptr)
+            (*ptr) ^= prng.next(INT_MAX);
+        data = std::string(buf, data.size());
+        delete buf;
+    }
+
+    void unblend(std::string& data, unsigned seed)
+    {
+        blend(data, seed);
+    }
+}
+
+//------------------------------------------------------------------------
+
+#undef  LC
+#define LC "[LevelDBCacheBin] "
+
+#undef  OE_TEST
+#define OE_TEST OE_NOTICE
+
+#define TIME_FIELD "leveldb.time"
+
+
+LevelDBCacheBin::LevelDBCacheBin(const std::string& binID,
+                                 leveldb::DB*       db,
+                                 Tracker*           tracker) :
+osgEarth::CacheBin( binID ),
+_db               ( db ),
+_tracker          ( tracker ),
+_debug            ( false )
+{
+    // reader to parse data:
+    _rw = osgDB::Registry::instance()->getReaderWriterForExtension( "osgb" );
+    _rwOptions = osgEarth::Registry::instance()->cloneOrCreateOptions();    
+    
+    if ( ::getenv("OSGEARTH_CACHE_DEBUG") )
+        _debug = true;
+}
+
+LevelDBCacheBin::~LevelDBCacheBin()
+{
+    // nop
+}
+
+bool
+LevelDBCacheBin::binValidForReading(bool silent)
+{
+    bool ok = _db != 0L;
+    if ( !ok && !silent )
+    {
+        OE_WARN << LC << "Failed to locate cache bin (" << getID() << ")" << std::endl;
+    }
+    return ok;
+}
+
+bool
+LevelDBCacheBin::binValidForWriting(bool silent)
+{
+    bool ok = _db != 0L;
+    if ( !ok && !silent )
+    {
+        OE_WARN << LC << "Failed to locate cache bin (" << getID() << ")" << std::endl;
+    }
+    return ok;
+}
+
+#define SEP std::string("!")
+
+std::string
+LevelDBCacheBin::binPhrase()
+{
+    return SEP + getID() + SEP;
+}
+
+std::string
+LevelDBCacheBin::binKey()
+{
+    return "b" + SEP + getID();
+}
+
+std::string
+LevelDBCacheBin::dataKey(const std::string& key)
+{
+    return "d" + SEP + binDataKeyTuple(key);
+}
+
+std::string
+LevelDBCacheBin::binDataKeyTuple(const std::string& key)
+{
+    return getID() + SEP + key;
+}
+
+std::string
+LevelDBCacheBin::dataKeyFromTuple(const std::string& tuple)
+{
+    return "d" + SEP + tuple;
+}
+
+std::string
+LevelDBCacheBin::dataBegin()
+{
+    return "d" + SEP + getID() + SEP;
+}
+
+std::string
+LevelDBCacheBin::dataEnd()
+{
+    return "d" + SEP + getID() + SEP + "\xff";
+}
+
+std::string
+LevelDBCacheBin::metaKey(const std::string& key)
+{
+    return "m" + SEP + binDataKeyTuple(key);
+}
+
+std::string
+LevelDBCacheBin::metaKeyFromTuple(const std::string& tuple)
+{
+    return "m" + SEP + tuple;
+}
+
+std::string
+LevelDBCacheBin::metaBegin()
+{
+    return "m" + SEP + getID() + SEP;
+}
+
+std::string
+LevelDBCacheBin::metaEnd()
+{
+    return "m" + SEP + getID() + SEP + "\xff";
+}
+
+std::string
+LevelDBCacheBin::timeKey(const DateTime& t, const std::string& key)
+{
+    return "t" + SEP + t.asCompactISO8601() + SEP + getID() + SEP + key;
+}
+
+std::string
+LevelDBCacheBin::timeBegin()
+{
+    return "t" + SEP + getID() + SEP;
+}
+
+std::string
+LevelDBCacheBin::timeEnd()
+{
+    return "t" + SEP + getID() + SEP + "\xff";
+}
+
+std::string
+LevelDBCacheBin::timeBeginGlobal()
+{
+    return "t" + SEP;
+}
+
+std::string
+LevelDBCacheBin::timeEndGlobal()
+{
+    return "t" + SEP + "\xff";
+}
+
+ReadResult
+LevelDBCacheBin::readImage(const std::string& key)
+{
+    return read(key, ImageReader(_rw.get(), _rwOptions.get()));    
+}
+
+ReadResult
+LevelDBCacheBin::readObject(const std::string& key)
+{
+    //OE_INFO << LC << "Read attempt: " << key << " from " << getID() << std::endl;
+    return read(key, ObjectReader(_rw.get(), _rwOptions.get()));
+}
+
+ReadResult
+LevelDBCacheBin::readNode(const std::string& key)
+{
+    return read(key, NodeReader(_rw.get(), _rwOptions.get()));
+}
+
+ReadResult
+LevelDBCacheBin::read(const std::string& key, const Reader& reader)
+{
+    if ( !binValidForReading() ) 
+        return ReadResult(ReadResult::RESULT_NOT_FOUND);
+
+    ++_tracker->reads;
+
+    Config metadata;
+    leveldb::Status status;
+    leveldb::ReadOptions ro;
+
+    // first read the metadata record.
+    std::string metavalue;
+    status = _db->Get( ro, metaKey(key), &metavalue );
+    TimeStamp lastModified;
+    if ( status.ok() )
+    {        
+        decodeMeta(metavalue, metadata);
+        DateTime t( metadata.value(TIME_FIELD));
+        lastModified = t.asTimeStamp();
+    }
+        
+    // next read the data record.
+    std::string datakey = dataKey(key);
+    std::string datavalue;
+    status = _db->Get( ro, datakey, &datavalue );
+    if ( !status.ok() )
+    {
+        // main record not found for some reason.
+        return ReadResult(ReadResult::RESULT_NOT_FOUND);
+    }
+
+    // blend the data string
+    if ( _tracker->seed().isSet() )
+        unblend(datavalue, _tracker->seed().value());
+
+    // finally, decode the OSGB stream into an object.
+    std::istringstream datastream(datavalue);
+    osgDB::ReaderWriter::ReadResult r = reader.read(datastream);
+    if ( !r.success() )
+    {
+        OE_WARN << LC << "Read failure - bad key?" << std::endl;
+        return ReadResult(ReadResult::RESULT_READER_ERROR);
+    }
+        
+    if ( _debug )
+    {
+        OE_NOTICE << LC << "Read (" << key << ") from bin " << getID() << std::endl;
+    }
+
+    // if there's a size limit, we need to 'touch' the record.
+    if ( _tracker->hasSizeLimit() )
+    {
+        // Room for optimization here since we already have the 
+        // meta/time records around.
+        touch( key );
+    }
+
+    ++_tracker->hits;
+    ReadResult rr(r.getObject(), metadata);
+    rr.setLastModifiedTime(lastModified);    
+    return rr;
+}
+
+ReadResult
+LevelDBCacheBin::readString(const std::string& key)
+{
+    ReadResult r = readObject(key);
+    if ( r.succeeded() )
+    {
+        if ( r.get<StringObject>() )
+            return r;
+        else
+            return ReadResult();
+    }
+    else
+    {
+        return r;
+    }
+}
+
+bool
+LevelDBCacheBin::write(const std::string& key, const osg::Object* object, const Config& meta)
+{
+    if ( !binValidForWriting() || !object ) 
+        return false;
+        
+    osgDB::ReaderWriter::WriteResult r;
+    bool objWriteOK = false;
+
+    std::string       data;
+    std::stringstream datastream;
+
+    if ( dynamic_cast<const osg::Image*>(object) )
+    {
+        r = _rw->writeImage( *static_cast<const osg::Image*>(object), datastream, _rwOptions.get() );
+        objWriteOK = r.success();
+    }
+    else if ( dynamic_cast<const osg::Node*>(object) )
+    {
+        r = _rw->writeNode( *static_cast<const osg::Node*>(object), datastream, _rwOptions.get() );
+        objWriteOK = r.success();
+    }
+    else
+    {
+        r = _rw->writeObject( *object, datastream );
+        objWriteOK = r.success();
+    }
+
+    if (objWriteOK)
+    {
+        DateTime now;
+        leveldb::WriteBatch batch;
+
+        // write the data:
+        data = datastream.str();
+        if ( _tracker->seed().isSet() )
+            blend(data, _tracker->seed().value());
+        batch.Put( dataKey(key), data );
+
+        // write the timestamp index:
+        batch.Put( timeKey(now, key), binDataKeyTuple(key) );
+
+        // write the metadata:
+        Config metadata(meta);
+        metadata.set( TIME_FIELD, now.asCompactISO8601() );
+        encodeMeta( metadata, data );
+        batch.Put( metaKey(key), data );
+
+        objWriteOK = _db->Write( leveldb::WriteOptions(), &batch ).ok();
+
+        if ( objWriteOK )
+        {
+            ++_tracker->writes;
+            postWrite();
+            
+            if ( _debug )
+            {
+                OE_NOTICE << LC << "Wrote (" << dataKey(key) << ") to bin " << getID() << std::endl;
+            }
+        }
+    }
+
+        
+    if ( !objWriteOK )
+    {
+        OE_WARN << LC << "FAILED to write \"" << key << "\" to bin " << getID()
+            << "; msg = \"" << r.message() << "\"" << std::endl;
+    }
+
+    return objWriteOK;
+}
+
+void
+LevelDBCacheBin::postWrite()
+{
+    if ( _tracker->hasSizeLimit() )
+    {
+        if ( _tracker->isOverLimit() )
+        {
+            if ( _tracker->isTimeToPurge() )
+            {
+                this->purgeOldest(_tracker->numToPurge());
+
+                if (_debug)
+                {
+                    off_t size = _tracker->calcSize();
+                    OE_NOTICE 
+                        << LC << "Cache size = " << (size/1048576) << " MB; " 
+                        << "Hit ratio = " << (float)_tracker->hits/(float)_tracker->reads << std::endl;
+                }
+            }
+        }
+        else
+        {
+            if ( _tracker->isTimeToCheckSize() )
+            {
+                off_t size = _tracker->calcSize();
+                if ( _debug )
+                {
+                    OE_NOTICE 
+                        << LC << "Cache size = " << (size/1048576) << " MB; " 
+                        << "Hit ratio = " << (float)_tracker->hits/(float)_tracker->reads << std::endl;
+                }
+            }
+        }
+    }
+}
+
+CacheBin::RecordStatus
+LevelDBCacheBin::getRecordStatus(const std::string& key)
+{
+    if ( !binValidForReading() ) 
+        return STATUS_NOT_FOUND;
+
+    leveldb::Status status;
+    leveldb::ReadOptions ro;
+
+    // read the metadata record.
+    std::string metavalue;
+    status = _db->Get( ro, metaKey(key), &metavalue );
+    if ( status.ok() )
+    {        
+        return STATUS_OK;
+    }
+    else
+    {
+        return STATUS_NOT_FOUND;
+    }
+}
+
+bool
+LevelDBCacheBin::remove(const std::string& key)
+{
+    if ( !binValidForReading() )
+        return false;
+
+    // first read in the time from the metadata record.
+    std::string metavalue;
+    if ( _db->Get(leveldb::ReadOptions(), metaKey(key), &metavalue).ok() == false )
+        return false;
+
+    Config metadata;
+    decodeMeta(metavalue, metadata);
+    DateTime t(metadata.value(TIME_FIELD));
+
+    leveldb::WriteBatch batch;
+    batch.Delete( dataKey(key) );
+    batch.Delete( metaKey(key) );
+    batch.Delete( timeKey(t, key) );
+        
+    leveldb::Status status = _db->Write(leveldb::WriteOptions(), &batch);
+    if ( !status.ok() )
+    {
+        OE_WARN << LC << "Failed to remove (" << key << ") from bin " << getID() << std::endl;
+        return false;
+    }
+    else if ( _debug )
+    {
+        OE_NOTICE << LC << "Removed (" << key << ") from bin " << getID() << std::endl;
+    }
+
+    return true;
+}
+
+bool
+LevelDBCacheBin::touch(const std::string& key)
+{    
+    if ( !binValidForWriting() )
+        return false;
+
+    // first read in the time from the metadata record.
+    std::string metavalue;
+    if ( _db->Get(leveldb::ReadOptions(), metaKey(key), &metavalue).ok() == false )
+        return false;
+
+    Config metadata;
+    decodeMeta(metavalue, metadata);
+    DateTime oldtime(metadata.value(TIME_FIELD));
+        
+    leveldb::WriteBatch batch;
+
+    // In a transaction, update the metadata record with the current time.
+    std::string newtime = DateTime().asCompactISO8601();
+    metadata.set(TIME_FIELD, newtime);
+    encodeMeta(metadata, metavalue);
+    batch.Put(metaKey(key), metavalue);
+
+    // ...remove the old time index record:
+    batch.Delete( timeKey(oldtime, key) );
+
+    // ...and write a new time index record.
+    batch.Put( timeKey(newtime, key), binDataKeyTuple(key) );
+
+    leveldb::Status status = _db->Write(leveldb::WriteOptions(), &batch);
+    if ( !status.ok() )
+    {
+        OE_WARN << LC << "Failed to touch (" << key << ") in bin " << getID() << std::endl;
+    }
+    else if ( _debug )
+    {
+        OE_NOTICE << LC << "Touched (" << key << ") in bin " << getID() << std::endl;
+    }
+    return status.ok();
+}
+
+bool
+LevelDBCacheBin::clear()
+{
+    if ( !binValidForWriting() )
+        return false;
+    
+    leveldb::WriteOptions wo;
+    std::string binphrase = binPhrase();
+    leveldb::WriteBatch batch;
+    leveldb::Iterator* i = _db->NewIterator(leveldb::ReadOptions());
+    for(i->SeekToFirst(); i->Valid(); i->Next())
+    {
+        std::string key = i->key().ToString();
+        if ( key.find(binphrase) != std::string::npos )
+        {
+            _db->Delete( wo, i->key() );
+        }
+    }
+    delete i;
+
+    if ( _debug )
+    {
+        OE_NOTICE << LC << "Cleared bin " << getID() << std::endl;
+    }
+
+    return true;
+}
+
+bool
+LevelDBCacheBin::compact()
+{
+    if ( !binValidForWriting() )
+        return false;
+
+    // This could take a while.
+    _db->CompactRange(0L, 0L);
+
+    return false;
+}
+
+unsigned
+LevelDBCacheBin::getStorageSize()
+{
+    if ( !binValidForReading() )
+        return false;
+
+    //Note: doesn't work..
+    leveldb::Range ranges[3];
+    uint64_t       sizes[3];
+
+    ranges[0] = leveldb::Range(dataBegin(), dataEnd());
+    ranges[1] = leveldb::Range(metaBegin(), metaEnd());
+    ranges[2] = leveldb::Range(timeBegin(), timeEnd());
+    sizes[0] = sizes[1] = sizes[2] = 0;
+
+    _db->GetApproximateSizes( ranges, 3, sizes );
+    return sizes[0] + sizes[1] + sizes[2];
+}
+
+Config
+LevelDBCacheBin::readMetadata()
+{
+    if ( !binValidForReading() )
+        return Config();
+
+    ScopedMutexLock exclusiveLock( _rwMutex );
+
+    std::string binvalue;
+    leveldb::Status status = _db->Get(leveldb::ReadOptions(), binKey(), &binvalue);
+    if ( !status.ok() )
+        return Config();
+
+    Config binMetadata;
+    decodeMeta(binvalue, binMetadata);
+    return binMetadata;
+}
+
+bool
+LevelDBCacheBin::writeMetadata(const Config& conf)
+{
+    if ( !binValidForWriting() )
+        return false;
+
+    ScopedMutexLock exclusiveLock( _rwMutex );
+
+    // inject the cache version
+    Config mutableConf(conf);
+    mutableConf.set("leveldb.cache_version", LEVELDB_CACHE_VERSION);
+
+    std::string value;
+    encodeMeta(mutableConf, value);
+
+    if ( _db->Put(leveldb::WriteOptions(), binKey(), value).ok() == false )
+    {
+        OE_WARN << LC << "Failed to write metadata record for bin (" << getID() << ")" << std::endl;
+        return false;
+    }
+
+    return true;
+}
+
+bool
+LevelDBCacheBin::purgeOldest(unsigned maxnum)
+{
+    if ( !binValidForWriting() )
+        return false;
+
+    leveldb::Iterator* it = _db->NewIterator(leveldb::ReadOptions());
+
+    unsigned count = 0;
+    std::string limit = timeEndGlobal();
+
+    // note: this will delete records NOT OF THIS BIN as well!
+    for(it->Seek(timeBeginGlobal());
+        count < maxnum && it->Valid() && it->key().ToString() < limit;
+        it->Next(), ++count )
+    {
+        if ( !it->status().ok() )
+            break;
+
+        std::string tuple = it->value().ToString();
+
+        // doing this in a WriteBatch did not work. The size of the
+        // database would never go down.
+        leveldb::WriteOptions wo;
+        _db->Delete( wo, dataKeyFromTuple(tuple) );
+        _db->Delete( wo, metaKeyFromTuple(tuple) );
+        _db->Delete( wo, it->key() );
+    }
+
+    delete it;
+
+    if ( _debug )
+    {
+        OE_NOTICE << LC << "Purged " << count << " record(s) for "
+            << (_tracker->calcSize()/1048576) << " MB" << std::endl;
+    }
+
+    return true;
+}
diff --git a/src/osgEarthDrivers/cache_leveldb/LevelDBCacheDriver.cpp b/src/osgEarthDrivers/cache_leveldb/LevelDBCacheDriver.cpp
new file mode 100644
index 0000000..0bd0b37
--- /dev/null
+++ b/src/osgEarthDrivers/cache_leveldb/LevelDBCacheDriver.cpp
@@ -0,0 +1,57 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include "LevelDBCache"
+#include <osgEarth/Cache>
+#include <osgDB/Registry>
+#include <osgDB/FileNameUtils>
+
+namespace osgEarth { namespace Drivers { namespace LevelDBCache
+{
+    /**
+     * This driver defers loading of the source data to the appropriate OSG plugin. You
+     * must explicity set an override profile when using this driver.
+     *
+     * For example, use this driver to load a simple jpeg file; then set the profile to
+     * tell osgEarth its projection.
+     */
+    class LevelDBCacheDriver : public osgEarth::CacheDriver
+    {
+    public:
+        LevelDBCacheDriver()
+        {
+            supportsExtension( "osgearth_cache_leveldb", "leveldb cache for osgEarth" );
+        }
+
+        virtual const char* className()
+        {
+            return "leveldb cache for osgEarth";
+        }
+
+        virtual ReadResult readObject(const std::string& file_name, const Options* options) const
+        {
+            if ( !acceptsExtension(osgDB::getLowerCaseFileExtension( file_name )))
+                return ReadResult::FILE_NOT_HANDLED;
+
+            return ReadResult( new LevelDBCacheImpl( getCacheOptions(options) ) );
+        }
+    };
+
+    REGISTER_OSGPLUGIN(osgearth_cache_leveldb, LevelDBCacheDriver);
+
+} } } // namespace osgEarth::Drivers::LevelDBCache
diff --git a/src/osgEarthDrivers/cache_leveldb/LevelDBCacheOptions b/src/osgEarthDrivers/cache_leveldb/LevelDBCacheOptions
new file mode 100644
index 0000000..43152eb
--- /dev/null
+++ b/src/osgEarthDrivers/cache_leveldb/LevelDBCacheOptions
@@ -0,0 +1,114 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_DRIVER_CACHE_LEVELDB_OPTIONS
+#define OSGEARTH_DRIVER_CACHE_LEVELDB_OPTIONS 1
+
+#include <osgEarth/Common>
+#include <osgEarth/Cache>
+#include <string>
+
+namespace osgEarth { namespace Drivers { namespace LevelDBCache
+{
+    using namespace osgEarth;
+    
+    /**
+     * Serializable options for the LevelDBCache.
+     */
+    class LevelDBCacheOptions : public CacheOptions
+    {
+    public:
+        LevelDBCacheOptions( const ConfigOptions& options =ConfigOptions() )
+            : CacheOptions    ( options ),
+              _maxSizeMB      ( 0 ),
+              _sizeCheckPeriod( 100 ),
+              _sizePurgePeriod( 75 ),
+              _blockSize      ( 262144 )// 256K
+        {
+            setDriver( "leveldb" );
+            fromConfig( _conf ); 
+        }
+
+        /** dtor */
+        virtual ~LevelDBCacheOptions() { }
+
+    public:
+        /** Folder containing the cache bins. */
+        optional<std::string>& rootPath() { return _path; }
+        const optional<std::string>& rootPath() const { return _path; }
+
+        /** Maximum size of the cache in megabytes. Note: this is not
+         *  a guarantee - merely a guideline. */
+        optional<unsigned>& maxSizeMB() { return _maxSizeMB; }
+        const optional<unsigned>& maxSizeMB() const { return _maxSizeMB; }
+
+        //--- Advanced options ---
+
+        /** Number of writes between cap checks */
+        optional<unsigned>& sizeCheckPeriod() { return _sizeCheckPeriod; }
+        const optional<unsigned>& sizeCheckPeriod() const { return _sizeCheckPeriod; }
+
+        /** Number of writer between cap purges */
+        optional<unsigned>& sizePurgePeriod() { return _sizePurgePeriod; }
+        const optional<unsigned>& sizePurgePeriod() const { return _sizePurgePeriod; }
+
+        /** Leveldb block size */
+        optional<unsigned>& blockSize() { return _blockSize; }
+        const optional<unsigned>& blockSize() const { return _blockSize; }
+
+        /** Obfuscation key string */
+        optional<std::string>& key() { return _key; }
+        const optional<std::string>& key() const { return _key; }
+
+    public:
+        virtual Config getConfig() const {
+            Config conf = ConfigOptions::getConfig();
+            conf.addIfSet( "path", _path );
+            conf.addIfSet( "max_size_mb", _maxSizeMB );
+            conf.addIfSet( "size_check_period", _sizeCheckPeriod );
+            conf.addIfSet( "size_purge_period", _sizePurgePeriod );
+            conf.addIfSet( "block_size", _blockSize );
+            conf.addIfSet( "key", _key );
+            return conf;
+        }
+        virtual void mergeConfig( const Config& conf ) {
+            ConfigOptions::mergeConfig( conf );
+            fromConfig( conf );
+        }
+
+    private:
+        void fromConfig( const Config& conf ) {
+            conf.getIfSet( "path", _path );
+            conf.getIfSet( "max_size_mb", _maxSizeMB );
+            conf.getIfSet( "size_check_period", _sizeCheckPeriod );
+            conf.getIfSet( "size_purge_period", _sizePurgePeriod );
+            conf.getIfSet( "block_size", _blockSize );
+            conf.getIfSet( "key", _key );
+        }
+
+        optional<std::string> _path;
+        optional<unsigned>    _maxSizeMB;
+        optional<unsigned>    _sizeCheckPeriod;
+        optional<unsigned>    _sizePurgePeriod;
+        optional<unsigned>    _blockSize;
+        optional<std::string> _key;
+    };
+
+} } } // namespace osgEarth::Drivers::LevelDBCache
+
+#endif // OSGEARTH_DRIVER_CACHE_LEVELDB_OPTIONS
diff --git a/src/osgEarthDrivers/cache_leveldb/Tracker b/src/osgEarthDrivers/cache_leveldb/Tracker
new file mode 100644
index 0000000..ebf29b5
--- /dev/null
+++ b/src/osgEarthDrivers/cache_leveldb/Tracker
@@ -0,0 +1,113 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_DRIVER_CACHE_LEVELDB_TRACKER
+#define OSGEARTH_DRIVER_CACHE_LEVELDB_TRACKER 1
+
+#include "LevelDBCacheOptions"
+#include <osgEarth/ThreadingUtils>
+#include <osgDB/FileUtils>
+#include <osgDB/FileNameUtils>
+#include <osg/Referenced>
+#include <sys/stat.h>
+#ifndef _WIN32
+#   include <unistd.h>
+#endif
+
+namespace osgEarth { namespace Drivers { namespace LevelDBCache
+{
+    typedef OpenThreads::Atomic unsigned_atomic;
+
+    /**
+     * Tracks usage metrics across a LevelDB cache
+     */
+    class Tracker : public osg::Referenced
+    {
+    public:
+        Tracker(const LevelDBCacheOptions& options,
+                const std::string&         path ) : 
+            _options(options),                 
+            _path(path),
+            _seed(0)
+        {
+            _maxBytes = (off_t)(options.maxSizeMB().get() * 1048576);
+            _size = (::off_t)0;
+
+            if (_options.key().isSet() && !_options.key()->empty())
+            {
+                _seed = osgEarth::hashString(_options.key().value());
+            }
+        }
+        virtual ~Tracker() { }
+
+    public:
+        unsigned_atomic reads;
+        unsigned_atomic hits;
+        unsigned_atomic writes;
+
+        bool hasSizeLimit() const {
+            return _options.maxSizeMB().isSet();
+        }
+
+        bool isOverLimit() const { 
+            return _size > _maxBytes; 
+        }
+
+        bool isTimeToCheckSize() const {
+            return ((unsigned)writes % _options.sizeCheckPeriod().value()) == 0;
+        }
+
+        bool isTimeToPurge() const {
+            unsigned w = (unsigned)writes;
+            return w == 1 || (w % _options.sizePurgePeriod().value()) == 0;
+        }
+
+        unsigned numToPurge() const {
+            return _options.sizePurgePeriod().value();
+        }
+
+        const optional<unsigned>& seed() const {
+            return _seed;
+        }
+
+        ::off_t calcSize()
+        {
+            ::off_t total = 0;
+            osgDB::DirectoryContents dir = osgDB::getDirectoryContents(_path);
+            for(osgDB::DirectoryContents::iterator i = dir.begin(); i != dir.end(); ++i)
+            {
+                std::string path = osgDB::concatPaths(_path, *i);
+                struct stat s;
+                ::stat( path.c_str(), &s );
+                total += s.st_size;
+            }
+            _size = total;
+            return total;
+        }
+
+    private:
+        const std::string         _path;
+        const LevelDBCacheOptions _options;
+        ::off_t                   _maxBytes;
+        ::off_t                   _size;
+        optional<unsigned>        _seed;
+    };
+
+} } } // namespace osgEarth::Drivers::LevelDBCache
+
+#endif // OSGEARTH_DRIVER_CACHE_LEVELDB_TRACKER
diff --git a/src/osgEarthDrivers/colorramp/CMakeLists.txt b/src/osgEarthDrivers/colorramp/CMakeLists.txt
new file mode 100644
index 0000000..9a59bbb
--- /dev/null
+++ b/src/osgEarthDrivers/colorramp/CMakeLists.txt
@@ -0,0 +1,15 @@
+SET(TARGET_H
+    ColorRampOptions
+)
+SET(TARGET_SRC 
+    ColorRampTileSource.cpp
+)
+SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES} osgEarthSymbology)
+SETUP_PLUGIN(osgearth_colorramp)
+
+
+# to install public driver includes:
+SET(LIB_NAME osg)
+SET(LIB_PUBLIC_HEADERS ColorRampOptions)
+INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
+
diff --git a/src/osgEarthDrivers/tilecache/TileCacheOptions b/src/osgEarthDrivers/colorramp/ColorRampOptions
similarity index 51%
copy from src/osgEarthDrivers/tilecache/TileCacheOptions
copy to src/osgEarthDrivers/colorramp/ColorRampOptions
index 27c89e9..08a872c 100644
--- a/src/osgEarthDrivers/tilecache/TileCacheOptions
+++ b/src/osgEarthDrivers/colorramp/ColorRampOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -16,64 +16,69 @@
  * You should have received a copy of the GNU Lesser General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
-#ifndef OSGEARTH_DRIVER_TILECACHE_DRIVEROPTIONS
-#define OSGEARTH_DRIVER_TILECACHE_DRIVEROPTIONS 1
+#ifndef OSGEARTH_DRIVER_COLORRAMP_DRIVEROPTIONS
+#define OSGEARTH_DRIVER_COLORRAMP_DRIVEROPTIONS 1
 
 #include <osgEarth/Common>
 #include <osgEarth/TileSource>
+#include <osgEarth/URI>
+#include <osgEarth/ElevationLayer>
 
 namespace osgEarth { namespace Drivers
 {
     using namespace osgEarth;
 
-    class TileCacheOptions : public TileSourceOptions // NO EXPORT; header only
+    class ColorRampOptions : public TileSourceOptions // NO EXPORT; header only
     {
-    public:
-        optional<URI>& url() { return _url; }
-        const optional<URI>& url() const { return _url; }
-
-        optional<std::string>& layer() { return _layer; }
-        const optional<std::string>& layer() const { return _layer; }
+    public:            
+        optional<ElevationLayerOptions>& elevationLayer() { return _elevationLayerOptions; }
+        const optional<ElevationLayerOptions>& elevationLayer() const { return _elevationLayerOptions; }
 
-        optional<std::string>& format() { return _format; }
-        const optional<std::string>& format() const { return _format; }
+        optional<URI>& ramp() { return _ramp; }
+        const optional<URI>& ramp() const { return _ramp; }
 
     public:
-        TileCacheOptions( const TileSourceOptions& opt =TileSourceOptions() ) :
-            TileSourceOptions( opt )
+        ColorRampOptions( const TileSourceOptions& opt =TileSourceOptions() ) :
+            TileSourceOptions( opt )      
         {
-            setDriver( "tilecache" );
+            setDriver( "colorramp" );
             fromConfig( _conf );
         }
 
         /** dtor */
-        virtual ~TileCacheOptions() { }
+        virtual ~ColorRampOptions() { }
 
-    protected:
+    public:
         Config getConfig() const {
             Config conf = TileSourceOptions::getConfig();
-            conf.updateIfSet("url", _url );
-            conf.updateIfSet("layer", _layer);
-            conf.updateIfSet("format", _format);
+            conf.updateObjIfSet("elevation", _elevationLayerOptions );
+            conf.updateIfSet("ramp", _ramp);
             return conf;
         }
+
+    protected:
         void mergeConfig( const Config& conf ) {
             TileSourceOptions::mergeConfig( conf );
             fromConfig( conf );
         }
+
     private:
         void fromConfig( const Config& conf ) {
-            conf.getIfSet( "url", _url );
-            conf.getIfSet( "layer", _layer );
-            conf.getIfSet( "format", _format );
+            conf.getObjIfSet("elevation", _elevationLayerOptions );
+            if (!_elevationLayerOptions.isSet())
+            {
+                // Try to get the settings from a heightfield specificiation instead
+                conf.getObjIfSet("heightfield", _elevationLayerOptions );
+            }
+            conf.getIfSet("ramp", _ramp);
         }
 
-        optional<URI>         _url;
-        optional<std::string> _layer;
-        optional<std::string> _format;
+      
+        optional<URI> _ramp;
+        optional<ElevationLayerOptions> _elevationLayerOptions;
     };
 
 } } // namespace osgEarth::Drivers
 
-#endif // OSGEARTH_DRIVER_TILECACHE_DRIVEROPTIONS
+#endif // OSGEARTH_DRIVER_COLORRAMP_DRIVEROPTIONS
 
diff --git a/src/osgEarthDrivers/colorramp/ColorRampTileSource.cpp b/src/osgEarthDrivers/colorramp/ColorRampTileSource.cpp
new file mode 100644
index 0000000..7b0b804
--- /dev/null
+++ b/src/osgEarthDrivers/colorramp/ColorRampTileSource.cpp
@@ -0,0 +1,166 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include "ColorRampOptions"
+
+#include <osgEarth/FileUtils>
+#include <osgEarth/ImageUtils>
+#include <osgEarth/Registry>
+#include <osgEarth/URI>
+#include <osgEarthSymbology/Color>
+#include <osgDB/FileNameUtils>
+#include <osgDB/FileUtils>
+#include <osg/TransferFunction>
+
+#include <cstring>
+
+#define LC "[ColorRamp Driver] "
+
+using namespace osgEarth;
+using namespace osgEarth::Symbology;
+using namespace osgEarth::Drivers;
+
+
+class ColorRampTileSource : public TileSource
+{
+public:
+    ColorRampTileSource( const TileSourceOptions& options ) :
+      TileSource( options ),            
+      _options( options )
+    {
+        //nop
+    }
+
+    Status initialize( const osgDB::Options* dbOptions )
+    {
+        osg::ref_ptr<osgDB::Options> localOptions = Registry::instance()->cloneOrCreateOptions(dbOptions);        
+
+        if (!_options.elevationLayer().isSet())
+        {
+            return Status::Error("Please specify a heightfield layer for the color ramp");
+        }
+        
+        _layer = new ElevationLayer(*_options.elevationLayer() );
+        if (!_layer.valid())
+        {
+            return Status::Error("Failed to initialize the Please specify a heightfield layer for the color ramp");
+        }
+
+        setProfile(_layer->getProfile());
+
+        initTransferFunction();
+               
+
+        return STATUS_OK;
+    }
+
+    void initTransferFunction()
+    {     
+        _transferFunction = loadCLRFile(_options.ramp()->full());
+        if (!_transferFunction.valid())
+        {
+            OE_WARN << LC << "Failed to load transfer function from " << _options.ramp()->full() << std::endl;
+            
+            // Just create a default ramp
+            _transferFunction = new osg::TransferFunction1D();
+            _transferFunction->setColor(0, osg::Vec4(1,0,0,1));
+            _transferFunction->setColor(100, osg::Vec4(0,1,0,1));
+        }
+    }  
+
+    osg::TransferFunction1D* loadCLRFile(const std::string& filename)
+    {
+        if (osgDB::fileExists(filename))
+        {
+            osg::TransferFunction1D* transfer = new osg::TransferFunction1D();
+
+            std::ifstream in(filename.c_str());
+            float value;
+            unsigned int r, g, b, a;
+            while(in >> value >> r >> g >> b >> a)
+            {                
+                transfer->setColor(value, osg::Vec4((float)r/255.0, (float)g/255.0, (float)b/255.0, (float)a/255.0));
+            }
+            return transfer;
+        }
+        return NULL;        
+    }
+      
+
+    osg::Image*
+    createImage( const TileKey& key, ProgressCallback* progress )
+    {
+        // Use the underlying ElevationLayer to create a heightfield and then color it.
+        GeoHeightField geoHF = _layer->createHeightField(key, progress);
+        if (geoHF.valid())
+        {
+            osg::HeightField* hf = geoHF.getHeightField(); 
+            osg::Image* image = new osg::Image();
+            image->allocateImage(hf->getNumColumns(),hf->getNumRows(),1, GL_RGBA, GL_UNSIGNED_BYTE);
+            memset(image->data(), 0, image->getImageSizeInBytes());
+            ImageUtils::PixelWriter writer(image);
+            for (unsigned int c = 0; c < hf->getNumColumns(); c++)
+            {
+                for (unsigned int r = 0; r < hf->getNumRows(); r++)
+                {
+                    float v = hf->getHeight(c, r );
+                    if (v != NO_DATA_VALUE)
+                    {                        
+                        osg::Vec4 color = _transferFunction->getColor(v);
+                        writer(color, c, r);
+                    }                    
+                }
+            } 
+            return image;
+
+        }
+        return NULL;
+    }
+
+
+private:
+    const ColorRampOptions _options;
+    osg::ref_ptr< ElevationLayer > _layer;
+    osg::ref_ptr< osg::TransferFunction1D> _transferFunction;
+};
+
+
+class ColorRampTileSourceFactory : public TileSourceDriver
+{
+public:
+    ColorRampTileSourceFactory()
+    {
+        supportsExtension( "osgearth_colorramp", "Color ramp driver for osgEarth" );
+    }
+
+    virtual const char* className()
+    {
+        return "ColorRamp Image Driver";
+    }
+
+    virtual ReadResult readObject(const std::string& file_name, const Options* options) const
+    {
+        if ( !acceptsExtension(osgDB::getLowerCaseFileExtension( file_name )))
+            return ReadResult::FILE_NOT_HANDLED;
+
+        return new ColorRampTileSource( getTileSourceOptions(options) );
+    }
+};
+
+REGISTER_OSGPLUGIN(osgearth_colorramp, ColorRampTileSourceFactory)
+
diff --git a/src/osgEarthDrivers/debug/DebugOptions b/src/osgEarthDrivers/debug/DebugOptions
index 81772ed..c90b1a9 100644
--- a/src/osgEarthDrivers/debug/DebugOptions
+++ b/src/osgEarthDrivers/debug/DebugOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/debug/DebugTileSource.cpp b/src/osgEarthDrivers/debug/DebugTileSource.cpp
index cee5395..3fbe049 100644
--- a/src/osgEarthDrivers/debug/DebugTileSource.cpp
+++ b/src/osgEarthDrivers/debug/DebugTileSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/earth/EarthFileSerializer b/src/osgEarthDrivers/earth/EarthFileSerializer
index b2d8006..ff139e5 100644
--- a/src/osgEarthDrivers/earth/EarthFileSerializer
+++ b/src/osgEarthDrivers/earth/EarthFileSerializer
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/earth/EarthFileSerializer1.cpp b/src/osgEarthDrivers/earth/EarthFileSerializer1.cpp
index b550cac..35caa0f 100644
--- a/src/osgEarthDrivers/earth/EarthFileSerializer1.cpp
+++ b/src/osgEarthDrivers/earth/EarthFileSerializer1.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -96,9 +96,7 @@ EarthFileSerializer1::deserialize( const Config& conf, const std::string& refere
     ConfigSet images = conf.children( "image" );
     for( ConfigSet::const_iterator i = images.begin(); i != images.end(); i++ )
     {
-        Config layerDriverConf = *i;
-        layerDriverConf.add( "default_tile_size", "256" );
-
+        Config layerDriverConf = *i;        
         ImageLayerOptions layerOpt( layerDriverConf );
         layerOpt.name() = layerDriverConf.value("name");
         layerOpt.driver() = TileSourceOptions( layerDriverConf );
@@ -114,8 +112,7 @@ EarthFileSerializer1::deserialize( const Config& conf, const std::string& refere
         ConfigSet heightfields = conf.children( tagName );
         for( ConfigSet::const_iterator i = heightfields.begin(); i != heightfields.end(); i++ )
         {
-            Config layerDriverConf = *i;
-            layerDriverConf.add( "default_tile_size", "16" );
+            Config layerDriverConf = *i;            
 
             ElevationLayerOptions layerOpt( layerDriverConf );
             layerOpt.name() = layerDriverConf.value( "name" );
diff --git a/src/osgEarthDrivers/earth/EarthFileSerializer2.cpp b/src/osgEarthDrivers/earth/EarthFileSerializer2.cpp
index 046bc5f..63f5c20 100644
--- a/src/osgEarthDrivers/earth/EarthFileSerializer2.cpp
+++ b/src/osgEarthDrivers/earth/EarthFileSerializer2.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -48,8 +48,7 @@ EarthFileSerializer2::deserialize( const Config& conf, const std::string& refere
     ConfigSet images = conf.children( "image" );
     for( ConfigSet::const_iterator i = images.begin(); i != images.end(); i++ )
     {
-        Config layerDriverConf = *i;
-        layerDriverConf.add( "default_tile_size", "256" );
+        Config layerDriverConf = *i;        
 
         ImageLayerOptions layerOpt( layerDriverConf );
         layerOpt.name() = layerDriverConf.value("name");
@@ -66,8 +65,7 @@ EarthFileSerializer2::deserialize( const Config& conf, const std::string& refere
         ConfigSet heightfields = conf.children( tagName );
         for( ConfigSet::const_iterator i = heightfields.begin(); i != heightfields.end(); i++ )
         {
-            Config layerDriverConf = *i;
-            layerDriverConf.add( "default_tile_size", "15" );
+            Config layerDriverConf = *i;            
 
             ElevationLayerOptions layerOpt( layerDriverConf );
             layerOpt.name() = layerDriverConf.value( "name" );
@@ -150,8 +148,7 @@ EarthFileSerializer2::serialize( MapNode* input ) const
         //Config layerConf = layer->getInitialOptions().getConfig();
         Config layerConf = layer->getImageLayerOptions().getConfig();
         layerConf.set("name", layer->getName());
-        layerConf.set("driver", layer->getInitialOptions().driver()->getDriver());
-        layerConf.remove("default_tile_size");
+        layerConf.set("driver", layer->getInitialOptions().driver()->getDriver());        
         mapConf.add( "image", layerConf );
     }
 
@@ -161,8 +158,7 @@ EarthFileSerializer2::serialize( MapNode* input ) const
         //Config layerConf = layer->getInitialOptions().getConfig();
         Config layerConf = layer->getElevationLayerOptions().getConfig();
         layerConf.set("name", layer->getName());
-        layerConf.set("driver", layer->getInitialOptions().driver()->getDriver());
-        layerConf.remove("default_tile_size");
+        layerConf.set("driver", layer->getInitialOptions().driver()->getDriver());        
         mapConf.add( "elevation", layerConf );
     }
 
diff --git a/src/osgEarthDrivers/earth/ReaderWriterOsgEarth.cpp b/src/osgEarthDrivers/earth/ReaderWriterOsgEarth.cpp
index 3ece305..8b996e9 100644
--- a/src/osgEarthDrivers/earth/ReaderWriterOsgEarth.cpp
+++ b/src/osgEarthDrivers/earth/ReaderWriterOsgEarth.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -148,7 +148,14 @@ class ReaderWriterEarth : public osgDB::ReaderWriter
 
             else
             {
-                osgEarth::ReadResult r = URI(fileName).readString( options );
+                std::string fullFileName = fileName;
+                if ( !osgDB::containsServerAddress( fileName ) )
+                {
+                    fullFileName = osgDB::findDataFile( fileName, options );
+                    if (fullFileName.empty()) return ReadResult::FILE_NOT_FOUND;
+                }
+
+                osgEarth::ReadResult r = URI(fullFileName).readString( options );
                 if ( r.failed() )
                     return ReadResult::ERROR_IN_READING_FILE;
 
@@ -156,7 +163,7 @@ class ReaderWriterEarth : public osgDB::ReaderWriter
                 // reference URI as well..
                 osg::ref_ptr<osgDB::Options> myOptions = Registry::instance()->cloneOrCreateOptions(options);
 
-                URIContext( fileName ).apply( myOptions.get() );
+                URIContext( fullFileName ).apply( myOptions.get() );
 
                 std::stringstream in( r.getString() );
                 return readNode( in, myOptions.get() );
diff --git a/src/osgEarthDrivers/engine_byo/BYOTerrainEngineDriver.cpp b/src/osgEarthDrivers/engine_byo/BYOTerrainEngineDriver.cpp
index fc0dfd4..1622358 100644
--- a/src/osgEarthDrivers/engine_byo/BYOTerrainEngineDriver.cpp
+++ b/src/osgEarthDrivers/engine_byo/BYOTerrainEngineDriver.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/engine_byo/BYOTerrainEngineNode b/src/osgEarthDrivers/engine_byo/BYOTerrainEngineNode
index 4a798eb..f87d0e4 100644
--- a/src/osgEarthDrivers/engine_byo/BYOTerrainEngineNode
+++ b/src/osgEarthDrivers/engine_byo/BYOTerrainEngineNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/engine_byo/BYOTerrainEngineNode.cpp b/src/osgEarthDrivers/engine_byo/BYOTerrainEngineNode.cpp
index e311de9..88d1ef3 100644
--- a/src/osgEarthDrivers/engine_byo/BYOTerrainEngineNode.cpp
+++ b/src/osgEarthDrivers/engine_byo/BYOTerrainEngineNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -62,9 +62,12 @@ BYOTerrainEngineNode::preInitialize( const Map* map, const TerrainOptions& optio
         {
             if ( myoptions.shaderPolicy() == SHADERPOLICY_GENERATE )
             {
-                ShaderGenerator gen;
-                gen.setProgramName( "osgEarth.BYOTerrainEngine" );
-                gen.run( node, new StateSetCache() );
+                osg::ref_ptr<StateSetCache> cache = new StateSetCache();
+                
+                Registry::shaderGenerator().run(
+                    node,
+                    "osgEarth.BYOTerrainEngine",
+                    cache.get() );
             }
             else if ( myoptions.shaderPolicy() == SHADERPOLICY_DISABLE )
             {
diff --git a/src/osgEarthDrivers/engine_byo/BYOTerrainEngineOptions b/src/osgEarthDrivers/engine_byo/BYOTerrainEngineOptions
index 0f036bb..c2751eb 100644
--- a/src/osgEarthDrivers/engine_byo/BYOTerrainEngineOptions
+++ b/src/osgEarthDrivers/engine_byo/BYOTerrainEngineOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/engine_byo/Common b/src/osgEarthDrivers/engine_byo/Common
index 0177e43..7f55bec 100644
--- a/src/osgEarthDrivers/engine_byo/Common
+++ b/src/osgEarthDrivers/engine_byo/Common
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/engine_mp/Common b/src/osgEarthDrivers/engine_mp/Common
index ebe553f..5520d0e 100644
--- a/src/osgEarthDrivers/engine_mp/Common
+++ b/src/osgEarthDrivers/engine_mp/Common
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -16,9 +16,9 @@
  * You should have received a copy of the GNU Lesser General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
-#ifndef OSGEARTH_ENGINE_MP_COMMON_H
-#define OSGEARTH_ENGINE_MP_COMMON_H 1
+#ifndef OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_COMMON_H
+#define OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_COMMON_H 1
 
 #include <osgEarth/Common>
 
-#endif // OSGEARTH_ENGINE_MP_COMMON_H
+#endif // OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_COMMON_H
diff --git a/src/osgEarthDrivers/engine_mp/DynamicLODScaleCallback b/src/osgEarthDrivers/engine_mp/DynamicLODScaleCallback
index 91b9125..341a1a4 100644
--- a/src/osgEarthDrivers/engine_mp/DynamicLODScaleCallback
+++ b/src/osgEarthDrivers/engine_mp/DynamicLODScaleCallback
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -23,7 +23,7 @@
 #include <osg/NodeCallback>
 #include <osg/CullStack>
 
-namespace osgEarth_engine_mp
+namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 {
     /**
      * Cull callback that dynamically computes an LOD scale based on
@@ -80,6 +80,6 @@ namespace osgEarth_engine_mp
         float _fallOff;
     };
 
-} // namespace osgEarth_engine_mp
+} } } // namespace osgEarth::Drivers::MPTerrainEngine
 
 #endif //OSGEARTH_ENGINE_OSGTERRAIN_DYNAMIC_LOD_SCALE_CALLBACK_H
diff --git a/src/osgEarthDrivers/engine_mp/FileLocationCallback b/src/osgEarthDrivers/engine_mp/FileLocationCallback
index 254c4e9..ec6aa62 100644
--- a/src/osgEarthDrivers/engine_mp/FileLocationCallback
+++ b/src/osgEarthDrivers/engine_mp/FileLocationCallback
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -24,13 +24,11 @@
 #include <osg/Version>
 #include <osgEarth/Export>
 
-
-#define USE_FILELOCATIONCALLBACK OSG_MIN_VERSION_REQUIRED(2,9,5)
-
+#define USE_FILELOCATIONCALLBACK 1
 
 #if USE_FILELOCATIONCALLBACK
 
-namespace osgEarth_engine_mp
+namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 {
     /**
      * A database pager callback that determines if the given filename is cached or not
@@ -43,7 +41,7 @@ namespace osgEarth_engine_mp
         /** dtor */
         virtual ~FileLocationCallback() { }
 
-        virtual Location fileLocation(const std::string& filename, const osgDB::Options* options)
+        Location fileLocation(const std::string& filename, const osgDB::Options* options)
         {
             Location result = REMOTE_FILE;
 
@@ -72,13 +70,15 @@ namespace osgEarth_engine_mp
                 }
             }
 
+            //OE_NOTICE << (result?"REMOTE":"LOCAL") << " - " << filename << std::endl;
+
             return result;
         }
 
-        virtual bool useFileCache() const { return false; }
+        bool useFileCache() const { return false; }
     };
 
-} // namespace osgEarth_engine_mp
+} } } // namespace osgEarth::Drivers::MPTerrainEngine
 
 #endif // USE_FILELOCATIONCALLBACK
 
diff --git a/src/osgEarthDrivers/engine_mp/KeyNodeFactory b/src/osgEarthDrivers/engine_mp/KeyNodeFactory
index 1bf7aae..8c623d4 100644
--- a/src/osgEarthDrivers/engine_mp/KeyNodeFactory
+++ b/src/osgEarthDrivers/engine_mp/KeyNodeFactory
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -26,7 +26,7 @@
 
 using namespace osgEarth;
 
-namespace osgEarth_engine_mp
+namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 {
     /**
     * Factory object that can create a scene graph given a TileKey.
@@ -38,7 +38,11 @@ namespace osgEarth_engine_mp
         /**
         * Creates a node for a tile key.
         */
-        virtual osg::Node* createNode( const TileKey& key, bool setupChildren, ProgressCallback* progress) =0;
+        virtual osg::Node* createNode(
+            const TileKey&    key, 
+            bool              accumulate,
+            bool              setupChildren,
+            ProgressCallback* progress ) =0;
 
 
     protected:
@@ -48,6 +52,6 @@ namespace osgEarth_engine_mp
         virtual ~KeyNodeFactory() { }
     };
 
-} // namespace osgEarth_engine_mp
+} } } // namespace osgEarth::Drivers::MPTerrainEngine
 
 #endif // OSGEARTH_ENGINE_KEY_NODE_FACTORY
diff --git a/src/osgEarthDrivers/engine_mp/KeyNodeFactory.cpp b/src/osgEarthDrivers/engine_mp/KeyNodeFactory.cpp
index 559d695..e308271 100644
--- a/src/osgEarthDrivers/engine_mp/KeyNodeFactory.cpp
+++ b/src/osgEarthDrivers/engine_mp/KeyNodeFactory.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -18,8 +18,7 @@
 */
 #include "KeyNodeFactory"
 
-using namespace osgEarth_engine_mp;
-using namespace osgEarth;
+using namespace osgEarth::Drivers::MPTerrainEngine;
 
 #define LC "[KeyNodeFactory] "
 
diff --git a/src/osgEarthDrivers/engine_mp/MPGeometry b/src/osgEarthDrivers/engine_mp/MPGeometry
index ab250c5..23a58ca 100644
--- a/src/osgEarthDrivers/engine_mp/MPGeometry
+++ b/src/osgEarthDrivers/engine_mp/MPGeometry
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -16,8 +16,8 @@
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
-#ifndef OSGEARTH_ENGINE_MP_MPGEOMETRY
-#define OSGEARTH_ENGINE_MP_MPGEOMETRY 1
+#ifndef OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_MPGEOMETRY
+#define OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_MPGEOMETRY 1
 
 #include "Common"
 #include "TileNode"
@@ -27,10 +27,9 @@
 #include <osgEarth/Map>
 #include <osgEarth/MapFrame>
 
-
 using namespace osgEarth;
 
-namespace osgEarth_engine_mp
+namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 {
     /**
      * A Geometry that will draw its primitive sets multiple time,
@@ -89,6 +88,8 @@ namespace osgEarth_engine_mp
         int _imageUnit;          // image unit for primary texture
         int _imageUnitParent;    // image unit for secondary (parent) texture
 
+        bool _supportsGLSL;
+
     public:
         
         // construct a new MPGeometry.
@@ -98,8 +99,10 @@ namespace osgEarth_engine_mp
         void setParentImageUnit(int value) { _imageUnitParent = value; }
 
         // render all passes of the geometry.
-        void renderPrimitiveSets(osg::State& state, bool usingVBOs) const;
+        void renderPrimitiveSets(osg::State& state, bool renderColor, bool usingVBOs) const;
 
+        // validate the geometry is OK.
+        void validate();
 
     public: // osg::Geometry overrides
 
@@ -116,7 +119,11 @@ namespace osgEarth_engine_mp
         void drawImplementation(osg::RenderInfo& renderInfo) const;
 
         // recalculate the bound for the tile key uniform.
+#if OSG_VERSION_GREATER_THAN(3,3,1)
+        osg::BoundingBox computeBoundingBox() const;
+#else
         osg::BoundingBox computeBound() const;
+#endif
 
     public:
         META_Object(osgEarth, MPGeometry);
@@ -125,6 +132,6 @@ namespace osgEarth_engine_mp
         virtual ~MPGeometry() { }
     };
 
-} // namespace osgEarth_engine_mp
+} } } // namespace osgEarth::Drivers::MPTerrainEngine
 
-#endif // OSGEARTH_ENGINE_MP_MPGEOMETRY
+#endif // OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_MPGEOMETRY
diff --git a/src/osgEarthDrivers/engine_mp/MPGeometry.cpp b/src/osgEarthDrivers/engine_mp/MPGeometry.cpp
index 46526c1..763dfff 100644
--- a/src/osgEarthDrivers/engine_mp/MPGeometry.cpp
+++ b/src/osgEarthDrivers/engine_mp/MPGeometry.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -21,23 +21,25 @@
 #include <osg/Version>
 #include <osgUtil/MeshOptimizers>
 #include <iterator>
+#include <osgEarth/Registry>
+#include <osgEarth/Capabilities>
+
+#include <osgUtil/IncrementalCompileOperation>
 
 using namespace osg;
-using namespace osgEarth_engine_mp;
+using namespace osgEarth::Drivers::MPTerrainEngine;
 using namespace osgEarth;
 
 #define LC "[MPGeometry] "
 
 
-//----------------------------------------------------------------------------
-
-//osg::buffered_object<MPGeometry::PerGC> MPGeometry::_perGC;
-
 MPGeometry::MPGeometry(const TileKey& key, const MapFrame& frame, int imageUnit) : 
 osg::Geometry    ( ),
 _frame           ( frame ),
 _imageUnit       ( imageUnit )
 {
+    _supportsGLSL = Registry::capabilities().supportsGLSL();
+
     unsigned tw, th;
     key.getProfile()->getNumTiles(key.getLOD(), tw, th);
     _tileKeyValue.set( key.getTileX(), th-key.getTileY()-1.0f, key.getLOD(), -1.0f );
@@ -52,13 +54,15 @@ _imageUnit       ( imageUnit )
     _opacityUniformNameID      = osg::Uniform::getNameID( "oe_layer_opacity" );
     _texMatParentUniformNameID = osg::Uniform::getNameID( "oe_layer_parent_matrix" );
 
-    this->setUseVertexBufferObjects(true);
+    // we will set these later (in TileModelCompiler)
+    this->setUseVertexBufferObjects(false);
     this->setUseDisplayList(false);
 }
 
 
 void
 MPGeometry::renderPrimitiveSets(osg::State& state,
+                                bool        renderColor,
                                 bool        usingVBOs) const
 {
     // check the map frame to see if it's up to date
@@ -87,20 +91,26 @@ MPGeometry::renderPrimitiveSets(osg::State& state,
 
     unsigned layersDrawn = 0;
 
-
     // access the GL extensions interface for the current GC:
-    unsigned contextID = state.getContextID();
-    osg::ref_ptr<osg::GL2Extensions> ext = osg::GL2Extensions::Get( contextID, true );
-    const osg::Program::PerContextProgram* pcp = state.getLastAppliedProgramObject();
+    const osg::Program::PerContextProgram* pcp = 0L;
+    osg::ref_ptr<osg::GL2Extensions> ext;
+    unsigned contextID;
+
+    if (_supportsGLSL)
+    {
+        contextID = state.getContextID();
+        ext = osg::GL2Extensions::Get( contextID, true );
+        pcp = state.getLastAppliedProgramObject();
+    }
 
     // cannot store these in the object since there could be multiple GCs (and multiple
     // PerContextPrograms) at large
-    GLint tileKeyLocation;
-    GLint birthTimeLocation;
-    GLint opacityLocation;
-    GLint uidLocation;
-    GLint orderLocation;
-    GLint texMatParentLocation;
+    GLint tileKeyLocation       = -1;
+    GLint birthTimeLocation     = -1;
+    GLint opacityLocation       = -1;
+    GLint uidLocation           = -1;
+    GLint orderLocation         = -1;
+    GLint texMatParentLocation  = -1;
 
     // The PCP can change (especially in a VirtualProgram environment). So we do need to
     // requery the uni locations each time unfortunately. TODO: explore optimizations.
@@ -136,11 +146,17 @@ MPGeometry::renderPrimitiveSets(osg::State& state,
     }
 
     // activate the tile coordinate set - same for all layers
-    state.setTexCoordPointer( _imageUnit+1, _tileCoords.get() );
+    if ( renderColor )
+    {
+        state.setTexCoordPointer( _imageUnit+1, _tileCoords.get() );
+    }
 
 #ifndef OSG_GLES2_AVAILABLE
-    // emit a default terrain color since we're not binding a color array:
-    glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+    if ( renderColor )
+    {
+        // emit a default terrain color since we're not binding a color array:
+        glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+    }
 #endif
 
     if ( _layers.size() > 0 )
@@ -148,24 +164,30 @@ MPGeometry::renderPrimitiveSets(osg::State& state,
         float prev_opacity        = -1.0f;
         float prev_alphaThreshold = -1.0f;
 
-        // first bind any shared layers
-        // TODO: optimize by pre-storing shared indexes
-        for(unsigned i=0; i<_layers.size(); ++i)
+        // first bind any shared layers. We still have to do this even if we are
+        // in !renderColor mode b/c these textures could be used by vertex shaders
+        // to alter the geometry.
+        int sharedLayers = 0;
+        if ( _supportsGLSL )
         {
-            const Layer& layer = _layers[i];
-
-            // a "shared" layer binds to a secondary texture unit so that other layers
-            // can see it and use it.
-            if ( layer._imageLayer->isShared() )
+            for(unsigned i=0; i<_layers.size(); ++i)
             {
-                int sharedUnit = layer._imageLayer->shareImageUnit().get();
+                const Layer& layer = _layers[i];
+
+                // a "shared" layer binds to a secondary texture unit so that other layers
+                // can see it and use it.
+                if ( layer._imageLayer->isShared() )
                 {
-                    state.setActiveTextureUnit( sharedUnit );
-                    state.setTexCoordPointer( sharedUnit, layer._texCoords.get() );
-                    // bind the texture for this layer to the active share unit.
-                    layer._tex->apply( state );
+                    ++sharedLayers;
+                    int sharedUnit = layer._imageLayer->shareImageUnit().get();
+                    {
+                        state.setActiveTextureUnit( sharedUnit );
+                        state.setTexCoordPointer( sharedUnit, layer._texCoords.get() );
+                        // bind the texture for this layer to the active share unit.
+                        layer._tex->apply( state );
 
-                    // no texture LOD blending for shared layers for now. maybe later.
+                        // no texture LOD blending for shared layers for now. maybe later.
+                    }
                 }
             }
         }
@@ -173,90 +195,106 @@ MPGeometry::renderPrimitiveSets(osg::State& state,
         // track the active image unit.
         int activeImageUnit = -1;
 
-        // find the first opaque layer, top-down, and start there:
-        unsigned first = 0;
-        for(first = _layers.size()-1; first > 0; --first)
+        if (renderColor)
         {
-            const Layer& layer = _layers[first];
-            if (layer._opaque && 
-                layer._imageLayer->getVisible() &&
-                layer._imageLayer->getOpacity() >= 1.0f)
+            // find the first opaque layer, top-down, and start there:
+            unsigned first = 0;
+            for(first = _layers.size()-1; first > 0; --first)
             {
-                break;
-            }
-        }
-
-        // interate over all the image layers
-        for(unsigned i=first; i<_layers.size(); ++i)
-        {
-            const Layer& layer = _layers[i];
-
-            if ( layer._imageLayer->getVisible() )
-            {       
-                // activate the visible unit if necessary:
-                if ( activeImageUnit != _imageUnit )
+                const Layer& layer = _layers[first];
+                if (layer._opaque && 
+                    layer._imageLayer->getVisible() &&
+                    layer._imageLayer->getOpacity() >= 1.0f)
                 {
-                    state.setActiveTextureUnit( _imageUnit );
-                    activeImageUnit = _imageUnit;
+                    break;
                 }
+            }
 
-                // bind the texture for this layer:
-                layer._tex->apply( state );
+            // interate over all the image layers
+            for(unsigned i=first; i<_layers.size(); ++i)
+            {
+                const Layer& layer = _layers[i];
 
-                // if we're using a parent texture for blending, activate that now
-                if ( texMatParentLocation >= 0 && layer._texParent.valid() )
-                {
-                    state.setActiveTextureUnit( _imageUnitParent );
-                    activeImageUnit = _imageUnitParent;
-                    layer._texParent->apply( state );
-                }
+                if ( layer._imageLayer->getVisible() && layer._imageLayer->getOpacity() > 0.0f )
+                {       
+                    // activate the visible unit if necessary:
+                    if ( activeImageUnit != _imageUnit )
+                    {
+                        state.setActiveTextureUnit( _imageUnit );
+                        activeImageUnit = _imageUnit;
+                    }
 
-                // bind the texture coordinates for this layer.
-                // TODO: can probably optimize this by sharing or using texture matrixes.
-                // State::setTexCoordPointer does some redundant work under the hood.
-                state.setTexCoordPointer( _imageUnit, layer._texCoords.get() );
+                    // bind the texture for this layer:
+                    layer._tex->apply( state );
 
-                // apply uniform values:
-                if ( pcp )
-                {
-                    // apply opacity:
-                    if ( opacityLocation >= 0 )
+                    // in FFP mode, we need to enable the GL mode for texturing:
+                    if (!_supportsGLSL)
                     {
-                        float opacity = layer._imageLayer->getOpacity();
-                        if ( opacity != prev_opacity )
-                        {
-                            ext->glUniform1f( opacityLocation, (GLfloat)opacity );
-                            prev_opacity = opacity;
-                        }
+                        state.applyMode(GL_TEXTURE_2D, true);
                     }
 
-                    // assign the layer UID:
-                    if ( uidLocation >= 0 )
+                    // if we're using a parent texture for blending, activate that now
+                    if ( texMatParentLocation >= 0 && layer._texParent.valid() )
                     {
-                        ext->glUniform1i( uidLocation, (GLint)layer._layerID );
+                        state.setActiveTextureUnit( _imageUnitParent );
+                        activeImageUnit = _imageUnitParent;
+                        layer._texParent->apply( state );
                     }
 
-                    // assign the layer order:
-                    if ( orderLocation >= 0 )
+                    // bind the texture coordinates for this layer.
+                    // TODO: can probably optimize this by sharing or using texture matrixes.
+                    // State::setTexCoordPointer does some redundant work under the hood.
+                    state.setTexCoordPointer( _imageUnit, layer._texCoords.get() );
+
+                    // apply uniform values:
+                    if ( pcp )
                     {
-                        ext->glUniform1i( orderLocation, (GLint)layersDrawn );
+                        // apply opacity:
+                        if ( opacityLocation >= 0 )
+                        {
+                            float opacity = layer._imageLayer->getOpacity();
+                            if ( opacity != prev_opacity )
+                            {
+                                ext->glUniform1f( opacityLocation, (GLfloat)opacity );
+                                prev_opacity = opacity;
+                            }
+                        }
+
+                        // assign the layer UID:
+                        if ( uidLocation >= 0 )
+                        {
+                            ext->glUniform1i( uidLocation, (GLint)layer._layerID );
+                        }
+
+                        // assign the layer order:
+                        if ( orderLocation >= 0 )
+                        {
+                            ext->glUniform1i( orderLocation, (GLint)layersDrawn );
+                        }
+
+                        // assign the parent texture matrix
+                        if ( texMatParentLocation >= 0 && layer._texParent.valid() )
+                        {
+                            ext->glUniformMatrix4fv( texMatParentLocation, 1, GL_FALSE, layer._texMatParent.ptr() );
+                        }
                     }
 
-                    // assign the parent texture matrix
-                    if ( texMatParentLocation >= 0 && layer._texParent.valid() )
+                    // draw the primitive sets.
+                    for(unsigned int primitiveSetNum=0; primitiveSetNum!=_primitives.size(); ++primitiveSetNum)
                     {
-                        ext->glUniformMatrix4fv( texMatParentLocation, 1, GL_FALSE, layer._texMatParent.ptr() );
+                        const osg::PrimitiveSet* primitiveset = _primitives[primitiveSetNum].get();
+                        if ( primitiveset )
+                        {
+                            primitiveset->draw(state, usingVBOs);
+                        }
+                        else
+                        {
+                            OE_WARN << LC << "Strange, MPGeometry had a 0L primset" << std::endl;
+                        }
                     }
-                }
 
-                // draw the primitive sets.
-                for(unsigned int primitiveSetNum=0; primitiveSetNum!=_primitives.size(); ++primitiveSetNum)
-                {
-                    const osg::PrimitiveSet* primitiveset = _primitives[primitiveSetNum].get();
-                    primitiveset->draw(state, usingVBOs);
+                    ++layersDrawn;
                 }
-
-                ++layersDrawn;
             }
         }
     }
@@ -283,25 +321,77 @@ MPGeometry::renderPrimitiveSets(osg::State& state,
     {
         // prevent texture leakage
         // TODO: find a way to remove this to speed things up
-        glBindTexture( GL_TEXTURE_2D, 0 );
+        if ( renderColor )
+        {
+            glBindTexture( GL_TEXTURE_2D, 0 );
+        }
     }
 }
 
+#if OSG_VERSION_GREATER_OR_EQUAL(3,3,2)
+#    define COMPUTE_BOUND computeBoundingBox
+#else
+#    define COMPUTE_BOUND computeBound
+#endif
+
+#if OSG_VERSION_GREATER_OR_EQUAL(3,1,8)
+#   define GET_ARRAY(a) (a)
+#else
+#   define GET_ARRAY(a) (a).array
+#endif
 
 osg::BoundingBox
-MPGeometry::computeBound() const
+MPGeometry:: COMPUTE_BOUND() const
 {
-    osg::BoundingBox bbox = osg::Geometry::computeBound();
+    osg::BoundingBox bbox = osg::Geometry:: COMPUTE_BOUND ();
     {
         // update the uniform.
         Threading::ScopedMutexLock exclusive(_frameSyncMutex);
-        osg::BoundingSphere bs(bbox);
-        osg::Vec4f tk;
-        _tileKeyValue.w() = bs.radius();
+        _tileKeyValue.w() = bbox.radius();
     }
     return bbox;
 }
 
+void
+MPGeometry::validate()
+{
+    unsigned numVerts = getVertexArray()->getNumElements();
+
+    for(unsigned i=0; i < _primitives.size(); ++i)
+    {
+        osg::DrawElements* de = static_cast<osg::DrawElements*>(_primitives[i].get());
+        if ( de->getMode() != GL_TRIANGLES )
+        {
+            OE_WARN << LC << "Invalid primitive set - not GL_TRIANGLES" << std::endl;
+            _primitives.clear();
+        }
+
+        else if ( de->getNumIndices() % 3 != 0 )
+        {
+            OE_WARN << LC << "Invalid primitive set - wrong number of indicies" << std::endl;
+            //_primitives.clear();
+            osg::DrawElementsUShort* deus = static_cast<osg::DrawElementsUShort*>(de);
+            int extra = de->getNumIndices() % 3;
+            deus->resize(de->getNumIndices() - extra);
+            OE_WARN << LC << "   ..removed " << extra << " indices" << std::endl;
+            //return;
+        }
+        else
+        {
+            for( unsigned j=0; j<de->getNumIndices(); ++j ) 
+            {
+                unsigned index = de->index(j);
+                if ( index >= numVerts )
+                {
+                    OE_WARN << LC << "Invalid primitive set - index out of bounds" << std::endl;
+                    _primitives.clear();
+                    return;
+                }
+            }
+        }
+    }
+}
+
 
 void 
 MPGeometry::releaseGLObjects(osg::State* state) const
@@ -342,28 +432,88 @@ MPGeometry::resizeGLObjectBuffers(unsigned maxSize)
     }
 }
 
+namespace
+{
+    void compileBufferObject(BufferObject* bo, unsigned contextID)
+    {
+        if ( bo )
+        {
+            GLBufferObject* glBufferObject = bo->getOrCreateGLBufferObject(contextID);
+            if (glBufferObject && glBufferObject->isDirty())
+            {
+                glBufferObject->compileBuffer();
+            }
+        }
+    }
+    void compileBufferObject(osg::Array* a, unsigned contextID)
+    {
+        if ( a )
+        {
+            compileBufferObject( a->getBufferObject(), contextID );
+        }
+    }
+}
+
 
 void 
 MPGeometry::compileGLObjects( osg::RenderInfo& renderInfo ) const
 {
-    osg::Geometry::compileGLObjects( renderInfo );
+    //osg::Geometry::compileGLObjects( renderInfo );
+    
+    State& state = *renderInfo.getState();
+    unsigned contextID = state.getContextID();
+    GLBufferObject::Extensions* extensions = GLBufferObject::getExtensions(contextID, true);
+    if (!extensions)
+        return;
+
+    MPGeometry* ncthis = const_cast<MPGeometry*>(this);
 
+    //ncthis->validate();
+
+    compileBufferObject(ncthis->getVertexArray(), contextID);
+    compileBufferObject(ncthis->getNormalArray(), contextID);
+
+    for(unsigned i=0; i<getVertexAttribArrayList().size(); ++i) 
+    {
+        osg::Array* a = GET_ARRAY( getVertexAttribArrayList()[i] ).get();
+        compileBufferObject( a, contextID );
+    }
+    
+    for(PrimitiveSetList::const_iterator i = _primitives.begin(); i != _primitives.end(); ++i )
+    {
+        compileBufferObject( i->get()->getBufferObject(), contextID );
+    }
+    
+    // compile the layer-specific things:
     for(unsigned i=0; i<_layers.size(); ++i)
     {
         const Layer& layer = _layers[i];
+
+        compileBufferObject( layer._texCoords.get(), contextID );
+
         if ( layer._tex.valid() )
             layer._tex->apply( *renderInfo.getState() );
     }
+
+    // unbind the BufferObjects
+    extensions->glBindBuffer(GL_ARRAY_BUFFER_ARB,0);
+    extensions->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB,0);
 }
 
 
 void 
 MPGeometry::drawImplementation(osg::RenderInfo& renderInfo) const
 {
+    // See if this is a pre-render depth-only camera. If so we can skip all the layers
+    // and just render the primitive sets.
+    osg::Camera* camera = renderInfo.getCurrentCamera();
+    bool renderColor =
+        (camera->getRenderOrder() != osg::Camera::PRE_RENDER) ||
+        ((camera->getClearMask() & GL_COLOR_BUFFER_BIT) != 0L);
+
     osg::State& state = *renderInfo.getState();
 
-    bool usingVertexBufferObjects = _useVertexBufferObjects && state.isVertexBufferObjectSupported();
-    bool handleVertexAttributes = !_vertexAttribList.empty();
+    bool hasVertexAttributes = !_vertexAttribList.empty();
 
     osg::ArrayDispatchers& arrayDispatchers = state.getArrayDispatchers();
 
@@ -371,26 +521,19 @@ MPGeometry::drawImplementation(osg::RenderInfo& renderInfo) const
     arrayDispatchers.setUseVertexAttribAlias(state.getUseVertexAttributeAliasing());
 
 
-    //Remove 
+    //Remove?
 #if OSG_VERSION_LESS_THAN(3,1,8)
     arrayDispatchers.setUseGLBeginEndAdapter(false);
 #endif
 
-
 #if OSG_MIN_VERSION_REQUIRED(3,1,8)
     arrayDispatchers.activateNormalArray(_normalArray.get());
-    arrayDispatchers.activateColorArray(_colorArray.get());
-    arrayDispatchers.activateSecondaryColorArray(_secondaryColorArray.get());
-    arrayDispatchers.activateFogCoordArray(_fogCoordArray.get());
 #else
     arrayDispatchers.activateNormalArray(_normalData.binding, _normalData.array.get(), _normalData.indices.get());
-    arrayDispatchers.activateColorArray(_colorData.binding, _colorData.array.get(), _colorData.indices.get());
-    arrayDispatchers.activateSecondaryColorArray(_secondaryColorData.binding, _secondaryColorData.array.get(), _secondaryColorData.indices.get());
-    arrayDispatchers.activateFogCoordArray(_fogCoordData.binding, _fogCoordData.array.get(), _fogCoordData.indices.get());
 #endif
     
 
-    if (handleVertexAttributes)
+    if (hasVertexAttributes)
     {
         for(unsigned int unit=0;unit<_vertexAttribList.size();++unit)
         {
@@ -414,47 +557,15 @@ MPGeometry::drawImplementation(osg::RenderInfo& renderInfo) const
 
     if (_normalArray.valid() && _normalArray->getBinding()==osg::Array::BIND_PER_VERTEX)
         state.setNormalPointer(_normalArray.get());
-
-    if (_colorArray.valid() && _colorArray->getBinding()==osg::Array::BIND_PER_VERTEX)
-        state.setColorPointer(_colorArray.get());
-
-    if (_secondaryColorArray.valid() && _secondaryColorArray->getBinding()==osg::Array::BIND_PER_VERTEX)
-        state.setSecondaryColorPointer(_secondaryColorArray.get());
-
-    if (_fogCoordArray.valid() && _fogCoordArray->getBinding()==osg::Array::BIND_PER_VERTEX)
-        state.setFogCoordPointer(_fogCoordArray.get());
 #else
     if( _vertexData.array.valid() )
         state.setVertexPointer(_vertexData.array.get());
 
     if (_normalData.binding==BIND_PER_VERTEX && _normalData.array.valid())
         state.setNormalPointer(_normalData.array.get());
+#endif
 
-    if (_colorData.binding==BIND_PER_VERTEX && _colorData.array.valid())
-        state.setColorPointer(_colorData.array.get());
-
-    if (_secondaryColorData.binding==BIND_PER_VERTEX && _secondaryColorData.array.valid())
-        state.setSecondaryColorPointer(_secondaryColorData.array.get());
-
-    if (_fogCoordData.binding==BIND_PER_VERTEX && _fogCoordData.array.valid())
-        state.setFogCoordPointer(_fogCoordData.array.get());
-#endif    
-        
-    for(unsigned int unit=0;unit<_texCoordList.size();++unit)
-    {
-#if OSG_MIN_VERSION_REQUIRED( 3, 1, 8)
-        const Array* array = _texCoordList[unit].get();
-        if (array)
-        {
-            state.setTexCoordPointer(unit,array);
-        }
-#else
-        const osg::Array* array = _texCoordList[unit].array.get();
-        if (array) state.setTexCoordPointer(unit,array);
-#endif        
-    }
-
-    if( handleVertexAttributes )
+    if( hasVertexAttributes )
     {
         for(unsigned int index = 0; index < _vertexAttribList.size(); ++index )
         {
@@ -488,7 +599,7 @@ MPGeometry::drawImplementation(osg::RenderInfo& renderInfo) const
     state.applyDisablingOfVertexAttributes();
 
     // draw the multipass geometry.
-    renderPrimitiveSets(state, usingVertexBufferObjects);
+    renderPrimitiveSets(state, renderColor, true);
 
     // unbind the VBO's if any are used.
     state.unbindVertexBufferObject();
diff --git a/src/osgEarthDrivers/engine_mp/MPTerrainEngineDriver.cpp b/src/osgEarthDrivers/engine_mp/MPTerrainEngineDriver.cpp
index e2e9996..77ba70c 100644
--- a/src/osgEarthDrivers/engine_mp/MPTerrainEngineDriver.cpp
+++ b/src/osgEarthDrivers/engine_mp/MPTerrainEngineDriver.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,160 +21,225 @@
 #include "TileNode"
 #include <osgEarth/Registry>
 #include <osgEarth/Progress>
+#include <osgEarth/Utils>
 #include <osgDB/FileNameUtils>
 #include <osgDB/FileUtils>
 #include <osgDB/Registry>
 #include <string>
+#include <sstream>
 
 #define LC "[engine_mp driver] "
 
-
-using namespace osgEarth::Drivers;
-using namespace osgEarth_engine_mp;
-
-/**
- * osgEarth driver for the MP terrain engine.
- */
-class osgEarth_MPTerrainEngineDriver : public osgDB::ReaderWriter
+namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 {
-public:
-    osgEarth_MPTerrainEngineDriver() { }
-
-    virtual const char* className()
+    /**
+     * osgEarth driver for the MP terrain engine.
+     */
+    class MPTerrainEngineDriver : public osgDB::ReaderWriter
     {
-        return "osgEarth MP Terrain Engine";
-    }
+    public:
+        int _profiling;
 
-    virtual bool acceptsExtension(const std::string& extension) const
-    {
-        return
-            osgDB::equalCaseInsensitive( extension, "osgearth_engine_mp" ) ||
-            osgDB::equalCaseInsensitive( extension, "osgearth_engine_mp_tile" ) ||
-            osgDB::equalCaseInsensitive( extension, "osgearth_engine_mp_standalone_tile" );
-    }
+        MPTerrainEngineDriver()
+        {
+            _profiling = 0;
+            const char* p = ::getenv("OSGEARTH_MP_PROFILE");
+            if ( p )
+                _profiling = as<int>(std::string(p), 1);
+        }
 
-    virtual ReadResult readObject(const std::string& uri, const Options* options) const
-    {
-        if ( "osgearth_engine_mp" == osgDB::getFileExtension( uri ) )
+        virtual const char* className()
+        {
+            return "osgEarth MP Terrain Engine";
+        }
+
+        virtual bool acceptsExtension(const std::string& extension) const
+        {
+            return
+                osgDB::equalCaseInsensitive( extension, "osgearth_engine_mp" ) ||
+                osgDB::equalCaseInsensitive( extension, "osgearth_engine_mp_tile" ) ||
+                osgDB::equalCaseInsensitive( extension, "osgearth_engine_mp_standalone_tile" );
+        }
+
+        virtual ReadResult readObject(const std::string& uri, const Options* options) const
         {
-            if ( "earth" == osgDB::getNameLessExtension( osgDB::getFileExtension( uri ) ) )
+            if ( "osgearth_engine_mp" == osgDB::getFileExtension( uri ) )
             {
-                return readNode( uri, options );
+                if ( "earth" == osgDB::getNameLessExtension( osgDB::getFileExtension( uri ) ) )
+                {
+                    return readNode( uri, options );
+                }
+                else
+                {
+                    MPTerrainEngineOptions terrainOpts;
+                    OE_INFO << LC << "Activated!" << std::endl;
+                    return ReadResult( new MPTerrainEngineNode() );
+                }
             }
             else
             {
-                MPTerrainEngineOptions terrainOpts;
-                OE_INFO << LC << "Activated!" << std::endl;
-                return ReadResult( new MPTerrainEngineNode() );
+                return readNode( uri, options );
             }
-        }
-        else
-        {
-            return readNode( uri, options );
-        }
-    }    
+        }    
 
-    virtual ReadResult readNode(const std::string& uri, const Options* options) const
-    {
-        std::string ext = osgDB::getFileExtension(uri);
-        if ( acceptsExtension(ext) )
+        virtual ReadResult readNode(const std::string& uri, const Options* options) const
         {
-            // See if the filename starts with server: and strip it off.  This will trick OSG
-            // into passing in the filename to our plugin instead of using the CURL plugin if
-            // the filename contains a URL.  So, if you want to read a URL, you can use the
-            // following format: osgDB::readNodeFile("server:http://myserver/myearth.earth").
-            // This should only be necessary for the first level as the other files will have
-            // a tilekey prepended to them.
-            if ((uri.length() > 7) && (uri.substr(0, 7) == "server:"))
-                return readNode(uri.substr(7), options);
-
-            // parse the tile key and engine ID:
-            std::string tileDef = osgDB::getNameLessExtension(uri);
-            unsigned int lod, x, y, engineID;
-            sscanf(tileDef.c_str(), "%d/%d/%d.%d", &lod, &x, &y, &engineID);
-
-            // find the appropriate engine:
-            osg::ref_ptr<MPTerrainEngineNode> engineNode;
-            MPTerrainEngineNode::getEngineByUID( (UID)engineID, engineNode );
-            if ( engineNode.valid() )
+            std::string ext = osgDB::getFileExtension(uri);
+            if ( acceptsExtension(ext) )
             {
-                osg::Timer_t start = osg::Timer::instance()->tick();
+                // See if the filename starts with server: and strip it off.  This will trick OSG
+                // into passing in the filename to our plugin instead of using the CURL plugin if
+                // the filename contains a URL.  So, if you want to read a URL, you can use the
+                // following format: osgDB::readNodeFile("server:http://myserver/myearth.earth").
+                // This should only be necessary for the first level as the other files will have
+                // a tilekey prepended to them.
+                if ((uri.length() > 7) && (uri.substr(0, 7) == "server:"))
+                    return readNode(uri.substr(7), options);
+
+                // parse the tile key and engine ID:
+                std::string tileDef = osgDB::getNameLessExtension(uri);
+                unsigned int lod, x, y, engineID;
+                sscanf(tileDef.c_str(), "%d/%d/%d.%d", &lod, &x, &y, &engineID);
+
+                // find the appropriate engine:
+                osg::ref_ptr<MPTerrainEngineNode> engineNode;
+                MPTerrainEngineNode::getEngineByUID( (UID)engineID, engineNode );
+                if ( engineNode.valid() )
+                {
+                    Registry::instance()->startActivity(uri);
 
-                // see if we have a progress tracker
-                ProgressCallback* progress = 
-                    options ? const_cast<ProgressCallback*>(
-                    dynamic_cast<const ProgressCallback*>(options->getUserData())) : 0L;
+                    OE_START_TIMER(tileLoadTime);
 
-                // assemble the key and create the node:
-                const Profile* profile = engineNode->getMap()->getProfile();
-                TileKey key( lod, x, y, profile );
+                    // see if we have a progress tracker
+                    ProgressCallback* progress = 
+                        options ? const_cast<ProgressCallback*>(
+                        dynamic_cast<const ProgressCallback*>(options->getUserData())) : 0L;
 
-                osg::ref_ptr<osg::Node> node;
+                    // must have a ProgressCallback if we're profiling.
+                    bool ownProgress = (progress == 0L);
+                    if ( !progress && _profiling )
+                        progress = new ProgressCallback();
 
-                if ( "osgearth_engine_mp_tile" == ext )
-                {
-                    node = engineNode->createNode(key, progress);
-                }
-                else if ( "osgearth_engine_mp_standalone_tile" == ext )
-                {
-                    node = engineNode->createStandaloneNode(key, progress);
-                }
+                    // assemble the key and create the node:
+                    const Profile* profile = engineNode->getMap()->getProfile();
+                    TileKey key( lod, x, y, profile );
 
+                    osg::ref_ptr<osg::Node> node;
 
-#if 0
-                osg::Timer_t end = osg::Timer::instance()->tick();
+                    if ( "osgearth_engine_mp_tile" == ext )
+                    {
+                        node = engineNode->createNode(key, progress);
+                    }
+                    else if ( "osgearth_engine_mp_standalone_tile" == ext )
+                    {
+                        node = engineNode->createStandaloneNode(key, progress);
+                    }
 
-                //if ( osgEarth::getNotifyLevel() >= osg::DEBUG_INFO )
-                {
-                    static Threading::Mutex s_statsMutex;
-                    static std::vector<double> s_times;
-                    Threading::ScopedMutexLock lock(s_statsMutex);
-                    s_times.push_back( osg::Timer::instance()->delta_s(start, end) );
-                    if ( s_times.size() % 50 == 0 )
+                    double tileLoadTime = OE_STOP_TIMER(tileLoadTime);
+                    if ( progress )
+                        progress->stats()["tile_load_time"] = tileLoadTime;
+
+                    // profiling level 1 = detailed stats about individual loads.
+                    if ( _profiling == 1 )
                     {
-                        double t = 0.0;
-                        for(unsigned i=0; i<s_times.size(); ++i)
-                            t += s_times[i];
-                        OE_NOTICE << LC << "Average time = " << (t/s_times.size()) << " s." << std::endl;
+                        progress->stats()["http_get_time_avg"] =
+                            progress->stats()["http_get_time"] / progress->stats()["http_get_count"];
+
+                        OE_NOTICE << "tile: " << tileDef << std::endl;
+                        for(std::map<std::string,double>::iterator i = progress->stats().begin();
+                            i != progress->stats().end();
+                            ++i)
+                        {
+                            std::stringstream buf;
+                            buf << i->first << " = " << std::setprecision(4) << i->second;
+                            if ( osgEarth::endsWith(i->first, "_time") )
+                                buf << " (" << (int)((i->second/tileLoadTime)*100) << "%)";
+                            OE_NOTICE << "   " << buf.str() << std::endl;
+                        }
+
+                        if ( ownProgress )
+                        {
+                            delete progress;
+                            progress = 0L;
+                        }
                     }
-                }
-#endif
 
-                
-                // Deal with failed loads.
-                if ( !node.valid() )
-                {
-                    if ( key.getLOD() == 0 || (progress && progress->isCanceled()) )
+                    // profiling level 2 = running 60-sample averages
+                    else if ( _profiling == 2 )
                     {
-                        // the tile will ask again next time.
-                        return ReadResult::FILE_NOT_FOUND;
+                        static std::deque<double> tileLoadTimes[3];
+                        static int    samples[3]       = { 64, 256, 1024 };
+                        static double runningTotals[3] = { 0.0, 0.0, 0.0 };
+                        //static int s0 = 60, s1 = 256, s2 = 1024;
+                        //static double runningTileLoadTime = 0.0;
+                        static Threading::Mutex averageMutex;
+
+                        averageMutex.lock();
+                        for(int i=0; i<3; ++i)
+                        {
+                            runningTotals[i] += tileLoadTime;
+                            tileLoadTimes[i].push_back( tileLoadTime );
+                            if ( tileLoadTimes[i].size() > samples[i] )
+                            {
+                                runningTotals[i] -= tileLoadTimes[i].front();
+                                tileLoadTimes[i].pop_front();
+                            }
+                        }
+                        OE_NOTICE << "(samples)time : "
+                            << "(" << samples[0] << "): " << (tileLoadTimes[0].size() == samples[0] ? (runningTotals[0]/(double)samples[0]) : -1.0) << "; "
+                            << "(" << samples[1] << "): " << (tileLoadTimes[1].size() == samples[1] ? (runningTotals[1]/(double)samples[1]) : -1.0) << "; "
+                            << "(" << samples[2] << "): " << (tileLoadTimes[2].size() == samples[2] ? (runningTotals[2]/(double)samples[2]) : -1.0) << "; "
+                            << std::endl;
+
+                        averageMutex.unlock();
                     }
-                    else
+                    
+                    Registry::instance()->endActivity(uri);
+
+                    // Deal with failed loads.
+                    if ( !node.valid() )
                     {
-                        // the parent tile will never ask again as long as it remains in memory.
-                        node = new InvalidTileNode( key );
+                        if ( key.getLOD() == 0  )
+                        {
+                            // the tile will ask again next time.
+                            return ReadResult::FILE_NOT_FOUND;
+                        }
+                        else if (progress && progress->isCanceled())
+                        {
+                            if ( _profiling )
+                            {
+                                OE_NOTICE << LC << "Tile " << key.str() << " -- canceled!" << std::endl;
+                            }
+                            return ReadResult::FILE_NOT_FOUND;
+                        }
+                        else
+                        {
+                            // the parent tile will never ask again as long as it remains in memory.
+                            node = new InvalidTileNode( key );
+                        }
+                    }
+                    else
+                    {   
+                        // notify the Terrain interface of a new tile
+                        osg::Timer_t start = osg::Timer::instance()->tick();
+                        engineNode->getTerrain()->notifyTileAdded(key, node.get());
+                        osg::Timer_t end = osg::Timer::instance()->tick();
                     }
+                    
+                    return ReadResult( node.get(), ReadResult::FILE_LOADED );
                 }
                 else
-                {   
-                    // notify the Terrain interface of a new tile
-                    osg::Timer_t start = osg::Timer::instance()->tick();
-                    engineNode->getTerrain()->notifyTileAdded(key, node.get());
-                    osg::Timer_t end = osg::Timer::instance()->tick();
+                {
+                    return ReadResult::FILE_NOT_FOUND;
                 }
-
-                return ReadResult( node.get(), ReadResult::FILE_LOADED );
             }
             else
             {
-                return ReadResult::FILE_NOT_FOUND;
+                return ReadResult::FILE_NOT_HANDLED;
             }
         }
-        else
-        {
-            return ReadResult::FILE_NOT_HANDLED;
-        }
-    }
-};
+    };
+
+    REGISTER_OSGPLUGIN(osgearth_engine_mp, MPTerrainEngineDriver);
 
-REGISTER_OSGPLUGIN(osgearth_engine_mp, osgEarth_MPTerrainEngineDriver)
+} } } // namespace osgEarth::Drivers::MPTerrainEngine
diff --git a/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode b/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode
index 4d2dbe0..1e172c5 100644
--- a/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode
+++ b/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -16,8 +16,8 @@
  * You should have received a copy of the GNU Lesser General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
-#ifndef OSGEARTH_ENGINE_MP_ENGINE_NODE_H
-#define OSGEARTH_ENGINE_MP_ENGINE_NODE_H 1
+#ifndef OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_ENGINE_NODE_H
+#define OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_ENGINE_NODE_H 1
 
 #include <osgEarth/TerrainEngineNode>
 #include <osgEarth/TextureCompositor>
@@ -37,7 +37,7 @@
 
 using namespace osgEarth;
 
-namespace osgEarth_engine_mp
+namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 {
     class MPTerrainEngineNode : public TerrainEngineNode
     {
@@ -57,12 +57,18 @@ namespace osgEarth_engine_mp
 
         // for standalone tile creation outside of a terrain
         osg::Node* createTile(const TileKey& key);
+        
+        // when incremental update is enabled, forces regeneration of tiles
+        // in the given region.
+        void invalidateRegion(
+            const GeoExtent& extent,
+            unsigned         minLevel,
+            unsigned         maxLevel);
 
     public: // internal TerrainEngineNode
 
         virtual void preInitialize( const Map* map, const TerrainOptions& options );
         virtual void postInitialize( const Map* map, const TerrainOptions& options );
-        virtual void validateTerrainOptions( TerrainOptions& options );
         virtual const TerrainOptions& getTerrainOptions() const { return _terrainOptions; }
         virtual osg::BoundingSphere computeBound() const;
 
@@ -96,7 +102,7 @@ namespace osgEarth_engine_mp
 
     protected:
         // override from TerrainEngineNode
-        virtual void updateTextureCombining() { updateShaders(); }
+        virtual void updateTextureCombining() { updateState(); }
 
     private:
         void init();
@@ -116,10 +122,10 @@ namespace osgEarth_engine_mp
         void moveImageLayer( unsigned int oldIndex, unsigned int newIndex );
         void moveElevationLayer( unsigned int oldIndex, unsigned int newIndex );
         
-        void updateShaders(); 
+        void updateState(); 
 
     private:
-        osgEarth::Drivers::MPTerrainEngineOptions _terrainOptions;
+        MPTerrainEngineOptions _terrainOptions;
 
         class TerrainNode* _terrain;
         UID                _uid;
@@ -128,7 +134,7 @@ namespace osgEarth_engine_mp
         Revision           _shaderLibRev;
         bool               _batchUpdateInProgress;
         bool               _refreshRequired;
-        bool               _shaderUpdateRequired;
+        bool               _stateUpdateRequired;
         bool               _rootTilesRegistered;
         Threading::Mutex   _rootTilesRegisteredMutex;
 
@@ -156,6 +162,6 @@ namespace osgEarth_engine_mp
         MPTerrainEngineNode( const MPTerrainEngineNode& rhs, const osg::CopyOp& op =osg::CopyOp::DEEP_COPY_ALL ) { }
     };
 
-} // namespace osgEarth_engine_mp
+} } } // namespace osgEarth::Drivers::MPTerrainEngine
 
-#endif // OSGEARTH_ENGINE_MP_ENGINE_NODE_H
+#endif // OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_ENGINE_NODE_H
diff --git a/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode.cpp b/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode.cpp
index d16a8f0..ad4d70d 100644
--- a/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode.cpp
+++ b/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -26,6 +26,7 @@
 #include <osgEarth/HeightFieldUtils>
 #include <osgEarth/ImageUtils>
 #include <osgEarth/Registry>
+#include <osgEarth/Capabilities>
 #include <osgEarth/VirtualProgram>
 #include <osgEarth/ShaderFactory>
 #include <osgEarth/MapModelChange>
@@ -41,7 +42,7 @@
 
 #define LC "[MPTerrainEngineNode] "
 
-using namespace osgEarth_engine_mp;
+using namespace osgEarth::Drivers::MPTerrainEngine;
 using namespace osgEarth;
 
 //------------------------------------------------------------------------
@@ -142,11 +143,11 @@ _terrain              ( 0L ),
 _update_mapf          ( 0L ),
 _tileCount            ( 0 ),
 _tileCreationTime     ( 0.0 ),
-_primaryUnit          ( 0 ),
-_secondaryUnit        ( 1 ),
+_primaryUnit          ( -1 ),
+_secondaryUnit        ( -1 ),
 _batchUpdateInProgress( false ),
 _refreshRequired      ( false ),
-_shaderUpdateRequired ( false )
+_stateUpdateRequired  ( false )
 {
     _uid = Registry::instance()->createUID();
 
@@ -168,10 +169,7 @@ void
 MPTerrainEngineNode::preInitialize( const Map* map, const TerrainOptions& options )
 {
     TerrainEngineNode::preInitialize( map, options );
-
-    // override the compositor technique because we want to do unit
-    // reservations but nothing else.
-    getTextureCompositor()->setTechnique( 0L );
+    //nop.
 }
 
 void
@@ -182,7 +180,7 @@ MPTerrainEngineNode::postInitialize( const Map* map, const TerrainOptions& optio
     // Initialize the map frames. We need one for the update thread and one for the
     // cull thread. Someday we can detect whether these are actually the same thread
     // (depends on the viewer's threading mode).
-    _update_mapf = new MapFrame( map, Map::MASKED_TERRAIN_LAYERS, "mp-update" );
+    _update_mapf = new MapFrame( map, Map::ENTIRE_MODEL, "mp-update" );
 
     // merge in the custom options:
     _terrainOptions.merge( options );
@@ -202,7 +200,6 @@ MPTerrainEngineNode::postInitialize( const Map* map, const TerrainOptions& optio
     }
     
     // initialize the model factory:
-    //_tileModelFactory = new TileModelFactory(getMap(), _liveTiles.get(), _terrainOptions );
     _tileModelFactory = new TileModelFactory(_liveTiles.get(), _terrainOptions );
 
     // handle an already-established map profile:
@@ -212,14 +209,6 @@ MPTerrainEngineNode::postInitialize( const Map* map, const TerrainOptions& optio
         onMapInfoEstablished( MapInfo(map) );
     }
 
-    // populate the terrain with whatever data is in the map to begin with:
-    if ( _terrain )
-    {
-        // reserve a GPU image unit and two attribute indexes.
-        this->getTextureCompositor()->reserveTextureImageUnit( _primaryUnit );
-        this->getTextureCompositor()->reserveTextureImageUnit( _secondaryUnit );
-    }
-
     // install a layer callback for processing further map actions:
     map->addMapCallback( new MPTerrainEngineNodeMapCallbackProxy(this) );
 
@@ -244,26 +233,45 @@ MPTerrainEngineNode::postInitialize( const Map* map, const TerrainOptions& optio
         osg::Uniform::FLOAT)->set( *_terrainOptions.minTileRangeFactor() );
 
     // set up the initial shaders
-    updateShaders();
+    updateState();
 
     // register this instance to the osgDB plugin can find it.
     registerEngine( this );
 
     // now that we have a map, set up to recompute the bounds
     dirtyBound();
+
+    OE_INFO << LC << "Edge normalization is " << (_terrainOptions.normalizeEdges() == true? "ON" : "OFF") << std::endl;
 }
 
 
 osg::BoundingSphere
 MPTerrainEngineNode::computeBound() const
 {
-    if ( _terrain && _terrain->getNumChildren() > 0 )
+    //if ( _terrain && _terrain->getNumChildren() > 0 )
+    //{
+    //    return _terrain->getBound();
+    //}
+    //else
     {
-        return _terrain->getBound();
+        return TerrainEngineNode::computeBound();
     }
-    else
+}
+
+void
+MPTerrainEngineNode::invalidateRegion(const GeoExtent& extent,
+                                      unsigned         minLevel,
+                                      unsigned         maxLevel)
+{
+    if (_terrainOptions.incrementalUpdate() == true && _liveTiles.valid())
     {
-        return TerrainEngineNode::computeBound();
+        GeoExtent extentLocal = extent;
+        if ( !extent.getSRS()->isEquivalentTo(this->getMap()->getSRS()) )
+        {
+            extent.transform(this->getMap()->getSRS(), extentLocal);
+        }
+        
+        _liveTiles->setDirty(extentLocal, minLevel, maxLevel);
     }
 }
 
@@ -298,8 +306,6 @@ MPTerrainEngineNode::refresh(bool forceDirty)
 void
 MPTerrainEngineNode::onMapInfoEstablished( const MapInfo& mapInfo )
 {
-    OE_INFO << LC << "Sample ratio = " << _terrainOptions.heightFieldSampleRatio().value() << std::endl;
-
     createTerrain();
 }
 
@@ -323,6 +329,16 @@ MPTerrainEngineNode::createTerrain()
         _terrain->getOrCreateStateSet()->setMode(GL_BLEND , osg::StateAttribute::ON);
     }
 
+    // reserve GPU space.
+    if ( _primaryUnit < 0 )
+    {
+        this->getTextureCompositor()->reserveTextureImageUnit( _primaryUnit );
+    }
+    if ( _secondaryUnit < 0 )
+    {
+        this->getTextureCompositor()->reserveTextureImageUnit( _secondaryUnit );
+    }
+
     // Factory to create the root keys:
     KeyNodeFactory* factory = getKeyNodeFactory();
 
@@ -343,7 +359,7 @@ MPTerrainEngineNode::createTerrain()
     unsigned child = 0;
     for( unsigned i=0; i<keys.size(); ++i )
     {
-        osg::ref_ptr<osg::Node> node = factory->createNode( keys[i], true, 0L );
+        osg::ref_ptr<osg::Node> node = factory->createNode( keys[i], true, true, 0L );
         if ( node.valid() )
         {
             root->addChild( node.get() );
@@ -359,7 +375,7 @@ MPTerrainEngineNode::createTerrain()
 
     _rootTilesRegistered = false;
 
-    updateShaders();
+    updateState();
 }
 
 namespace
@@ -385,7 +401,10 @@ MPTerrainEngineNode::traverse(osg::NodeVisitor& nv)
 {
     if ( nv.getVisitorType() == nv.CULL_VISITOR )
     {
-        // since the root tiles are manually added, the pager never has a change to 
+
+#if 0 // believe this is now unnecessary
+
+        // since the root tiles are manually added, the pager never has a chance to 
         // register the PagedLODs in their children. So we have to do it manually here.
         if ( !_rootTilesRegistered )
         {
@@ -402,6 +421,14 @@ MPTerrainEngineNode::traverse(osg::NodeVisitor& nv)
                 }
             }
         }
+#endif
+
+        // Inform the registry of the current frame so that Tiles have access
+        // to the information.
+        if ( _liveTiles.valid() && nv.getFrameStamp() )
+        {
+            _liveTiles->setTraversalFrame( nv.getFrameStamp()->getFrameNumber() );
+        }
     }
 
 #if 0
@@ -430,6 +457,7 @@ MPTerrainEngineNode::getKeyNodeFactory()
         // A compiler specific to this thread:
         TileModelCompiler* compiler = new TileModelCompiler(
             _update_mapf->terrainMaskLayers(),
+            _update_mapf->modelLayers(),
             _primaryUnit,
             optimizeTriangleOrientation,
             _terrainOptions );
@@ -442,11 +470,10 @@ MPTerrainEngineNode::getKeyNodeFactory()
             _liveTiles.get(),
             _deadTiles.get(),
             _terrainOptions,
-            _terrain, 
             _uid );
     }
 
-    return knf.get();
+    return knf.release();
 }
 
 osg::Node*
@@ -460,7 +487,7 @@ MPTerrainEngineNode::createNode(const TileKey&    key,
 
     OE_DEBUG << LC << "Create node for \"" << key.str() << "\"" << std::endl;
 
-    return getKeyNodeFactory()->createNode( key, true, progress );
+    return getKeyNodeFactory()->createNode( key, true, true, progress );
 }
 
 osg::Node*
@@ -474,14 +501,15 @@ MPTerrainEngineNode::createStandaloneNode(const TileKey&    key,
 
     OE_DEBUG << LC << "Create standalone node for \"" << key.str() << "\"" << std::endl;
 
-    return getKeyNodeFactory()->createNode( key, false, progress );
+    return getKeyNodeFactory()->createNode( key, true, false, progress );
 }
 
 osg::Node*
 MPTerrainEngineNode::createTile( const TileKey& key )
 {
-    // make a node, but don't include any subtile information
-    return getKeyNodeFactory()->createNode( key, false, 0L );
+    // make a node, but don't include any subtile information and don't
+    // accumulate data from parents.
+    return getKeyNodeFactory()->createNode( key, false, false, 0L );
 }
 
 
@@ -500,8 +528,8 @@ MPTerrainEngineNode::onMapModelChanged( const MapModelChange& change )
         if ( _refreshRequired )
             refresh();
 
-        if ( _shaderUpdateRequired )
-            updateShaders();
+        if ( _stateUpdateRequired )
+            updateState();
     }
 
     else
@@ -515,12 +543,6 @@ MPTerrainEngineNode::onMapModelChanged( const MapModelChange& change )
         // dispatch the change handler
         if ( change.getLayer() )
         {
-            // first inform the texture compositor with the new model changes:
-            if ( _texCompositor.valid() && change.getImageLayer() )
-            {
-                _texCompositor->applyMapModelChange( change );
-            }
-
             // then apply the actual change:
             switch( change.getAction() )
             {
@@ -607,7 +629,7 @@ MPTerrainEngineNode::removeImageLayer( ImageLayer* layerRemoved )
 void
 MPTerrainEngineNode::moveImageLayer( unsigned int oldIndex, unsigned int newIndex )
 {
-    updateShaders();
+    updateState();
 }
 
 void
@@ -641,263 +663,189 @@ MPTerrainEngineNode::toggleElevationLayer( ElevationLayer* layer )
     refresh();
 }
 
-void
-MPTerrainEngineNode::validateTerrainOptions( TerrainOptions& options )
-{
-    TerrainEngineNode::validateTerrainOptions( options );
-    
-    //nop for now.
-    //note: to validate plugin-specific features, we would create an MPTerrainEngineOptions
-    // and do the validation on that. You would then re-integrate it by calling
-    // options.mergeConfig( osgTerrainOptions ).
-}
-
 
 // Generates the main shader code for rendering the terrain.
 void
-MPTerrainEngineNode::updateShaders()
+MPTerrainEngineNode::updateState()
 {
     if ( _batchUpdateInProgress )
     {
-        _shaderUpdateRequired = true;
+        _stateUpdateRequired = true;
     }
     else
     {
         osg::StateSet* terrainStateSet = _terrain->getOrCreateStateSet();
+        
+        // required for multipass tile rendering to work
+        terrainStateSet->setAttributeAndModes(
+            new osg::Depth(osg::Depth::LEQUAL, 0, 1, true) );
 
-        VirtualProgram* vp = new VirtualProgram();
-        vp->setName( "osgEarth::engine_mp:TerrainNode" );
-        terrainStateSet->setAttributeAndModes( vp, osg::StateAttribute::ON );
-
-        // bind the vertex attributes generated by the tile compiler.
-        vp->addBindAttribLocation( "oe_terrain_attr",  osg::Drawable::ATTRIBUTE_6 );
-        vp->addBindAttribLocation( "oe_terrain_attr2", osg::Drawable::ATTRIBUTE_7 );
-
-        // Vertex shader:
-        std::string vs = Stringify() <<
-            "#version " GLSL_VERSION_STR "\n"
-            GLSL_DEFAULT_PRECISION_FLOAT "\n"
-            "varying vec4 oe_layer_texc;\n"
-            "varying vec4 oe_layer_tilec;\n"
-            "void oe_mp_setup_coloring(inout vec4 VertexModel) \n"
-            "{ \n"
-            "    oe_layer_texc  = gl_MultiTexCoord" << _primaryUnit << ";\n"
-            "    oe_layer_tilec = gl_MultiTexCoord" << _secondaryUnit << ";\n"
-            "}\n";
-
-        bool useTerrainColor = _terrainOptions.color().isSet();
-
-        bool useBlending = _terrainOptions.enableBlending() == true;
-
-        // Fragment Shader for normal blending:
-        std::string fs = Stringify() <<
-            "#version " GLSL_VERSION_STR "\n"
-            GLSL_DEFAULT_PRECISION_FLOAT "\n"
-            "varying vec4 oe_layer_texc; \n"
-            "uniform sampler2D oe_layer_tex; \n"
-            "uniform int oe_layer_uid; \n"
-            "uniform int oe_layer_order; \n"
-            "uniform float oe_layer_opacity; \n"
-            << (useTerrainColor ?
-            "uniform vec4 oe_terrain_color; \n" : ""
-            ) <<
-            "void oe_mp_apply_coloring(inout vec4 color) \n"
-            "{ \n"
-            << (useTerrainColor ?
-            "    color = oe_terrain_color; \n" : ""
-            ) <<
-            //"    color = vec4(1,1,1,1); \n"
-            "    vec4 texel; \n"
-            "    if ( oe_layer_uid >= 0 ) { \n"
-            "        texel = texture2D(oe_layer_tex, oe_layer_texc.st); \n"
-            "        texel.a *= oe_layer_opacity; \n"
-            "    } \n"
-            "    else \n"
-            "        texel = color; \n"
-            "    "
-            << (useBlending ?
-            "    if ( oe_layer_order == 0 ) \n"
-            "        color = texel*texel.a + color*(1.0-texel.a); \n" // simulate src_alpha, 1-src_alpha blens
-            "    else \n" : ""
-            ) <<
-            "        color = texel; \n"
-            "} \n";
-
-        // Fragment shader with pre-multiplied alpha blending:
-        std::string fs_pma = Stringify() <<
-            "#version " GLSL_VERSION_STR "\n"
-            GLSL_DEFAULT_PRECISION_FLOAT "\n"
-            "varying vec4 oe_layer_texc; \n"
-            "uniform sampler2D oe_layer_tex; \n"
-            "uniform int oe_layer_uid; \n"
-            "uniform int oe_layer_order; \n"
-            "uniform float oe_layer_opacity; \n"
-            << (useTerrainColor ?
-            "uniform vec4 oe_terrain_color; \n" : ""
-            ) <<
-
-            "void oe_mp_apply_coloring_pma( inout vec4 color ) \n"
-            "{ \n"
-            "    vec4 texelpma; \n"
-            << (useTerrainColor ?
-            "    color = oe_terrain_color; \n" : ""
-            ) <<
-
-            // a UID < 0 means no texture.
-            "    if ( oe_layer_uid >= 0 ) \n"
-            "        texelpma = texture2D(oe_layer_tex, oe_layer_texc.st) * oe_layer_opacity; \n"
-            "    else \n"
-            "        texelpma = color * color.a * oe_layer_opacity; \n" // to PMA.
-
-            // first layer must PMA-blend with the globe color.
-            << (useBlending ?
-            "    if (oe_layer_order == 0) { \n"
-            "        color.rgb *= color.a; \n"
-            "        color = texelpma + color*(1.0-texelpma.a); \n" // simulate one, 1-src_alpha blend
-            "    } \n" : ""
-            ) <<
-            "    else \n"
-            "        color = texelpma; \n"
-            "} \n";
-
-
-        // Color filter frag function:
-        std::string fs_colorfilters =
-            "#version " GLSL_VERSION_STR "\n"
-            GLSL_DEFAULT_PRECISION_FLOAT "\n"
-            "uniform int oe_layer_uid; \n"
-            "__COLOR_FILTER_HEAD__"
-            "void oe_mp_apply_filters(inout vec4 color) \n"
-            "{ \n"
-                "__COLOR_FILTER_BODY__"
-            "} \n";
-
-        vp->setFunction( "oe_mp_setup_coloring", vs, ShaderComp::LOCATION_VERTEX_MODEL, 0.0 );
-
-        if ( _terrainOptions.premultipliedAlpha() == true )
-            vp->setFunction( "oe_mp_apply_coloring_pma", fs_pma, ShaderComp::LOCATION_FRAGMENT_COLORING, 0.0 );
-        else
-            vp->setFunction( "oe_mp_apply_coloring", fs, ShaderComp::LOCATION_FRAGMENT_COLORING, 0.0 );
-
+        // activate standard mix blending.
+        terrainStateSet->setAttributeAndModes( 
+            new osg::BlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA),
+            osg::StateAttribute::ON );
 
-        // assemble color filter code snippets.
-        bool haveColorFilters = false;
+        // install shaders, if we're using them.
+        if ( Registry::capabilities().supportsGLSL() )
         {
-            std::stringstream cf_head;
-            std::stringstream cf_body;
-            const char* I = "    ";
-
-            if ( _terrainOptions.premultipliedAlpha() == true )
-            {
-                // un-PMA the color before passing it to the color filters.
-                cf_body << I << "if (color.a > 0.0) color.rgb /= color.a; \n";
-            }
+            VirtualProgram* vp = new VirtualProgram();
+            vp->setName( "osgEarth.engine_mp.TerrainNode" );
+            terrainStateSet->setAttributeAndModes( vp, osg::StateAttribute::ON );
+
+            // bind the vertex attributes generated by the tile compiler.
+            vp->addBindAttribLocation( "oe_terrain_attr",  osg::Drawable::ATTRIBUTE_6 );
+            vp->addBindAttribLocation( "oe_terrain_attr2", osg::Drawable::ATTRIBUTE_7 );
+
+            // Vertex shader:
+            std::string vs = Stringify() <<
+                "#version " GLSL_VERSION_STR "\n"
+                GLSL_DEFAULT_PRECISION_FLOAT "\n"
+                "varying vec4 oe_layer_texc;\n"
+                "varying vec4 oe_layer_tilec;\n"
+                "void oe_mp_setup_coloring(inout vec4 VertexModel) \n"
+                "{ \n"
+                "    oe_layer_texc  = gl_MultiTexCoord" << _primaryUnit << ";\n"
+                "    oe_layer_tilec = gl_MultiTexCoord" << _secondaryUnit << ";\n"
+                "}\n";
+
+            bool useTerrainColor = _terrainOptions.color().isSet();
+
+            bool useBlending = _terrainOptions.enableBlending() == true;
+
+            // Fragment Shader for normal blending:
+            std::string fs = Stringify() <<
+                "#version " GLSL_VERSION_STR "\n"
+                GLSL_DEFAULT_PRECISION_FLOAT "\n"
+                "varying vec4 oe_layer_texc; \n"
+                "uniform sampler2D oe_layer_tex; \n"
+                "uniform int oe_layer_uid; \n"
+                "uniform int oe_layer_order; \n"
+                "uniform float oe_layer_opacity; \n"
+                << (useTerrainColor ?
+                "uniform vec4 oe_terrain_color; \n" : ""
+                ) <<
+                "void oe_mp_apply_coloring(inout vec4 color) \n"
+                "{ \n"
+                << (useTerrainColor ?
+                "    color = oe_terrain_color; \n" : ""
+                ) <<
+                "    vec4 texel; \n"
+                "    if ( oe_layer_uid >= 0 ) { \n"
+                "        texel = texture2D(oe_layer_tex, oe_layer_texc.st); \n"
+                "        texel.a *= oe_layer_opacity; \n"
+                "    } \n"
+                "    else { \n"
+                "        texel = color; \n"
+                "    }\n"
+                << (useBlending ?
+                "    if ( oe_layer_order == 0 ) { \n"
+                "        color = texel*texel.a + color*(1.0-texel.a); \n" // simulate src_alpha, 1-src_alpha blens
+                "    } \n"
+                "    else \n" : ""
+                ) <<
+                "        color = texel; \n"
+                "} \n";
+
+            // Color filter frag function:
+            std::string fs_colorfilters =
+                "#version " GLSL_VERSION_STR "\n"
+                GLSL_DEFAULT_PRECISION_FLOAT "\n"
+                "uniform int oe_layer_uid; \n"
+                "__COLOR_FILTER_HEAD__"
+                "void oe_mp_apply_filters(inout vec4 color) \n"
+                "{ \n"
+                    "__COLOR_FILTER_BODY__"
+                "} \n";
+
+            vp->setFunction( "oe_mp_setup_coloring", vs, ShaderComp::LOCATION_VERTEX_MODEL, 0.0 );
+            vp->setFunction( "oe_mp_apply_coloring", fs, ShaderComp::LOCATION_FRAGMENT_COLORING, 0.0 );
 
-            // second, install the per-layer color filter functions AND shared layer bindings.
-            bool ifStarted = false;
-            int numImageLayers = _update_mapf->imageLayers().size();
-            for( int i=0; i<numImageLayers; ++i )
+            // assemble color filter code snippets.
+            bool haveColorFilters = false;
             {
-                ImageLayer* layer = _update_mapf->getImageLayerAt(i);
-                if ( layer->getEnabled() )
+                std::stringstream cf_head;
+                std::stringstream cf_body;
+                const char* I = "    ";
+
+                // second, install the per-layer color filter functions AND shared layer bindings.
+                bool ifStarted = false;
+                int numImageLayers = _update_mapf->imageLayers().size();
+                for( int i=0; i<numImageLayers; ++i )
                 {
-                    // install Color Filter function calls:
-                    const ColorFilterChain& chain = layer->getColorFilters();
-                    if ( chain.size() > 0 )
+                    ImageLayer* layer = _update_mapf->getImageLayerAt(i);
+                    if ( layer->getEnabled() )
                     {
-                        haveColorFilters = true;
-                        if ( ifStarted ) cf_body << I << "else if ";
-                        else             cf_body << I << "if ";
-                        cf_body << "(oe_layer_uid == " << layer->getUID() << ") {\n";
-                        for( ColorFilterChain::const_iterator j = chain.begin(); j != chain.end(); ++j )
+                        // install Color Filter function calls:
+                        const ColorFilterChain& chain = layer->getColorFilters();
+                        if ( chain.size() > 0 )
                         {
-                            const ColorFilter* filter = j->get();
-                            cf_head << "void " << filter->getEntryPointFunctionName() << "(inout vec4 color);\n";
-                            cf_body << I << I << filter->getEntryPointFunctionName() << "(color);\n";
-                            filter->install( terrainStateSet );
+                            haveColorFilters = true;
+                            if ( ifStarted ) cf_body << I << "else if ";
+                            else             cf_body << I << "if ";
+                            cf_body << "(oe_layer_uid == " << layer->getUID() << ") {\n";
+                            for( ColorFilterChain::const_iterator j = chain.begin(); j != chain.end(); ++j )
+                            {
+                                const ColorFilter* filter = j->get();
+                                cf_head << "void " << filter->getEntryPointFunctionName() << "(inout vec4 color);\n";
+                                cf_body << I << I << filter->getEntryPointFunctionName() << "(color);\n";
+                                filter->install( terrainStateSet );
+                            }
+                            cf_body << I << "}\n";
+                            ifStarted = true;
                         }
-                        cf_body << I << "}\n";
-                        ifStarted = true;
                     }
                 }
-            }
 
-            if ( _terrainOptions.premultipliedAlpha() == true )
-            {
-                // re-PMA the color after it passes through the color filters.
-                cf_body << I << "color.rgb *= color.a; \n";
-            }
-
-            if ( haveColorFilters )
-            {
-                std::string cf_head_str, cf_body_str;
-                cf_head_str = cf_head.str();
-                cf_body_str = cf_body.str();
+                if ( haveColorFilters )
+                {
+                    std::string cf_head_str, cf_body_str;
+                    cf_head_str = cf_head.str();
+                    cf_body_str = cf_body.str();
 
-                replaceIn( fs_colorfilters, "__COLOR_FILTER_HEAD__", cf_head_str );
-                replaceIn( fs_colorfilters, "__COLOR_FILTER_BODY__", cf_body_str );
+                    replaceIn( fs_colorfilters, "__COLOR_FILTER_HEAD__", cf_head_str );
+                    replaceIn( fs_colorfilters, "__COLOR_FILTER_BODY__", cf_body_str );
 
-                vp->setFunction( "oe_mp_apply_filters", fs_colorfilters, ShaderComp::LOCATION_FRAGMENT_COLORING, 0.0 );
+                    vp->setFunction( "oe_mp_apply_filters", fs_colorfilters, ShaderComp::LOCATION_FRAGMENT_COLORING, 0.0 );
+                }
             }
-        }
 
+            // binding for the terrain texture
+            terrainStateSet->getOrCreateUniform( 
+                "oe_layer_tex", osg::Uniform::SAMPLER_2D )->set( _primaryUnit );
 
+            // binding for the secondary texture (for LOD blending)
+            terrainStateSet->getOrCreateUniform(
+                "oe_layer_tex_parent", osg::Uniform::SAMPLER_2D )->set( _secondaryUnit );
 
-        if ( _terrainOptions.premultipliedAlpha() == true )
-        {
-            // activate PMA blending.
-            terrainStateSet->setAttributeAndModes( 
-                new osg::BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA),
-                osg::StateAttribute::ON );
-        }
-        else
-        {
-            // activate standard mix blending.
-            terrainStateSet->setAttributeAndModes( 
-                new osg::BlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA),
-                osg::StateAttribute::ON );
-        }
+            // binding for the default secondary texture matrix
+            osg::Matrixf parent_mat;
+            parent_mat(0,0) = 0.0f;
+            terrainStateSet->getOrCreateUniform(
+                "oe_layer_parent_matrix", osg::Uniform::FLOAT_MAT4 )->set( parent_mat );
 
-        // required for multipass tile rendering to work
-        terrainStateSet->setAttributeAndModes(
-            new osg::Depth(osg::Depth::LEQUAL, 0, 1, true) );
+            // uniform that controls per-layer opacity
+            terrainStateSet->getOrCreateUniform(
+                "oe_layer_opacity", osg::Uniform::FLOAT )->set( 1.0f );
 
-        // binding for the terrain texture
-        terrainStateSet->getOrCreateUniform( 
-            "oe_layer_tex", osg::Uniform::SAMPLER_2D )->set( _primaryUnit );
-
-        // binding for the secondary texture (for LOD blending)
-        terrainStateSet->getOrCreateUniform(
-            "oe_layer_tex_parent", osg::Uniform::SAMPLER_2D )->set( _secondaryUnit );
-
-        // binding for the default secondary texture matrix
-        osg::Matrixf parent_mat;
-        parent_mat(0,0) = 0.0f;
-        terrainStateSet->getOrCreateUniform(
-            "oe_layer_parent_matrix", osg::Uniform::FLOAT_MAT4 )->set( parent_mat );
-
-        // uniform that controls per-layer opacity
-        terrainStateSet->getOrCreateUniform(
-            "oe_layer_opacity", osg::Uniform::FLOAT )->set( 1.0f );
-
-        // uniform that conveys the layer UID to the shaders; necessary
-        // for per-layer branching (like color filters)
-        // UID -1 => no image layer (no texture)
-        terrainStateSet->getOrCreateUniform(
-            "oe_layer_uid", osg::Uniform::INT )->set( -1 );
-
-        // uniform that conveys the render order, since the shaders
-        // need to know which is the first layer in order to blend properly
-        terrainStateSet->getOrCreateUniform(
-            "oe_layer_order", osg::Uniform::INT )->set( 0 );
-
-        // base terrain color.
-        if ( useTerrainColor )
-        {
+            // uniform that conveys the layer UID to the shaders; necessary
+            // for per-layer branching (like color filters)
+            // UID -1 => no image layer (no texture)
             terrainStateSet->getOrCreateUniform(
-                "oe_terrain_color", osg::Uniform::FLOAT_VEC4 )->set( *_terrainOptions.color() );
+                "oe_layer_uid", osg::Uniform::INT )->set( -1 );
+
+            // uniform that conveys the render order, since the shaders
+            // need to know which is the first layer in order to blend properly
+            terrainStateSet->getOrCreateUniform(
+                "oe_layer_order", osg::Uniform::INT )->set( 0 );
+
+            // base terrain color.
+            if ( useTerrainColor )
+            {
+                terrainStateSet->getOrCreateUniform(
+                    "oe_terrain_color", osg::Uniform::FLOAT_VEC4 )->set( *_terrainOptions.color() );
+            }
         }
 
-        _shaderUpdateRequired = false;
+        _stateUpdateRequired = false;
     }
 }
diff --git a/src/osgEarthDrivers/engine_mp/MPTerrainEngineOptions b/src/osgEarthDrivers/engine_mp/MPTerrainEngineOptions
index 0b6791f..47a4c7f 100644
--- a/src/osgEarthDrivers/engine_mp/MPTerrainEngineOptions
+++ b/src/osgEarthDrivers/engine_mp/MPTerrainEngineOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -16,14 +16,14 @@
  * You should have received a copy of the GNU Lesser General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
-#ifndef OSGEARTH_ENGINE_MP_OPTIONS
-#define OSGEARTH_ENGINE_MP_OPTIONS 1
+#ifndef OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_OPTIONS
+#define OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_OPTIONS 1
 
 #include <osgEarth/Common>
 #include <osgEarth/TerrainOptions>
 #include <osgEarthSymbology/Color>
 
-namespace osgEarth { namespace Drivers
+namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 {
     using namespace osgEarth;
     using namespace osgEarth::Symbology;
@@ -38,12 +38,12 @@ namespace osgEarth { namespace Drivers
             _skirtRatio        ( 0.05 ),
             _quickRelease      ( true ),
             _lodFallOff        ( 0.0 ),
-            _normalizeEdges    ( true ),
+            _normalizeEdges    ( false ),
             _rangeMode         ( osg::LOD::DISTANCE_FROM_EYE_POINT ),
             _tilePixelSize     ( 256 ),
-            _premultAlpha      ( false ),
             _color             ( Color::White ),
-            _incrementalUpdate ( false )
+            _incrementalUpdate ( false ),
+            _optimizeTiles     ( false )
         {
             setDriver( "mp" );
             fromConfig( _conf );
@@ -74,10 +74,6 @@ namespace osgEarth { namespace Drivers
         optional<float>& tilePixelSize() { return _tilePixelSize; }
         const optional<float>& tilePixelSize() const { return _tilePixelSize; }
 
-        /** Whether to use pre-multiplied alpha blending for terrain imagery */
-        optional<bool>& premultipliedAlpha() { return _premultAlpha; }
-        const optional<bool>& premultipliedAlpha() const { return _premultAlpha; }
-
         /** The color of the globe surface where no images are applied */
         optional<Color>& color() { return _color; }
         const optional<Color>& color() const { return _color; }
@@ -91,6 +87,11 @@ namespace osgEarth { namespace Drivers
         optional<float>& lodFallOff() { return _lodFallOff; }
         const optional<float>& lodFallOff() const { return _lodFallOff; }
 
+        /** Whether to run the mesh consolidation and vertex cache optimizations on terrain tiles.
+          * They run faster, but take a lot longer to build. */
+        optional<bool>& optimizeTiles() { return _optimizeTiles; }
+        const optional<bool>& optimizeTiles() const { return _optimizeTiles; }
+
     protected:
         virtual Config getConfig() const {
             Config conf = TerrainOptions::getConfig();
@@ -101,9 +102,9 @@ namespace osgEarth { namespace Drivers
             conf.updateIfSet( "tile_pixel_size", _tilePixelSize );
             conf.updateIfSet( "range_mode", "PIXEL_SIZE_ON_SCREEN", _rangeMode, osg::LOD::PIXEL_SIZE_ON_SCREEN );
             conf.updateIfSet( "range_mode", "DISTANCE_FROM_EYE_POINT", _rangeMode, osg::LOD::DISTANCE_FROM_EYE_POINT);
-            conf.updateIfSet( "premultiplied_alpha", _premultAlpha );
             conf.updateIfSet( "color", _color );
             conf.updateIfSet( "incremental_update", _incrementalUpdate );
+            conf.updateIfSet( "optimize_tiles", _optimizeTiles );
 
             return conf;
         }
@@ -123,9 +124,9 @@ namespace osgEarth { namespace Drivers
 
             conf.getIfSet( "range_mode", "PIXEL_SIZE_ON_SCREEN", _rangeMode, osg::LOD::PIXEL_SIZE_ON_SCREEN );
             conf.getIfSet( "range_mode", "DISTANCE_FROM_EYE_POINT", _rangeMode, osg::LOD::DISTANCE_FROM_EYE_POINT);
-            conf.getIfSet( "premultiplied_alpha", _premultAlpha );
             conf.getIfSet( "color", _color );
             conf.getIfSet( "incremental_update", _incrementalUpdate );
+            conf.getIfSet( "optimize_tiles", _optimizeTiles );
         }
 
         optional<float>               _skirtRatio;
@@ -134,11 +135,11 @@ namespace osgEarth { namespace Drivers
         optional<bool>                _normalizeEdges;
         optional<osg::LOD::RangeMode> _rangeMode;
         optional<float>               _tilePixelSize;
-        optional<bool>                _premultAlpha;
         optional<Color>               _color;
         optional<bool>                _incrementalUpdate;
+        optional<bool>                _optimizeTiles;
     };
 
-} } // namespace osgEarth::Drivers
+} } } // namespace osgEarth::Drivers::MPTerrainEngine
 
-#endif // OSGEARTH_ENGINE_MP_OPTIONS
+#endif // OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_OPTIONS
diff --git a/src/osgEarthDrivers/engine_mp/QuickReleaseGLObjects b/src/osgEarthDrivers/engine_mp/QuickReleaseGLObjects
index 8f5c121..ff839dd 100644
--- a/src/osgEarthDrivers/engine_mp/QuickReleaseGLObjects
+++ b/src/osgEarthDrivers/engine_mp/QuickReleaseGLObjects
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -16,14 +16,14 @@
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
-#ifndef OSGEARTH_ENGINE_MP_QUICK_RELEASE_GL_OBJECTS
-#define OSGEARTH_ENGINE_MP_QUICK_RELEASE_GL_OBJECTS 1
+#ifndef OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_QUICK_RELEASE_GL_OBJECTS
+#define OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_QUICK_RELEASE_GL_OBJECTS 1
 
 #include "Common"
 #include "TileNodeRegistry"
 #include <osg/Camera>
 
-namespace osgEarth_engine_mp
+namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 {
     /**
      * A draw callback to calls another, nested draw callback.
@@ -86,6 +86,6 @@ namespace osgEarth_engine_mp
         osg::ref_ptr<TileNodeRegistry> _tilesToRelease;
     };
 
-} // namespace osgEarth_engine_mp
+} } } // namespace
 
-#endif // OSGEARTH_ENGINE_MP_QUICK_RELEASE_GL_OBJECTS
+#endif // OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_QUICK_RELEASE_GL_OBJECTS
diff --git a/src/osgEarthDrivers/engine_mp/SingleKeyNodeFactory b/src/osgEarthDrivers/engine_mp/SingleKeyNodeFactory
index 6a55948..ce786f0 100644
--- a/src/osgEarthDrivers/engine_mp/SingleKeyNodeFactory
+++ b/src/osgEarthDrivers/engine_mp/SingleKeyNodeFactory
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -16,8 +16,8 @@
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
-#ifndef OSGEARTH_ENGINE_MP_SINGLE_KEY_NODE_FACTORY
-#define OSGEARTH_ENGINE_MP_SINGLE_KEY_NODE_FACTORY 1
+#ifndef OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_SINGLE_KEY_NODE_FACTORY
+#define OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_SINGLE_KEY_NODE_FACTORY 1
 
 #include "Common"
 #include "KeyNodeFactory"
@@ -29,9 +29,8 @@
 #include <osgEarth/Progress>
 
 using namespace osgEarth;
-using namespace osgEarth::Drivers;
 
-namespace osgEarth_engine_mp
+namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 {
     class SingleKeyNodeFactory : public KeyNodeFactory
     {
@@ -43,7 +42,6 @@ namespace osgEarth_engine_mp
             TileNodeRegistry*                   liveTiles,
             TileNodeRegistry*                   deadTiles,
             const MPTerrainEngineOptions&       options,
-            TerrainNode*                        terrain,
             UID                                 engineUID );
 
         /** dtor */
@@ -56,12 +54,17 @@ namespace osgEarth_engine_mp
          * Creates a TileNode or TileGroup corresponding to the TileKey.
          *
          * @param key           TileKey for which to create a new node
+         * @param accumulate    Whether to accumulate data from parent tiles if necessary
          * @param setupChildren When true, build and include the necessary structures to
          *                      page in subtiles if and when necessary. If false, you just get
          *                      the tile alone with no paging support.
          * @param progress      Callback for cancelation and progress reporting
          */
-        osg::Node* createNode( const TileKey& key, bool setupChildren, ProgressCallback* progress );
+        osg::Node* createNode(
+            const TileKey&    key, 
+            bool              accumulate,
+            bool              setupChildren,
+            ProgressCallback* progress );
 
     protected:
         osg::Node* createTile(TileModel* model, bool setupChildren);
@@ -72,10 +75,11 @@ namespace osgEarth_engine_mp
         osg::ref_ptr<TileNodeRegistry>      _liveTiles;
         osg::ref_ptr<TileNodeRegistry>      _deadTiles;
         const MPTerrainEngineOptions&       _options;
-        osg::ref_ptr< TerrainNode >         _terrain;
         UID                                 _engineUID;
+
+        unsigned getMinimumRequiredLevel();
     };
 
-} // namespace osgEarth_engine_mp
+} } } // namespace osgEarth::Drivers::MPTerrainEngine
 
-#endif // OSGEARTH_ENGINE_MP_SINGLE_KEY_NODE_FACTORY
+#endif // OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_SINGLE_KEY_NODE_FACTORY
diff --git a/src/osgEarthDrivers/engine_mp/SingleKeyNodeFactory.cpp b/src/osgEarthDrivers/engine_mp/SingleKeyNodeFactory.cpp
index 35ee710..343f257 100644
--- a/src/osgEarthDrivers/engine_mp/SingleKeyNodeFactory.cpp
+++ b/src/osgEarthDrivers/engine_mp/SingleKeyNodeFactory.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -25,56 +25,13 @@
 #include <osgEarth/Registry>
 #include <osgEarth/HeightFieldUtils>
 #include <osgEarth/Progress>
+#include <osgEarth/Containers>
 
-using namespace osgEarth_engine_mp;
+using namespace osgEarth::Drivers::MPTerrainEngine;
 using namespace osgEarth;
-using namespace OpenThreads;
 
 #define LC "[SingleKeyNodeFactory] "
 
-namespace
-{
-    struct MyProgressCallback : public osgEarth::ProgressCallback
-    {
-        osg::observer_ptr<osg::PagedLOD> _plod;
-
-        MyProgressCallback( osg::PagedLOD* plod )
-            : _plod(plod) { }
-
-        bool isCanceled() const
-        {
-            if ( _canceled )
-                return true;
-
-            if ( !_plod.valid() )
-            {
-                _canceled = true;
-                OE_INFO << "CANCEL, plod = null." << std::endl;
-            }
-            else
-            {
-                osg::ref_ptr<osg::PagedLOD> plod;
-                if ( _plod.lock(plod) )
-                {
-                    osg::ref_ptr<osg::Referenced> dbr = plod->getDatabaseRequest( 1 );
-                    if ( !dbr.valid() || dbr->referenceCount() < 2 )
-                    {
-                        _canceled = true;
-                        OE_INFO << "CANCEL, REFCOUNT = " << dbr->referenceCount() << std::endl;
-                    }
-                }
-                else
-                {
-                    _canceled = true;
-                    OE_INFO << "CANCEL, plod = null." << std::endl;
-                }
-            }
-
-            return _canceled;
-        }
-    };
-}
-
 
 SingleKeyNodeFactory::SingleKeyNodeFactory(const Map*                    map,
                                            TileModelFactory*             modelFactory,
@@ -82,7 +39,6 @@ SingleKeyNodeFactory::SingleKeyNodeFactory(const Map*                    map,
                                            TileNodeRegistry*             liveTiles,
                                            TileNodeRegistry*             deadTiles,
                                            const MPTerrainEngineOptions& options,
-                                           TerrainNode*                  terrain,
                                            UID                           engineUID ) :
 _frame           ( map ),
 _modelFactory    ( modelFactory ),
@@ -90,18 +46,53 @@ _modelCompiler   ( modelCompiler ),
 _liveTiles       ( liveTiles ),
 _deadTiles       ( deadTiles ),
 _options         ( options ),
-_terrain         ( terrain ),
 _engineUID       ( engineUID )
 {
     //nop
 }
 
+unsigned
+SingleKeyNodeFactory::getMinimumRequiredLevel()
+{
+    // highest required level in the map:
+    unsigned minLevel = _frame.getHighestMinLevel();
+
+    return _options.minLOD().isSet() ?
+        std::max( _options.minLOD().value(), minLevel ) :
+        minLevel;
+}
+
+//Experimental: this speeds up tile loading a lot; it's sort of an alternative
+// to the OSG_MAX_PAGEDLOD cache.
+//#define EXPERIMENTAL_TILE_NODE_CACHE
+#ifdef EXPERIMENTAL_TILE_NODE_CACHE
+namespace
+{
+    typedef LRUCache<TileKey, osg::ref_ptr<TileNode> > TileNodeCache;
+    TileNodeCache cache(true, 16384);
+}
+#endif
 
 osg::Node*
 SingleKeyNodeFactory::createTile(TileModel* model, bool setupChildrenIfNecessary)
 {
+#ifdef EXPERIMENTAL_TILE_NODE_CACHE
+    osg::ref_ptr<TileNode> tileNode;
+    TileNodeCache::Record rec;
+    cache.get(model->_tileKey, rec);
+    if ( rec.valid() )
+    {
+        tileNode = rec.value().get();
+    }
+    else
+    {
+        tileNode = _modelCompiler->compile( model, _frame );
+        cache.insert(model->_tileKey, tileNode);
+    }
+#else
     // compile the model into a node:
     TileNode* tileNode = _modelCompiler->compile( model, _frame );
+#endif
 
     // see if this tile might have children.
     bool prepareForChildren =
@@ -112,55 +103,73 @@ SingleKeyNodeFactory::createTile(TileModel* model, bool setupChildrenIfNecessary
 
     if ( prepareForChildren )
     {
-        //Compute the min range based on the 2D size of the tile
         osg::BoundingSphere bs = tileNode->getBound();
-        GeoExtent extent = model->_tileKey.getExtent();
-        GeoPoint lowerLeft(extent.getSRS(), extent.xMin(), extent.yMin(), 0.0, ALTMODE_ABSOLUTE);
-        GeoPoint upperRight(extent.getSRS(), extent.xMax(), extent.yMax(), 0.0, ALTMODE_ABSOLUTE);
-        osg::Vec3d ll, ur;
-        lowerLeft.toWorld( ll );
-        upperRight.toWorld( ur );
-        double radius = (ur - ll).length() / 2.0;
-        float minRange = (float)(radius * _options.minTileRangeFactor().value());
-
         TilePagedLOD* plod = new TilePagedLOD( _engineUID, _liveTiles, _deadTiles );
         plod->setCenter  ( bs.center() );
         plod->addChild   ( tileNode );
-        plod->setRange   ( 0, minRange, FLT_MAX );
         plod->setFileName( 1, Stringify() << tileNode->getKey().str() << "." << _engineUID << ".osgearth_engine_mp_tile" );
-        plod->setRange   ( 1, 0, minRange );
+
+        if ( _options.rangeMode().value() == osg::LOD::DISTANCE_FROM_EYE_POINT )
+        {
+            //Compute the min range based on the 2D size of the tile
+            GeoExtent extent = model->_tileKey.getExtent();
+            GeoPoint lowerLeft(extent.getSRS(), extent.xMin(), extent.yMin(), 0.0, ALTMODE_ABSOLUTE);
+            GeoPoint upperRight(extent.getSRS(), extent.xMax(), extent.yMax(), 0.0, ALTMODE_ABSOLUTE);
+            osg::Vec3d ll, ur;
+            lowerLeft.toWorld( ll );
+            upperRight.toWorld( ur );
+            double radius = (ur - ll).length() / 2.0;
+            float minRange = (float)(radius * _options.minTileRangeFactor().value());
+
+            plod->setRange( 0, minRange, FLT_MAX );
+            plod->setRange( 1, 0, minRange );
+            plod->setRangeMode( osg::LOD::DISTANCE_FROM_EYE_POINT );
+        }
+        else
+        {
+            plod->setRange( 0, 0.0f, _options.tilePixelSize().value() );
+            plod->setRange( 1, _options.tilePixelSize().value(), FLT_MAX );
+            plod->setRangeMode( osg::LOD::PIXEL_SIZE_ON_SCREEN );
+        }
+
+
+
+        // DBPager will set a priority based on the ratio range/maxRange.
+        // This will offset that number with a full LOD #, giving LOD precedence.
+        // Experimental.
+        //plod->setPriorityScale( 1, model->_tileKey.getLOD()+1 );
 
 #if USE_FILELOCATIONCALLBACK
-        osgDB::Options* options = Registry::instance()->cloneOrCreateOptions();
+        osgDB::Options* options = plod->getOrCreateDBOptions();
         options->setFileLocationCallback( new FileLocationCallback() );
-        plod->setDatabaseOptions( options );
 #endif
         
         result = plod;
+
+        // this one rejects back-facing tiles:
+        if ( _frame.getMapInfo().isGeocentric() && _options.clusterCulling() == true )
+        {
+            osg::HeightField* hf =
+                model->_elevationData.getHeightField();
+
+            result->addCullCallback( HeightFieldUtils::createClusterCullingCallback(
+                hf,
+                tileNode->getKey().getProfile()->getSRS()->getEllipsoid(),
+                *_options.verticalScale() ) );
+        }
     }
     else
     {
         result = tileNode;
     }
 
-    // this one rejects back-facing tiles:
-    if ( _frame.getMapInfo().isGeocentric() && _options.clusterCulling() == true )
-    {
-        osg::HeightField* hf =
-            model->_elevationData.getHeightField();
-
-        result->addCullCallback( HeightFieldUtils::createClusterCullingCallback(
-            hf,
-            tileNode->getKey().getProfile()->getSRS()->getEllipsoid(),
-            *_options.verticalScale() ) );
-    }
-
     return result;
 }
 
 
 osg::Node*
 SingleKeyNodeFactory::createNode(const TileKey&    key, 
+                                 bool              accumulate,
                                  bool              setupChildren,
                                  ProgressCallback* progress )
 {
@@ -169,32 +178,66 @@ SingleKeyNodeFactory::createNode(const TileKey&    key,
 
     _frame.sync();
     
+    OE_START_TIMER(create_model);
+
     osg::ref_ptr<TileModel> model[4];
     for(unsigned q=0; q<4; ++q)
     {
+        if ( progress && progress->isCanceled() )
+            return 0L;
+        
         TileKey child = key.createChildKey(q);
-        _modelFactory->createTileModel( child, _frame, model[q] );
+        _modelFactory->createTileModel( child, _frame, accumulate, model[q], progress );
+
+        // if any one of the TileModel creations fail, we will be unable to build
+        // this quadtile. So goodbye.
+        if ( !model[q].valid() )
+        {
+            OE_DEBUG << LC << "Bailed on key " << key.str() << " due to a NULL model." << std::endl;
+            return 0L;
+        }
+    }
+
+    if (progress)
+        progress->stats()["create_tilemodel_time"] += OE_STOP_TIMER(create_model);
+
+    bool makeTile;
+
+    // If this is a request for a root tile, make it no matter what.
+    if ( key.getLOD() == 0 || (key.getLOD()-1) == _options.firstLOD().value() )
+    {
+        makeTile = true;
     }
 
-    bool subdivide =
-        _options.minLOD().isSet() && 
-        key.getLOD() < _options.minLOD().value();
+    // If there's a minimum LOD set, and we haven't reached it yet, make the tile.
+    else if ( key.getLOD() <= getMinimumRequiredLevel() )
+    {
+        makeTile = true;
+    }
 
-    if ( !subdivide )
+    // Otherwise, only make the tile if at least one quadrant has REAL data
+    // (not fallback data).
+    else
     {
+        makeTile = false;
         for(unsigned q=0; q<4; ++q)
         {
             if ( model[q]->hasRealData() )
             {
-                subdivide = true;
+                makeTile = true;
                 break;
             }
         }
     }
+    
+    if ( progress && progress->isCanceled() )
+        return 0L;
+
+    OE_START_TIMER(compile_tile);
 
     osg::ref_ptr<osg::Group> quad;
 
-    if ( subdivide )
+    if ( makeTile )
     {
         if ( _options.incrementalUpdate() == true )
         {
@@ -211,5 +254,8 @@ SingleKeyNodeFactory::createNode(const TileKey&    key,
         }
     }
 
+    if (progress)
+        progress->stats()["compile_tilemodel_time"] += OE_STOP_TIMER(compile_tile);
+
     return quad.release();
 }
diff --git a/src/osgEarthDrivers/engine_mp/TerrainNode b/src/osgEarthDrivers/engine_mp/TerrainNode
index c7be201..2642c2b 100644
--- a/src/osgEarthDrivers/engine_mp/TerrainNode
+++ b/src/osgEarthDrivers/engine_mp/TerrainNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -16,13 +16,13 @@
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
-#ifndef OSGEARTH_ENGINE_MP_TERRAIN_NODE
-#define OSGEARTH_ENGINE_MP_TERRAIN_NODE 1
+#ifndef OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_TERRAIN_NODE
+#define OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_TERRAIN_NODE 1
 
 #include "Common"
 #include "TileNodeRegistry"
 
-namespace osgEarth_engine_mp
+namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 {
     class TileFactory;
 
@@ -54,6 +54,6 @@ namespace osgEarth_engine_mp
         bool _quickReleaseCallbackInstalled;
     };
 
-} // namespace osgEarth_engine_mp
+} } } // namespace osgEarth::Drivers::MPTerrainEngine
 
-#endif // OSGEARTH_ENGINE_MP_TERRAIN_NODE
+#endif // OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_TERRAIN_NODE
diff --git a/src/osgEarthDrivers/engine_mp/TerrainNode.cpp b/src/osgEarthDrivers/engine_mp/TerrainNode.cpp
index 8192e3c..fab316b 100644
--- a/src/osgEarthDrivers/engine_mp/TerrainNode.cpp
+++ b/src/osgEarthDrivers/engine_mp/TerrainNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -29,7 +29,7 @@
 #include <osg/Node>
 #include <osgGA/EventVisitor>
 
-using namespace osgEarth_engine_mp;
+using namespace osgEarth::Drivers::MPTerrainEngine;
 using namespace osgEarth;
 using namespace OpenThreads;
 
diff --git a/src/osgEarthDrivers/engine_mp/TileGroup b/src/osgEarthDrivers/engine_mp/TileGroup
index 921edcd..bc0dd7b 100644
--- a/src/osgEarthDrivers/engine_mp/TileGroup
+++ b/src/osgEarthDrivers/engine_mp/TileGroup
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -16,8 +16,8 @@
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
-#ifndef OSGEARTH_ENGINE_MP_TILE_GROUP
-#define OSGEARTH_ENGINE_MP_TILE_GROUP 1
+#ifndef OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_TILE_GROUP
+#define OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_TILE_GROUP 1
 
 #include "Common"
 #include "TileNode"
@@ -25,7 +25,7 @@
 #include <osg/Group>
 #include <osgEarth/ThreadingUtils>
 
-namespace osgEarth_engine_mp
+namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 {
     using namespace osgEarth;
 
@@ -66,6 +66,6 @@ namespace osgEarth_engine_mp
         osg::ref_ptr<TileNodeRegistry> _dead;
     };
 
-} // namespace osgEarth_engine_mp
+} } } // namespace osgEarth::Drivers::MPTerrainEngine
 
-#endif // OSGEARTH_ENGINE_MP_TILE_NODE
+#endif // OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_TILE_NODE
diff --git a/src/osgEarthDrivers/engine_mp/TileGroup.cpp b/src/osgEarthDrivers/engine_mp/TileGroup.cpp
index 8aa737b..40886bf 100644
--- a/src/osgEarthDrivers/engine_mp/TileGroup.cpp
+++ b/src/osgEarthDrivers/engine_mp/TileGroup.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -22,7 +22,7 @@
 
 #include <osg/NodeVisitor>
 
-using namespace osgEarth_engine_mp;
+using namespace osgEarth::Drivers::MPTerrainEngine;
 using namespace osgEarth;
 
 #define LC "[TileGroup] "
diff --git a/src/osgEarthDrivers/engine_mp/TileModel b/src/osgEarthDrivers/engine_mp/TileModel
index 418ee1f..9f92da6 100644
--- a/src/osgEarthDrivers/engine_mp/TileModel
+++ b/src/osgEarthDrivers/engine_mp/TileModel
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -17,8 +17,8 @@
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
 
-#ifndef OSGEARTH_ENGINE_MP_TILE_MODEL
-#define OSGEARTH_ENGINE_MP_TILE_MODEL 1
+#ifndef OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_TILE_MODEL
+#define OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_TILE_MODEL 1
 
 #include "Common"
 #include <osgEarth/Common>
@@ -34,9 +34,10 @@
 #include <osg/StateSet>
 #include <osg/Texture2D>
 #include <osg/State>
+#include <osg/NodeVisitor>
 #include <map>
 
-namespace osgEarth_engine_mp
+namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 {
     using namespace osgEarth;
 
@@ -117,6 +118,8 @@ namespace osgEarth_engine_mp
         {
         public:
             ColorData() : _fallbackData(true) { }
+
+            /** Copy ctor - shallow */
             ColorData(const ColorData& rhs);
 
             /** dtor */
@@ -127,7 +130,6 @@ namespace osgEarth_engine_mp
                 unsigned                    order,
                 osg::Image*                 image,
                 GeoLocator*                 locator,
-                const osgEarth::TileKey&    tileKey,
                 bool                        fallbackData =false );
     
             void resizeGLObjectBuffers(unsigned maxSize);
@@ -146,18 +148,19 @@ namespace osgEarth_engine_mp
                 return _locator.get();
             }
 
-            osg::Texture2D* getTexture() const {
+            osg::Texture* getTexture() const {
                 return _texture.get();
             }
 
-            const osgEarth::TileKey& getTileKey() const {
-                return _tileKey; }
-
             const osgEarth::ImageLayer* getMapLayer() const {
                 return _layer.get(); }
 
             bool isFallbackData() const {
-                return _fallbackData;}
+                return _fallbackData;
+            }
+            void setIsFallbackData(bool value) {
+                _fallbackData = value;
+            }
 
             bool hasAlpha() const {
                 return _hasAlpha;
@@ -179,9 +182,7 @@ namespace osgEarth_engine_mp
 
             osg::ref_ptr<const osgEarth::ImageLayer> _layer;
             osg::ref_ptr<GeoLocator>                 _locator;
-            osg::ref_ptr<osg::Image>                 _image;
-            osg::ref_ptr<osg::Texture2D>             _texture;
-            osgEarth::TileKey                        _tileKey;
+            osg::ref_ptr<osg::Texture>               _texture;
             bool                                     _fallbackData;
             unsigned                                 _order;
             bool                                     _hasAlpha;
@@ -242,8 +243,12 @@ namespace osgEarth_engine_mp
             }
             return false;
         }
+
+        bool requiresUpdateTraverse() const;
+
+        void updateTraverse(osg::NodeVisitor&) const;
     };
 
-} // namespace osgEarth_engine_mp
+} } } // namespace osgEarth::Drivers::MPTerrainEngine
 
-#endif // OSGEARTH_ENGINE_MP_TILE_MODEL
+#endif // OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_TILE_MODEL
diff --git a/src/osgEarthDrivers/engine_mp/TileModel.cpp b/src/osgEarthDrivers/engine_mp/TileModel.cpp
index 6357cb0..53eda83 100644
--- a/src/osgEarthDrivers/engine_mp/TileModel.cpp
+++ b/src/osgEarthDrivers/engine_mp/TileModel.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -20,9 +20,12 @@
 #include <osgEarth/MapInfo>
 #include <osgEarth/HeightFieldUtils>
 #include <osgEarth/ImageUtils>
+#include <osgEarth/Registry>
+#include <osg/Texture2D>
+#include <osg/Texture2DArray>
 #include <osgTerrain/Locator>
 
-using namespace osgEarth_engine_mp;
+using namespace osgEarth::Drivers::MPTerrainEngine;
 using namespace osgEarth;
 
 #define LC "[TileModel] "
@@ -86,15 +89,21 @@ TileModel::ElevationData::getNormal(const osg::Vec3d&      ndc,
     osg::Vec3d hf_ndc;
     GeoLocator::convertLocalCoordBetween( *ndcLocator, ndc, *_locator.get(), hf_ndc );
 
+    float centerHeight = HeightFieldUtils::getHeightAtNormalizedLocation(_hf.get(), hf_ndc.x(), hf_ndc.y(), interp);
+
     osg::Vec3d west ( hf_ndc.x()-xres, hf_ndc.y(), 0.0 );
     osg::Vec3d east ( hf_ndc.x()+xres, hf_ndc.y(), 0.0 );
     osg::Vec3d south( hf_ndc.x(), hf_ndc.y()-yres, 0.0 );
     osg::Vec3d north( hf_ndc.x(), hf_ndc.y()+yres, 0.0 );
 
-    west.z()  = HeightFieldUtils::getHeightAtNormalizedLocation(_neighbors, west.x(),  west.y(),  interp);
-    east.z()  = HeightFieldUtils::getHeightAtNormalizedLocation(_neighbors, east.x(),  east.y(),  interp);
-    south.z() = HeightFieldUtils::getHeightAtNormalizedLocation(_neighbors, south.x(), south.y(), interp);
-    north.z() = HeightFieldUtils::getHeightAtNormalizedLocation(_neighbors, north.x(), north.y(), interp);
+    if (!HeightFieldUtils::getHeightAtNormalizedLocation(_neighbors, west.x(),  west.y(),  west.z(), interp))
+        west.z() = centerHeight;
+    if (!HeightFieldUtils::getHeightAtNormalizedLocation(_neighbors, east.x(),  east.y(),  east.z(), interp))
+        east.z() = centerHeight;
+    if (!HeightFieldUtils::getHeightAtNormalizedLocation(_neighbors, south.x(), south.y(), south.z(), interp))
+        south.z() = centerHeight;
+    if (!HeightFieldUtils::getHeightAtNormalizedLocation(_neighbors, north.x(), north.y(), north.z(), interp))
+        north.z() = centerHeight;
 
     osg::Vec3d westWorld, eastWorld, southWorld, northWorld;
     _locator->unitToModel(west,  westWorld);
@@ -114,26 +123,57 @@ TileModel::ColorData::ColorData(const osgEarth::ImageLayer* layer,
                                 unsigned                    order,
                                 osg::Image*                 image,
                                 GeoLocator*                 locator,
-                                const osgEarth::TileKey&    tileKey,
                                 bool                        fallbackData) :
 _layer       ( layer ),
 _order       ( order ),
 _locator     ( locator ),
-_tileKey     ( tileKey ),
 _fallbackData( fallbackData )
 {
     osg::Texture::FilterMode minFilter = layer->getImageLayerOptions().minFilter().get();
     osg::Texture::FilterMode magFilter = layer->getImageLayerOptions().magFilter().get();
 
-    _texture = new osg::Texture2D( image );
-    _texture->setUnRefImageDataAfterApply( true );
-    _texture->setMaxAnisotropy( 16.0f );
+    if (image->r() <= 1)
+    {
+        _texture = new osg::Texture2D( image );
+    }
+    else // image->r() > 1
+    {
+        // If the image has a third dimension, split it into separate images
+        // and stick them into a texture array.
+        std::vector< osg::ref_ptr<osg::Image> > images;
+        ImageUtils::flattenImage(image, images);
+
+        osg::Texture2DArray* tex = new osg::Texture2DArray();
+        tex->setTextureDepth(images.size());
+        tex->setInternalFormat(images[0]->getInternalTextureFormat());
+        tex->setSourceFormat(images[0]->getPixelFormat());
+        for (int i = 0; i < (int) images.size(); ++i)
+            tex->setImage( i, images[i].get() );
+
+        _texture = tex;
+    }
+
+
+    const optional<bool>& unRefPolicy = Registry::instance()->unRefImageDataAfterApply();
+    if ( unRefPolicy.isSet() )
+        _texture->setUnRefImageDataAfterApply( unRefPolicy.get() );
+
+    _texture->setMaxAnisotropy( 4.0f );
     _texture->setResizeNonPowerOfTwoHint(false);
     _texture->setFilter( osg::Texture::MAG_FILTER, magFilter );
-    _texture->setFilter( osg::Texture::MIN_FILTER, minFilter );
+    _texture->setFilter( osg::Texture::MIN_FILTER, minFilter  );
     _texture->setWrap( osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE );
     _texture->setWrap( osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE );
 
+    layer->applyTextureCompressionMode( _texture.get() );
+
+    // Disable mip mapping for npot tiles
+    if (!ImageUtils::isPowerOfTwo( image ) || (!image->isMipmap() && ImageUtils::isCompressed(image)))
+    {
+        OE_DEBUG<<"Disabling mipmapping for non power of two tile size("<<image->s()<<", "<<image->t()<<")"<<std::endl;
+        _texture->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR );
+    }    
+
     _hasAlpha = image && ImageUtils::hasTransparency(image);
 }
 
@@ -141,7 +181,6 @@ TileModel::ColorData::ColorData(const TileModel::ColorData& rhs) :
 _layer       ( rhs._layer.get() ),
 _locator     ( rhs._locator.get() ),
 _texture     ( rhs._texture.get() ),
-_tileKey     ( rhs._tileKey ),
 _fallbackData( rhs._fallbackData ),
 _order       ( rhs._order ),
 _hasAlpha    ( rhs._hasAlpha )
@@ -170,18 +209,54 @@ TileModel::ColorData::releaseGLObjects(osg::State* state) const
 //------------------------------------------------------------------
 
 TileModel::TileModel(const TileModel& rhs) :
-_mapInfo       ( rhs._mapInfo ),
-_revision      ( rhs._revision ),
-_tileKey       ( rhs._tileKey ),
-_tileLocator   ( rhs._tileLocator.get() ),
-_colorData     ( rhs._colorData ),
-_elevationData ( rhs._elevationData ),
-_sampleRatio   ( rhs._sampleRatio ),
-_parentStateSet( rhs._parentStateSet )
+_mapInfo         ( rhs._mapInfo ),
+_revision        ( rhs._revision ),
+_tileKey         ( rhs._tileKey ),
+_tileLocator     ( rhs._tileLocator.get() ),
+_colorData       ( rhs._colorData ),
+_elevationData   ( rhs._elevationData ),
+_sampleRatio     ( rhs._sampleRatio ),
+_parentStateSet  ( rhs._parentStateSet )
 {
     //nop
 }
 
+bool
+TileModel::requiresUpdateTraverse() const
+{
+    for(ColorDataByUID::const_iterator i = _colorData.begin(); i != _colorData.end(); ++i )
+    {
+        if ( i->second.getMapLayer()->isDynamic() )
+            return true;
+    }
+    return false;
+}
+
+void
+TileModel::updateTraverse(osg::NodeVisitor& nv) const
+{
+    // Supports updatable images (ImageStream, etc.), since the built-in
+    // mechanism for doing so requires the Texture/Image to be in a StateSet
+    // in the scene graph, and we don't keep it there.
+    for(ColorDataByUID::const_iterator i = _colorData.begin(); i != _colorData.end(); ++i )
+    {
+        if ( i->second.getMapLayer()->isDynamic() )
+        {
+            osg::Texture* tex = i->second.getTexture();
+            if ( tex )
+            {
+                for(int r=0; r<(int)tex->getNumImages(); ++r )
+                {
+                    osg::Image* image = tex->getImage(r);
+                    if ( image && image->requiresUpdateCall() )
+                    {
+                        image->update(&nv);
+                    }
+                }
+            }
+        }
+    }
+}
 
 TileModel*
 TileModel::createQuadrant(unsigned q) const
diff --git a/src/osgEarthDrivers/engine_mp/TileModelCompiler b/src/osgEarthDrivers/engine_mp/TileModelCompiler
index 0e4def2..de67647 100644
--- a/src/osgEarthDrivers/engine_mp/TileModelCompiler
+++ b/src/osgEarthDrivers/engine_mp/TileModelCompiler
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -16,8 +16,8 @@
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
-#ifndef OSGEARTH_ENGINE_MP_TILE_MODEL_COMPILER
-#define OSGEARTH_ENGINE_MP_TILE_MODEL_COMPILER 1
+#ifndef OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_TILE_MODEL_COMPILER
+#define OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_TILE_MODEL_COMPILER 1
 
 #include "Common"
 #include "TileModel"
@@ -32,10 +32,9 @@
 #include <osg/Drawable>
 #include <osg/Array>
 
-namespace osgEarth_engine_mp
+namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 {
     using namespace osgEarth;
-    using namespace osgEarth::Drivers;
 
     /**
      * Cache used by the TileModelCompiler. This lets us share common data across
@@ -78,7 +77,8 @@ namespace osgEarth_engine_mp
     {
     public:
         TileModelCompiler(
-            const MaskLayerVector&        masks,
+            const MaskLayerVector&        maskLayers,
+            const ModelLayerVector&       modelLayers,
             int                           textureImageUnit,
             bool                          optimizeTriangleOrientation,
             const MPTerrainEngineOptions& options);
@@ -89,7 +89,8 @@ namespace osgEarth_engine_mp
         TileNode* compile(const TileModel* model, const MapFrame& frame);
 
     protected:
-        const MaskLayerVector&                    _masks;
+        const MaskLayerVector&                    _maskLayers;
+        const ModelLayerVector&                   _modelLayers;
         int                                       _textureImageUnit;
         bool                                      _optimizeTriOrientation;
         const MPTerrainEngineOptions&             _options;
@@ -97,6 +98,6 @@ namespace osgEarth_engine_mp
         CompilerCache                             _cache;
     };
 
-} // namespace osgEarth_engine_mp
+} } } // namespace osgEarth::Drivers::MPTerrainEngine
 
-#endif // OSGEARTH_ENGINE_MP_TILE_MODEL_COMPILER
+#endif // OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_TILE_MODEL_COMPILER
diff --git a/src/osgEarthDrivers/engine_mp/TileModelCompiler.cpp b/src/osgEarthDrivers/engine_mp/TileModelCompiler.cpp
index d4b48a8..4aed7d7 100644
--- a/src/osgEarthDrivers/engine_mp/TileModelCompiler.cpp
+++ b/src/osgEarthDrivers/engine_mp/TileModelCompiler.cpp
@@ -1,1932 +1,2077 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-* GNU Lesser General Public License for more details.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#include "TileModelCompiler"
-#include "MPGeometry"
-
-#include <osgEarth/Locators>
-#include <osgEarth/Registry>
-#include <osgEarth/Capabilities>
-#include <osgEarth/MapFrame>
-#include <osgEarth/HeightFieldUtils>
-#include <osgEarth/ImageUtils>
-#include <osgEarth/Utils>
-#include <osgEarthSymbology/Geometry>
-#include <osgEarthSymbology/MeshConsolidator>
-
-#include <osg/Geode>
-#include <osg/Geometry>
-#include <osg/MatrixTransform>
-#include <osg/GL2Extensions>
-#include <osgUtil/DelaunayTriangulator>
-#include <osgUtil/Optimizer>
-#include <osgUtil/MeshOptimizers>
-
-using namespace osgEarth_engine_mp;
-using namespace osgEarth;
-using namespace osgEarth::Drivers;
-using namespace osgEarth::Symbology;
-
-#define LC "[TileModelCompiler] "
-
-//#define USE_TEXCOORD_CACHE 1
-
-//------------------------------------------------------------------------
-
-osg::ref_ptr<osg::Vec2Array>&
-CompilerCache::TexCoordArrayCache::get(const osg::Vec4d& mat,
-                                       unsigned          cols,
-                                       unsigned          rows)
-{
-    for( iterator i = begin(); i != end(); ++i )
-    {
-        CompilerCache::TexCoordTableKey& key = i->first;
-        if ( key._mat == mat && key._cols == cols && key._rows == rows )
-        {
-            return i->second;
-        }
-    }
-    
-    CompilerCache::TexCoordTableKey newKey;
-    newKey._mat     = mat;
-    newKey._cols    = cols;
-    newKey._rows    = rows;
-    this->push_back( std::make_pair(newKey, (osg::Vec2Array*)0L) );
-    return this->back().second;
-}
-
-
-//------------------------------------------------------------------------
-
-
-#define MATCH_TOLERANCE 0.000001
-
-namespace
-{    
-    // Data for a single renderable color layer
-    struct RenderLayer
-    {
-        TileModel::ColorData           _layer;
-        TileModel::ColorData           _layerParent;
-        osg::ref_ptr<const GeoLocator> _locator;
-        osg::ref_ptr<osg::Vec2Array>   _texCoords;
-        osg::ref_ptr<osg::Vec2Array>   _stitchTexCoords;
-        bool _ownsTexCoords;
-        RenderLayer() : 
-            _ownsTexCoords( false ) { }
-    };
-
-    typedef std::vector< RenderLayer > RenderLayerVector;
-
-
-    // Record that stores the data for a single masking region.
-    struct MaskRecord
-    {
-        osg::ref_ptr<osg::Vec3dArray> _boundary;
-        osg::Vec3d                    _ndcMin, _ndcMax;
-        MPGeometry*                   _geom;
-        osg::ref_ptr<osg::Vec3Array>  _internal;
-
-        MaskRecord(osg::Vec3dArray* boundary, osg::Vec3d& ndcMin, osg::Vec3d& ndcMax, MPGeometry* geom) 
-            : _boundary(boundary), _ndcMin(ndcMin), _ndcMax(ndcMax), _geom(geom), _internal(new osg::Vec3Array()) { }
-    };
-
-    typedef std::vector<MaskRecord> MaskRecordVector;
-
-
-    typedef std::vector<int> Indices;
-
-
-    struct Data
-    {
-        Data(const TileModel* in_model, const MapFrame& in_frame, const MaskLayerVector& in_maskLayers)
-            : model     ( in_model ), 
-              frame     ( in_frame ),
-              maskLayers( in_maskLayers )
-        {
-            surfaceGeode     = 0L;
-            surface          = 0L;
-//            ss_verts         = 0L;
-            scaleHeight      = 1.0f;
-            createSkirt      = false;
-            i_sampleFactor   = 1.0f;
-            j_sampleFactor   = 1.0f;
-            useVBOs = true; //!Registry::capabilities().preferDisplayListsForStaticGeometry();
-            textureImageUnit = 0;
-            renderTileCoords = 0L;
-            ownsTileCoords   = false;
-            stitchTileCoords = 0L;
-//            stitchSkirtTileCoords = 0L;
-        }
-
-        const MapFrame& frame;
-
-        bool                     useVBOs;
-        int                      textureImageUnit;
-
-        const TileModel*              model;                   // the tile's data model
-        osg::ref_ptr<const TileModel> parentModel;             // parent model reference
-
-        const MaskLayerVector&   maskLayers;                    // map-global masking layer set
-        osg::ref_ptr<GeoLocator> geoLocator;                    // tile locator adjusted to geographic
-        osg::Vec3d               centerModel;                   // tile center in model (world) coords
-
-        RenderLayerVector            renderLayers;
-        osg::ref_ptr<osg::Vec2Array> renderTileCoords;
-        bool                         ownsTileCoords;
-
-        // tile coords for masked areas; always owned (never shared)
-        osg::ref_ptr<osg::Vec2Array> stitchTileCoords;
-
-        // surface data:
-        osg::Geode*                   surfaceGeode;
-        MPGeometry*                   surface;
-        osg::Vec3Array*               surfaceVerts;
-        osg::Vec3Array*               normals;
-        osg::Vec4Array*               surfaceAttribs;
-        osg::Vec4Array*               surfaceAttribs2;
-        unsigned                      numVerticesInSurface;
-        osg::ref_ptr<osg::FloatArray> elevations;
-        Indices                       indices;
-        osg::BoundingSphere           surfaceBound;
-
-        // skirt data:
-        //MPGeometry*              skirt;
-        unsigned                 numVerticesInSkirt;
-        bool                     createSkirt;
-
-        // sampling grid parameters:
-        unsigned                 numRows;
-        unsigned                 numCols;
-        double                   i_sampleFactor;
-        double                   j_sampleFactor;
-        double                   scaleHeight;
-        unsigned                 originalNumRows;
-        unsigned                 originalNumCols;
-        
-        // for masking/stitching:
-        MaskRecordVector         maskRecords;
-//        MPGeometry*              stitching_skirts;
-//        osg::Vec3Array*          ss_verts;
-    };
-
-
-
-    /**
-     * Set up the masking records for this build. Here we check all the map's mask layer
-     * boundary geometries and find any that intersect the current tile. For an intersection
-     * we create a MaskRecord that we'll use later in the process.
-     */
-    void setupMaskRecords( Data& d )
-    {
-        // TODO: Set up the boundary sets globally in the TileModelCompiler instead of
-        // generating the boundaries every time for every tile.
-
-        for (MaskLayerVector::const_iterator it = d.maskLayers.begin(); it != d.maskLayers.end(); ++it)
-        {
-          // When displaying Plate Carre, Heights have to be converted from meters to degrees.
-          // This is also true for mask feature
-          // TODO: adjust this calculation based on the actual EllipsoidModel.
-          float scale = d.scaleHeight;
-          if (d.model->_tileLocator->getCoordinateSystemType() == osgEarth::GeoLocator::GEOGRAPHIC)
-          {
-            scale = d.scaleHeight / 111319.0f;
-          }
-
-          // TODO: no need to do this for every tile right?
-          osg::Vec3dArray* boundary = (*it)->getOrCreateBoundary(
-              scale, 
-              d.model->_tileLocator->getDataExtent().getSRS() );
-
-          if ( boundary )
-          {
-              osg::Vec3d min, max;
-              min = max = boundary->front();
-
-              for (osg::Vec3dArray::iterator it = boundary->begin(); it != boundary->end(); ++it)
-              {
-                if (it->x() < min.x())
-                  min.x() = it->x();
-
-                if (it->y() < min.y())
-                  min.y() = it->y();
-
-                if (it->x() > max.x())
-                  max.x() = it->x();
-
-                if (it->y() > max.y())
-                  max.y() = it->y();
-              }
-
-              osg::Vec3d min_ndc, max_ndc;
-              d.geoLocator->modelToUnit(min, min_ndc);
-              d.geoLocator->modelToUnit(max, max_ndc);
-
-              bool x_match = ((min_ndc.x() >= 0.0 && max_ndc.x() <= 1.0) ||
-                              (min_ndc.x() <= 0.0 && max_ndc.x() > 0.0) ||
-                              (min_ndc.x() < 1.0 && max_ndc.x() >= 1.0));
-
-              bool y_match = ((min_ndc.y() >= 0.0 && max_ndc.y() <= 1.0) ||
-                              (min_ndc.y() <= 0.0 && max_ndc.y() > 0.0) ||
-                              (min_ndc.y() < 1.0 && max_ndc.y() >= 1.0));
-
-              if (x_match && y_match)
-              {
-                  MPGeometry* mask_geom = new MPGeometry( d.model->_tileKey, d.frame, d.textureImageUnit );
-                  mask_geom->setUseVertexBufferObjects(d.useVBOs);
-                  d.surfaceGeode->addDrawable(mask_geom);
-                  d.maskRecords.push_back( MaskRecord(boundary, min_ndc, max_ndc, mask_geom) );
-              }
-           }
-        }
-
-#if 0
-        if (d.maskRecords.size() > 0)
-        {
-          //d.stitching_skirts = new osg::Geometry();
-          d.stitching_skirts = new MPGeometry( d.model->_tileKey, d.frame, d.textureImageUnit );
-          d.stitching_skirts->setUseVertexBufferObjects(d.useVBOs);
-          d.surfaceGeode->addDrawable( d.stitching_skirts );
-
-          d.ss_verts = new osg::Vec3Array();
-          d.stitching_skirts->setVertexArray(d.ss_verts);
-
-          if ( d.ss_verts->getVertexBufferObject() )
-              d.ss_verts->getVertexBufferObject()->setUsage(GL_STATIC_DRAW_ARB);
-        }
-#endif
-    }
-
-
-    /**
-     * Calculates the sample rate and allocates all the vertex, normal, and color
-     * arrays for the tile.
-     */
-    void setupGeometryAttributes( Data& d, double sampleRatio )
-    {
-        d.numRows = 8;
-        d.numCols = 8;
-        d.originalNumRows = 8;
-        d.originalNumCols = 8;        
-
-        // read the row/column count and skirt size from the model:
-        osg::HeightField* hf = d.model->_elevationData.getHeightField();
-        if ( hf )
-        {
-            d.numCols = hf->getNumColumns();
-            d.numRows = hf->getNumRows();
-            d.originalNumCols = d.numCols;
-            d.originalNumRows = d.numRows;
-        }
-
-        // calculate the elevation sampling factors that we'll use to step though
-        // the tile's NDC space.
-        d.i_sampleFactor = 1.0f;
-        d.j_sampleFactor = 1.0f;
-
-        if ( sampleRatio != 1.0f )
-        {            
-            d.numCols = osg::maximum((unsigned int) (float(d.originalNumCols)*sqrtf(sampleRatio)), 4u);
-            d.numRows = osg::maximum((unsigned int) (float(d.originalNumRows)*sqrtf(sampleRatio)), 4u);
-
-            d.i_sampleFactor = double(d.originalNumCols-1)/double(d.numCols-1);
-            d.j_sampleFactor = double(d.originalNumRows-1)/double(d.numRows-1);
-        }
-
-
-        // calculate the total number of verts:
-        d.numVerticesInSkirt   = d.createSkirt ? (2 * (d.numCols*2 + d.numRows*2 - 4)) : 0;
-        d.numVerticesInSurface = d.numCols * d.numRows + d.numVerticesInSkirt;
-
-        // allocate and assign vertices
-        d.surfaceVerts = new osg::Vec3Array();
-        d.surfaceVerts->reserve( d.numVerticesInSurface );
-        d.surface->setVertexArray( d.surfaceVerts );
-
-        // allocate and assign normals
-        d.normals = new osg::Vec3Array();
-        d.normals->reserve( d.numVerticesInSurface );
-        d.surface->setNormalArray( d.normals );
-        d.surface->setNormalBinding( osg::Geometry::BIND_PER_VERTEX );
-
-        // vertex attribution
-        // for each vertex, a vec4 containing a unit extrusion vector in [0..2] and the raw elevation in [3]
-        d.surfaceAttribs = new osg::Vec4Array();
-        d.surfaceAttribs->reserve( d.numVerticesInSurface );
-        d.surface->setVertexAttribArray( osg::Drawable::ATTRIBUTE_6, d.surfaceAttribs );
-        d.surface->setVertexAttribBinding( osg::Drawable::ATTRIBUTE_6, osg::Geometry::BIND_PER_VERTEX );
-        d.surface->setVertexAttribNormalize( osg::Drawable::ATTRIBUTE_6, false );
-
-        // for each vertex, index 0 holds the interpolated elevation from the lower lod (for morphing)
-        d.surfaceAttribs2 = new osg::Vec4Array();
-        d.surfaceAttribs2->reserve( d.numVerticesInSurface );
-        d.surface->setVertexAttribArray( osg::Drawable::ATTRIBUTE_7, d.surfaceAttribs2 );
-        d.surface->setVertexAttribBinding( osg::Drawable::ATTRIBUTE_7, osg::Geometry::BIND_PER_VERTEX );
-        d.surface->setVertexAttribNormalize( osg::Drawable::ATTRIBUTE_7, false );
-        
-        // temporary data structures for triangulation support
-        d.elevations = new osg::FloatArray();
-        d.elevations->reserve( d.numVerticesInSurface );
-        d.indices.resize( d.numVerticesInSurface, -1 );
-    }
-
-
-    /**
-     * Generates the texture coordinate arrays for each layer.
-     */
-    void setupTextureAttributes( Data& d, CompilerCache& cache )
-    {
-        // Any color entries that have the same Locator will share a texcoord
-        // array, saving on memory.
-        d.renderLayers.reserve( d.model->_colorData.size() );
-
-#ifdef USE_TEXCOORD_CACHE
-        // unit tile coords - [0..1] always across the tile.
-        osg::Vec4d idmat;
-        idmat[0] = 0.0;
-        idmat[1] = 0.0;
-        idmat[2] = 1.0;
-        idmat[3] = 1.0;
-
-        osg::ref_ptr<osg::Vec2Array>& tileCoords = cache._surfaceTexCoordArrays.get( idmat, d.numCols, d.numRows );
-        if ( !tileCoords.valid() )
-        {
-            // Note: anything in the cache must have its own VBO. No sharing!
-            tileCoords = new osg::Vec2Array();
-            tileCoords->setVertexBufferObject( new osg::VertexBufferObject() );
-            tileCoords->reserve( d.numVerticesInSurface );
-            d.ownsTileCoords = true;
-        }
-        d.renderTileCoords = tileCoords.get();
-
-#else // not USE_TEXCOORD_CACHE
-        d.renderTileCoords = new osg::Vec2Array();
-        d.renderTileCoords->reserve( d.numVerticesInSurface );
-        d.ownsTileCoords = true;
-#endif
-
-
-        // build a list of "render layers", in rendering order, sharing texture coordinate
-        // arrays wherever possible.
-        for( TileModel::ColorDataByUID::const_iterator i = d.model->_colorData.begin(); i != d.model->_colorData.end(); ++i )
-        {
-            const TileModel::ColorData& colorLayer = i->second;
-            RenderLayer r;
-            r._layer = colorLayer;
-
-            const GeoLocator* locator = r._layer.getLocator();
-            if ( locator )
-            {
-                // if we have no mask records, we can use the texture coordinate array cache.
-                if ( d.maskLayers.size() == 0 && locator->isLinear() )
-                {
-                    const GeoExtent& locex = locator->getDataExtent();
-                    const GeoExtent& keyex = d.model->_tileKey.getExtent();
-
-                    osg::Vec4d mat;
-                    mat[0] = (keyex.xMin() - locex.xMin())/locex.width();
-                    mat[1] = (keyex.yMin() - locex.yMin())/locex.height();
-                    mat[2] = (keyex.width() / locex.width());
-                    mat[3] = (keyex.height() / locex.height());
-
-                    //OE_DEBUG << "key=" << d.model->_tileKey.str() << ": off=[" <<mat[0]<< ", " <<mat[1] << "] scale=["
-                    //    << mat[2]<< ", " << mat[3] << "]" << std::endl;
-
-#ifdef USE_TEXCOORD_CACHE
-                    osg::ref_ptr<osg::Vec2Array>& surfaceTexCoords = cache._surfaceTexCoordArrays.get( mat, d.numCols, d.numRows );
-                    if ( !surfaceTexCoords.valid() )
-                    {
-                        // Note: anything in the cache must have its own VBO. No sharing!
-                        surfaceTexCoords = new osg::Vec2Array();
-                        surfaceTexCoords->setVertexBufferObject( new osg::VertexBufferObject() );
-                        surfaceTexCoords->reserve( d.numVerticesInSurface );
-                        r._ownsTexCoords = true;
-                    }
-                    r._texCoords = surfaceTexCoords.get();
-
-#else // not USE_TEXCOORD_CACHE
-                    r._texCoords = new osg::Vec2Array();
-                    r._texCoords->reserve( d.numVerticesInSurface );
-                    r._ownsTexCoords = true;
-#endif
-
-#if 0
-                    osg::ref_ptr<osg::Vec2Array>& skirtTexCoords = cache._skirtTexCoordArrays.get( mat, d.numCols, d.numRows );
-                    if ( !skirtTexCoords.valid() )
-                    {
-                        // Note: anything in the cache must have its own VBO. No sharing!
-                        skirtTexCoords = new osg::Vec2Array();
-                        skirtTexCoords->setVertexBufferObject( new osg::VertexBufferObject() );
-                        skirtTexCoords->reserve( d.numVerticesInSkirt );
-                        r._ownsSkirtTexCoords = true;
-                    }
-                    r._skirtTexCoords = skirtTexCoords.get();
-#endif
-                }
-
-                else
-                {
-                    // cannot use the tex coord array cache if there are masking records.
-                    r._texCoords = new osg::Vec2Array();
-                    r._texCoords->reserve( d.numVerticesInSurface );
-                    r._ownsTexCoords = true;
-
-                    if ( d.maskRecords.size() > 0 )
-                    {
-                        r._stitchTexCoords = new osg::Vec2Array();
-                        if ( !d.stitchTileCoords.valid() )
-                            d.stitchTileCoords = new osg::Vec2Array();
-                    }
-                }
-
-                // install the locator:
-                r._locator = locator;
-                if ( locator->getCoordinateSystemType() == osgTerrain::Locator::GEOCENTRIC )
-                {
-                    r._locator = locator->getGeographicFromGeocentric();
-                }
-
-                // install the parent color data layer if necessary.
-                if ( d.parentModel.valid() )
-                {
-                    d.parentModel->getColorData( r._layer.getUID(), r._layerParent );
-                }
-                else
-                {
-                    r._layerParent = r._layer;
-                }
-
-                d.renderLayers.push_back( r );
-
-                // Note that we don't actually assign the tex coord arrays to the geometry yet.
-                // That must wait until the end. See the comments in assignTextureArrays()
-                // to understand why.
-            }
-            else
-            {
-                OE_WARN << LC << "Found a Locator, but it wasn't a GeoLocator." << std::endl;
-            }
-        }
-    }
-
-
-    /**
-     * Iterate over the sampling grid and calculate the vertex positions and normals
-     * for each sampling point.
-     */
-    void createSurfaceGeometry( Data& d )
-    {
-        d.surfaceBound.init();
-
-        //osgTerrain::HeightFieldLayer* elevationLayer = d.model->_elevationData.getHFLayer();
-
-        osg::HeightField* hf            = d.model->_elevationData.getHeightField();
-        GeoLocator*       hfLocator     = d.model->_elevationData.getLocator();
-
-        // populate vertex and tex coord arrays    
-        for(unsigned j=0; j < d.numRows; ++j)
-        {
-            for(unsigned i=0; i < d.numCols; ++i)
-            {
-                unsigned int iv = j*d.numCols + i;
-                osg::Vec3d ndc( ((double)i)/(double)(d.numCols-1), ((double)j)/(double)(d.numRows-1), 0.0);
-
-                // raw height:
-                float heightValue = 0.0f;
-                bool  validValue  = true;
-
-                if ( hf )
-                {
-                    validValue = d.model->_elevationData.getHeight( ndc, d.model->_tileLocator, heightValue, INTERP_TRIANGULATE );
-                }
-
-                ndc.z() = heightValue * d.scaleHeight;
-
-                if ( !validValue )
-                {
-                    d.indices[iv] = -1;
-                }
-
-                // First check whether the sampling point falls within a mask's bounding box.
-                // If so, skip the sampling and mark it as a mask location
-                if ( validValue && d.maskRecords.size() > 0 )
-                {
-                    for (MaskRecordVector::iterator mr = d.maskRecords.begin(); mr != d.maskRecords.end(); ++mr)
-                    {
-                        if(ndc.x() >= (*mr)._ndcMin.x() && ndc.x() <= (*mr)._ndcMax.x() &&
-                            ndc.y() >= (*mr)._ndcMin.y() && ndc.y() <= (*mr)._ndcMax.y())
-                        {
-                            validValue = false;
-                            d.indices[iv] = -2;
-
-                            (*mr)._internal->push_back(ndc);
-
-                            break;
-                        }
-                    }
-                }
-                
-                if ( validValue )
-                {
-                    d.indices[iv] = d.surfaceVerts->size();
-
-                    osg::Vec3d model;
-                    d.model->_tileLocator->unitToModel( ndc, model );
-
-                    (*d.surfaceVerts).push_back(model - d.centerModel);
-
-                    // grow the bounding sphere:
-                    d.surfaceBound.expandBy( (*d.surfaceVerts).back() );
-
-                    // the separate texture space requires separate transformed texcoords for each layer.
-                    for( RenderLayerVector::const_iterator r = d.renderLayers.begin(); r != d.renderLayers.end(); ++r )
-                    {
-                        if ( r->_ownsTexCoords )
-                        {
-                            if ( !r->_locator->isEquivalentTo( *d.geoLocator.get() ) )
-                            {
-                                osg::Vec3d color_ndc;
-                                osgTerrain::Locator::convertLocalCoordBetween( *d.geoLocator.get(), ndc, *r->_locator.get(), color_ndc );
-                                r->_texCoords->push_back( osg::Vec2( color_ndc.x(), color_ndc.y() ) );
-                            }
-                            else
-                            {
-                                r->_texCoords->push_back( osg::Vec2( ndc.x(), ndc.y() ) );
-                            }
-                        }
-                    }
-
-                    if ( d.ownsTileCoords )
-                    {
-                        d.renderTileCoords->push_back( osg::Vec2(ndc.x(), ndc.y()) );
-                    }
-
-                    // record the raw elevation value in our float array for later
-                    (*d.elevations).push_back(ndc.z());
-
-                    // compute the local normal (up vector)
-                    osg::Vec3d ndc_plus_one(ndc.x(), ndc.y(), ndc.z() + 1.0);
-                    osg::Vec3d model_up;
-                    d.model->_tileLocator->unitToModel(ndc_plus_one, model_up);
-                    model_up = model_up - model;
-                    model_up.normalize();
-
-                    (*d.normals).push_back(model_up);
-
-                    // Calculate and store the "old height", i.e the height value from
-                    // the parent LOD.
-                    float     oldHeightValue = heightValue;
-                    osg::Vec3 oldNormal;
-
-                    // This only works if the tile size is an odd number in both directions.
-                    if (d.model->_tileKey.getLOD() > 0 && (d.numCols&1) && (d.numRows&1) && d.parentModel.valid())
-                    {
-                        d.parentModel->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), oldHeightValue, INTERP_TRIANGULATE );
-                        d.parentModel->_elevationData.getNormal( ndc, d.model->_tileLocator.get(), oldNormal, INTERP_TRIANGULATE );
-                    }
-                    else
-                    {
-                        d.model->_elevationData.getNormal(ndc, d.model->_tileLocator.get(), oldNormal, INTERP_TRIANGULATE );
-                    }
-
-                    // first attribute set has the unit extrusion vector and the
-                    // raw height value.
-                    (*d.surfaceAttribs).push_back( osg::Vec4f(
-                        model_up.x(),
-                        model_up.y(),
-                        model_up.z(),
-                        heightValue) );
-
-                    // second attribute set has the old height value in "w"
-                    (*d.surfaceAttribs2).push_back( osg::Vec4f(
-                        oldNormal.x(),
-                        oldNormal.y(),
-                        oldNormal.z(),
-                        oldHeightValue ) );
-                }
-            }
-        }
-
-        //if ( d.renderLayers[0]._texCoords->size() < d.surfaceVerts->size() )
-        //{
-        //    OE_WARN << LC << "not good. mask error." << std::endl;
-        //}
-    }
-
-
-    /**
-     * If there are masking records, calculate the vertices to bound the masked area
-     * and the internal verticies to populate it. Then build a triangulation of the
-     * area inside the masking bounding box and add this to the surface geode.
-     */
-    void createMaskGeometry( Data& d )
-    {
-        bool hasElev = d.model->hasElevation();
-
-        for (MaskRecordVector::iterator mr = d.maskRecords.begin(); mr != d.maskRecords.end(); ++mr)
-        {
-            int min_i = (int)floor((*mr)._ndcMin.x() * (double)(d.numCols-1));
-            if (min_i < 0) min_i = 0;
-            if (min_i >= (int)d.numCols) min_i = d.numCols - 1;
-
-            int min_j = (int)floor((*mr)._ndcMin.y() * (double)(d.numRows-1));
-            if (min_j < 0) min_j = 0;
-            if (min_j >= (int)d.numCols) min_j = d.numCols - 1;
-
-            int max_i = (int)ceil((*mr)._ndcMax.x() * (double)(d.numCols-1));
-            if (max_i < 0) max_i = 0;
-            if (max_i >= (int)d.numCols) max_i = d.numCols - 1;
-
-            int max_j = (int)ceil((*mr)._ndcMax.y() * (double)(d.numRows-1));
-            if (max_j < 0) max_j = 0;
-            if (max_j >= (int)d.numCols) max_j = d.numCols - 1;
-
-            if (min_i >= 0 && max_i >= 0 && min_j >= 0 && max_j >= 0)
-            {
-                int num_i = max_i - min_i + 1;
-                int num_j = max_j - min_j + 1;
-
-                osg::ref_ptr<Polygon> maskSkirtPoly = new Polygon();
-                maskSkirtPoly->resize(num_i * 2 + num_j * 2 - 4);
-
-                for (int i = 0; i < num_i; i++)
-                {
-                    //int index = indices[min_j*numColumns + i + min_i];
-                    {
-                        osg::Vec3d ndc( ((double)(i + min_i))/(double)(d.numCols-1), ((double)min_j)/(double)(d.numRows-1), 0.0);
-
-                        //if (elevationLayer)
-                        if ( hasElev )
-                        {
-                            float value = 0.0f;
-                            if ( d.model->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), value, INTERP_BILINEAR ) )
-                                ndc.z() = value * d.scaleHeight;
-                        }
-
-                        (*maskSkirtPoly)[i] = ndc;
-                    }
-
-                    //index = indices[max_j*numColumns + i + min_i];
-                    {
-                        osg::Vec3d ndc( ((double)(i + min_i))/(double)(d.numCols-1), ((double)max_j)/(double)(d.numRows-1), 0.0);
-
-                        if ( hasElev )
-                        {
-                            float value = 0.0f;
-                            if ( d.model->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), value, INTERP_BILINEAR ) )
-                                ndc.z() = value * d.scaleHeight;
-                        }
-
-                        (*maskSkirtPoly)[i + (2 * num_i + num_j - 3) - 2 * i] = ndc;
-                    }
-                }
-                for (int j = 0; j < num_j - 2; j++)
-                {
-                    //int index = indices[(min_j + j + 1)*numColumns + max_i];
-                    {
-                        osg::Vec3d ndc( ((double)max_i)/(double)(d.numCols-1), ((double)(min_j + j + 1))/(double)(d.numRows-1), 0.0);
-
-                        if ( hasElev )
-                        {
-                            float value = 0.0f;
-                            if ( d.model->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), value, INTERP_BILINEAR ) )
-                                ndc.z() = value * d.scaleHeight;
-                        }
-
-#if 0
-                        if (elevationLayer)
-                        {
-                            unsigned int i_equiv = d.i_sampleFactor==1.0 ? max_i : (unsigned int) (double(max_i)*d.i_sampleFactor);
-                            unsigned int j_equiv = d.j_sampleFactor==1.0 ? min_j + j + 1 : (unsigned int) (double(min_j + j + 1)*d.j_sampleFactor);
-
-                            float value = 0.0f;
-                            if (elevationLayer->getValidValue(i_equiv,j_equiv, value))
-                                ndc.z() = value*d.scaleHeight;
-                        }
-#endif
-
-                        (*maskSkirtPoly)[j + num_i] = ndc;
-                    }
-
-                    //index = indices[(min_j + j + 1)*numColumns + min_i];
-                    {
-                        osg::Vec3d ndc( ((double)min_i)/(double)(d.numCols-1), ((double)(min_j + j + 1))/(double)(d.numRows-1), 0.0);
-
-                        if ( hasElev )
-                        {
-                            float value = 0.0f;
-                            if ( d.model->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), value, INTERP_BILINEAR ) )
-                                ndc.z() = value * d.scaleHeight;
-                        }
-
-                        (*maskSkirtPoly)[j + (2 * num_i + 2 * num_j - 5) - 2 * j] = ndc;
-                    }
-                }
-
-                //Create local polygon representing mask
-                osg::ref_ptr<Polygon> maskPoly = new Polygon();
-                for (osg::Vec3dArray::iterator it = (*mr)._boundary->begin(); it != (*mr)._boundary->end(); ++it)
-                {
-                    osg::Vec3d local;
-                    d.geoLocator->convertModelToLocal(*it, local);
-                    maskPoly->push_back(local);
-                }
-
-
-                // Use delaunay triangulation for stitching:
-
-                // Add the outter stitching bounds to the collection of vertices to be used for triangulation
-                (*mr)._internal->insert((*mr)._internal->end(), maskSkirtPoly->begin(), maskSkirtPoly->end());
-
-                osg::ref_ptr<osgUtil::DelaunayTriangulator> trig=new osgUtil::DelaunayTriangulator();
-                trig->setInputPointArray((*mr)._internal.get());
-
-
-                // Add mask bounds as a triangulation constraint
-                osg::ref_ptr<osgUtil::DelaunayConstraint> dc=new osgUtil::DelaunayConstraint;
-
-                osg::Vec3Array* maskConstraint = new osg::Vec3Array();
-                dc->setVertexArray(maskConstraint);
-
-                //Crop the mask to the stitching poly (for case where mask crosses tile edge)
-                osg::ref_ptr<Geometry> maskCrop;
-                maskPoly->crop(maskSkirtPoly.get(), maskCrop);
-
-                GeometryIterator i( maskCrop.get(), false );
-                while( i.hasMore() )
-                {
-                    Geometry* part = i.next();
-                    if (!part)
-                        continue;
-
-                    if (part->getType() == Geometry::TYPE_POLYGON)
-                    {
-                        osg::Vec3Array* partVerts = part->toVec3Array();
-                        maskConstraint->insert(maskConstraint->end(), partVerts->begin(), partVerts->end());
-                        dc->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_LOOP, maskConstraint->size() - partVerts->size(), partVerts->size()));
-                    }
-                }
-
-                // Cropping strips z-values so need reassign
-                std::vector<int> isZSet;
-                for (osg::Vec3Array::iterator it = maskConstraint->begin(); it != maskConstraint->end(); ++it)
-                {
-                    int zSet = 0;
-
-                    //Look for verts that belong to the original mask skirt polygon
-                    for (Polygon::iterator mit = maskSkirtPoly->begin(); mit != maskSkirtPoly->end(); ++mit)
-                    {
-                        if (osg::absolute((*mit).x() - (*it).x()) < MATCH_TOLERANCE && osg::absolute((*mit).y() - (*it).y()) < MATCH_TOLERANCE)
-                        {
-                            (*it).z() = (*mit).z();
-                            zSet += 1;
-                            break;
-                        }
-                    }
-
-                    //Look for verts that belong to the mask polygon
-                    for (Polygon::iterator mit = maskPoly->begin(); mit != maskPoly->end(); ++mit)
-                    {
-                        if (osg::absolute((*mit).x() - (*it).x()) < MATCH_TOLERANCE && osg::absolute((*mit).y() - (*it).y()) < MATCH_TOLERANCE)
-                        {
-                            (*it).z() = (*mit).z();
-                            zSet += 2;
-                            break;
-                        }
-                    }
-
-                    isZSet.push_back(zSet);
-                }
-
-                //Any mask skirt verts that are still unset are newly created verts where the skirt
-                //meets the mask. Find the mask segment the point lies along and calculate the
-                //appropriate z value for the point.
-                int count = 0;
-                for (osg::Vec3Array::iterator it = maskConstraint->begin(); it != maskConstraint->end(); ++it)
-                {
-                    //If the z-value was set from a mask vertex there is no need to change it.  If
-                    //it was set from a vertex from the stitching polygon it may need overriden if
-                    //the vertex lies along a mask edge.  Or if it is unset, it will need to be set.
-                    //if (isZSet[count] < 2)
-                    if (!isZSet[count])
-                    {
-                        osg::Vec3d p2 = *it;
-                        double closestZ = 0.0;
-                        double closestRatio = DBL_MAX;
-                        for (Polygon::iterator mit = maskPoly->begin(); mit != maskPoly->end(); ++mit)
-                        {
-                            osg::Vec3d p1 = *mit;
-                            osg::Vec3d p3 = mit == --maskPoly->end() ? maskPoly->front() : (*(mit + 1));
-
-                            //Truncated vales to compensate for accuracy issues
-                            double p1x = ((int)(p1.x() * 1000000)) / 1000000.0L;
-                            double p3x = ((int)(p3.x() * 1000000)) / 1000000.0L;
-                            double p2x = ((int)(p2.x() * 1000000)) / 1000000.0L;
-
-                            double p1y = ((int)(p1.y() * 1000000)) / 1000000.0L;
-                            double p3y = ((int)(p3.y() * 1000000)) / 1000000.0L;
-                            double p2y = ((int)(p2.y() * 1000000)) / 1000000.0L;
-
-                            if ((p1x < p3x ? p2x >= p1x && p2x <= p3x : p2x >= p3x && p2x <= p1x) &&
-                                (p1y < p3y ? p2y >= p1y && p2y <= p3y : p2y >= p3y && p2y <= p1y))
-                            {
-                                double l1 =(osg::Vec2d(p2.x(), p2.y()) - osg::Vec2d(p1.x(), p1.y())).length();
-                                double lt = (osg::Vec2d(p3.x(), p3.y()) - osg::Vec2d(p1.x(), p1.y())).length();
-                                double zmag = p3.z() - p1.z();
-
-                                double foundZ = (l1 / lt) * zmag + p1.z();
-
-                                double mRatio = 1.0;
-                                if (osg::absolute(p1x - p3x) < MATCH_TOLERANCE)
-                                {
-                                    if (osg::absolute(p1x-p2x) < MATCH_TOLERANCE)
-                                        mRatio = 0.0;
-                                }
-                                else
-                                {
-                                    double m1 = p1x == p2x ? 0.0 : (p2y - p1y) / (p2x - p1x);
-                                    double m2 = p1x == p3x ? 0.0 : (p3y - p1y) / (p3x - p1x);
-                                    mRatio = m2 == 0.0 ? m1 : osg::absolute(1.0L - m1 / m2);
-                                }
-
-                                if (mRatio < 0.01)
-                                {
-                                    (*it).z() = foundZ;
-                                    isZSet[count] = 2;
-                                    break;
-                                }
-                                else if (mRatio < closestRatio)
-                                {
-                                    closestRatio = mRatio;
-                                    closestZ = foundZ;
-                                }
-                            }
-                        }
-
-                        if (!isZSet[count] && closestRatio < DBL_MAX)
-                        {
-                            (*it).z() = closestZ;
-                            isZSet[count] = 2;
-                        }
-                    }
-
-                    if (!isZSet[count])
-                        OE_WARN << LC << "Z-value not set for mask constraint vertex" << std::endl;
-
-                    count++;
-                }
-
-                trig->addInputConstraint(dc.get());
-
-
-                // Create array to hold vertex normals
-                osg::Vec3Array *norms=new osg::Vec3Array;
-                trig->setOutputNormalArray(norms);
-
-
-                // Triangulate vertices and remove triangles that lie within the contraint loop
-                trig->triangulate();
-                trig->removeInternalTriangles(dc.get());
-
-
-                // Set up new arrays to hold final vertices and normals
-                osg::Geometry* stitch_geom = (*mr)._geom;
-                osg::Vec3Array* stitch_verts = new osg::Vec3Array();
-                stitch_verts->reserve(trig->getInputPointArray()->size());
-                stitch_geom->setVertexArray(stitch_verts);
-                osg::Vec3Array* stitch_norms = new osg::Vec3Array(trig->getInputPointArray()->size());
-                stitch_geom->setNormalArray( stitch_norms );
-                stitch_geom->setNormalBinding( osg::Geometry::BIND_PER_VERTEX );
-
-
-                //Initialize tex coords
-                if ( d.renderLayers.size() > 0 )
-                {
-                    for (unsigned int i = 0; i < d.renderLayers.size(); ++i)
-                    {
-                        d.renderLayers[i]._stitchTexCoords->reserve(trig->getInputPointArray()->size());
-                    }
-                }
-                d.stitchTileCoords->reserve(trig->getInputPointArray()->size());
-
-                // Iterate through point to convert to model coords, calculate normals, and set up tex coords
-                int norm_i = -1;
-                for (osg::Vec3Array::iterator it = trig->getInputPointArray()->begin(); it != trig->getInputPointArray()->end(); ++it)
-                {
-                    // get model coords
-                    osg::Vec3d model;
-                    d.model->_tileLocator->convertLocalToModel(*it, model);
-                    model = model - d.centerModel;
-
-                    stitch_verts->push_back(model);
-
-                    // calc normals
-                    osg::Vec3d local_one(*it);
-                    local_one.z() += 1.0;
-                    osg::Vec3d model_one;
-                    d.model->_tileLocator->convertLocalToModel( local_one, model_one );
-                    model_one = model_one - model;
-                    model_one.normalize();
-                    (*stitch_norms)[++norm_i] = model_one;
-
-                    // set up text coords
-                    if (d.renderLayers.size() > 0)
-                    {
-                        for (unsigned int i = 0; i < d.renderLayers.size(); ++i)
-                        {
-                            if (!d.renderLayers[i]._locator->isEquivalentTo( *d.geoLocator.get() )) //*masterTextureLocator.get()))
-                            {
-                                osg::Vec3d color_ndc;
-                                osgTerrain::Locator::convertLocalCoordBetween(*d.geoLocator.get(), (*it), *d.renderLayers[i]._locator.get(), color_ndc);
-                                d.renderLayers[i]._stitchTexCoords->push_back(osg::Vec2(color_ndc.x(), color_ndc.y()));
-                            }
-                            else
-                            {
-                                d.renderLayers[i]._stitchTexCoords->push_back(osg::Vec2((*it).x(), (*it).y()));
-                            }
-                        }
-                    }
-                    d.stitchTileCoords->push_back(osg::Vec2((*it).x(), (*it).y()));
-                }
-
-
-                // Get triangles from triangulator and add as primative set to the geometry
-                stitch_geom->addPrimitiveSet(trig->getTriangles());
-            }
-        }
-    }
-
-
-    /**
-     * Build the geometry for the tile "skirts" -- this the vertical geometry around the
-     * tile edges that hides the gap effect caused when you render two adjacent tiles at
-     * different LODs.
-     */
-    void createSkirtGeometry( Data& d, double skirtRatio )
-    {
-        // surface normals will double as our skirt extrusion vectors
-        osg::Vec3Array* skirtVectors = d.normals;
-
-        // find the skirt height
-        double skirtHeight = d.surfaceBound.radius() * skirtRatio;
-
-        // build the verts first:
-        osg::Vec3Array* skirtVerts = static_cast<osg::Vec3Array*>(d.surface->getVertexArray());
-        osg::Vec3Array* skirtNormals = static_cast<osg::Vec3Array*>(d.surface->getNormalArray());
-        osg::Vec4Array* skirtAttribs = static_cast<osg::Vec4Array*>(d.surface->getVertexAttribArray(osg::Drawable::ATTRIBUTE_6)); //new osg::Vec4Array();
-        osg::Vec4Array* skirtAttribs2 = static_cast<osg::Vec4Array*>(d.surface->getVertexAttribArray(osg::Drawable::ATTRIBUTE_7)); //new osg::Vec4Array();
-
-        osg::ref_ptr<osg::DrawElementsUShort> elements = new osg::DrawElementsUShort(GL_TRIANGLE_STRIP);
-
-        // bottom:
-        for( unsigned int c=0; c<d.numCols-1; ++c )
-        {
-            int orig_i = d.indices[c];
-
-            if (orig_i < 0)
-            {
-                if ( elements->size() > 0 )
-                    d.surface->addPrimitiveSet( elements.get() );
-                elements = new osg::DrawElementsUShort(GL_TRIANGLE_STRIP);
-            }
-            else
-            {
-                const osg::Vec3f& surfaceVert = (*d.surfaceVerts)[orig_i];
-                skirtVerts->push_back( surfaceVert - ((*skirtVectors)[orig_i])*skirtHeight );
-
-                const osg::Vec3f& surfaceNormal = (*d.normals)[orig_i];
-                skirtNormals->push_back( surfaceNormal );
-
-                const osg::Vec4f& surfaceAttribs = (*d.surfaceAttribs)[orig_i];
-                skirtAttribs->push_back( surfaceAttribs - osg::Vec4f(0,0,0,skirtHeight) );
-
-                const osg::Vec4f& surfaceAttribs2 = (*d.surfaceAttribs2)[orig_i];
-                skirtAttribs2->push_back( surfaceAttribs2 - osg::Vec4f(0,0,0,skirtHeight) );
-
-                if ( d.renderLayers.size() > 0 )
-                {
-                    for (unsigned int i = 0; i < d.renderLayers.size(); ++i)
-                    {
-                        if ( d.renderLayers[i]._ownsTexCoords )
-                        {
-                            const osg::Vec2& tc = (*d.renderLayers[i]._texCoords.get())[orig_i];
-                            d.renderLayers[i]._texCoords->push_back( tc );
-                        }
-                    }
-                }
-
-                elements->addElement(orig_i);
-                elements->addElement(skirtVerts->size()-1);
-            }
-        }
-
-        // right:
-        for( unsigned int r=0; r<d.numRows-1; ++r )
-        {
-            int orig_i = d.indices[r*d.numCols+(d.numCols-1)];
-            if (orig_i < 0)
-            {
-                if ( elements->size() > 0 )
-                    d.surface->addPrimitiveSet( elements.get() );
-                elements = new osg::DrawElementsUShort(GL_TRIANGLE_STRIP);
-            }
-            else
-            {
-                const osg::Vec3f& surfaceVert = (*d.surfaceVerts)[orig_i];
-                skirtVerts->push_back( surfaceVert - ((*skirtVectors)[orig_i])*skirtHeight );
-
-                const osg::Vec3f& surfaceNormal = (*d.normals)[orig_i];
-                skirtNormals->push_back( surfaceNormal );
-
-                const osg::Vec4f& surfaceAttribs = (*d.surfaceAttribs)[orig_i];
-                skirtAttribs->push_back( surfaceAttribs - osg::Vec4f(0,0,0,skirtHeight) );
-
-                const osg::Vec4f& surfaceAttribs2 = (*d.surfaceAttribs2)[orig_i];
-                skirtAttribs2->push_back( surfaceAttribs2 - osg::Vec4f(0,0,0,skirtHeight) );
-
-                if ( d.renderLayers.size() > 0 )
-                {
-                    for (unsigned int i = 0; i < d.renderLayers.size(); ++i)
-                    {
-                        if ( d.renderLayers[i]._ownsTexCoords )
-                        {
-                            const osg::Vec2& tc = (*d.renderLayers[i]._texCoords.get())[orig_i];
-                            d.renderLayers[i]._texCoords->push_back( tc );
-                        }
-                    }
-                }
-
-                elements->addElement(orig_i);
-                elements->addElement(skirtVerts->size()-1);
-            }
-        }
-
-        // top:
-        for( int c=d.numCols-1; c>0; --c )
-        {
-            int orig_i = d.indices[(d.numRows-1)*d.numCols+c];
-            if (orig_i < 0)
-            {
-                if ( elements->size() > 0 )
-                    d.surface->addPrimitiveSet( elements.get() );
-                elements = new osg::DrawElementsUShort(GL_TRIANGLE_STRIP);
-            }
-            else
-            {
-                const osg::Vec3f& surfaceVert = (*d.surfaceVerts)[orig_i];
-                skirtVerts->push_back( surfaceVert - ((*skirtVectors)[orig_i])*skirtHeight );
-
-                const osg::Vec3f& surfaceNormal = (*d.normals)[orig_i];
-                skirtNormals->push_back( surfaceNormal );
-
-                const osg::Vec4f& surfaceAttribs = (*d.surfaceAttribs)[orig_i];
-                skirtAttribs->push_back( surfaceAttribs - osg::Vec4f(0,0,0,skirtHeight) );
-
-                const osg::Vec4f& surfaceAttribs2 = (*d.surfaceAttribs2)[orig_i];
-                skirtAttribs2->push_back( surfaceAttribs2 - osg::Vec4f(0,0,0,skirtHeight) );
-
-                if ( d.renderLayers.size() > 0 )
-                {
-                    for (unsigned int i = 0; i < d.renderLayers.size(); ++i)
-                    {
-                        if ( d.renderLayers[i]._ownsTexCoords )
-                        {
-                            const osg::Vec2& tc = (*d.renderLayers[i]._texCoords.get())[orig_i];
-                            d.renderLayers[i]._texCoords->push_back( tc );
-                        }
-                    }
-                }
-
-                elements->addElement(orig_i);
-                elements->addElement(skirtVerts->size()-1);
-            }
-        }
-
-        // left:
-        for( int r=d.numRows-1; r>=0; --r )
-        {
-            int orig_i = d.indices[r*d.numCols];
-            if (orig_i < 0)
-            {
-                if ( elements->size() > 0 )
-                    d.surface->addPrimitiveSet( elements.get() );
-                elements = new osg::DrawElementsUShort(GL_TRIANGLE_STRIP);
-            }
-            else
-            {
-                const osg::Vec3f& surfaceVert = (*d.surfaceVerts)[orig_i];
-                skirtVerts->push_back( surfaceVert - ((*skirtVectors)[orig_i])*skirtHeight );
-
-                const osg::Vec3f& surfaceNormal = (*d.normals)[orig_i];
-                skirtNormals->push_back( surfaceNormal );
-
-                const osg::Vec4f& surfaceAttribs = (*d.surfaceAttribs)[orig_i];
-                skirtAttribs->push_back( surfaceAttribs - osg::Vec4f(0,0,0,skirtHeight) );
-
-                const osg::Vec4f& surfaceAttribs2 = (*d.surfaceAttribs2)[orig_i];
-                skirtAttribs2->push_back( surfaceAttribs2 - osg::Vec4f(0,0,0,skirtHeight) );
-
-                if ( d.renderLayers.size() > 0 )
-                {
-                    for (unsigned int i = 0; i < d.renderLayers.size(); ++i)
-                    {
-                        if ( d.renderLayers[i]._ownsTexCoords )
-                        {
-                            const osg::Vec2& tc = (*d.renderLayers[i]._texCoords.get())[orig_i];
-                            d.renderLayers[i]._texCoords->push_back( tc );
-                        }
-                    }
-                }
-
-                elements->addElement(orig_i);
-                elements->addElement(skirtVerts->size()-1);
-            }
-        }
-
-        // add the final prim set.
-        if ( elements->size() > 0 )
-        {
-            d.surface->addPrimitiveSet( elements.get() );
-        }
-    }
-
-
-
-    /**
-     * Builds triangles for the surface geometry, and recalculates the surface normals
-     * to be optimized for slope.
-     */
-    void tessellateSurfaceGeometry( Data& d, bool optimizeTriangleOrientation, bool normalizeEdges )
-    {    
-        bool swapOrientation = !(d.model->_tileLocator->orientationOpenGL());
-
-        bool recalcNormals   = d.model->hasElevation();
-        unsigned numSurfaceNormals = d.numRows * d.numCols;
-
-        osg::DrawElements* elements = new osg::DrawElementsUShort(GL_TRIANGLES);
-        elements->reserveElements((d.numRows-1) * (d.numCols-1) * 6);
-        d.surface->insertPrimitiveSet(0, elements); // because we always want this first.
-
-        if ( recalcNormals )
-        {
-            // first clear out all the normals on the surface (but not the skirts)
-            // TODO: someday go back and re-apply the skirt normals to match the
-            // corresponding recalculated surface normals.
-            for(unsigned n=0; n<numSurfaceNormals && n<d.normals->size(); ++n)
-            {
-                (*d.normals)[n].set( 0.0f, 0.0f, 0.0f );
-            }
-        }
-
-        for(unsigned j=0; j<d.numRows-1; ++j)
-        {
-            for(unsigned i=0; i<d.numCols-1; ++i)
-            {
-                int i00;
-                int i01;
-                if (swapOrientation)
-                {
-                    i01 = j*d.numCols + i;
-                    i00 = i01+d.numCols;
-                }
-                else
-                {
-                    i00 = j*d.numCols + i;
-                    i01 = i00+d.numCols;
-                }
-
-                int i10 = i00+1;
-                int i11 = i01+1;
-
-                // remap indices to final vertex positions
-                i00 = d.indices[i00];
-                i01 = d.indices[i01];
-                i10 = d.indices[i10];
-                i11 = d.indices[i11];
-
-                unsigned int numValid = 0;
-                if (i00>=0) ++numValid;
-                if (i01>=0) ++numValid;
-                if (i10>=0) ++numValid;
-                if (i11>=0) ++numValid;                
-
-                if (numValid==4)
-                {
-                    bool VALID = true;
-                    for (MaskRecordVector::iterator mr = d.maskRecords.begin(); mr != d.maskRecords.end(); ++mr)
-                    {
-                        float min_i = (*mr)._ndcMin.x() * (double)(d.numCols-1);
-                        float min_j = (*mr)._ndcMin.y() * (double)(d.numRows-1);
-                        float max_i = (*mr)._ndcMax.x() * (double)(d.numCols-1);
-                        float max_j = (*mr)._ndcMax.y() * (double)(d.numRows-1);
-
-                        // We test if mask is completely in square
-                        if(i+1 >= min_i && i <= max_i && j+1 >= min_j && j <= max_j)
-                        {
-                            VALID = false;
-                            break;
-                        }
-                    }
-
-                    if (VALID) {
-                        float e00 = (*d.elevations)[i00];
-                        float e10 = (*d.elevations)[i10];
-                        float e01 = (*d.elevations)[i01];
-                        float e11 = (*d.elevations)[i11];
-
-                        osg::Vec3f& v00 = (*d.surfaceVerts)[i00];
-                        osg::Vec3f& v10 = (*d.surfaceVerts)[i10];
-                        osg::Vec3f& v01 = (*d.surfaceVerts)[i01];
-                        osg::Vec3f& v11 = (*d.surfaceVerts)[i11];
-
-                        if (!optimizeTriangleOrientation || fabsf(e00-e11)<fabsf(e01-e10))
-                        {
-                            elements->addElement(i01);
-                            elements->addElement(i00);
-                            elements->addElement(i11);
-
-                            elements->addElement(i00);
-                            elements->addElement(i10);
-                            elements->addElement(i11);
-
-                            if (recalcNormals)
-                            {                        
-                                osg::Vec3 normal1 = (v00-v01) ^ (v11-v01);
-                                (*d.normals)[i01] += normal1;
-                                (*d.normals)[i00] += normal1;
-                                (*d.normals)[i11] += normal1;
-
-                                osg::Vec3 normal2 = (v10-v00) ^ (v11-v00);
-                                (*d.normals)[i00] += normal2;
-                                (*d.normals)[i10] += normal2;
-                                (*d.normals)[i11] += normal2;
-                            }
-                        }
-                        else
-                        {
-                            elements->addElement(i01);
-                            elements->addElement(i00);
-                            elements->addElement(i10);
-
-                            elements->addElement(i01);
-                            elements->addElement(i10);
-                            elements->addElement(i11);
-
-                            if (recalcNormals)
-                            {                       
-                                osg::Vec3 normal1 = (v00-v01) ^ (v10-v01);
-                                (*d.normals)[i01] += normal1;
-                                (*d.normals)[i00] += normal1;
-                                (*d.normals)[i10] += normal1;
-
-                                osg::Vec3 normal2 = (v10-v01) ^ (v11-v01);
-                                (*d.normals)[i01] += normal2;
-                                (*d.normals)[i10] += normal2;
-                                (*d.normals)[i11] += normal2;
-                            }
-                        }
-                    }
-                }
-            }
-        }        
-        
-        if (recalcNormals && normalizeEdges)
-        {            
-            //OE_DEBUG << LC << "Normalizing edges" << std::endl;
-
-            //Compute the edge normals if we have neighbor data
-            //Get all the neighbors
-            osg::HeightField* w_neighbor  = d.model->_elevationData.getNeighbor( -1, 0 );
-            osg::HeightField* e_neighbor  = d.model->_elevationData.getNeighbor( 1, 0 );
-            osg::HeightField* s_neighbor  = d.model->_elevationData.getNeighbor( 0, 1 );
-            osg::HeightField* n_neighbor  = d.model->_elevationData.getNeighbor( 0, -1 );
-
-            // Utility arrays:
-            std::vector<osg::Vec3> boundaryVerts;
-            boundaryVerts.reserve( 2 * std::max(d.numRows, d.numCols) );
-
-            std::vector< float > boundaryElevations;
-            boundaryElevations.reserve( 2 * std::max(d.numRows, d.numCols) );
-
-            //Recalculate the west side
-            if (w_neighbor && w_neighbor->getNumColumns() == d.originalNumCols && w_neighbor->getNumRows() == d.originalNumRows)
-            {
-                boundaryVerts.clear();
-                boundaryElevations.clear();
-                
-                //Compute the verts for the west side
-                for (int j = 0; j < (int)d.numRows; j++)
-                {
-                    for (int i = (int)d.numCols-2; i <= (int)d.numCols-1; i++)
-                    {                          
-                        osg::Vec3d ndc( (double)(i - static_cast<int>(d.numCols-1))/(double)(d.numCols-1), ((double)j)/(double)(d.numRows-1), 0.0);                                                                        
-
-                        // use the sampling factor to determine the lookup index:
-                        unsigned i_equiv = d.i_sampleFactor==1.0 ? i : (unsigned) (double(i)*d.i_sampleFactor);
-                        unsigned j_equiv = d.j_sampleFactor==1.0 ? j : (unsigned) (double(j)*d.j_sampleFactor);
-
-                        //TODO:  Should probably use an interpolated method here
-                        float heightValue = w_neighbor->getHeight( i_equiv, j_equiv );
-                        ndc.z() = heightValue;
-
-                        osg::Vec3d model;
-                        d.model->_tileLocator->unitToModel( ndc, model );
-                        osg::Vec3d v = model - d.centerModel;
-                        boundaryVerts.push_back( v );
-                        boundaryElevations.push_back( heightValue );
-                    }
-                }   
-
-                //The boundary verts are now populated, so go through and triangulate them add add the normals to the existing normal array
-                for (int j = 0; j < (int)d.numRows-1; j++)
-                {                    
-                    int i00;
-                    int i01;
-                    int i = 0;
-                    if (swapOrientation)
-                    {
-                        i01 = j*d.numCols + i;
-                        i00 = i01+d.numCols;
-                    }
-                    else
-                    {
-                        i00 = j*d.numCols + i;
-                        i01 = i00+d.numCols;
-                    }
-
-
-
-                    //remap indices to final vertex position
-                    i00 = d.indices[i00];
-                    i01 = d.indices[i01];
-
-                    if ( i00 >= 0 && i01 >= 0 )
-                    {
-                        int baseIndex = 2 * j;
-                        osg::Vec3f& v00 = boundaryVerts[baseIndex    ];
-                        osg::Vec3f& v10 = boundaryVerts[baseIndex + 1];
-                        osg::Vec3f& v01 = boundaryVerts[baseIndex + 2];
-                        osg::Vec3f& v11 = boundaryVerts[baseIndex + 3];
-
-                        float e00 = boundaryElevations[baseIndex];
-                        float e10 = boundaryElevations[baseIndex + 1];
-                        float e01 = boundaryElevations[baseIndex + 2];
-                        float e11 = boundaryElevations[baseIndex + 3];
-
-                       
-                        if (!optimizeTriangleOrientation || fabsf(e00-e11)<fabsf(e01-e10))
-                        {                            
-                            osg::Vec3 normal1 = (v00-v01) ^ (v11-v01);
-                            (*d.normals)[i01] += normal1;                        
-
-                            osg::Vec3 normal2 = (v10-v00) ^ (v11-v00);
-                            (*d.normals)[i00] += normal2;                        
-                            (*d.normals)[i01] += normal2;                                                
-                        }
-                        else
-                        {                            
-                            osg::Vec3 normal1 = (v00-v01) ^ (v10-v01);
-                            (*d.normals)[i00] += normal1;                                               
-
-                            osg::Vec3 normal2 = (v10-v01) ^ (v11-v01);
-                            (*d.normals)[i00] += normal2;                                               
-                            (*d.normals)[i01] += normal2;                        
-                        }
-                    }
-                }
-            }
-
-                        
-            //Recalculate the east side
-            if (e_neighbor && e_neighbor->getNumColumns() == d.originalNumCols && e_neighbor->getNumRows() == d.originalNumRows)            
-            {
-                boundaryVerts.clear();
-                boundaryElevations.clear();
-
-                //Compute the verts for the east side
-                for (int j = 0; j < (int)d.numRows; j++)
-                {
-                    for (int i = 0; i <= 1; i++)
-                    {                           
-                        osg::Vec3d ndc( ((double)(d.numCols -1 + i))/(double)(d.numCols-1), ((double)j)/(double)(d.numRows-1), 0.0);
-
-                        unsigned i_equiv = d.i_sampleFactor==1.0 ? i : (unsigned) (double(i)*d.i_sampleFactor);
-                        unsigned j_equiv = d.j_sampleFactor==1.0 ? j : (unsigned) (double(j)*d.j_sampleFactor);
-                        
-                        //TODO:  Should probably use an interpolated method here
-                        float heightValue = e_neighbor->getHeight( i_equiv, j_equiv );
-                        ndc.z() = heightValue;
-
-                        osg::Vec3d model;
-                        d.model->_tileLocator->unitToModel( ndc, model );
-                        osg::Vec3d v = model - d.centerModel;
-                        boundaryVerts.push_back( v );
-                        boundaryElevations.push_back( heightValue );
-                    }
-                }   
-
-                //The boundary verts are now populated, so go through and triangulate them add add the normals to the existing normal array
-                for (int j = 0; j < (int)d.numRows-1; j++)
-                {
-                    int i00;
-                    int i01;
-                    int i = d.numCols-1;
-                    if (swapOrientation)
-                    {
-                        i01 = j*d.numCols + i;
-                        i00 = i01+d.numCols;
-                    }
-                    else
-                    {
-                        i00 = j*d.numCols + i;
-                        i01 = i00+d.numCols;
-                    }
-
-                    //remap indices to final vertex position
-                    i00 = d.indices[i00];
-                    i01 = d.indices[i01];
-
-                    if ( i00 >= 0 && i01 >= 0 )
-                    {
-                        int baseIndex = 2 * j;
-                        osg::Vec3f& v00 = boundaryVerts[baseIndex    ];
-                        osg::Vec3f& v10 = boundaryVerts[baseIndex + 1];
-                        osg::Vec3f& v01 = boundaryVerts[baseIndex + 2];
-                        osg::Vec3f& v11 = boundaryVerts[baseIndex + 3];
-
-                        float e00 = boundaryElevations[baseIndex];
-                        float e10 = boundaryElevations[baseIndex + 1];
-                        float e01 = boundaryElevations[baseIndex + 2];
-                        float e11 = boundaryElevations[baseIndex + 3];
-
-                       
-                        if (!optimizeTriangleOrientation || fabsf(e00-e11)<fabsf(e01-e10))
-                        {                            
-                            osg::Vec3 normal1 = (v00-v01) ^ (v11-v01);                       
-                            (*d.normals)[i00] += normal1;                        
-                            (*d.normals)[i01] += normal1;
-
-                            osg::Vec3 normal2 = (v10-v00) ^ (v11-v00);                        
-                            (*d.normals)[i00] += normal2;                                                
-                        }
-                        else
-                        {                            
-                            osg::Vec3 normal1 = (v00-v01) ^ (v10-v01);
-                            (*d.normals)[i00] += normal1;                        
-                            (*d.normals)[i01] += normal1;                                                                        
-
-                            osg::Vec3 normal2 = (v10-v01) ^ (v11-v01);
-                            (*d.normals)[i01] += normal2;                        
-                        }
-                    }
-                }
-            }
-
-            //Recalculate the north side
-            if (n_neighbor && n_neighbor->getNumColumns() == d.originalNumCols && n_neighbor->getNumRows() == d.originalNumRows)            
-            {
-                boundaryVerts.clear();
-                boundaryElevations.clear();
-
-                //Compute the verts for the north side               
-                for (int j = 0; j <= 1; j++)
-                {
-                    for (int i = 0; i < (int)d.numCols; i++)                    
-                    {                           
-                        osg::Vec3d ndc( (double)(i)/(double)(d.numCols-1), (double)(d.numRows -1 + j)/(double)(d.numRows-1), 0.0);
-
-                        unsigned i_equiv = d.i_sampleFactor==1.0 ? i : (unsigned) (double(i)*d.i_sampleFactor);
-                        unsigned j_equiv = d.j_sampleFactor==1.0 ? j : (unsigned) (double(j)*d.j_sampleFactor);
-                        
-                        //TODO:  Should probably use an interpolated method here
-                        float heightValue = n_neighbor->getHeight( i_equiv, j_equiv );
-                        ndc.z() = heightValue;
-
-                        osg::Vec3d model;
-                        d.model->_tileLocator->unitToModel( ndc, model );
-                        osg::Vec3d v = model - d.centerModel;
-                        boundaryVerts.push_back( v );
-                        boundaryElevations.push_back( heightValue );
-                    }
-                }   
-
-                //The boundary verts are now populated, so go through and triangulate them add add the normals to the existing normal array                
-                for (int i = 0; i < (int)d.numCols-1; i++)
-                {                    
-                    int i00;                    
-                    int j = d.numRows-1;
-                    if (swapOrientation)
-                    {         
-                        int i01 = j * d.numCols + i;
-                        i00 = i01+d.numCols;
-                    }
-                    else
-                    {
-                        i00 = j*d.numCols + i;                        
-                    }
-
-                    int i10 = i00+1;
-
-                    //remap indices to final vertex position
-                    i00 = d.indices[i00];
-                    i10 = d.indices[i10];
-
-
-                    if ( i00 >= 0 && i10 >= 0 )
-                    {
-                        int baseIndex = i;
-                        osg::Vec3f& v00 = boundaryVerts[baseIndex    ];
-                        osg::Vec3f& v10 = boundaryVerts[baseIndex + 1];
-                        osg::Vec3f& v01 = boundaryVerts[baseIndex + d.numCols];
-                        osg::Vec3f& v11 = boundaryVerts[baseIndex + d.numCols + 1];
-
-                        float e00 = boundaryElevations[baseIndex];
-                        float e10 = boundaryElevations[baseIndex + 1];
-                        float e01 = boundaryElevations[baseIndex + d.numCols];
-                        float e11 = boundaryElevations[baseIndex + d.numCols + 1];
-
-                       
-                        if (!optimizeTriangleOrientation || fabsf(e00-e11)<fabsf(e01-e10))
-                        {                            
-                            osg::Vec3 normal1 = (v00-v01) ^ (v11-v01);                       
-                            (*d.normals)[i00] += normal1;                        
-                            (*d.normals)[i10] += normal1;
-
-                            osg::Vec3 normal2 = (v10-v00) ^ (v11-v00);                        
-                            (*d.normals)[i10] += normal2;                                                
-                        }
-                        else
-                        {                            
-                            osg::Vec3 normal1 = (v00-v01) ^ (v10-v01);
-                            (*d.normals)[i00] += normal1;                                                
-
-                            osg::Vec3 normal2 = (v10-v01) ^ (v11-v01);
-                            (*d.normals)[i00] += normal2;                                                
-                            (*d.normals)[i10] += normal2;                        
-                        }
-                    }
-                }
-            }
-
-            //Recalculate the south side
-            if (s_neighbor && s_neighbor->getNumColumns() == d.originalNumCols && s_neighbor->getNumRows() == d.originalNumRows)            
-            {
-                boundaryVerts.clear();
-                boundaryElevations.clear();
-
-                //Compute the verts for the south side               
-                for (int j = (int)d.numRows-2; j <= (int)d.numRows-1; j++)
-                {
-                    for (int i = 0; i < (int)d.numCols; i++)                    
-                    {                           
-                        osg::Vec3d ndc( (double)(i)/(double)(d.numCols-1), (double)(j - static_cast<int>(d.numRows-1))/(double)(d.numRows-1), 0.0);                                                
-
-                        unsigned i_equiv = d.i_sampleFactor==1.0 ? i : (unsigned) (double(i)*d.i_sampleFactor);
-                        unsigned j_equiv = d.j_sampleFactor==1.0 ? j : (unsigned) (double(j)*d.j_sampleFactor);
-                        
-                        //TODO:  Should probably use an interpolated method here
-                        float heightValue = s_neighbor->getHeight( i_equiv, j_equiv );                        
-                        ndc.z() = heightValue;
-
-                        osg::Vec3d model;
-                        d.model->_tileLocator->unitToModel( ndc, model );
-                        osg::Vec3d v = model - d.centerModel;
-                        boundaryVerts.push_back( v );
-                        boundaryElevations.push_back( heightValue ); 
-                    }
-                }   
-
-                //The boundary verts are now populated, so go through and triangulate them add add the normals to the existing normal array                
-                for (int i = 0; i < (int)d.numCols-1; i++)
-                {                    
-                    int i00;                    
-                    int j = 0;
-
-
-                    if (swapOrientation)
-                    {                   
-                        int i01 = j*d.numCols + i;
-                        i00 = i01+d.numCols;                    
-                    }
-                    else
-                    {
-                        i00 = j*d.numCols + i;                        
-                    }                    
-
-                    int i10 = i00+1;
-
-                    //remap indices to final vertex position
-                    i00 = d.indices[i00];
-                    i10 = d.indices[i10];
-
-
-                    if ( i00 >= 0 && i10 >= 0 )
-                    {
-                        int baseIndex = i;
-                        osg::Vec3f& v00 = boundaryVerts[baseIndex    ];
-                        osg::Vec3f& v10 = boundaryVerts[baseIndex + 1];
-                        osg::Vec3f& v01 = boundaryVerts[baseIndex + d.numCols];
-                        osg::Vec3f& v11 = boundaryVerts[baseIndex + d.numCols + 1];
-
-                        float e00 = boundaryElevations[baseIndex];
-                        float e10 = boundaryElevations[baseIndex + 1];
-                        float e01 = boundaryElevations[baseIndex + d.numCols];
-                        float e11 = boundaryElevations[baseIndex + d.numCols + 1];
-
-                       
-                        if (!optimizeTriangleOrientation || fabsf(e00-e11)<fabsf(e01-e10))
-                        {                            
-                            osg::Vec3 normal1 = (v00-v01) ^ (v11-v01);                       
-                            (*d.normals)[i00] += normal1;                                                
-
-                            osg::Vec3 normal2 = (v10-v00) ^ (v11-v00);                        
-                            (*d.normals)[i00] += normal2;                                                
-                            (*d.normals)[i10] += normal2;                                                
-                        }
-                        else
-                        {                            
-                            osg::Vec3 normal1 = (v00-v01) ^ (v10-v01);
-                            (*d.normals)[i00] += normal1;                                                
-                            (*d.normals)[i10] += normal1;                                                
-
-                            osg::Vec3 normal2 = (v10-v01) ^ (v11-v01);                        
-                            (*d.normals)[i10] += normal2;                        
-                        }
-                    }
-                }
-            }            
-        }
-
-        if (recalcNormals)
-        {
-            for( osg::Vec3Array::iterator nitr = d.normals->begin(); nitr != d.normals->end(); ++nitr )
-            {
-                nitr->normalize();
-            }       
-        }
-    }
-
-
-    void installRenderData( Data& d )
-    {
-        // pre-size all vectors:
-        unsigned size = d.renderLayers.size();
-
-        d.surface->_layers.resize( size );
-
-        for ( MaskRecordVector::iterator mr = d.maskRecords.begin(); mr != d.maskRecords.end(); ++mr )
-            mr->_geom->_layers.resize( size );
-        
-        if ( d.renderTileCoords.valid() )
-            d.surface->_tileCoords = d.renderTileCoords;
-            
-        // TODO: evaluate this suspicious code. -gw
-        if ( d.stitchTileCoords.valid() )
-            d.surface->_tileCoords = d.stitchTileCoords.get();
-
-        // install the render data for each layer:
-        for( RenderLayerVector::const_iterator r = d.renderLayers.begin(); r != d.renderLayers.end(); ++r )
-        {
-            unsigned order = r->_layer.getOrder();
-
-            MPGeometry::Layer layer;
-            layer._layerID        = r->_layer.getUID();
-            layer._imageLayer     = r->_layer.getMapLayer();
-            layer._tex            = r->_layer.getTexture();
-            layer._texParent      = r->_layerParent.getTexture();
-
-            // cache stock opacity. Disable if a color filter is installed, since
-            // it can modify the alpha.
-            layer._opaque =
-                (r->_layer.getMapLayer()->getColorFilters().size() == 0 ) &&
-                (layer._tex.valid() && !r->_layer.hasAlpha()) &&
-                (!layer._texParent.valid() || !r->_layerParent.hasAlpha());
-
-            // parent texture matrix: it's a scale/bias matrix encoding the difference
-            // between the two locators.
-            if ( r->_layerParent.getLocator() )
-            {
-                osg::Matrixd sbmatrix;
-
-                r->_layerParent.getLocator()->createScaleBiasMatrix(
-                    r->_layer.getLocator()->getDataExtent(),
-                    sbmatrix );
-
-                layer._texMatParent = sbmatrix;
-            }
-
-            // the surface:
-            layer._texCoords  = r->_texCoords;
-            d.surface->_layers[order] = layer;
-
-            // the mask geometries:
-            for ( MaskRecordVector::iterator mr = d.maskRecords.begin(); mr != d.maskRecords.end(); ++mr )
-            {
-                layer._texCoords = r->_stitchTexCoords.get();
-                mr->_geom->_layers[order] = layer;
-            }
-        }
-    }
-
-
-    // Optimize the data. Convert all modes to GL_TRIANGLES and run the
-    // critical vertex cache optimizations.
-    void optimize( Data& d )
-    {
-        // the optimization pass is incompatible with the shared arrays used
-        // during masking.
-        if ( d.maskRecords.size() > 0 )
-        {
-            OE_DEBUG
-                << LC << "Skipping optimization pass for tile " << d.model->_tileKey.str()
-                << " because it contains masking geometry" <<std::endl;
-            return;
-        }
- 
-        // For vertex cache optimization to work, all the arrays must be in
-        // the geometry. MP doesn't store texture/tile coords in the geometry
-        // so we need to temporarily add them.
-
-#if OSG_MIN_VERSION_REQUIRED(3, 1, 8) // after osg::Geometry API changes
-
-        osg::Geometry::ArrayList& tdl = d.surface->getTexCoordArrayList();
-        int u=0;
-        for( RenderLayerVector::const_iterator r = d.renderLayers.begin(); r != d.renderLayers.end(); ++r )
-        {
-            if ( r->_texCoords.valid() && r->_ownsTexCoords )
-            {
-                r->_texCoords->setBinding( osg::Array::BIND_PER_VERTEX );
-                tdl.push_back( r->_texCoords.get() );
-            }
-        }
-        if ( d.renderTileCoords.valid() && d.ownsTileCoords )
-        {
-            d.renderTileCoords->setBinding( osg::Array::BIND_PER_VERTEX );
-            tdl.push_back( d.renderTileCoords.get() );
-        }
-
-#else // OSG version < 3.1.8 (before osg::Geometry API changes)
-
-        osg::Geometry::ArrayDataList& tdl = d.surface->getTexCoordArrayList();
-        int u=0;
-        for( RenderLayerVector::const_iterator r = d.renderLayers.begin(); r != d.renderLayers.end(); ++r )
-        {
-            if ( r->_ownsTexCoords && r->_texCoords.valid() )
-            {
-                tdl.push_back( osg::Geometry::ArrayData(r->_texCoords.get(), osg::Geometry::BIND_PER_VERTEX) );
-            }
-        }
-        if ( d.renderTileCoords.valid() && d.ownsTileCoords )
-        {
-            tdl.push_back( osg::Geometry::ArrayData(d.renderTileCoords.get(), osg::Geometry::BIND_PER_VERTEX) );
-        }
-
-#endif
-
-        osgUtil::Optimizer o;
-        o.optimize( d.surfaceGeode, 
-            osgUtil::Optimizer::VERTEX_PRETRANSFORM |
-            osgUtil::Optimizer::INDEX_MESH |
-            osgUtil::Optimizer::VERTEX_POSTTRANSFORM );
-
-        tdl.clear();
-    }
-
-
-    struct CullByTraversalMask : public osg::Drawable::CullCallback
-    {
-        CullByTraversalMask( unsigned mask ) : _mask(mask) { }
-        unsigned _mask;
-
-        bool cull(osg::NodeVisitor* nv, osg::Drawable* drawable, osg::RenderInfo* renderInfo) const 
-        {
-            return ((unsigned)nv->getTraversalMask() & ((unsigned)nv->getNodeMaskOverride() | _mask)) == 0;
-        }
-    };
-}
-
-//------------------------------------------------------------------------
-
-TileModelCompiler::TileModelCompiler(const MaskLayerVector&              masks,
-                                     int                                 texImageUnit,
-                                     bool                                optimizeTriOrientation,
-                                     const MPTerrainEngineOptions& options) :
-_masks                 ( masks ),
-_optimizeTriOrientation( optimizeTriOrientation ),
-_options               ( options ),
-_textureImageUnit      ( texImageUnit )
-{
-    _cullByTraversalMask = new CullByTraversalMask(*options.secondaryTraversalMask());
-}
-
-
-TileNode*
-TileModelCompiler::compile(const TileModel* model,
-                           const MapFrame&  frame)
-{
-    TileNode* tile = new TileNode( model->_tileKey, model );
-
-    // Working data for the build.
-    Data d(model, frame, _masks);
-
-    d.parentModel = model->getParentTileModel();
-    d.scaleHeight = *_options.verticalScale();
-
-    // build the localization matrix for this tile:
-    model->_tileLocator->unitToModel( osg::Vec3(0.5f, 0.5f, 0.0), d.centerModel );
-    tile->setMatrix( osg::Matrix::translate(d.centerModel) );
-
-    // A Geode/Geometry for the surface:
-    d.surface = new MPGeometry( d.model->_tileKey, d.frame, _textureImageUnit );
-    d.surface->setUseVertexBufferObjects(d.useVBOs);
-    d.surface->setUseDisplayList(!d.useVBOs);
-    d.surfaceGeode = new osg::Geode();
-    d.surfaceGeode->addDrawable( d.surface );
-    d.surfaceGeode->setNodeMask( *_options.primaryTraversalMask() );
-
-    tile->addChild( d.surfaceGeode );
-
-    // A Geode/Geometry for the skirt. This is good for traversal masking (e.g. shadows)
-    // but bad since we're not combining the entire tile into a single geometry.
-    // TODO: make this optional?
-    d.createSkirt = (_options.heightFieldSkirtRatio().value() > 0.0);
-
-    // adjust the tile locator for geocentric mode:
-    d.geoLocator = model->_tileLocator->getCoordinateSystemType() == GeoLocator::GEOCENTRIC ? 
-        model->_tileLocator->getGeographicFromGeocentric() :
-        model->_tileLocator.get();
-
-    // Set up any geometry-cutting masks:
-    if ( d.maskLayers.size() > 0 )
-        setupMaskRecords( d );
-
-    // allocate all the vertex, normal, and color arrays.
-    double sampleRatio = *_options.heightFieldSampleRatio();
-    if ( sampleRatio <= 0.0f )
-        sampleRatio = osg::clampBetween( model->_tileKey.getLOD()/20.0, 0.0625, 1.0 );
-
-    setupGeometryAttributes( d, sampleRatio ); //, *_options.color() );
-
-    // set up the list of layers to render and their shared arrays.
-    setupTextureAttributes( d, _cache );
-
-    // calculate the vertex and normals for the surface geometry.
-    createSurfaceGeometry( d );
-
-    // build geometry for the masked areas, if applicable
-    if ( d.maskRecords.size() > 0 )
-        createMaskGeometry( d );
-
-    // build the skirts.
-    if ( d.createSkirt )
-        createSkirtGeometry( d, *_options.heightFieldSkirtRatio() );
-
-    // tesselate the surface verts into triangles.
-    tessellateSurfaceGeometry( d, _optimizeTriOrientation, *_options.normalizeEdges() );
-
-    // installs the per-layer rendering data into the Geometry objects.
-    installRenderData( d );
-
-    // performance optimizations.
-    optimize( d );
-
-#if 0 // this is covered by the opt above.
-    // convert mask geometry to tris.
-    for (MaskRecordVector::iterator mr = d.maskRecords.begin(); mr != d.maskRecords.end(); ++mr)
-    {
-        MeshConsolidator::convertToTriangles( *((*mr)._geom), true );
-    }
-#endif
-    
-    if (osgDB::Registry::instance()->getBuildKdTreesHint()==osgDB::ReaderWriter::Options::BUILD_KDTREES &&
-        osgDB::Registry::instance()->getKdTreeBuilder())
-    {            
-        osg::ref_ptr<osg::KdTreeBuilder> builder = osgDB::Registry::instance()->getKdTreeBuilder()->clone();
-        tile->accept(*builder);
-    }
-
-    // Temporary solution to the OverlayDecorator techniques' inappropriate setting of
-    // uniform values during the CULL traversal, which causes corruption of the RTT 
-    // camera matricies when DRAW overlaps the next frame's CULL. Please see my comments
-    // in DrapingTechnique.cpp for more information.
-    // NOTE: cannot set this until optimizations (above) are complete
-    SetDataVarianceVisitor sdv( osg::Object::DYNAMIC );
-    tile->accept( sdv );
-
-    return tile;
-}
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include "TileModelCompiler"
+#include "MPGeometry"
+
+#include <osgEarth/Locators>
+#include <osgEarth/Registry>
+#include <osgEarth/Capabilities>
+#include <osgEarth/MapFrame>
+#include <osgEarth/HeightFieldUtils>
+#include <osgEarth/ImageUtils>
+#include <osgEarth/Utils>
+#include <osgEarthSymbology/Geometry>
+#include <osgEarthSymbology/MeshConsolidator>
+
+#include <osg/Geode>
+#include <osg/Geometry>
+#include <osg/MatrixTransform>
+#include <osg/GL2Extensions>
+#include <osgUtil/DelaunayTriangulator>
+#include <osgUtil/Optimizer>
+#include <osgUtil/MeshOptimizers>
+
+using namespace osgEarth::Drivers::MPTerrainEngine;
+using namespace osgEarth;
+using namespace osgEarth::Drivers;
+using namespace osgEarth::Symbology;
+
+#define LC "[TileModelCompiler] "
+
+//#define USE_TEXCOORD_CACHE 1
+
+//------------------------------------------------------------------------
+
+osg::ref_ptr<osg::Vec2Array>&
+CompilerCache::TexCoordArrayCache::get(const osg::Vec4d& mat,
+                                       unsigned          cols,
+                                       unsigned          rows)
+{
+    for( iterator i = begin(); i != end(); ++i )
+    {
+        CompilerCache::TexCoordTableKey& key = i->first;
+        if ( key._mat == mat && key._cols == cols && key._rows == rows )
+        {
+            return i->second;
+        }
+    }
+    
+    CompilerCache::TexCoordTableKey newKey;
+    newKey._mat     = mat;
+    newKey._cols    = cols;
+    newKey._rows    = rows;
+    this->push_back( std::make_pair(newKey, (osg::Vec2Array*)0L) );
+    return this->back().second;
+}
+
+
+//------------------------------------------------------------------------
+
+
+#define MATCH_TOLERANCE 0.000001
+
+namespace
+{    
+    // Data for a single renderable color layer
+    struct RenderLayer
+    {
+        TileModel::ColorData           _layer;
+        TileModel::ColorData           _layerParent;
+        osg::ref_ptr<const GeoLocator> _locator;
+        osg::ref_ptr<osg::Vec2Array>   _texCoords;
+        osg::ref_ptr<osg::Vec2Array>   _stitchTexCoords;
+        bool _ownsTexCoords;
+        RenderLayer() : 
+            _ownsTexCoords( false ) { }
+    };
+
+    typedef std::vector< RenderLayer > RenderLayerVector;
+
+
+    // Record that stores the data for a single masking region.
+    struct MaskRecord
+    {
+        osg::ref_ptr<osg::Vec3dArray> _boundary;
+        osg::Vec3d                    _ndcMin, _ndcMax;
+        MPGeometry*                   _geom;
+        osg::ref_ptr<osg::Vec3Array>  _internal;
+
+        MaskRecord(osg::Vec3dArray* boundary, osg::Vec3d& ndcMin, osg::Vec3d& ndcMax, MPGeometry* geom) 
+            : _boundary(boundary), _ndcMin(ndcMin), _ndcMax(ndcMax), _geom(geom), _internal(new osg::Vec3Array()) { }
+    };
+
+    typedef std::vector<MaskRecord> MaskRecordVector;
+
+
+    typedef std::vector<int> Indices;
+
+
+    struct Data
+    {
+        Data(const TileModel* in_model, 
+             const MapFrame&  in_frame,
+             const MaskLayerVector& in_maskLayers,
+             const ModelLayerVector& in_modelLayers)
+
+            : model     ( in_model ), 
+              frame     ( in_frame ),
+              maskLayers( in_maskLayers ),
+              modelLayers( in_modelLayers )
+        {
+            surfaceGeode     = 0L;
+            surface          = 0L;
+            heightScale      = 1.0f;
+            heightOffset     = 0.0f;
+            createSkirt      = false;
+            i_sampleFactor   = 1.0f;
+            j_sampleFactor   = 1.0f;
+            useVBOs          = true; 
+            textureImageUnit = 0;
+            renderTileCoords = 0L;
+            ownsTileCoords   = false;
+            stitchTileCoords = 0L;
+            stitchGeom       = 0L;
+        }
+
+        const MapFrame& frame;
+
+        bool                     useVBOs;
+        int                      textureImageUnit;
+
+        const TileModel*              model;                   // the tile's data model
+        osg::ref_ptr<const TileModel> parentModel;             // parent model reference
+
+        const MaskLayerVector&   maskLayers;                    // map-global masking layer set
+        const ModelLayerVector&  modelLayers;                   // model layers with masks set
+        osg::ref_ptr<GeoLocator> geoLocator;                    // tile locator adjusted to geographic
+        osg::Vec3d               centerModel;                   // tile center in model (world) coords
+
+        RenderLayerVector            renderLayers;
+        osg::ref_ptr<osg::Vec2Array> renderTileCoords;
+        bool                         ownsTileCoords;
+
+        // tile coords for masked areas; always owned (never shared)
+        osg::ref_ptr<osg::Vec2Array> stitchTileCoords;
+
+        // surface data:
+        osg::Geode*                   surfaceGeode;
+        MPGeometry*                   surface;
+        osg::Vec3Array*               surfaceVerts;
+        osg::Vec3Array*               normals;
+        osg::Vec4Array*               surfaceAttribs;
+        osg::Vec4Array*               surfaceAttribs2;
+        unsigned                      numVerticesInSurface;
+        osg::ref_ptr<osg::FloatArray> elevations;
+        Indices                       indices;
+        osg::BoundingSphere           surfaceBound;
+
+        // skirt data:
+        unsigned                 numVerticesInSkirt;
+        bool                     createSkirt;
+
+        // sampling grid parameters:
+        unsigned                 numRows;
+        unsigned                 numCols;
+        double                   i_sampleFactor;
+        double                   j_sampleFactor;
+        double                   heightScale;
+        double                   heightOffset;
+        unsigned                 originalNumRows;
+        unsigned                 originalNumCols;
+        
+        // for masking/stitching:
+        MaskRecordVector         maskRecords;
+        MPGeometry*              stitchGeom;
+    };
+
+
+    /**
+     * Set up an single masking geometry. Called by setupMaskRecords
+     */
+    void setupMaskRecord(Data& d, osg::Vec3dArray* boundary)
+    {
+        if ( boundary )
+        {
+            osg::Vec3d min, max;
+            min = max = boundary->front();
+
+            for (osg::Vec3dArray::iterator it = boundary->begin(); it != boundary->end(); ++it)
+            {
+                if (it->x() < min.x())
+                min.x() = it->x();
+
+                if (it->y() < min.y())
+                min.y() = it->y();
+
+                if (it->x() > max.x())
+                max.x() = it->x();
+
+                if (it->y() > max.y())
+                max.y() = it->y();
+            }
+
+            osg::Vec3d min_ndc, max_ndc;
+            d.geoLocator->modelToUnit(min, min_ndc);
+            d.geoLocator->modelToUnit(max, max_ndc);
+
+            bool x_match = ((min_ndc.x() >= 0.0 && max_ndc.x() <= 1.0) ||
+                            (min_ndc.x() <= 0.0 && max_ndc.x() > 0.0) ||
+                            (min_ndc.x() < 1.0 && max_ndc.x() >= 1.0));
+
+            bool y_match = ((min_ndc.y() >= 0.0 && max_ndc.y() <= 1.0) ||
+                            (min_ndc.y() <= 0.0 && max_ndc.y() > 0.0) ||
+                            (min_ndc.y() < 1.0 && max_ndc.y() >= 1.0));
+
+            if (x_match && y_match)
+            {
+                d.stitchGeom = new MPGeometry( d.model->_tileKey, d.frame, d.textureImageUnit );
+                //d.stitchGeom->setUseVertexBufferObjects(d.useVBOs);
+                d.surfaceGeode->addDrawable(d.stitchGeom);
+                d.maskRecords.push_back( MaskRecord(boundary, min_ndc, max_ndc, d.stitchGeom) );
+            }
+        }
+    }
+
+    
+    /**
+     * Set up the masking records for this build. Here we check all the map's mask layer
+     * boundary geometries and find any that intersect the current tile. For an intersection
+     * we create a MaskRecord that we'll use later in the process.
+     */
+    void setupMaskRecords(Data& d)
+    {
+        // When displaying Plate Carre, Heights have to be converted from meters to degrees.
+        // This is also true for mask feature
+        // TODO: adjust this calculation based on the actual EllipsoidModel.
+        float scale = d.heightScale;
+        if (d.model->_tileLocator->getCoordinateSystemType() == osgEarth::GeoLocator::GEOGRAPHIC)
+        {
+            scale = d.heightScale / 111319.0f;
+        }      
+
+        for(MaskLayerVector::const_iterator it = d.maskLayers.begin();
+            it != d.maskLayers.end(); 
+            ++it)
+        {
+            MaskLayer* layer = it->get();
+            if ( layer->getMinLevel() <= d.model->_tileKey.getLevelOfDetail() )
+            {
+                setupMaskRecord( d, layer->getOrCreateMaskBoundary(
+                    scale,
+                    d.model->_tileLocator->getDataExtent().getSRS(),
+                    (ProgressCallback*)0L ) );
+            }
+        }
+
+        for(ModelLayerVector::const_iterator it = d.modelLayers.begin();
+            it != d.modelLayers.end();
+            ++it)
+        {
+            ModelLayer* layer = it->get();
+            if (layer->getMaskSource() &&
+                layer->getMaskMinLevel() <= d.model->_tileKey.getLevelOfDetail() )
+            {
+                setupMaskRecord( d, layer->getOrCreateMaskBoundary(
+                    scale,
+                    d.model->_tileLocator->getDataExtent().getSRS(),
+                    (ProgressCallback*)0L ) );
+            }
+        }
+    }
+
+    /**
+     * Calculates the sample rate and allocates all the vertex, normal, and color
+     * arrays for the tile.
+     */
+    void setupGeometryAttributes( Data& d, double sampleRatio )
+    {
+        d.numRows = 8;
+        d.numCols = 8;
+        d.originalNumRows = 8;
+        d.originalNumCols = 8;        
+
+        // read the row/column count and skirt size from the model:
+        osg::HeightField* hf = d.model->_elevationData.getHeightField();
+        if ( hf )
+        {
+            d.numCols = hf->getNumColumns();
+            d.numRows = hf->getNumRows();
+            d.originalNumCols = d.numCols;
+            d.originalNumRows = d.numRows;
+        }
+
+        // calculate the elevation sampling factors that we'll use to step though
+        // the tile's NDC space.
+        d.i_sampleFactor = 1.0f;
+        d.j_sampleFactor = 1.0f;
+
+        if ( sampleRatio != 1.0f )
+        {            
+            d.numCols = osg::maximum((unsigned int) (float(d.originalNumCols)*sqrtf(sampleRatio)), 4u);
+            d.numRows = osg::maximum((unsigned int) (float(d.originalNumRows)*sqrtf(sampleRatio)), 4u);
+
+            d.i_sampleFactor = double(d.originalNumCols-1)/double(d.numCols-1);
+            d.j_sampleFactor = double(d.originalNumRows-1)/double(d.numRows-1);
+        }
+
+
+        // calculate the total number of verts:
+        d.numVerticesInSkirt   = d.createSkirt ? (2 * (d.numCols*2 + d.numRows*2 - 4)) : 0;
+        d.numVerticesInSurface = d.numCols * d.numRows + d.numVerticesInSkirt;
+
+        // allocate and assign vertices
+        d.surfaceVerts = new osg::Vec3Array();
+        d.surfaceVerts->reserve( d.numVerticesInSurface );
+        d.surface->setVertexArray( d.surfaceVerts );
+
+        // allocate and assign normals
+        d.normals = new osg::Vec3Array();
+        d.normals->reserve( d.numVerticesInSurface );
+        d.surface->setNormalArray( d.normals );
+        d.surface->setNormalBinding( osg::Geometry::BIND_PER_VERTEX );
+
+        // vertex attribution
+        // for each vertex, a vec4 containing a unit extrusion vector in [0..2] and the raw elevation in [3]
+        d.surfaceAttribs = new osg::Vec4Array();
+        d.surfaceAttribs->reserve( d.numVerticesInSurface );
+        d.surface->setVertexAttribArray( osg::Drawable::ATTRIBUTE_6, d.surfaceAttribs );
+        d.surface->setVertexAttribBinding( osg::Drawable::ATTRIBUTE_6, osg::Geometry::BIND_PER_VERTEX );
+        d.surface->setVertexAttribNormalize( osg::Drawable::ATTRIBUTE_6, false );
+
+        // for each vertex, index 0 holds the interpolated elevation from the lower lod (for morphing)
+        d.surfaceAttribs2 = new osg::Vec4Array();
+        d.surfaceAttribs2->reserve( d.numVerticesInSurface );
+        d.surface->setVertexAttribArray( osg::Drawable::ATTRIBUTE_7, d.surfaceAttribs2 );
+        d.surface->setVertexAttribBinding( osg::Drawable::ATTRIBUTE_7, osg::Geometry::BIND_PER_VERTEX );
+        d.surface->setVertexAttribNormalize( osg::Drawable::ATTRIBUTE_7, false );
+        
+        // temporary data structures for triangulation support
+        d.elevations = new osg::FloatArray();
+        d.elevations->reserve( d.numVerticesInSurface );
+        d.indices.resize( d.numVerticesInSurface, -1 );
+    }
+
+
+    /**
+     * Generates the texture coordinate arrays for each layer.
+     */
+    void setupTextureAttributes( Data& d, CompilerCache& cache )
+    {
+        // Any color entries that have the same Locator will share a texcoord
+        // array, saving on memory.
+        d.renderLayers.reserve( d.model->_colorData.size() );
+
+#ifdef USE_TEXCOORD_CACHE
+        // unit tile coords - [0..1] always across the tile.
+        osg::Vec4d idmat;
+        idmat[0] = 0.0;
+        idmat[1] = 0.0;
+        idmat[2] = 1.0;
+        idmat[3] = 1.0;
+
+        osg::ref_ptr<osg::Vec2Array>& tileCoords = cache._surfaceTexCoordArrays.get( idmat, d.numCols, d.numRows );
+        if ( !tileCoords.valid() )
+        {
+            // Note: anything in the cache must have its own VBO. No sharing!
+            tileCoords = new osg::Vec2Array();
+            tileCoords->setVertexBufferObject( new osg::VertexBufferObject() );
+            tileCoords->reserve( d.numVerticesInSurface );
+            d.ownsTileCoords = true;
+        }
+        d.renderTileCoords = tileCoords.get();
+
+#else // not USE_TEXCOORD_CACHE
+        d.renderTileCoords = new osg::Vec2Array();
+        d.renderTileCoords->reserve( d.numVerticesInSurface );
+        d.ownsTileCoords = true;
+#endif
+
+
+        // build a list of "render layers", in rendering order, sharing texture coordinate
+        // arrays wherever possible.
+        for( TileModel::ColorDataByUID::const_iterator i = d.model->_colorData.begin(); i != d.model->_colorData.end(); ++i )
+        {
+            const TileModel::ColorData& colorLayer = i->second;
+            RenderLayer r;
+            r._layer = colorLayer;
+
+            const GeoLocator* locator = r._layer.getLocator();
+            if ( locator )
+            {
+                // if we have no mask records, we can use the texture coordinate array cache.
+                if ( d.maskLayers.size() == 0 && locator->isLinear() )
+                {
+                    const GeoExtent& locex = locator->getDataExtent();
+                    const GeoExtent& keyex = d.model->_tileKey.getExtent();
+
+                    osg::Vec4d mat;
+                    mat[0] = (keyex.xMin() - locex.xMin())/locex.width();
+                    mat[1] = (keyex.yMin() - locex.yMin())/locex.height();
+                    mat[2] = (keyex.width() / locex.width());
+                    mat[3] = (keyex.height() / locex.height());
+
+                    //OE_DEBUG << "key=" << d.model->_tileKey.str() << ": off=[" <<mat[0]<< ", " <<mat[1] << "] scale=["
+                    //    << mat[2]<< ", " << mat[3] << "]" << std::endl;
+
+#ifdef USE_TEXCOORD_CACHE
+                    osg::ref_ptr<osg::Vec2Array>& surfaceTexCoords = cache._surfaceTexCoordArrays.get( mat, d.numCols, d.numRows );
+                    if ( !surfaceTexCoords.valid() )
+                    {
+                        // Note: anything in the cache must have its own VBO. No sharing!
+                        surfaceTexCoords = new osg::Vec2Array();
+                        surfaceTexCoords->setVertexBufferObject( new osg::VertexBufferObject() );
+                        surfaceTexCoords->reserve( d.numVerticesInSurface );
+                        r._ownsTexCoords = true;
+                    }
+                    r._texCoords = surfaceTexCoords.get();
+
+#else // not USE_TEXCOORD_CACHE
+                    r._texCoords = new osg::Vec2Array();
+                    r._texCoords->reserve( d.numVerticesInSurface );
+                    r._ownsTexCoords = true;
+#endif
+                }
+
+                else
+                {
+                    // cannot use the tex coord array cache if there are masking records.
+                    r._texCoords = new osg::Vec2Array();
+                    r._texCoords->reserve( d.numVerticesInSurface );
+                    r._ownsTexCoords = true;
+
+                    if ( d.maskRecords.size() > 0 )
+                    {
+                        r._stitchTexCoords = new osg::Vec2Array();
+                        //r._stitchTexCoords->setVertexBufferObject( d.stitchGeom->getOrCreateVertexBufferObject() );
+                        if ( !d.stitchTileCoords.valid() )
+                        {
+                            d.stitchTileCoords = new osg::Vec2Array();
+                            //d.stitchTileCoords->setVertexBufferObject( d.stitchGeom->getOrCreateVertexBufferObject() );
+                        }
+                    }
+                }
+
+                // install the locator:
+                r._locator = locator;
+                if ( locator->getCoordinateSystemType() == osgTerrain::Locator::GEOCENTRIC )
+                {
+                    r._locator = locator->getGeographicFromGeocentric();
+                }
+
+                // install the parent color data layer if necessary.
+                if ( d.parentModel.valid() )
+                {                    
+                    if (!d.parentModel->getColorData( r._layer.getUID(), r._layerParent ))
+                    {
+                        // If we can't get the color data from the parent that means it doesn't exist, perhaps b/c of a min level setting
+                        // So we create a false layer parent with a transparent image so it will fade into the real data.
+                        r._layerParent = r._layer;
+                        r._layerParent._texture = new osg::Texture2D(ImageUtils::createEmptyImage());
+                        r._layerParent._hasAlpha = true;
+                    }
+                }
+                else
+                {
+                    r._layerParent = r._layer;
+                }
+
+                d.renderLayers.push_back( r );
+
+                // Note that we don't actually assign the tex coord arrays to the geometry yet.
+                // That must wait until the end. See the comments in assignTextureArrays()
+                // to understand why.
+            }
+            else
+            {
+                OE_WARN << LC << "Found a Locator, but it wasn't a GeoLocator." << std::endl;
+            }
+        }
+    }
+
+
+    /**
+     * Iterate over the sampling grid and calculate the vertex positions and normals
+     * for each sampling point.
+     */
+    void createSurfaceGeometry( Data& d )
+    {
+        d.surfaceBound.init();
+
+        //osgTerrain::HeightFieldLayer* elevationLayer = d.model->_elevationData.getHFLayer();
+
+        osg::HeightField* hf            = d.model->_elevationData.getHeightField();
+        GeoLocator*       hfLocator     = d.model->_elevationData.getLocator();
+
+        // populate vertex and tex coord arrays    
+        for(unsigned j=0; j < d.numRows; ++j)
+        {
+            for(unsigned i=0; i < d.numCols; ++i)
+            {
+                unsigned int iv = j*d.numCols + i;
+                osg::Vec3d ndc( ((double)i)/(double)(d.numCols-1), ((double)j)/(double)(d.numRows-1), 0.0);
+
+                // raw height:
+                float heightValue = 0.0f;
+                bool  validValue  = true;
+
+                if ( hf )
+                {
+                    validValue = d.model->_elevationData.getHeight( ndc, d.model->_tileLocator, heightValue, INTERP_TRIANGULATE );
+                }
+
+                ndc.z() = heightValue * d.heightScale + d.heightOffset;
+
+                if ( !validValue )
+                {
+                    d.indices[iv] = -1;
+                }
+
+                // First check whether the sampling point falls within a mask's bounding box.
+                // If so, skip the sampling and mark it as a mask location
+                if ( validValue && d.maskRecords.size() > 0 )
+                {
+					double minndcx = d.maskRecords[0]._ndcMin.x();
+					double minndcy = d.maskRecords[0]._ndcMin.y();
+					double maxndcx = d.maskRecords[0]._ndcMax.x();
+					double maxndcy = d.maskRecords[0]._ndcMax.y();
+					for (int mrs = 1; mrs < d.maskRecords.size(); ++mrs)
+					{
+						if ( d.maskRecords[mrs]._ndcMin.x()< minndcx)
+						{
+							minndcx = d.maskRecords[mrs]._ndcMin.x();
+						}
+						if ( d.maskRecords[mrs]._ndcMin.y()< minndcy)
+						{
+							minndcy = d.maskRecords[mrs]._ndcMin.y();
+						}
+						if ( d.maskRecords[mrs]._ndcMax.x()> maxndcx)
+						{
+							maxndcx = d.maskRecords[mrs]._ndcMax.x();
+						}
+						if ( d.maskRecords[mrs]._ndcMax.y()> maxndcy)
+						{
+							maxndcy = d.maskRecords[mrs]._ndcMax.y();
+						}
+
+
+					}
+					if(ndc.x() >= (minndcx) && ndc.x() <= (maxndcx) &&
+						        ndc.y() >= (minndcy) && ndc.y() <= (maxndcy))
+				    {
+				        validValue = false;
+				        d.indices[iv] = -2;
+				    
+				    }
+                }
+                
+                if ( validValue )
+                {
+                    d.indices[iv] = d.surfaceVerts->size();
+
+                    osg::Vec3d model;
+                    d.model->_tileLocator->unitToModel( ndc, model );
+
+                    (*d.surfaceVerts).push_back(model - d.centerModel);
+
+                    // grow the bounding sphere:
+                    d.surfaceBound.expandBy( (*d.surfaceVerts).back() );
+
+                    // the separate texture space requires separate transformed texcoords for each layer.
+                    for( RenderLayerVector::const_iterator r = d.renderLayers.begin(); r != d.renderLayers.end(); ++r )
+                    {
+                        if ( r->_ownsTexCoords )
+                        {
+                            if ( !r->_locator->isEquivalentTo( *d.geoLocator.get() ) )
+                            {
+                                osg::Vec3d color_ndc;
+                                osgTerrain::Locator::convertLocalCoordBetween( *d.geoLocator.get(), ndc, *r->_locator.get(), color_ndc );
+                                r->_texCoords->push_back( osg::Vec2( color_ndc.x(), color_ndc.y() ) );
+                            }
+                            else
+                            {
+                                r->_texCoords->push_back( osg::Vec2( ndc.x(), ndc.y() ) );
+                            }
+                        }
+                    }
+
+                    if ( d.ownsTileCoords )
+                    {
+                        d.renderTileCoords->push_back( osg::Vec2(ndc.x(), ndc.y()) );
+                    }
+
+                    // record the raw elevation value in our float array for later
+                    (*d.elevations).push_back(ndc.z());
+
+                    // compute the local normal (up vector)
+                    osg::Vec3d ndc_plus_one(ndc.x(), ndc.y(), ndc.z() + 1.0);
+                    osg::Vec3d model_up;
+                    d.model->_tileLocator->unitToModel(ndc_plus_one, model_up);
+                    model_up = model_up - model;
+                    model_up.normalize();
+
+                    (*d.normals).push_back(model_up);
+
+                    // Calculate and store the "old height", i.e the height value from
+                    // the parent LOD.
+                    float     oldHeightValue = heightValue;
+                    osg::Vec3 oldNormal;
+
+                    // This only works if the tile size is an odd number in both directions.
+                    if (d.model->_tileKey.getLOD() > 0 && (d.numCols&1) && (d.numRows&1) && d.parentModel.valid())
+                    {
+                        d.parentModel->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), oldHeightValue, INTERP_TRIANGULATE );
+                        d.parentModel->_elevationData.getNormal( ndc, d.model->_tileLocator.get(), oldNormal, INTERP_TRIANGULATE );
+                    }
+                    else
+                    {
+                        d.model->_elevationData.getNormal(ndc, d.model->_tileLocator.get(), oldNormal, INTERP_TRIANGULATE );
+                    }
+
+                    // first attribute set has the unit extrusion vector and the
+                    // raw height value.
+                    (*d.surfaceAttribs).push_back( osg::Vec4f(
+                        model_up.x(),
+                        model_up.y(),
+                        model_up.z(),
+                        heightValue) );
+
+                    // second attribute set has the old height value in "w"
+                    (*d.surfaceAttribs2).push_back( osg::Vec4f(
+                        oldNormal.x(),
+                        oldNormal.y(),
+                        oldNormal.z(),
+                        oldHeightValue ) );
+                }
+            }
+        }
+
+        //if ( d.renderLayers[0]._texCoords->size() < d.surfaceVerts->size() )
+        //{
+        //    OE_WARN << LC << "not good. mask error." << std::endl;
+        //}
+    }
+
+
+    /**
+     * If there are masking records, calculate the vertices to bound the masked area
+     * and the internal verticies to populate it. Then build a triangulation of the
+     * area inside the masking bounding box and add this to the surface geode.
+     */
+    void createMaskGeometry( Data& d )
+    {
+        bool hasElev = d.model->hasElevation();
+
+		osg::ref_ptr<osgUtil::DelaunayTriangulator> trig=new osgUtil::DelaunayTriangulator();
+
+		std::vector<osg::ref_ptr<osgUtil::DelaunayConstraint> > alldcs;
+	
+		osg::ref_ptr<osg::Vec3Array> coordsArray = new osg::Vec3Array;
+						
+		double minndcx = d.maskRecords[0]._ndcMin.x();
+		double minndcy = d.maskRecords[0]._ndcMin.y();
+		double maxndcx = d.maskRecords[0]._ndcMax.x();
+		double maxndcy = d.maskRecords[0]._ndcMax.y();
+        for (int mrs = 1; mrs < d.maskRecords.size(); ++mrs)
+		{
+			if ( d.maskRecords[mrs]._ndcMin.x()< minndcx)
+			{
+				minndcx = d.maskRecords[mrs]._ndcMin.x();
+			}
+			if ( d.maskRecords[mrs]._ndcMin.y()< minndcy)
+			{
+				minndcy = d.maskRecords[mrs]._ndcMin.y();
+			}
+			if ( d.maskRecords[mrs]._ndcMax.x()> maxndcx)
+			{
+				maxndcx = d.maskRecords[mrs]._ndcMax.x();
+			}
+			if ( d.maskRecords[mrs]._ndcMax.y()> maxndcy)
+			{
+				maxndcy = d.maskRecords[mrs]._ndcMax.y();
+			}
+
+			
+		}
+       
+            int min_i = (int)floor(minndcx * (double)(d.numCols-1));
+            if (min_i < 0) min_i = 0;
+            if (min_i >= (int)d.numCols) min_i = d.numCols - 1;
+
+            int min_j = (int)floor(minndcy * (double)(d.numRows-1));
+            if (min_j < 0) min_j = 0;
+            if (min_j >= (int)d.numRows) min_j = d.numRows - 1;
+
+            int max_i = (int)ceil(maxndcx * (double)(d.numCols-1));
+            if (max_i < 0) max_i = 0;
+            if (max_i >= (int)d.numCols) max_i = d.numCols - 1;
+
+            int max_j = (int)ceil(maxndcy * (double)(d.numRows-1));
+            if (max_j < 0) max_j = 0;
+            if (max_j >= (int)d.numRows) max_j = d.numRows - 1;
+
+            if (min_i >= 0 && max_i >= 0 && min_j >= 0 && max_j >= 0)
+            {
+                int num_i = max_i - min_i + 1;
+                int num_j = max_j - min_j + 1;
+
+                osg::ref_ptr<Polygon> maskSkirtPoly = new Polygon();
+                maskSkirtPoly->resize(num_i * 2 + num_j * 2 - 4);
+
+                for (int i = 0; i < num_i; i++)
+                {
+                    //int index = indices[min_j*numColumns + i + min_i];
+                    {
+                        osg::Vec3d ndc( ((double)(i + min_i))/(double)(d.numCols-1), ((double)min_j)/(double)(d.numRows-1), 0.0);
+
+                        //if (elevationLayer)
+                        if ( hasElev )
+                        {
+                            float value = 0.0f;
+                            if ( d.model->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), value, INTERP_BILINEAR ) )
+                                ndc.z() = value * d.heightScale + d.heightOffset;
+                        }
+
+                        (*maskSkirtPoly)[i] = ndc;
+                    }
+
+                    //index = indices[max_j*numColumns + i + min_i];
+                    {
+                        osg::Vec3d ndc( ((double)(i + min_i))/(double)(d.numCols-1), ((double)max_j)/(double)(d.numRows-1), 0.0);
+
+                        if ( hasElev )
+                        {
+                            float value = 0.0f;
+                            if ( d.model->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), value, INTERP_BILINEAR ) )
+                                ndc.z() = value * d.heightScale + d.heightOffset;
+                        }
+
+                        (*maskSkirtPoly)[i + (2 * num_i + num_j - 3) - 2 * i] = ndc;
+                    }
+                }
+                for (int j = 0; j < num_j - 2; j++)
+                {
+                    //int index = indices[(min_j + j + 1)*numColumns + max_i];
+                    {
+                        osg::Vec3d ndc( ((double)max_i)/(double)(d.numCols-1), ((double)(min_j + j + 1))/(double)(d.numRows-1), 0.0);
+
+                        if ( hasElev )
+                        {
+                            float value = 0.0f;
+                            if ( d.model->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), value, INTERP_BILINEAR ) )
+                                ndc.z() = value * d.heightScale + d.heightOffset;
+                        }
+
+                        (*maskSkirtPoly)[j + num_i] = ndc;
+                    }
+
+                    //index = indices[(min_j + j + 1)*numColumns + min_i];
+                    {
+                        osg::Vec3d ndc( ((double)min_i)/(double)(d.numCols-1), ((double)(min_j + j + 1))/(double)(d.numRows-1), 0.0);
+
+                        if ( hasElev )
+                        {
+                            float value = 0.0f;
+                            if ( d.model->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), value, INTERP_BILINEAR ) )
+                                ndc.z() = value * d.heightScale + d.heightOffset;
+                        }
+
+                        (*maskSkirtPoly)[j + (2 * num_i + 2 * num_j - 5) - 2 * j] = ndc;
+                    }
+                }
+
+			    for (int j = 0; j < num_j; j++)
+					for (int i = 0; i < num_i; i++)
+					{
+						//int index = indices[min_j*numColumns + i + min_i];
+						{
+							osg::Vec3d ndc( ((double)(i + min_i))/(double)(d.numCols-1), ((double)(j+min_j))/(double)(d.numRows-1), 0.0);
+
+							//if (elevationLayer)
+							if ( hasElev )
+							{
+								float value = 0.0f;
+								if ( d.model->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), value, INTERP_BILINEAR ) )
+									ndc.z() = value * d.heightScale + d.heightOffset;
+							}
+
+							coordsArray->push_back(ndc) ;
+						}						
+					}
+
+
+                // Use delaunay triangulation for stitching:
+                for (MaskRecordVector::iterator mr = d.maskRecords.begin();mr != d.maskRecords.end();mr++)
+                {
+
+					 // Add the outter stitching bounds to the collection of vertices to be used for triangulation
+				//	coordsArray->insert(coordsArray->end(), (*mr)._internal->begin(), (*mr)._internal->end());
+					//Create local polygon representing mask
+					osg::ref_ptr<Polygon> maskPoly = new Polygon();
+					for (osg::Vec3dArray::iterator it = (*mr)._boundary->begin(); it != (*mr)._boundary->end(); ++it)
+					{
+						osg::Vec3d local;
+						d.geoLocator->convertModelToLocal(*it, local);
+						maskPoly->push_back(local);
+					}
+					// Add mask bounds as a triangulation constraint
+										
+					osg::ref_ptr<osgUtil::DelaunayConstraint> newdc=new osgUtil::DelaunayConstraint;
+					osg::Vec3Array* maskConstraint = new osg::Vec3Array();
+					newdc->setVertexArray(maskConstraint);
+					
+					//Crop the mask to the stitching poly (for case where mask crosses tile edge)
+					osg::ref_ptr<Geometry> maskCrop;
+					maskPoly->crop(maskSkirtPoly.get(), maskCrop);
+
+                GeometryIterator i( maskCrop.get(), false );
+                while( i.hasMore() )
+                {
+                    Geometry* part = i.next();
+                    if (!part)
+                        continue;
+
+						if (part->getType() == Geometry::TYPE_POLYGON)
+						{
+							osg::Vec3Array* partVerts = part->toVec3Array();
+							maskConstraint->insert(maskConstraint->end(), partVerts->begin(), partVerts->end());
+							newdc->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_LOOP, maskConstraint->size() - partVerts->size(), partVerts->size()));
+						}
+					}
+
+                // Cropping strips z-values so need reassign
+                std::vector<int> isZSet;
+                for (osg::Vec3Array::iterator it = maskConstraint->begin(); it != maskConstraint->end(); ++it)
+                {
+                    int zSet = 0;
+
+                    //Look for verts that belong to the original mask skirt polygon
+                    for (Polygon::iterator mit = maskSkirtPoly->begin(); mit != maskSkirtPoly->end(); ++mit)
+                    {
+                        if (osg::absolute((*mit).x() - (*it).x()) < MATCH_TOLERANCE && osg::absolute((*mit).y() - (*it).y()) < MATCH_TOLERANCE)
+                        {
+                            (*it).z() = (*mit).z();
+                            zSet += 1;
+                            break;
+                        }
+                    }
+
+                    //Look for verts that belong to the mask polygon
+                    for (Polygon::iterator mit = maskPoly->begin(); mit != maskPoly->end(); ++mit)
+                    {
+                        if (osg::absolute((*mit).x() - (*it).x()) < MATCH_TOLERANCE && osg::absolute((*mit).y() - (*it).y()) < MATCH_TOLERANCE)
+                        {
+                            (*it).z() = (*mit).z();
+                            zSet += 2;
+                            break;
+                        }
+                    }
+
+                    isZSet.push_back(zSet);
+                }
+
+                //Any mask skirt verts that are still unset are newly created verts where the skirt
+                //meets the mask. Find the mask segment the point lies along and calculate the
+                //appropriate z value for the point.
+                int count = 0;
+                for (osg::Vec3Array::iterator it = maskConstraint->begin(); it != maskConstraint->end(); ++it)
+                {
+                    //If the z-value was set from a mask vertex there is no need to change it.  If
+                    //it was set from a vertex from the stitching polygon it may need overriden if
+                    //the vertex lies along a mask edge.  Or if it is unset, it will need to be set.
+                    //if (isZSet[count] < 2)
+                    if (!isZSet[count])
+                    {
+                        osg::Vec3d p2 = *it;
+                        double closestZ = 0.0;
+                        double closestRatio = DBL_MAX;
+                        for (Polygon::iterator mit = maskPoly->begin(); mit != maskPoly->end(); ++mit)
+                        {
+                            osg::Vec3d p1 = *mit;
+                            osg::Vec3d p3 = mit == --maskPoly->end() ? maskPoly->front() : (*(mit + 1));
+
+                            //Truncated vales to compensate for accuracy issues
+                            double p1x = ((int)(p1.x() * 1000000)) / 1000000.0L;
+                            double p3x = ((int)(p3.x() * 1000000)) / 1000000.0L;
+                            double p2x = ((int)(p2.x() * 1000000)) / 1000000.0L;
+
+                            double p1y = ((int)(p1.y() * 1000000)) / 1000000.0L;
+                            double p3y = ((int)(p3.y() * 1000000)) / 1000000.0L;
+                            double p2y = ((int)(p2.y() * 1000000)) / 1000000.0L;
+
+                            if ((p1x < p3x ? p2x >= p1x && p2x <= p3x : p2x >= p3x && p2x <= p1x) &&
+                                (p1y < p3y ? p2y >= p1y && p2y <= p3y : p2y >= p3y && p2y <= p1y))
+                            {
+                                double l1 =(osg::Vec2d(p2.x(), p2.y()) - osg::Vec2d(p1.x(), p1.y())).length();
+                                double lt = (osg::Vec2d(p3.x(), p3.y()) - osg::Vec2d(p1.x(), p1.y())).length();
+                                double zmag = p3.z() - p1.z();
+
+                                double foundZ = (l1 / lt) * zmag + p1.z();
+
+                                double mRatio = 1.0;
+                                if (osg::absolute(p1x - p3x) < MATCH_TOLERANCE)
+                                {
+                                    if (osg::absolute(p1x-p2x) < MATCH_TOLERANCE)
+                                        mRatio = 0.0;
+                                }
+                                else
+                                {
+                                    double m1 = p1x == p2x ? 0.0 : (p2y - p1y) / (p2x - p1x);
+                                    double m2 = p1x == p3x ? 0.0 : (p3y - p1y) / (p3x - p1x);
+                                    mRatio = m2 == 0.0 ? m1 : osg::absolute(1.0L - m1 / m2);
+                                }
+
+                                if (mRatio < 0.01)
+                                {
+                                    (*it).z() = foundZ;
+                                    isZSet[count] = 2;
+                                    break;
+                                }
+                                else if (mRatio < closestRatio)
+                                {
+                                    closestRatio = mRatio;
+                                    closestZ = foundZ;
+                                }
+                            }
+                        }
+
+                        if (!isZSet[count] && closestRatio < DBL_MAX)
+                        {
+                            (*it).z() = closestZ;
+                            isZSet[count] = 2;
+                        }
+                    }
+
+                    if (!isZSet[count])
+                        OE_WARN << LC << "Z-value not set for mask constraint vertex" << std::endl;
+
+						count++;
+					}
+					
+					alldcs.push_back(newdc);
+                }
+                          							
+          
+				//coordsArray->insert(coordsArray->end(),maskSkirtPoly->begin(),maskSkirtPoly->end());
+				trig->setInputPointArray(coordsArray.get());
+
+
+				for (int dcnum =0; dcnum < alldcs.size();dcnum++)
+				{
+					trig->addInputConstraint(alldcs[dcnum].get());
+				}
+		       
+		         // Create array to hold vertex normals
+                osg::Vec3Array *norms=new osg::Vec3Array;
+                trig->setOutputNormalArray(norms);
+
+
+                // Triangulate vertices and remove triangles that lie within the contraint loop
+                trig->triangulate();
+				for (int dcnum =0; dcnum < alldcs.size();dcnum++)
+				{
+					
+					trig->removeInternalTriangles(alldcs[dcnum].get());
+				}             
+
+
+				MaskRecordVector::iterator mr = d.maskRecords.begin();
+                // Set up new arrays to hold final vertices and normals
+                osg::Geometry* stitch_geom = (*mr)._geom;
+                osg::Vec3Array* stitch_verts = new osg::Vec3Array();
+                stitch_verts->reserve(trig->getInputPointArray()->size());
+                stitch_geom->setVertexArray(stitch_verts);
+                osg::Vec3Array* stitch_norms = new osg::Vec3Array(trig->getInputPointArray()->size());
+                stitch_geom->setNormalArray( stitch_norms );
+                stitch_geom->setNormalBinding( osg::Geometry::BIND_PER_VERTEX );
+
+
+                //Initialize tex coords
+                if ( d.renderLayers.size() > 0 )
+                {
+                    for (unsigned int i = 0; i < d.renderLayers.size(); ++i)
+                    {
+                        d.renderLayers[i]._stitchTexCoords->reserve(trig->getInputPointArray()->size());
+                    }
+                }
+                d.stitchTileCoords->reserve(trig->getInputPointArray()->size());
+
+                // Iterate through point to convert to model coords, calculate normals, and set up tex coords
+                int norm_i = -1;
+                for (osg::Vec3Array::iterator it = trig->getInputPointArray()->begin(); it != trig->getInputPointArray()->end(); ++it)
+                {
+                    // get model coords
+                    osg::Vec3d model;
+                    d.model->_tileLocator->convertLocalToModel(*it, model);
+                    model = model - d.centerModel;
+
+                    stitch_verts->push_back(model);
+
+                    // calc normals
+                    osg::Vec3d local_one(*it);
+                    local_one.z() += 1.0;
+                    osg::Vec3d model_one;
+                    d.model->_tileLocator->convertLocalToModel( local_one, model_one );
+                    model_one = model_one - model;
+                    model_one.normalize();
+                    (*stitch_norms)[++norm_i] = model_one;
+
+                    // set up text coords
+                    if (d.renderLayers.size() > 0)
+                    {
+                        for (unsigned int i = 0; i < d.renderLayers.size(); ++i)
+                        {
+                            if (!d.renderLayers[i]._locator->isEquivalentTo( *d.geoLocator.get() )) //*masterTextureLocator.get()))
+                            {
+                                osg::Vec3d color_ndc;
+                                osgTerrain::Locator::convertLocalCoordBetween(*d.geoLocator.get(), (*it), *d.renderLayers[i]._locator.get(), color_ndc);
+                                d.renderLayers[i]._stitchTexCoords->push_back(osg::Vec2(color_ndc.x(), color_ndc.y()));
+                            }
+                            else
+                            {
+                                d.renderLayers[i]._stitchTexCoords->push_back(osg::Vec2((*it).x(), (*it).y()));
+                            }
+                        }
+                    }
+                    d.stitchTileCoords->push_back(osg::Vec2((*it).x(), (*it).y()));
+                }
+
+
+                // Get triangles from triangulator and add as primative set to the geometry
+                stitch_geom->addPrimitiveSet(trig->getTriangles());
+        }
+    }
+
+
+    /**
+     * Build the geometry for the tile "skirts" -- this the vertical geometry around the
+     * tile edges that hides the gap effect caused when you render two adjacent tiles at
+     * different LODs.
+     */
+    void createSkirtGeometry( Data& d, double skirtRatio )
+    {
+        // surface normals will double as our skirt extrusion vectors
+        osg::Vec3Array* skirtVectors = d.normals;
+
+        // find the skirt height
+        double skirtHeight = d.surfaceBound.radius() * skirtRatio;
+
+        // build the verts first:
+        osg::Vec3Array* skirtVerts = static_cast<osg::Vec3Array*>(d.surface->getVertexArray());
+        osg::Vec3Array* skirtNormals = static_cast<osg::Vec3Array*>(d.surface->getNormalArray());
+        osg::Vec4Array* skirtAttribs = static_cast<osg::Vec4Array*>(d.surface->getVertexAttribArray(osg::Drawable::ATTRIBUTE_6)); //new osg::Vec4Array();
+        osg::Vec4Array* skirtAttribs2 = static_cast<osg::Vec4Array*>(d.surface->getVertexAttribArray(osg::Drawable::ATTRIBUTE_7)); //new osg::Vec4Array();
+
+        osg::ref_ptr<osg::DrawElementsUShort> elements = new osg::DrawElementsUShort(GL_TRIANGLE_STRIP);
+
+        // bottom:
+        for( unsigned int c=0; c<d.numCols-1; ++c )
+        {
+            int orig_i = d.indices[c];
+
+            if (orig_i < 0)
+            {
+                if ( elements->size() > 0 )
+                    d.surface->addPrimitiveSet( elements.get() );
+                elements = new osg::DrawElementsUShort(GL_TRIANGLE_STRIP);
+            }
+            else
+            {
+                const osg::Vec3f& surfaceVert = (*d.surfaceVerts)[orig_i];
+                skirtVerts->push_back( surfaceVert - ((*skirtVectors)[orig_i])*skirtHeight );
+
+                const osg::Vec3f& surfaceNormal = (*d.normals)[orig_i];
+                skirtNormals->push_back( surfaceNormal );
+
+                const osg::Vec4f& surfaceAttribs = (*d.surfaceAttribs)[orig_i];
+                skirtAttribs->push_back( surfaceAttribs - osg::Vec4f(0,0,0,skirtHeight) );
+
+                const osg::Vec4f& surfaceAttribs2 = (*d.surfaceAttribs2)[orig_i];
+                skirtAttribs2->push_back( surfaceAttribs2 - osg::Vec4f(0,0,0,skirtHeight) );
+
+                if ( d.renderLayers.size() > 0 )
+                {
+                    for (unsigned int i = 0; i < d.renderLayers.size(); ++i)
+                    {
+                        if ( d.renderLayers[i]._ownsTexCoords )
+                        {
+                            const osg::Vec2& tc = (*d.renderLayers[i]._texCoords.get())[orig_i];
+                            d.renderLayers[i]._texCoords->push_back( tc );
+                        }
+                    }
+                }
+
+                elements->addElement(orig_i);
+                elements->addElement(skirtVerts->size()-1);
+            }
+        }
+
+        // right:
+        for( unsigned int r=0; r<d.numRows-1; ++r )
+        {
+            int orig_i = d.indices[r*d.numCols+(d.numCols-1)];
+            if (orig_i < 0)
+            {
+                if ( elements->size() > 0 )
+                    d.surface->addPrimitiveSet( elements.get() );
+                elements = new osg::DrawElementsUShort(GL_TRIANGLE_STRIP);
+            }
+            else
+            {
+                const osg::Vec3f& surfaceVert = (*d.surfaceVerts)[orig_i];
+                skirtVerts->push_back( surfaceVert - ((*skirtVectors)[orig_i])*skirtHeight );
+
+                const osg::Vec3f& surfaceNormal = (*d.normals)[orig_i];
+                skirtNormals->push_back( surfaceNormal );
+
+                const osg::Vec4f& surfaceAttribs = (*d.surfaceAttribs)[orig_i];
+                skirtAttribs->push_back( surfaceAttribs - osg::Vec4f(0,0,0,skirtHeight) );
+
+                const osg::Vec4f& surfaceAttribs2 = (*d.surfaceAttribs2)[orig_i];
+                skirtAttribs2->push_back( surfaceAttribs2 - osg::Vec4f(0,0,0,skirtHeight) );
+
+                if ( d.renderLayers.size() > 0 )
+                {
+                    for (unsigned int i = 0; i < d.renderLayers.size(); ++i)
+                    {
+                        if ( d.renderLayers[i]._ownsTexCoords )
+                        {
+                            const osg::Vec2& tc = (*d.renderLayers[i]._texCoords.get())[orig_i];
+                            d.renderLayers[i]._texCoords->push_back( tc );
+                        }
+                    }
+                }
+
+                elements->addElement(orig_i);
+                elements->addElement(skirtVerts->size()-1);
+            }
+        }
+
+        // top:
+        for( int c=d.numCols-1; c>0; --c )
+        {
+            int orig_i = d.indices[(d.numRows-1)*d.numCols+c];
+            if (orig_i < 0)
+            {
+                if ( elements->size() > 0 )
+                    d.surface->addPrimitiveSet( elements.get() );
+                elements = new osg::DrawElementsUShort(GL_TRIANGLE_STRIP);
+            }
+            else
+            {
+                const osg::Vec3f& surfaceVert = (*d.surfaceVerts)[orig_i];
+                skirtVerts->push_back( surfaceVert - ((*skirtVectors)[orig_i])*skirtHeight );
+
+                const osg::Vec3f& surfaceNormal = (*d.normals)[orig_i];
+                skirtNormals->push_back( surfaceNormal );
+
+                const osg::Vec4f& surfaceAttribs = (*d.surfaceAttribs)[orig_i];
+                skirtAttribs->push_back( surfaceAttribs - osg::Vec4f(0,0,0,skirtHeight) );
+
+                const osg::Vec4f& surfaceAttribs2 = (*d.surfaceAttribs2)[orig_i];
+                skirtAttribs2->push_back( surfaceAttribs2 - osg::Vec4f(0,0,0,skirtHeight) );
+
+                if ( d.renderLayers.size() > 0 )
+                {
+                    for (unsigned int i = 0; i < d.renderLayers.size(); ++i)
+                    {
+                        if ( d.renderLayers[i]._ownsTexCoords )
+                        {
+                            const osg::Vec2& tc = (*d.renderLayers[i]._texCoords.get())[orig_i];
+                            d.renderLayers[i]._texCoords->push_back( tc );
+                        }
+                    }
+                }
+
+                elements->addElement(orig_i);
+                elements->addElement(skirtVerts->size()-1);
+            }
+        }
+
+        // left:
+        for( int r=d.numRows-1; r>=0; --r )
+        {
+            int orig_i = d.indices[r*d.numCols];
+            if (orig_i < 0)
+            {
+                if ( elements->size() > 0 )
+                    d.surface->addPrimitiveSet( elements.get() );
+                elements = new osg::DrawElementsUShort(GL_TRIANGLE_STRIP);
+            }
+            else
+            {
+                const osg::Vec3f& surfaceVert = (*d.surfaceVerts)[orig_i];
+                skirtVerts->push_back( surfaceVert - ((*skirtVectors)[orig_i])*skirtHeight );
+
+                const osg::Vec3f& surfaceNormal = (*d.normals)[orig_i];
+                skirtNormals->push_back( surfaceNormal );
+
+                const osg::Vec4f& surfaceAttribs = (*d.surfaceAttribs)[orig_i];
+                skirtAttribs->push_back( surfaceAttribs - osg::Vec4f(0,0,0,skirtHeight) );
+
+                const osg::Vec4f& surfaceAttribs2 = (*d.surfaceAttribs2)[orig_i];
+                skirtAttribs2->push_back( surfaceAttribs2 - osg::Vec4f(0,0,0,skirtHeight) );
+
+                if ( d.renderLayers.size() > 0 )
+                {
+                    for (unsigned int i = 0; i < d.renderLayers.size(); ++i)
+                    {
+                        if ( d.renderLayers[i]._ownsTexCoords )
+                        {
+                            const osg::Vec2& tc = (*d.renderLayers[i]._texCoords.get())[orig_i];
+                            d.renderLayers[i]._texCoords->push_back( tc );
+                        }
+                    }
+                }
+
+                elements->addElement(orig_i);
+                elements->addElement(skirtVerts->size()-1);
+            }
+        }
+
+        // add the final prim set.
+        if ( elements->size() > 0 )
+        {
+            d.surface->addPrimitiveSet( elements.get() );
+        }
+    }
+
+
+
+    /**
+     * Builds triangles for the surface geometry, and recalculates the surface normals
+     * to be optimized for slope.
+     */
+    void tessellateSurfaceGeometry( Data& d, bool optimizeTriangleOrientation, bool normalizeEdges )
+    {    
+        bool swapOrientation = !(d.model->_tileLocator->orientationOpenGL());
+
+        bool recalcNormals   = d.model->hasElevation();
+        unsigned numSurfaceNormals = d.numRows * d.numCols;
+
+        osg::DrawElements* elements = new osg::DrawElementsUShort(GL_TRIANGLES);
+        elements->reserveElements((d.numRows-1) * (d.numCols-1) * 6);
+        d.surface->insertPrimitiveSet(0, elements); // because we always want this first.
+
+        if ( recalcNormals )
+        {
+            // first clear out all the normals on the surface (but not the skirts)
+            // TODO: someday go back and re-apply the skirt normals to match the
+            // corresponding recalculated surface normals.
+            for(unsigned n=0; n<numSurfaceNormals && n<d.normals->size(); ++n)
+            {
+                (*d.normals)[n].set( 0.0f, 0.0f, 0.0f );
+            }
+        }
+
+        for(unsigned j=0; j<d.numRows-1; ++j)
+        {
+            for(unsigned i=0; i<d.numCols-1; ++i)
+            {
+                int i00;
+                int i01;
+                if (swapOrientation)
+                {
+                    i01 = j*d.numCols + i;
+                    i00 = i01+d.numCols;
+                }
+                else
+                {
+                    i00 = j*d.numCols + i;
+                    i01 = i00+d.numCols;
+                }
+
+                int i10 = i00+1;
+                int i11 = i01+1;
+
+                // remap indices to final vertex positions
+                i00 = d.indices[i00];
+                i01 = d.indices[i01];
+                i10 = d.indices[i10];
+                i11 = d.indices[i11];
+
+                unsigned int numValid = 0;
+                if (i00>=0) ++numValid;
+                if (i01>=0) ++numValid;
+                if (i10>=0) ++numValid;
+                if (i11>=0) ++numValid;                
+
+                if (numValid==4)
+                {
+                    bool VALID = true;
+                    for (MaskRecordVector::iterator mr = d.maskRecords.begin(); mr != d.maskRecords.end(); ++mr)
+                    {
+                        float min_i = (*mr)._ndcMin.x() * (double)(d.numCols-1);
+                        float min_j = (*mr)._ndcMin.y() * (double)(d.numRows-1);
+                        float max_i = (*mr)._ndcMax.x() * (double)(d.numCols-1);
+                        float max_j = (*mr)._ndcMax.y() * (double)(d.numRows-1);
+
+                        // We test if mask is completely in square
+                        if(i+1 >= min_i && i <= max_i && j+1 >= min_j && j <= max_j)
+                        {
+                            VALID = false;
+                            break;
+                        }
+                    }
+
+                    if (VALID) {
+                        float e00 = (*d.elevations)[i00];
+                        float e10 = (*d.elevations)[i10];
+                        float e01 = (*d.elevations)[i01];
+                        float e11 = (*d.elevations)[i11];
+
+                        osg::Vec3f& v00 = (*d.surfaceVerts)[i00];
+                        osg::Vec3f& v10 = (*d.surfaceVerts)[i10];
+                        osg::Vec3f& v01 = (*d.surfaceVerts)[i01];
+                        osg::Vec3f& v11 = (*d.surfaceVerts)[i11];
+
+                        if (!optimizeTriangleOrientation || fabsf(e00-e11)<fabsf(e01-e10))
+                        {
+                            elements->addElement(i01);
+                            elements->addElement(i00);
+                            elements->addElement(i11);
+
+                            elements->addElement(i00);
+                            elements->addElement(i10);
+                            elements->addElement(i11);
+
+                            if (recalcNormals)
+                            {                        
+                                osg::Vec3 normal1 = (v00-v01) ^ (v11-v01);
+                                (*d.normals)[i01] += normal1;
+                                (*d.normals)[i00] += normal1;
+                                (*d.normals)[i11] += normal1;
+
+                                osg::Vec3 normal2 = (v10-v00) ^ (v11-v00);
+                                (*d.normals)[i00] += normal2;
+                                (*d.normals)[i10] += normal2;
+                                (*d.normals)[i11] += normal2;
+                            }
+                        }
+                        else
+                        {
+                            elements->addElement(i01);
+                            elements->addElement(i00);
+                            elements->addElement(i10);
+
+                            elements->addElement(i01);
+                            elements->addElement(i10);
+                            elements->addElement(i11);
+
+                            if (recalcNormals)
+                            {                       
+                                osg::Vec3 normal1 = (v00-v01) ^ (v10-v01);
+                                (*d.normals)[i01] += normal1;
+                                (*d.normals)[i00] += normal1;
+                                (*d.normals)[i10] += normal1;
+
+                                osg::Vec3 normal2 = (v10-v01) ^ (v11-v01);
+                                (*d.normals)[i01] += normal2;
+                                (*d.normals)[i10] += normal2;
+                                (*d.normals)[i11] += normal2;
+                            }
+                        }
+                    }
+                }
+            }
+        }        
+        
+        if (recalcNormals && normalizeEdges)
+        {            
+            //OE_DEBUG << LC << "Normalizing edges" << std::endl;
+
+            //Compute the edge normals if we have neighbor data
+            //Get all the neighbors
+            osg::HeightField* w_neighbor  = d.model->_elevationData.getNeighbor( -1, 0 );
+            osg::HeightField* e_neighbor  = d.model->_elevationData.getNeighbor( 1, 0 );
+            osg::HeightField* s_neighbor  = d.model->_elevationData.getNeighbor( 0, 1 );
+            osg::HeightField* n_neighbor  = d.model->_elevationData.getNeighbor( 0, -1 );
+
+            // Utility arrays:
+            std::vector<osg::Vec3> boundaryVerts;
+            boundaryVerts.reserve( 2 * std::max(d.numRows, d.numCols) );
+
+            std::vector< float > boundaryElevations;
+            boundaryElevations.reserve( 2 * std::max(d.numRows, d.numCols) );
+
+            //Recalculate the west side
+            if (w_neighbor && w_neighbor->getNumColumns() == d.originalNumCols && w_neighbor->getNumRows() == d.originalNumRows)
+            {
+                boundaryVerts.clear();
+                boundaryElevations.clear();
+                
+                //Compute the verts for the west side
+                for (int j = 0; j < (int)d.numRows; j++)
+                {
+                    for (int i = (int)d.numCols-2; i <= (int)d.numCols-1; i++)
+                    {                          
+                        osg::Vec3d ndc( (double)(i - static_cast<int>(d.numCols-1))/(double)(d.numCols-1), ((double)j)/(double)(d.numRows-1), 0.0);                                                                        
+
+                        // use the sampling factor to determine the lookup index:
+                        unsigned i_equiv = d.i_sampleFactor==1.0 ? i : (unsigned) (double(i)*d.i_sampleFactor);
+                        unsigned j_equiv = d.j_sampleFactor==1.0 ? j : (unsigned) (double(j)*d.j_sampleFactor);
+
+                        //TODO:  Should probably use an interpolated method here
+                        float heightValue = w_neighbor->getHeight( i_equiv, j_equiv );
+                        ndc.z() = heightValue;
+
+                        osg::Vec3d model;
+                        d.model->_tileLocator->unitToModel( ndc, model );
+                        osg::Vec3d v = model - d.centerModel;
+                        boundaryVerts.push_back( v );
+                        boundaryElevations.push_back( heightValue );
+                    }
+                }   
+
+                //The boundary verts are now populated, so go through and triangulate them add add the normals to the existing normal array
+                for (int j = 0; j < (int)d.numRows-1; j++)
+                {                    
+                    int i00;
+                    int i01;
+                    int i = 0;
+                    if (swapOrientation)
+                    {
+                        i01 = j*d.numCols + i;
+                        i00 = i01+d.numCols;
+                    }
+                    else
+                    {
+                        i00 = j*d.numCols + i;
+                        i01 = i00+d.numCols;
+                    }
+
+
+
+                    //remap indices to final vertex position
+                    i00 = d.indices[i00];
+                    i01 = d.indices[i01];
+
+                    if ( i00 >= 0 && i01 >= 0 )
+                    {
+                        int baseIndex = 2 * j;
+                        osg::Vec3f& v00 = boundaryVerts[baseIndex    ];
+                        osg::Vec3f& v10 = boundaryVerts[baseIndex + 1];
+                        osg::Vec3f& v01 = boundaryVerts[baseIndex + 2];
+                        osg::Vec3f& v11 = boundaryVerts[baseIndex + 3];
+
+                        float e00 = boundaryElevations[baseIndex];
+                        float e10 = boundaryElevations[baseIndex + 1];
+                        float e01 = boundaryElevations[baseIndex + 2];
+                        float e11 = boundaryElevations[baseIndex + 3];
+
+                       
+                        if (!optimizeTriangleOrientation || fabsf(e00-e11)<fabsf(e01-e10))
+                        {                            
+                            osg::Vec3 normal1 = (v00-v01) ^ (v11-v01);
+                            (*d.normals)[i01] += normal1;                        
+
+                            osg::Vec3 normal2 = (v10-v00) ^ (v11-v00);
+                            (*d.normals)[i00] += normal2;                        
+                            (*d.normals)[i01] += normal2;                                                
+                        }
+                        else
+                        {                            
+                            osg::Vec3 normal1 = (v00-v01) ^ (v10-v01);
+                            (*d.normals)[i00] += normal1;                                               
+
+                            osg::Vec3 normal2 = (v10-v01) ^ (v11-v01);
+                            (*d.normals)[i00] += normal2;                                               
+                            (*d.normals)[i01] += normal2;                        
+                        }
+                    }
+                }
+            }
+
+                        
+            //Recalculate the east side
+            if (e_neighbor && e_neighbor->getNumColumns() == d.originalNumCols && e_neighbor->getNumRows() == d.originalNumRows)            
+            {
+                boundaryVerts.clear();
+                boundaryElevations.clear();
+
+                //Compute the verts for the east side
+                for (int j = 0; j < (int)d.numRows; j++)
+                {
+                    for (int i = 0; i <= 1; i++)
+                    {                           
+                        osg::Vec3d ndc( ((double)(d.numCols -1 + i))/(double)(d.numCols-1), ((double)j)/(double)(d.numRows-1), 0.0);
+
+                        unsigned i_equiv = d.i_sampleFactor==1.0 ? i : (unsigned) (double(i)*d.i_sampleFactor);
+                        unsigned j_equiv = d.j_sampleFactor==1.0 ? j : (unsigned) (double(j)*d.j_sampleFactor);
+                        
+                        //TODO:  Should probably use an interpolated method here
+                        float heightValue = e_neighbor->getHeight( i_equiv, j_equiv );
+                        ndc.z() = heightValue;
+
+                        osg::Vec3d model;
+                        d.model->_tileLocator->unitToModel( ndc, model );
+                        osg::Vec3d v = model - d.centerModel;
+                        boundaryVerts.push_back( v );
+                        boundaryElevations.push_back( heightValue );
+                    }
+                }   
+
+                //The boundary verts are now populated, so go through and triangulate them add add the normals to the existing normal array
+                for (int j = 0; j < (int)d.numRows-1; j++)
+                {
+                    int i00;
+                    int i01;
+                    int i = d.numCols-1;
+                    if (swapOrientation)
+                    {
+                        i01 = j*d.numCols + i;
+                        i00 = i01+d.numCols;
+                    }
+                    else
+                    {
+                        i00 = j*d.numCols + i;
+                        i01 = i00+d.numCols;
+                    }
+
+                    //remap indices to final vertex position
+                    i00 = d.indices[i00];
+                    i01 = d.indices[i01];
+
+                    if ( i00 >= 0 && i01 >= 0 )
+                    {
+                        int baseIndex = 2 * j;
+                        osg::Vec3f& v00 = boundaryVerts[baseIndex    ];
+                        osg::Vec3f& v10 = boundaryVerts[baseIndex + 1];
+                        osg::Vec3f& v01 = boundaryVerts[baseIndex + 2];
+                        osg::Vec3f& v11 = boundaryVerts[baseIndex + 3];
+
+                        float e00 = boundaryElevations[baseIndex];
+                        float e10 = boundaryElevations[baseIndex + 1];
+                        float e01 = boundaryElevations[baseIndex + 2];
+                        float e11 = boundaryElevations[baseIndex + 3];
+
+                       
+                        if (!optimizeTriangleOrientation || fabsf(e00-e11)<fabsf(e01-e10))
+                        {                            
+                            osg::Vec3 normal1 = (v00-v01) ^ (v11-v01);                       
+                            (*d.normals)[i00] += normal1;                        
+                            (*d.normals)[i01] += normal1;
+
+                            osg::Vec3 normal2 = (v10-v00) ^ (v11-v00);                        
+                            (*d.normals)[i00] += normal2;                                                
+                        }
+                        else
+                        {                            
+                            osg::Vec3 normal1 = (v00-v01) ^ (v10-v01);
+                            (*d.normals)[i00] += normal1;                        
+                            (*d.normals)[i01] += normal1;                                                                        
+
+                            osg::Vec3 normal2 = (v10-v01) ^ (v11-v01);
+                            (*d.normals)[i01] += normal2;                        
+                        }
+                    }
+                }
+            }
+
+            //Recalculate the north side
+            if (n_neighbor && n_neighbor->getNumColumns() == d.originalNumCols && n_neighbor->getNumRows() == d.originalNumRows)            
+            {
+                boundaryVerts.clear();
+                boundaryElevations.clear();
+
+                //Compute the verts for the north side               
+                for (int j = 0; j <= 1; j++)
+                {
+                    for (int i = 0; i < (int)d.numCols; i++)                    
+                    {                           
+                        osg::Vec3d ndc( (double)(i)/(double)(d.numCols-1), (double)(d.numRows -1 + j)/(double)(d.numRows-1), 0.0);
+
+                        unsigned i_equiv = d.i_sampleFactor==1.0 ? i : (unsigned) (double(i)*d.i_sampleFactor);
+                        unsigned j_equiv = d.j_sampleFactor==1.0 ? j : (unsigned) (double(j)*d.j_sampleFactor);
+                        
+                        //TODO:  Should probably use an interpolated method here
+                        float heightValue = n_neighbor->getHeight( i_equiv, j_equiv );
+                        ndc.z() = heightValue;
+
+                        osg::Vec3d model;
+                        d.model->_tileLocator->unitToModel( ndc, model );
+                        osg::Vec3d v = model - d.centerModel;
+                        boundaryVerts.push_back( v );
+                        boundaryElevations.push_back( heightValue );
+                    }
+                }   
+
+                //The boundary verts are now populated, so go through and triangulate them add add the normals to the existing normal array                
+                for (int i = 0; i < (int)d.numCols-1; i++)
+                {                    
+                    int i00;                    
+                    int j = d.numRows-1;
+                    if (swapOrientation)
+                    {         
+                        int i01 = j * d.numCols + i;
+                        i00 = i01+d.numCols;
+                    }
+                    else
+                    {
+                        i00 = j*d.numCols + i;                        
+                    }
+
+                    int i10 = i00+1;
+
+                    //remap indices to final vertex position
+                    i00 = d.indices[i00];
+                    i10 = d.indices[i10];
+
+
+                    if ( i00 >= 0 && i10 >= 0 )
+                    {
+                        int baseIndex = i;
+                        osg::Vec3f& v00 = boundaryVerts[baseIndex    ];
+                        osg::Vec3f& v10 = boundaryVerts[baseIndex + 1];
+                        osg::Vec3f& v01 = boundaryVerts[baseIndex + d.numCols];
+                        osg::Vec3f& v11 = boundaryVerts[baseIndex + d.numCols + 1];
+
+                        float e00 = boundaryElevations[baseIndex];
+                        float e10 = boundaryElevations[baseIndex + 1];
+                        float e01 = boundaryElevations[baseIndex + d.numCols];
+                        float e11 = boundaryElevations[baseIndex + d.numCols + 1];
+
+                       
+                        if (!optimizeTriangleOrientation || fabsf(e00-e11)<fabsf(e01-e10))
+                        {                            
+                            osg::Vec3 normal1 = (v00-v01) ^ (v11-v01);                       
+                            (*d.normals)[i00] += normal1;                        
+                            (*d.normals)[i10] += normal1;
+
+                            osg::Vec3 normal2 = (v10-v00) ^ (v11-v00);                        
+                            (*d.normals)[i10] += normal2;                                                
+                        }
+                        else
+                        {                            
+                            osg::Vec3 normal1 = (v00-v01) ^ (v10-v01);
+                            (*d.normals)[i00] += normal1;                                                
+
+                            osg::Vec3 normal2 = (v10-v01) ^ (v11-v01);
+                            (*d.normals)[i00] += normal2;                                                
+                            (*d.normals)[i10] += normal2;                        
+                        }
+                    }
+                }
+            }
+
+            //Recalculate the south side
+            if (s_neighbor && s_neighbor->getNumColumns() == d.originalNumCols && s_neighbor->getNumRows() == d.originalNumRows)            
+            {
+                boundaryVerts.clear();
+                boundaryElevations.clear();
+
+                //Compute the verts for the south side               
+                for (int j = (int)d.numRows-2; j <= (int)d.numRows-1; j++)
+                {
+                    for (int i = 0; i < (int)d.numCols; i++)                    
+                    {                           
+                        osg::Vec3d ndc( (double)(i)/(double)(d.numCols-1), (double)(j - static_cast<int>(d.numRows-1))/(double)(d.numRows-1), 0.0);                                                
+
+                        unsigned i_equiv = d.i_sampleFactor==1.0 ? i : (unsigned) (double(i)*d.i_sampleFactor);
+                        unsigned j_equiv = d.j_sampleFactor==1.0 ? j : (unsigned) (double(j)*d.j_sampleFactor);
+                        
+                        //TODO:  Should probably use an interpolated method here
+                        float heightValue = s_neighbor->getHeight( i_equiv, j_equiv );                        
+                        ndc.z() = heightValue;
+
+                        osg::Vec3d model;
+                        d.model->_tileLocator->unitToModel( ndc, model );
+                        osg::Vec3d v = model - d.centerModel;
+                        boundaryVerts.push_back( v );
+                        boundaryElevations.push_back( heightValue ); 
+                    }
+                }   
+
+                //The boundary verts are now populated, so go through and triangulate them add add the normals to the existing normal array                
+                for (int i = 0; i < (int)d.numCols-1; i++)
+                {                    
+                    int i00;                    
+                    int j = 0;
+
+
+                    if (swapOrientation)
+                    {                   
+                        int i01 = j*d.numCols + i;
+                        i00 = i01+d.numCols;                    
+                    }
+                    else
+                    {
+                        i00 = j*d.numCols + i;                        
+                    }                    
+
+                    int i10 = i00+1;
+
+                    //remap indices to final vertex position
+                    i00 = d.indices[i00];
+                    i10 = d.indices[i10];
+
+
+                    if ( i00 >= 0 && i10 >= 0 )
+                    {
+                        int baseIndex = i;
+                        osg::Vec3f& v00 = boundaryVerts[baseIndex    ];
+                        osg::Vec3f& v10 = boundaryVerts[baseIndex + 1];
+                        osg::Vec3f& v01 = boundaryVerts[baseIndex + d.numCols];
+                        osg::Vec3f& v11 = boundaryVerts[baseIndex + d.numCols + 1];
+
+                        float e00 = boundaryElevations[baseIndex];
+                        float e10 = boundaryElevations[baseIndex + 1];
+                        float e01 = boundaryElevations[baseIndex + d.numCols];
+                        float e11 = boundaryElevations[baseIndex + d.numCols + 1];
+
+                       
+                        if (!optimizeTriangleOrientation || fabsf(e00-e11)<fabsf(e01-e10))
+                        {                            
+                            osg::Vec3 normal1 = (v00-v01) ^ (v11-v01);                       
+                            (*d.normals)[i00] += normal1;                                                
+
+                            osg::Vec3 normal2 = (v10-v00) ^ (v11-v00);                        
+                            (*d.normals)[i00] += normal2;                                                
+                            (*d.normals)[i10] += normal2;                                                
+                        }
+                        else
+                        {                            
+                            osg::Vec3 normal1 = (v00-v01) ^ (v10-v01);
+                            (*d.normals)[i00] += normal1;                                                
+                            (*d.normals)[i10] += normal1;                                                
+
+                            osg::Vec3 normal2 = (v10-v01) ^ (v11-v01);                        
+                            (*d.normals)[i10] += normal2;                        
+                        }
+                    }
+                }
+            }            
+        }
+
+        if (recalcNormals)
+        {
+            for( osg::Vec3Array::iterator nitr = d.normals->begin(); nitr != d.normals->end(); ++nitr )
+            {
+                nitr->normalize();
+            }       
+        }
+    }
+
+
+    void installRenderData( Data& d )
+    {
+        // pre-size all vectors:
+        unsigned size = d.renderLayers.size();
+
+        d.surface->_layers.resize( size );
+
+        for ( MaskRecordVector::iterator mr = d.maskRecords.begin(); mr != d.maskRecords.end(); ++mr )
+            mr->_geom->_layers.resize( size );
+        
+        if ( d.renderTileCoords.valid() )
+            d.surface->_tileCoords = d.renderTileCoords;
+            
+        // TODO: evaluate this suspicious code. -gw
+        if ( d.stitchTileCoords.valid() )
+            d.surface->_tileCoords = d.stitchTileCoords.get();
+
+        // install the render data for each layer:
+        for( RenderLayerVector::const_iterator r = d.renderLayers.begin(); r != d.renderLayers.end(); ++r )
+        {
+            unsigned order = r->_layer.getOrder();
+
+            MPGeometry::Layer layer;
+            layer._layerID        = r->_layer.getUID();
+            layer._imageLayer     = r->_layer.getMapLayer();
+            layer._tex            = r->_layer.getTexture();
+            layer._texParent      = r->_layerParent.getTexture();
+
+            // cache stock opacity. Disable if a color filter is installed, since
+            // it can modify the alpha.
+            layer._opaque =
+                (r->_layer.getMapLayer()->getColorFilters().size() == 0 ) &&
+                (layer._tex.valid() && !r->_layer.hasAlpha()) &&
+                (!layer._texParent.valid() || !r->_layerParent.hasAlpha());
+
+            // parent texture matrix: it's a scale/bias matrix encoding the difference
+            // between the two locators.
+            if ( r->_layerParent.getLocator() )
+            {
+                osg::Matrixd sbmatrix;
+
+                r->_layerParent.getLocator()->createScaleBiasMatrix(
+                    r->_layer.getLocator()->getDataExtent(),
+                    sbmatrix );
+
+                layer._texMatParent = sbmatrix;
+            }
+
+            // the surface:
+            layer._texCoords  = r->_texCoords;
+            d.surface->_layers[order] = layer;
+
+            // the mask geometries:
+            for ( MaskRecordVector::iterator mr = d.maskRecords.begin(); mr != d.maskRecords.end(); ++mr )
+            {
+                layer._texCoords = r->_stitchTexCoords.get();
+                mr->_geom->_layers[order] = layer;
+            }
+        }
+    }
+
+
+    void allocateVBOs( Data& d )
+    {
+        d.surface->setUseVertexBufferObjects(false);
+        d.surface->setUseVertexBufferObjects(true);
+
+        if ( d.stitchGeom )
+        {
+            d.stitchGeom->setUseVertexBufferObjects(false);
+            d.stitchGeom->setUseVertexBufferObjects(true);
+        }
+    }
+
+
+    // Optimize the data. Convert all modes to GL_TRIANGLES and run the
+    // critical vertex cache optimizations.
+    void optimize( Data& d, bool runMeshOptimizers )
+    { 
+        // For vertex cache optimization to work, all the arrays must be in
+        // the geometry. MP doesn't store texture/tile coords in the geometry
+        // so we need to temporarily add them.
+        //
+        // Note: the MeshOptimizer will duplicate shared arrays if it finds any.
+        // We don't want that. And since we already have pointers to our texture
+        // arrays elsewhere, it mistakingly thinks there are shared (sine they
+        // have refcount>1). So we need to UNREF them, optimize, and then RE-REF
+        // them afterwards. :/ -gw
+
+#if OSG_MIN_VERSION_REQUIRED(3, 1, 8) // after osg::Geometry API changes
+
+        osg::Geometry::ArrayList* surface_tdl = &d.surface->getTexCoordArrayList();
+        osg::Geometry::ArrayList* stitch_tdl  = d.stitchGeom ? &d.stitchGeom->getTexCoordArrayList() : 0L;
+        int u=0;
+        for( RenderLayerVector::const_iterator r = d.renderLayers.begin(); r != d.renderLayers.end(); ++r )
+        {
+            if ( r->_texCoords.valid() && r->_ownsTexCoords )
+            {
+                r->_texCoords->setBinding( osg::Array::BIND_PER_VERTEX );
+                surface_tdl->push_back( r->_texCoords.get() );
+                r->_texCoords->unref_nodelete();
+            }
+            if ( stitch_tdl && r->_stitchTexCoords.valid() )
+            {
+                r->_stitchTexCoords->setBinding( osg::Array::BIND_PER_VERTEX );
+                stitch_tdl->push_back( r->_stitchTexCoords.get() );
+                r->_stitchTexCoords->unref_nodelete();
+            }
+        }
+        if ( d.renderTileCoords.valid() && d.ownsTileCoords )
+        {
+            d.renderTileCoords->setBinding( osg::Array::BIND_PER_VERTEX );
+            surface_tdl->push_back( d.renderTileCoords.get() );
+            d.renderTileCoords->unref_nodelete();
+        }
+        if ( stitch_tdl && d.stitchTileCoords.valid() && d.ownsTileCoords )
+        {
+            d.stitchTileCoords->setBinding( osg::Array::BIND_PER_VERTEX );
+            stitch_tdl->push_back( d.stitchTileCoords.get() );
+            d.stitchTileCoords->unref_nodelete();
+        }
+
+#else // OSG version < 3.1.8 (before osg::Geometry API changes)
+
+        osg::Geometry::ArrayDataList* surface_tdl = &d.surface->getTexCoordArrayList();
+        osg::Geometry::ArrayDataList* stitch_tdl = d.stitchGeom ? &d.stitchGeom->getTexCoordArrayList() : 0L;
+        int u=0;
+        for( RenderLayerVector::const_iterator r = d.renderLayers.begin(); r != d.renderLayers.end(); ++r )
+        {
+            if ( r->_ownsTexCoords && r->_texCoords.valid() )
+            {
+                surface_tdl->push_back( osg::Geometry::ArrayData(r->_texCoords.get(), osg::Geometry::BIND_PER_VERTEX) );
+                r->_texCoords->unref_nodelete();
+            }
+            if ( stitch_tdl && r->_stitchTexCoords.valid() )
+            {
+                stitch_tdl->push_back( osg::Geometry::ArrayData(r->_stitchTexCoords.get(), osg::Geometry::BIND_PER_VERTEX) );
+                r->_stitchTexCoords->unref_nodelete();
+            }
+        }
+        if ( d.renderTileCoords.valid() && d.ownsTileCoords )
+        {
+            surface_tdl->push_back( osg::Geometry::ArrayData(d.renderTileCoords.get(), osg::Geometry::BIND_PER_VERTEX) );
+            d.renderTileCoords->unref_nodelete();
+        }
+        if ( stitch_tdl && d.stitchTileCoords.valid() && d.ownsTileCoords )
+        {
+            stitch_tdl->push_back( osg::Geometry::ArrayData(d.stitchTileCoords.get(), osg::Geometry::BIND_PER_VERTEX) );
+            d.stitchTileCoords->unref_nodelete();
+        }
+
+#endif
+
+		if (runMeshOptimizers && d.maskRecords.size() < 1)
+		{
+			osgUtil::Optimizer o;
+            
+			o.optimize( d.surfaceGeode,
+				osgUtil::Optimizer::VERTEX_PRETRANSFORM |
+				osgUtil::Optimizer::INDEX_MESH |
+				osgUtil::Optimizer::VERTEX_POSTTRANSFORM );
+		}
+
+        // do this while all the objects are in the geometry.
+        allocateVBOs( d );
+
+        // re-ref all the things we un-ref'd earlier.
+        for( RenderLayerVector::const_iterator r = d.renderLayers.begin(); r != d.renderLayers.end(); ++r )
+        {
+            if ( r->_texCoords.valid() && r->_ownsTexCoords )
+            {
+                r->_texCoords->ref();
+            }
+            if ( stitch_tdl && r->_stitchTexCoords.valid() )
+            {
+                r->_stitchTexCoords->ref();
+            }
+        }
+        if ( d.renderTileCoords.valid() && d.ownsTileCoords )
+        {
+            d.renderTileCoords->ref();
+        }
+        if ( stitch_tdl && d.stitchTileCoords.valid() && d.ownsTileCoords )
+        {
+            d.stitchTileCoords->ref();
+        }
+
+        // clear the data out of the actual geometry now that we're done optimizing.
+        surface_tdl->clear();
+
+        if (stitch_tdl)
+            stitch_tdl->clear();
+    }
+
+
+
+    struct CullByTraversalMask : public osg::Drawable::CullCallback
+    {
+        CullByTraversalMask( unsigned mask ) : _mask(mask) { }
+        unsigned _mask;
+
+        bool cull(osg::NodeVisitor* nv, osg::Drawable* drawable, osg::RenderInfo* renderInfo) const 
+        {
+            return ((unsigned)nv->getTraversalMask() & ((unsigned)nv->getNodeMaskOverride() | _mask)) == 0;
+        }
+    };
+}
+
+//------------------------------------------------------------------------
+
+TileModelCompiler::TileModelCompiler(const MaskLayerVector&              maskLayers,
+                                     const ModelLayerVector&             modelLayers,
+                                     int                                 texImageUnit,
+                                     bool                                optimizeTriOrientation,
+                                     const MPTerrainEngineOptions& options) :
+_maskLayers            ( maskLayers ),
+_modelLayers           ( modelLayers ),
+_optimizeTriOrientation( optimizeTriOrientation ),
+_options               ( options ),
+_textureImageUnit      ( texImageUnit )
+{
+    _cullByTraversalMask = new CullByTraversalMask(*options.secondaryTraversalMask());
+}
+
+
+TileNode*
+TileModelCompiler::compile(const TileModel* model,
+                           const MapFrame&  frame)
+{
+    TileNode* tile = new TileNode( model->_tileKey, model );
+
+    // Working data for the build.
+    Data d(model, frame, _maskLayers, _modelLayers);
+
+    d.parentModel = model->getParentTileModel();
+    d.heightScale = *_options.verticalScale();
+    d.heightOffset = *_options.verticalOffset();
+
+    // build the localization matrix for this tile:
+    model->_tileLocator->unitToModel( osg::Vec3(0.5f, 0.5f, 0.0), d.centerModel );
+    tile->setMatrix( osg::Matrix::translate(d.centerModel) );
+
+    // A Geode/Geometry for the surface:
+    d.surface = new MPGeometry( d.model->_tileKey, d.frame, _textureImageUnit );
+    d.surfaceGeode = new osg::Geode();
+    d.surfaceGeode->addDrawable( d.surface );
+    d.surfaceGeode->setNodeMask( *_options.primaryTraversalMask() );
+
+    tile->addChild( d.surfaceGeode );
+
+    // A Geode/Geometry for the skirt. This is good for traversal masking (e.g. shadows)
+    // but bad since we're not combining the entire tile into a single geometry.
+    // TODO: make this optional?
+    d.createSkirt = (_options.heightFieldSkirtRatio().value() > 0.0);
+
+    // adjust the tile locator for geocentric mode:
+    d.geoLocator = model->_tileLocator->getCoordinateSystemType() == GeoLocator::GEOCENTRIC ? 
+        model->_tileLocator->getGeographicFromGeocentric() :
+        model->_tileLocator.get();
+
+    // Set up any geometry-cutting masks:
+    if ( d.maskLayers.size() > 0 || d.modelLayers.size() > 0 )
+        setupMaskRecords( d );
+
+    // allocate all the vertex, normal, and color arrays.
+    double sampleRatio = *_options.heightFieldSampleRatio();
+    if ( sampleRatio <= 0.0f )
+        sampleRatio = osg::clampBetween( model->_tileKey.getLOD()/20.0, 0.0625, 1.0 );
+
+    setupGeometryAttributes( d, sampleRatio );
+
+    // set up the list of layers to render and their shared arrays.
+    setupTextureAttributes( d, _cache );
+
+    // calculate the vertex and normals for the surface geometry.
+    createSurfaceGeometry( d );
+
+    // build geometry for the masked areas, if applicable
+    if ( d.maskRecords.size() > 0 )
+        createMaskGeometry( d );
+
+    // build the skirts.
+    if ( d.createSkirt )
+        createSkirtGeometry( d, *_options.heightFieldSkirtRatio() );
+
+    // tesselate the surface verts into triangles.
+    tessellateSurfaceGeometry( d, _optimizeTriOrientation, *_options.normalizeEdges() );
+
+    // performance optimizations.
+    optimize( d, _options.optimizeTiles() == true );
+
+    // installs the per-layer rendering data into the Geometry objects.
+    installRenderData( d );
+    
+    if (osgDB::Registry::instance()->getBuildKdTreesHint()==osgDB::ReaderWriter::Options::BUILD_KDTREES &&
+        osgDB::Registry::instance()->getKdTreeBuilder())
+    {            
+        osg::ref_ptr<osg::KdTreeBuilder> builder = osgDB::Registry::instance()->getKdTreeBuilder()->clone();
+        tile->accept(*builder);
+    }
+
+    // Temporary solution to the OverlayDecorator techniques' inappropriate setting of
+    // uniform values during the CULL traversal, which causes corruption of the RTT 
+    // camera matricies when DRAW overlaps the next frame's CULL. Please see my comments
+    // in DrapingTechnique.cpp for more information.
+    // NOTE: cannot set this until optimizations (above) are complete
+    SetDataVarianceVisitor sdv( osg::Object::DYNAMIC );
+    tile->accept( sdv );
+
+#if 0
+    //test: run the geometry validator to make sure geometry it legal
+    osgEarth::GeometryValidator validator;
+    tile->accept(validator);
+#endif
+
+    return tile;
+}
diff --git a/src/osgEarthDrivers/engine_mp/TileModelFactory b/src/osgEarthDrivers/engine_mp/TileModelFactory
index 3c19d11..021aeca 100644
--- a/src/osgEarthDrivers/engine_mp/TileModelFactory
+++ b/src/osgEarthDrivers/engine_mp/TileModelFactory
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -16,14 +16,15 @@
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
-#ifndef OSGEARTH_ENGINE_MP_TILE_MODEL_FACTORY
-#define OSGEARTH_ENGINE_MP_TILE_MODEL_FACTORY 1
+#ifndef OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_TILE_MODEL_FACTORY
+#define OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_TILE_MODEL_FACTORY 1
 
 #include "Common"
 #include "TileNode"
 #include "TileNodeRegistry"
 #include "MPTerrainEngineOptions"
 #include <osgEarth/Map>
+#include <osgEarth/Progress>
 #include <osgEarth/ThreadingUtils>
 #include <osgEarth/Containers>
 #include <osgEarth/HeightFieldUtils>
@@ -31,27 +32,26 @@
 #include <osgEarth/MapInfo>
 #include <osg/Group>
 
-namespace osgEarth_engine_mp
+namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 {
     using namespace osgEarth;
 
+    /** Key into the height field cache */
     struct HFKey {
-        TileKey _key;
-        Revision _revision;
-        bool    _fallback;
-        bool    _convertToHAE;
+        TileKey               _key;
+        Revision              _revision;
         ElevationSamplePolicy _samplePolicy;
+
         bool operator < (const HFKey& rhs) const {
             if ( _key < rhs._key ) return true;
             if ( rhs._key < _key ) return false;
             if ( _revision < rhs._revision ) return true;
             if ( _revision > rhs._revision ) return false;
-            if ( _fallback != rhs._fallback ) return true;
-            if ( _convertToHAE != rhs._convertToHAE ) return true;
             return _samplePolicy < rhs._samplePolicy;
         }
     };
 
+    /** value in the height field cache */
     struct HFValue {
         osg::ref_ptr<osg::HeightField> _hf;
         bool                           _isFallback;
@@ -60,67 +60,106 @@ namespace osgEarth_engine_mp
     class HeightFieldCache : public osg::Referenced, public Revisioned
     {
     public:
-        HeightFieldCache():
-          _cache    ( true, 128 )
+        HeightFieldCache(TileNodeRegistry* tiles, const MPTerrainEngineOptions& options) :
+          _tiles  ( tiles ),
+          _cache  ( true, 128 )
         {
-
+            _firstLOD = options.firstLOD().get();
         }
 
         bool getOrCreateHeightField( 
                 const MapFrame&                 frame,
                 const TileKey&                  key,
-                bool                            fallback,
+                bool                            cummulative,  // whether to start with the parent as a template
                 osg::ref_ptr<osg::HeightField>& out_hf,
-                bool*                           out_isFallback =0L,
-                bool                            convertToHAE   =true,
-                ElevationSamplePolicy           samplePolicy   =SAMPLE_FIRST_VALID,
-                ProgressCallback*               progress       =0L ) const
+                bool&                           out_isFallback,
+                ElevationSamplePolicy           samplePolicy,
+                ElevationInterpolation          interp,
+                ProgressCallback*               progress )
         {                
+            // default
+            out_isFallback = false;
+
             // check the quick cache.
             HFKey cachekey;
             cachekey._key          = key;
             cachekey._revision     = frame.getRevision();
-            cachekey._fallback     = fallback;
-            cachekey._convertToHAE = convertToHAE;
             cachekey._samplePolicy = samplePolicy;
 
+            if (progress)
+                progress->stats()["hfcache_try_count"] += 1;
+
             bool hit = false;
             LRUCache<HFKey,HFValue>::Record rec;
             if ( _cache.get(cachekey, rec) )
             {
                 out_hf = rec.value()._hf.get();
-                if ( out_isFallback )
-                    *out_isFallback = rec.value()._isFallback;
+                out_isFallback = rec.value()._isFallback;
+
+                if (progress)
+                {
+                    progress->stats()["hfcache_hit_count"] += 1;
+                    progress->stats()["hfcache_hit_rate"] = progress->stats()["hfcache_hit_count"]/progress->stats()["hfcache_try_count"];
+                }
 
-                //OE_NOTICE << "Found HF " << key.str() << " in HF cache." << std::endl;
                 return true;
             }
 
-            bool isFallback;
-
-            bool ok = frame.getHeightField( key, fallback, out_hf, &isFallback, convertToHAE, samplePolicy, progress );
-
-            if ( ok )
-            {            
-                // Treat Plate Carre specially by scaling the height values. (There is no need
-                // to do this with an empty heightfield)
-                const MapInfo& mapInfo = frame.getMapInfo();
-                if ( mapInfo.isPlateCarre() )
+            // Find the parent tile and start with its heightfield.
+            if ( cummulative )
+            {
+                osg::ref_ptr<TileNode> parentNode;
+                TileKey parentKey = key.createParentKey();
+                if ( _tiles->get(parentKey, parentNode) )
                 {
-                    HeightFieldUtils::scaleHeightFieldToDegrees( out_hf.get() );
+                    const TileModel* parentModel = parentNode->getTileModel();
+                    if ( parentModel )
+                    {
+                        osg::HeightField* parentHF = parentModel->_elevationData.getHeightField();
+                        if ( parentHF )
+                        {
+                            out_hf = HeightFieldUtils::createSubSample(
+                                parentHF,
+                                parentKey.getExtent(),
+                                key.getExtent(),
+                                interp );
+                        }
+                    }
                 }
 
-                if ( out_isFallback )
-                    *out_isFallback = isFallback;
+                if ( !out_hf.valid() && ((int)key.getLOD())-1 > _firstLOD )
+                {
+                    // This most likely means that a parent tile expired while we were building the child.
+                    // No harm done in that case as this tile will soo be discarded as well.
+                    OE_DEBUG << "MP HFC: Unable to find tile " << key.str() << " in the live tile registry"
+                        << std::endl;
+                    return false;
+                }
+            }
 
-                // cache me
-                HFValue cacheval;
-                cacheval._hf = out_hf.get();
-                cacheval._isFallback = isFallback;
-                _cache.insert( cachekey, cacheval );
+            bool populated = frame.populateHeightField(
+                out_hf,
+                key,
+                true, // convertToHAE
+                samplePolicy,
+                progress );
+     
+            // Treat Plate Carre specially by scaling the height values. (There is no need
+            // to do this with an empty heightfield)
+            const MapInfo& mapInfo = frame.getMapInfo();
+            if ( mapInfo.isPlateCarre() )
+            {
+                HeightFieldUtils::scaleHeightFieldToDegrees( out_hf.get() );
             }
 
-            return ok;
+            // cache it.
+            HFValue cacheval;
+            cacheval._hf = out_hf.get();
+            cacheval._isFallback = !populated;
+            _cache.insert( cachekey, cacheval );
+
+            out_isFallback = !populated;
+            return true;
         }
 
         void clear()
@@ -130,8 +169,11 @@ namespace osgEarth_engine_mp
 
     private:
         mutable LRUCache<HFKey,HFValue> _cache;
+        TileNodeRegistry*               _tiles;
+        int                             _firstLOD;
     };
 
+
     /**
      * For a given TileKey, this class builds a a corresponding TileNode from
      * the map's data model.
@@ -144,8 +186,8 @@ namespace osgEarth_engine_mp
     {
     public:
         TileModelFactory(
-            TileNodeRegistry*                      liveTiles,
-            const Drivers::MPTerrainEngineOptions& terrainOptions );
+            TileNodeRegistry*             liveTiles,
+            const MPTerrainEngineOptions& terrainOptions );
 
         HeightFieldCache* getHeightFieldCache() const;
 
@@ -153,19 +195,26 @@ namespace osgEarth_engine_mp
         virtual ~TileModelFactory() { }
 
         void createTileModel(
-            const TileKey&           key,
-            const MapFrame&          frame,
-            osg::ref_ptr<TileModel>& out_model);//,
-            //bool&                    out_hasRealData);
+            const TileKey&           key,           // key for which to create model
+            const MapFrame&          frame,         // map frame from which to get data
+            bool                     accumulate,    // whether to accumulate values from parent tile(s)
+            osg::ref_ptr<TileModel>& out_model,     // output or NULL upon failure
+            ProgressCallback*        progress);     // progess tracking
 
     private:        
 
-        //const Map*                             _map;
-        osg::ref_ptr<TileNodeRegistry>         _liveTiles;
-        const Drivers::MPTerrainEngineOptions& _terrainOptions;
-        osg::ref_ptr< HeightFieldCache >       _hfCache;
+        osg::ref_ptr<TileNodeRegistry>   _liveTiles;
+        const MPTerrainEngineOptions&    _terrainOptions;
+        osg::ref_ptr< HeightFieldCache > _hfCache;
+        
+        void buildElevation(
+            const TileKey&    key,
+            const MapFrame&   frame,
+            bool              accumulate,
+            TileModel*        model,
+            ProgressCallback* progress);
     };
 
-} // namespace osgEarth_engine_mp
+} } } // namespace osgEarth::Drivers::MPTerrainEngine
 
-#endif // OSGEARTH_ENGINE_MP_TILE_MODEL_FACTORY
+#endif // OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_TILE_MODEL_FACTORY
diff --git a/src/osgEarthDrivers/engine_mp/TileModelFactory.cpp b/src/osgEarthDrivers/engine_mp/TileModelFactory.cpp
index ecb50ec..16e9e2a 100644
--- a/src/osgEarthDrivers/engine_mp/TileModelFactory.cpp
+++ b/src/osgEarthDrivers/engine_mp/TileModelFactory.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -21,8 +21,9 @@
 #include <osgEarth/MapInfo>
 #include <osgEarth/ImageUtils>
 #include <osgEarth/HeightFieldUtils>
+#include <osgEarth/Progress>
 
-using namespace osgEarth_engine_mp;
+using namespace osgEarth::Drivers::MPTerrainEngine;
 using namespace osgEarth;
 using namespace osgEarth::Drivers;
 using namespace OpenThreads;
@@ -40,33 +41,41 @@ namespace
                    unsigned                            order,
                    const MapInfo&                      mapInfo,
                    const MPTerrainEngineOptions&       opt, 
-                   TileModel*                          model )
+                   TileNodeRegistry*                   tiles,
+                   TileModel*                          model)
         {
             _key      = key;
             _layer    = layer;
             _order    = order;
             _mapInfo  = &mapInfo;
             _opt      = &opt;
+            _tiles    = tiles;
             _model    = model;
         }
 
-        bool execute()
+        bool execute(ProgressCallback* progress)
         {
+            bool ok = false;
+
+            // This will only go true if we are requesting a ROOT TILE but we have to
+            // fall back on lower resolution data to create it.
+            bool isFallback = false;
+
             GeoImage geoImage;
-            bool isFallbackData = false;
 
+            // The "fast path" preserves mercator tiles without reprojection.
             bool useMercatorFastPath =
                 _opt->enableMercatorFastPath() != false &&
                 _mapInfo->isGeocentric()                &&
                 _layer->getProfile()                    &&
                 _layer->getProfile()->getSRS()->isSphericalMercator();
 
-            // fetch the image from the layer.
-
-            //bool autoFallback = _key.getLevelOfDetail() <= 1;
-            bool autoFallback = false;
+            // If this is a ROOT tile, we will try to fall back on lower-resolution
+            // data if we can't find something at the optimal LOD.
+            bool isRootKey =
+                (_key.getLOD() == 0) || // should never be
+                (_key.getLOD()-1 == _opt->firstLOD().value());
 
-            TileKey imageKey( _key );
             TileSource*    tileSource   = _layer->getTileSource();
             const Profile* layerProfile = _layer->getProfile();
 
@@ -82,28 +91,57 @@ namespace
                 hasDataInExtent = tileSource->hasDataInExtent( ext );
             }
             
-            if (hasDataInExtent)
+            // fetch the image from the layer.
+            if (hasDataInExtent && _layer->isKeyInRange(_key))
             {
-                while( !geoImage.valid() && imageKey.valid() && _layer->isKeyValid(imageKey) )
+                if ( useMercatorFastPath )
                 {
-                    if ( useMercatorFastPath )
+                    geoImage = _layer->createImageInNativeProfile( _key, progress );
+
+                    // If this is a root tile, try to find lower-resolution data to
+                    // fulfill the request.
+                    if ( isRootKey && !geoImage.valid() )
                     {
-                        bool mercFallbackData = false;
-                        geoImage = _layer->createImageInNativeProfile( imageKey, 0L, autoFallback, mercFallbackData );
-                        if ( geoImage.valid() && mercFallbackData )
+                        isFallback = true;
+
+                        for(TileKey fallbackKey = _key.createParentKey();
+                            fallbackKey.valid() && !geoImage.valid();
+                            fallbackKey = fallbackKey.createParentKey())
                         {
-                            isFallbackData = true;
+                            geoImage = _layer->createImageInNativeProfile( fallbackKey, progress );
+                            if ( geoImage.valid() )
+                            {
+                                OE_DEBUG << LC << "Fell back from (" 
+                                    << _key.str() << ") to ("
+                                    << fallbackKey.str() << ") for a root tile request."
+                                    << std::endl;
+                            }
                         }
                     }
-                    else
-                    {
-                        geoImage = _layer->createImage( imageKey, 0L, autoFallback );
-                    }
+                }
+                else
+                {
+                    geoImage = _layer->createImage( _key, progress );
 
-                    if ( !geoImage.valid() )
+                    // If this is a root tile, try to find lower-resolution data to
+                    // fulfill the request.
+                    if ( isRootKey && !geoImage.valid() )
                     {
-                        imageKey = imageKey.createParentKey();
-                        isFallbackData = true;
+                        isFallback = true;
+
+                        for(TileKey fallbackKey = _key.createParentKey();
+                            fallbackKey.valid() && !geoImage.valid();
+                            fallbackKey = fallbackKey.createParentKey())
+                        {
+                            geoImage = _layer->createImage( fallbackKey, progress );
+                            if ( geoImage.valid() )
+                            {
+                                OE_DEBUG << LC << "Fell back from (" 
+                                    << _key.str() << ") to ("
+                                    << fallbackKey.str() << ") for a root tile request."
+                                    << std::endl;
+                            }
+                        }
                     }
                 }
             }
@@ -117,136 +155,61 @@ namespace
                 else
                     locator = GeoLocator::createForExtent(geoImage.getExtent(), *_mapInfo);
 
-                // convert the image to PMA. This must be done in the CPU; for some
-                // reason (which we could not determine) it fails to try this after the
-                // texture lookup in the shader.
-                if ( _opt->premultipliedAlpha() == true )
-                {
-                    ImageUtils::convertToPremultipliedAlpha( geoImage.getImage() );
-                }
-
                 // add the color layer to the repo.
                 _model->_colorData[_layer->getUID()] = TileModel::ColorData(
                     _layer,
                     _order,
                     geoImage.getImage(),
                     locator,
-                    _key,
-                    isFallbackData );
+                    isFallback ); // isFallbackData
 
-                return true;
+                ok = true;
             }
 
-            else
+            else // fall back on parent tile.
             {
-                return false;
-            }
-        }
-
-        TileKey        _key;
-        const MapInfo* _mapInfo;
-        ImageLayer*    _layer;
-        unsigned       _order;
-        TileModel*     _model;
-        const MPTerrainEngineOptions* _opt;
-    };
-}
-
-//------------------------------------------------------------------------
-
-namespace
-{
-    struct BuildElevationData
-    {
-        void init(const TileKey& key, const MapFrame& mapf, const MPTerrainEngineOptions& opt, TileModel* model, HeightFieldCache* hfCache) //sourceTileNodeBuilder::SourceRepo& repo)
-        {
-            _key   = key;
-            _mapf  = &mapf;
-            _opt   = &opt;
-            _model = model;
-            _hfCache = hfCache;
-        }
-
-        void execute()
-        {            
-            const MapInfo& mapInfo = _mapf->getMapInfo();
-
-            // Request a heightfield from the map, falling back on lower resolution tiles
-            // if necessary (fallback=true)
-            osg::ref_ptr<osg::HeightField> hf;
-            bool isFallback = false;
-
-            //if ( _mapf->getHeightField( _key, true, hf, &isFallback ) )
-            if (_hfCache->getOrCreateHeightField( *_mapf, _key, true, hf, &isFallback) )
-            {
-                _model->_elevationData = TileModel::ElevationData(
-                    hf,
-                    GeoLocator::createForKey( _key, mapInfo ),
-                    isFallback );
-
-#if 1
-                if ( *_opt->normalizeEdges() )
-#endif
+                TileKey parentKey = _key.createParentKey();
+                osg::ref_ptr<TileNode> parentNode;
+                _tiles->get(parentKey, parentNode);
+                if ( parentNode.valid() )
                 {
-                    // next, query the neighboring tiles to get adjacency information.
-                    for( int x=-1; x<=1; x++ )
-                    {
-                        for( int y=-1; y<=1; y++ )
-                        {
-                            if ( x != 0 || y != 0 )
-                            {
-                                TileKey nk = _key.createNeighborKey(x, y);
-                                if ( nk.valid() )
-                                {
-                                    if (_hfCache->getOrCreateHeightField( *_mapf, nk, true, hf, &isFallback) )
-                                    {
-                                        if ( mapInfo.isPlateCarre() )
-                                        {
-                                            HeightFieldUtils::scaleHeightFieldToDegrees( hf.get() );
-                                        }
-
-                                        _model->_elevationData.setNeighbor( x, y, hf.get() );
-                                    }
-                                }
-                            }
-                        }
-                    }
-
-                    // parent too.
-                    if ( _key.getLOD() > 0 )
+                    const TileModel* parentModel = parentNode->getTileModel();
+                    if ( parentModel )
                     {
-                        if ( _hfCache->getOrCreateHeightField( *_mapf, _key.createParentKey(), true, hf, &isFallback) )
+                        TileModel::ColorData parentColorData;
+                        if ( parentModel->getColorData(_layer->getUID(), parentColorData) )
                         {
-                            if ( mapInfo.isPlateCarre() )
-                            {
-                                HeightFieldUtils::scaleHeightFieldToDegrees( hf.get() );
-                            }
-
-                            _model->_elevationData.setParent( hf.get() );
+                            TileModel::ColorData& colorData = _model->_colorData[_layer->getUID()];
+                            colorData = TileModel::ColorData(parentColorData);
+                            colorData._order = _order;
+                            colorData.setIsFallbackData( true );
+                            ok = true;
                         }
                     }
                 }
             }
+
+            return ok;
         }
 
-        TileKey                  _key;
-        const MapFrame*          _mapf;
+        TileKey           _key;
+        const MapInfo*    _mapInfo;
+        TileNodeRegistry* _tiles;
+        ImageLayer*       _layer;
+        unsigned          _order;
+        TileModel*        _model;
         const MPTerrainEngineOptions* _opt;
-        TileModel* _model;
-        osg::ref_ptr< HeightFieldCache> _hfCache;
     };
 }
 
 //------------------------------------------------------------------------
 
-TileModelFactory::TileModelFactory(//const Map*                          map, 
-                                   TileNodeRegistry*                   liveTiles,
+TileModelFactory::TileModelFactory(TileNodeRegistry*             liveTiles,
                                    const MPTerrainEngineOptions& terrainOptions ) :
-//_map           ( map ),
 _liveTiles     ( liveTiles ),
 _terrainOptions( terrainOptions )
 {
-    _hfCache = new HeightFieldCache();
+    _hfCache = new HeightFieldCache(liveTiles, terrainOptions);
 }
 
 HeightFieldCache*
@@ -257,28 +220,94 @@ TileModelFactory::getHeightFieldCache() const
 
 
 void
+TileModelFactory::buildElevation(const TileKey&    key,
+                                 const MapFrame&   frame,
+                                 bool              accumulate,
+                                 TileModel*        model,
+                                 ProgressCallback* progress)
+{     
+    const MapInfo& mapInfo = frame.getMapInfo();
+
+    const osgEarth::ElevationInterpolation& interp =
+        frame.getMapOptions().elevationInterpolation().get();
+
+    // Request a heightfield from the map, falling back on lower resolution tiles
+    // if necessary (fallback=true)
+    osg::ref_ptr<osg::HeightField> hf;
+
+    bool isFallback = false;
+
+    if (_hfCache->getOrCreateHeightField(frame, key, accumulate, hf, isFallback, SAMPLE_FIRST_VALID, interp, progress))
+    {
+        model->_elevationData = TileModel::ElevationData(
+            hf,
+            GeoLocator::createForKey( key, mapInfo ),
+            isFallback );
+
+        // Edge normalization: requires adjacency information
+        if ( _terrainOptions.normalizeEdges() == true )
+        {
+            for( int x=-1; x<=1; x++ )
+            {
+                for( int y=-1; y<=1; y++ )
+                {
+                    if ( x != 0 || y != 0 )
+                    {
+                        TileKey nk = key.createNeighborKey(x, y);
+                        if ( nk.valid() )
+                        {
+                            osg::ref_ptr<osg::HeightField> hf;
+                            if (_hfCache->getOrCreateHeightField(frame, nk, accumulate, hf, isFallback, SAMPLE_FIRST_VALID, interp, progress) )
+                            {
+                                model->_elevationData.setNeighbor( x, y, hf.get() );
+                            }
+                        }
+                    }
+                }
+            }
+
+            // parent too.
+            if ( key.getLOD() > 0 )
+            {
+                osg::ref_ptr<osg::HeightField> hf;
+                if ( _hfCache->getOrCreateHeightField(frame, key.createParentKey(), accumulate, hf, isFallback, SAMPLE_FIRST_VALID, interp, progress) )
+                {
+                    model->_elevationData.setParent( hf.get() );
+                }
+            }
+        }
+    }
+}
+
+
+void
 TileModelFactory::createTileModel(const TileKey&           key, 
                                   const MapFrame&          frame,
-                                  osg::ref_ptr<TileModel>& out_model) //,
-                                  //bool&                    out_hasRealData)
+                                  bool                     accumulate,
+                                  osg::ref_ptr<TileModel>& out_model,
+                                  ProgressCallback*        progress)
 {
 
     osg::ref_ptr<TileModel> model = new TileModel( frame.getRevision(), frame.getMapInfo() );
+
     model->_tileKey = key;
     model->_tileLocator = GeoLocator::createForKey(key, frame.getMapInfo());
-    
+
+    OE_START_TIMER(fetch_imagery);
+
     // Fetch the image data and make color layers.
+    unsigned index = 0;
     unsigned order = 0;
     for( ImageLayerVector::const_iterator i = frame.imageLayers().begin(); i != frame.imageLayers().end(); ++i )
     {
         ImageLayer* layer = i->get();
 
-        if ( layer->getEnabled() )
+        if ( layer->getEnabled() && layer->isKeyInRange(key) )
         {
             BuildColorData build;
-            build.init( key, layer, order, frame.getMapInfo(), _terrainOptions, model.get() );
-            
-            bool addedToModel = build.execute();
+            build.init( key, layer, order, frame.getMapInfo(), _terrainOptions, _liveTiles.get(), model.get() );
+
+            bool addedToModel = build.execute(progress);
             if ( addedToModel )
             {
                 // only bump the order if we added something to the data model.
@@ -287,13 +316,22 @@ TileModelFactory::createTileModel(const TileKey&           key,
         }
     }
 
+    if (progress)
+        progress->stats()["fetch_imagery_time"] += OE_STOP_TIMER(fetch_imagery);
+
+    
+    OE_START_TIMER(fetch_elevation);
+
     // make an elevation layer.
-    BuildElevationData build;
-    build.init( key, frame, _terrainOptions, model.get(), _hfCache );
-    build.execute();
+    buildElevation(key, frame, accumulate, model.get(), progress);
+
+    if (progress)
+        progress->stats()["fetch_elevation_time"] += OE_STOP_TIMER(fetch_elevation);
 
 
-    // Bail out now if there's no data to be had.
+    // If nothing was added, not even a fallback heightfield, something went
+    // horribly wrong. Leave without a tile model. Chances are that a parent tile
+    // not not found in the live-tile registry.
     if ( model->_colorData.size() == 0 && !model->_elevationData.getHeightField() )
     {
         return;
diff --git a/src/osgEarthDrivers/engine_mp/TileNode b/src/osgEarthDrivers/engine_mp/TileNode
index c9a592c..83bdf6c 100644
--- a/src/osgEarthDrivers/engine_mp/TileNode
+++ b/src/osgEarthDrivers/engine_mp/TileNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -16,14 +16,14 @@
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
-#ifndef OSGEARTH_ENGINE_MP_TILE_NODE
-#define OSGEARTH_ENGINE_MP_TILE_NODE 1
+#ifndef OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_TILE_NODE
+#define OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_TILE_NODE 1
 
 #include "Common"
 #include "TileModel"
 #include <osg/MatrixTransform>
 
-namespace osgEarth_engine_mp
+namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 {
     using namespace osgEarth;
     class TileNode;
@@ -109,7 +109,6 @@ namespace osgEarth_engine_mp
 
         TileKey                            _key;
         osg::ref_ptr<const TileModel>      _model;
-        osg::ref_ptr<osg::Uniform>         _tileParentMatrixUniform;
         unsigned                           _lastTraversalFrame;
         Revision                           _maprevision;
         bool                               _outOfDate;
@@ -134,6 +133,6 @@ namespace osgEarth_engine_mp
     };
 
 
-} // namespace osgEarth_engine_mp
+} } } // namespace osgEarth::Drivers::MPTerrainEngine
 
-#endif // OSGEARTH_ENGINE_MP_TILE_NODE
+#endif // OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_TILE_NODE
diff --git a/src/osgEarthDrivers/engine_mp/TileNode.cpp b/src/osgEarthDrivers/engine_mp/TileNode.cpp
index e221203..7ca454f 100644
--- a/src/osgEarthDrivers/engine_mp/TileNode.cpp
+++ b/src/osgEarthDrivers/engine_mp/TileNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -23,7 +23,7 @@
 #include <osg/NodeVisitor>
 #include <osg/Uniform>
 
-using namespace osgEarth_engine_mp;
+using namespace osgEarth::Drivers::MPTerrainEngine;
 using namespace osgEarth;
 using namespace OpenThreads;
 
@@ -43,7 +43,13 @@ _outOfDate         ( false )
 
     // revisions are initially in sync:
     if ( model )
+    {
         _maprevision = model->_revision;
+        if ( model->requiresUpdateTraverse() )
+        {
+            this->setNumChildrenRequiringUpdateTraversal(1);
+        }
+    }
 }
 
 
@@ -57,21 +63,28 @@ TileNode::setLastTraversalFrame(unsigned frame)
 void
 TileNode::traverse( osg::NodeVisitor& nv )
 {
-    if ( _model.valid() && nv.getVisitorType() == nv.CULL_VISITOR )
+    if ( _model.valid() )
     {
-        osg::ClusterCullingCallback* ccc = dynamic_cast<osg::ClusterCullingCallback*>(getCullCallback());
-        if (ccc)
+        if ( nv.getVisitorType() == nv.CULL_VISITOR )
         {
-            if (ccc->cull(&nv,0,static_cast<osg::State *>(0))) return;
+            osg::ClusterCullingCallback* ccc = dynamic_cast<osg::ClusterCullingCallback*>(getCullCallback());
+            if (ccc)
+            {
+                if (ccc->cull(&nv,0,static_cast<osg::State *>(0))) return;
+            }
+
+            // if this tile is marked dirty, bump the marker so the engine knows it
+            // needs replacing.
+            if ( _dirty || _model->_revision != _maprevision )
+            {
+                _outOfDate = true;
+            }
         }
-
-        // if this tile is marked dirty, bump the marker so the engine knows it
-        // needs replacing.
-        if ( _dirty || _model->_revision != _maprevision )
+        else if (nv.getVisitorType() == nv.UPDATE_VISITOR)
         {
-            _outOfDate = true;
+            _model->updateTraverse(nv);
         }
-    }
+    }    
 
     osg::MatrixTransform::traverse( nv );
 }
diff --git a/src/osgEarthDrivers/engine_mp/TileNodeRegistry b/src/osgEarthDrivers/engine_mp/TileNodeRegistry
index 3d31723..dbc233a 100644
--- a/src/osgEarthDrivers/engine_mp/TileNodeRegistry
+++ b/src/osgEarthDrivers/engine_mp/TileNodeRegistry
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -16,16 +16,17 @@
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
-#ifndef OSGEARTH_ENGINE_MP_TILE_NODE_REGISTRY
-#define OSGEARTH_ENGINE_MP_TILE_NODE_REGISTRY 1
+#ifndef OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_TILE_NODE_REGISTRY
+#define OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_TILE_NODE_REGISTRY 1
 
 #include "Common"
 #include "TileNode"
 #include <osgEarth/Revisioning>
 #include <osgEarth/ThreadingUtils>
+#include <OpenThreads/Atomic>
 #include <map>
 
-namespace osgEarth_engine_mp
+namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 {
     using namespace osgEarth;
 
@@ -66,6 +67,23 @@ namespace osgEarth_engine_mp
         /** Map revision that the reg will assign to new tiles. */
         const Revision& getMapRevision() const { return _maprev; }
 
+        /**
+         * Marks all tiles intersecting the extent as dirty. If incremental
+         * update is enabled, they will automatically reload.
+         *
+         * NOTE: Input extent SRS must match the terrain's SRS exactly.
+         *       The method does not check.
+         */
+        void setDirty(const GeoExtent& extent, unsigned minLevel, unsigned maxLevel);
+
+        /**
+         * Sets the current cull traversal frame number so that tiles have
+         * access to the information. Atomic.
+         */
+        void setTraversalFrame(unsigned frame) { _frameNumber.exchange(frame); }
+
+        unsigned getTraversalFrame() const { return _frameNumber; }
+
         virtual ~TileNodeRegistry() { }
 
         /** Adds a tile to the registry */
@@ -104,9 +122,10 @@ namespace osgEarth_engine_mp
         Revision                          _maprev;
         std::string                       _name;
         TileNodeMap                       _tiles;
+        OpenThreads::Atomic               _frameNumber;
         mutable Threading::ReadWriteMutex _tilesMutex;
     };
 
-} // namespace osgEarth_engine_mp
+} } } // namespace osgEarth::Drivers::MPTerrainEngine
 
-#endif // OSGEARTH_ENGINE_MP_TILE_NODE_REGISTRY
+#endif // OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_TILE_NODE_REGISTRY
diff --git a/src/osgEarthDrivers/engine_mp/TileNodeRegistry.cpp b/src/osgEarthDrivers/engine_mp/TileNodeRegistry.cpp
index 3f93d3c..0224f73 100644
--- a/src/osgEarthDrivers/engine_mp/TileNodeRegistry.cpp
+++ b/src/osgEarthDrivers/engine_mp/TileNodeRegistry.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -18,7 +18,7 @@
 */
 #include "TileNodeRegistry"
 
-using namespace osgEarth_engine_mp;
+using namespace osgEarth::Drivers::MPTerrainEngine;
 using namespace osgEarth;
 
 #define LC "[TileNodeRegistry] "
@@ -31,7 +31,8 @@ using namespace osgEarth;
 
 TileNodeRegistry::TileNodeRegistry(const std::string& name) :
 _name              ( name ),
-_revisioningEnabled( false )
+_revisioningEnabled( false ),
+_frameNumber       ( 0u )
 {
     //nop
 }
@@ -70,6 +71,29 @@ TileNodeRegistry::setMapRevision(const Revision& rev,
 }
 
 
+//NOTE: this method assumes the input extent is the same SRS as
+// the terrain profile SRS.
+void
+TileNodeRegistry::setDirty(const GeoExtent& extent,
+                           unsigned         minLevel,
+                           unsigned         maxLevel)
+{
+    Threading::ScopedWriteLock exclusive( _tilesMutex );
+    
+    bool checkSRS = false;
+    for( TileNodeMap::iterator i = _tiles.begin(); i != _tiles.end(); ++i )
+    {
+        const TileKey& key = i->first;
+        if (minLevel <= key.getLOD() && 
+            maxLevel >= key.getLOD() &&
+            extent.intersects(i->first.getExtent(), checkSRS) )
+        {
+            i->second->setDirty();
+        }
+    }
+}
+
+
 void
 TileNodeRegistry::add( TileNode* tile )
 {
diff --git a/src/osgEarthDrivers/engine_mp/TilePagedLOD b/src/osgEarthDrivers/engine_mp/TilePagedLOD
index c0c7b3c..f5272da 100644
--- a/src/osgEarthDrivers/engine_mp/TilePagedLOD
+++ b/src/osgEarthDrivers/engine_mp/TilePagedLOD
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -16,17 +16,18 @@
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
-#ifndef OSGEARTH_ENGINE_MP_TILE_PAGED_LOD
-#define OSGEARTH_ENGINE_MP_TILE_PAGED_LOD 1
+#ifndef OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_TILE_PAGED_LOD
+#define OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_TILE_PAGED_LOD 1
 
 #include "Common"
 #include "TileNodeRegistry"
 #include <osg/PagedLOD>
 #include <osgEarth/ThreadingUtils>
+#include <osgEarth/Progress>
 
 using namespace osgEarth;
 
-namespace osgEarth_engine_mp
+namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
 {
     /**
      * TilePagedLOD is an extension to osg::PagedLOD that supports the tile registry.
@@ -43,11 +44,15 @@ namespace osgEarth_engine_mp
         TileNode* getTileNode();
         void setTileNode(TileNode* tilenode);
 
+        osgDB::Options* getOrCreateDBOptions();
+
     public: // osg::Group
 
         /** called by the OSG DatabasePager when a paging result is ready. */
         bool addChild( osg::Node* node );
 
+        void traverse(osg::NodeVisitor& nv);
+
     public: // osg::PagedLOD
 
         /** override to manage the tile node registries. */
@@ -61,8 +66,17 @@ namespace osgEarth_engine_mp
         osg::ref_ptr<TileNodeRegistry> _dead;
         UID                            _engineUID;
         Threading::Mutex               _updateMutex;
+
+        struct MyProgressCallback : public ProgressCallback
+        {
+            bool isCanceled(); // override
+            unsigned _frameOfLastCull;
+            TileNodeRegistry* _tiles;
+            void update(unsigned frame);
+        };
+        osg::ref_ptr<MyProgressCallback> _progress;
     };
 
-} // namespace osgEarth_engine_mp
+} } } // namespace osgEarth::Drivers::MPTerrainEngine
 
-#endif // OSGEARTH_ENGINE_MP_TILE_PAGED_LOD
+#endif // OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_TILE_PAGED_LOD
diff --git a/src/osgEarthDrivers/engine_mp/TilePagedLOD.cpp b/src/osgEarthDrivers/engine_mp/TilePagedLOD.cpp
index cfd3f03..53128f1 100644
--- a/src/osgEarthDrivers/engine_mp/TilePagedLOD.cpp
+++ b/src/osgEarthDrivers/engine_mp/TilePagedLOD.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -19,9 +19,10 @@
 #include "TilePagedLOD"
 #include "TileNodeRegistry"
 #include <osg/Version>
+#include <osgEarth/Registry>
 #include <cassert>
 
-using namespace osgEarth_engine_mp;
+using namespace osgEarth::Drivers::MPTerrainEngine;
 using namespace osgEarth;
 
 #define LC "[TilePagedLOD] "
@@ -62,6 +63,37 @@ namespace
 }
 
 
+bool
+TilePagedLOD::MyProgressCallback::isCanceled()
+{
+    if (!ProgressCallback::isCanceled() &&
+        _frameOfLastCull > 0 && 
+        ((int)_tiles->getTraversalFrame() - (int)_frameOfLastCull > 2))
+    {
+        _frameOfLastCull = 0;
+        cancel();
+        stats().clear();
+    }
+    return ProgressCallback::isCanceled();
+}
+
+void
+TilePagedLOD::MyProgressCallback::update(unsigned frame)
+{
+    if ( ProgressCallback::isCanceled() )
+    {
+        // this lets up re-use a progress callback if the tile was previously
+        // canceled and then queued up again later without being expired
+        reset();
+        _frameOfLastCull = 0;
+    }
+    else
+    {
+        _frameOfLastCull = frame;
+    }
+}
+
+
 TilePagedLOD::TilePagedLOD(const UID&        engineUID,
                            TileNodeRegistry* live,
                            TileNodeRegistry* dead) :
@@ -70,7 +102,15 @@ _engineUID( engineUID ),
 _live     ( live ),
 _dead     ( dead )
 {
-    //nop
+    if ( live )
+    {
+        _progress = new MyProgressCallback();
+        _progress->_frameOfLastCull = 0;
+        _progress->_tiles = live;
+        osgDB::Options* options = Registry::instance()->cloneOrCreateOptions();
+        options->setUserData( _progress.get() );
+        setDatabaseOptions( options );
+    }
 }
 
 TilePagedLOD::~TilePagedLOD()
@@ -82,6 +122,14 @@ TilePagedLOD::~TilePagedLOD()
     this->accept( collector );
 }
 
+osgDB::Options*
+TilePagedLOD::getOrCreateDBOptions()
+{
+    if ( !getDatabaseOptions() )
+        setDatabaseOptions( Registry::instance()->cloneOrCreateOptions() );
+    return static_cast<osgDB::Options*>(getDatabaseOptions());
+}
+
 TileNode*
 TilePagedLOD::getTileNode()
 {
@@ -131,6 +179,19 @@ TilePagedLOD::addChild(osg::Node* node)
     return false;
 }
 
+void
+TilePagedLOD::traverse(osg::NodeVisitor& nv)
+{
+    if (_progress.valid() && 
+        nv.getVisitorType() == nv.CULL_VISITOR && 
+        nv.getFrameStamp() )
+    {
+        _progress->update( nv.getFrameStamp()->getFrameNumber() );
+    }
+    
+    osg::PagedLOD::traverse(nv);
+}
+
 
 // The osgDB::DatabasePager will call this automatically to purge expired
 // tiles from the scene graph.
diff --git a/src/osgEarthDrivers/engine_osgterrain/FileLocationCallback b/src/osgEarthDrivers/engine_osgterrain/FileLocationCallback
index 3089ad4..64abee7 100644
--- a/src/osgEarthDrivers/engine_osgterrain/FileLocationCallback
+++ b/src/osgEarthDrivers/engine_osgterrain/FileLocationCallback
@@ -27,7 +27,7 @@
 
 namespace osgEarth_engine_osgterrain
 {
-#define USE_FILELOCATIONCALLBACK OSG_MIN_VERSION_REQUIRED(2,9,5)
+#define USE_FILELOCATIONCALLBACK 1
 
 #if USE_FILELOCATIONCALLBACK
 
diff --git a/src/osgEarthDrivers/engine_osgterrain/MultiPassTerrainTechnique.cpp b/src/osgEarthDrivers/engine_osgterrain/MultiPassTerrainTechnique.cpp
index 7a2d74b..48b1863 100644
--- a/src/osgEarthDrivers/engine_osgterrain/MultiPassTerrainTechnique.cpp
+++ b/src/osgEarthDrivers/engine_osgterrain/MultiPassTerrainTechnique.cpp
@@ -86,15 +86,6 @@ MultiPassTerrainTechnique::~MultiPassTerrainTechnique()
 {
 }
 
-#if 0
-void
-#if OSG_MIN_VERSION_REQUIRED(2,9,8)
-MultiPassTerrainTechnique::init(int dirtyMask, bool assumeMultiThreaded)
-#else
-MultiPassTerrainTechnique::init()
-#endif
-#endif
-
 void
 MultiPassTerrainTechnique::init()
 {
@@ -881,16 +872,6 @@ void MultiPassTerrainTechnique::traverse(osg::NodeVisitor& nv)
     {
         _tile->init();
         _terrainTileInitialized = true;
-
-#if 0
-#if OSG_MIN_VERSION_REQUIRED(2,9,8)
-        _terrainTile->init(~0x0, true);
-#else
-        _terrainTile->init();
-#endif
-
-        _terrainTileInitialized = true;
-#endif
     }
     
     if ( nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR )
diff --git a/src/osgEarthDrivers/engine_osgterrain/SinglePassTerrainTechnique b/src/osgEarthDrivers/engine_osgterrain/SinglePassTerrainTechnique
index 86b64f2..6e980ca 100644
--- a/src/osgEarthDrivers/engine_osgterrain/SinglePassTerrainTechnique
+++ b/src/osgEarthDrivers/engine_osgterrain/SinglePassTerrainTechnique
@@ -79,14 +79,6 @@ namespace osgEarth_engine_osgterrain
 
         virtual void init();
 
-    #if 0
-    #if OSG_MIN_VERSION_REQUIRED(2,9,8)        
-        virtual void init(int dirtyMask, bool assumeMultiThreaded);
-    #else
-        virtual void init();
-    #endif
-    #endif
-
         void setParentTile( Tile* tile ) { _parentTile = tile; }
 
         void compile( const TileUpdate& updateSpec, ProgressCallback* progress );
diff --git a/src/osgEarthDrivers/engine_quadtree/FileLocationCallback b/src/osgEarthDrivers/engine_quadtree/FileLocationCallback
index 19ffea3..0616198 100644
--- a/src/osgEarthDrivers/engine_quadtree/FileLocationCallback
+++ b/src/osgEarthDrivers/engine_quadtree/FileLocationCallback
@@ -25,7 +25,7 @@
 #include <osgEarth/Export>
 
 
-#define USE_FILELOCATIONCALLBACK OSG_MIN_VERSION_REQUIRED(2,9,5)
+#define USE_FILELOCATIONCALLBACK 1
 
 
 #if USE_FILELOCATIONCALLBACK
diff --git a/src/osgEarthDrivers/feature_ogr/FeatureCursorOGR b/src/osgEarthDrivers/feature_ogr/FeatureCursorOGR
index 1fde287..fd241e9 100644
--- a/src/osgEarthDrivers/feature_ogr/FeatureCursorOGR
+++ b/src/osgEarthDrivers/feature_ogr/FeatureCursorOGR
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/feature_ogr/FeatureCursorOGR.cpp b/src/osgEarthDrivers/feature_ogr/FeatureCursorOGR.cpp
index a051eaa..92e87c9 100644
--- a/src/osgEarthDrivers/feature_ogr/FeatureCursorOGR.cpp
+++ b/src/osgEarthDrivers/feature_ogr/FeatureCursorOGR.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -28,6 +28,38 @@
 
 using namespace osgEarth;
 using namespace osgEarth::Features;
+using namespace osgEarth::Symbology;
+
+namespace
+{
+    /**
+     * Determine whether a point is valid or not.  Some shapefiles can have points that are ridiculously big, which are really invalid data
+     * but shapefiles have no way of marking the data as invalid.  So instead we check for really large values that are indiciative of something being wrong.
+     */
+    inline bool isPointValid( const osg::Vec3d& v, double thresh = 1e10 )
+    {
+        return (!v.isNaN() && osg::absolute( v.x() ) < thresh && osg::absolute( v.y() ) < thresh && osg::absolute( v.z() ) < thresh );
+    }
+
+    /**
+     * Checks to see if all points in the Geometry are valid.
+     */
+    inline bool isGeometryValid( Geometry* geometry )
+    {        
+        if (!geometry) return false;
+
+        if (!geometry->isValid()) return false;
+
+        for (Geometry::const_iterator i = geometry->begin(); i != geometry->end(); ++i)
+        {
+            if (!isPointValid( *i ))
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+}
 
 
 FeatureCursorOGR::FeatureCursorOGR(OGRDataSourceH           dsHandle,
@@ -75,9 +107,8 @@ _filters          ( filters )
             expr = query.expression().value();
 
             // if the expression is just a where clause, expand it into a complete SQL expression.
-            std::string temp = expr;
-            std::transform( temp.begin(), temp.end(), temp.begin(), ::tolower );
-            //bool complete = temp.find( "select" ) == 0;
+            std::string temp = osgEarth::toLower(expr);
+
             if ( temp.find( "select" ) != 0 )
             {
                 std::stringstream buf;
@@ -99,8 +130,7 @@ _filters          ( filters )
         {                     
             std::string orderby = query.orderby().value();
             
-            std::string temp = orderby;
-            std::transform( temp.begin(), temp.end(), temp.begin(), ::tolower );
+            std::string temp = osgEarth::toLower(orderby);
 
             if ( temp.find( "order by" ) != 0 )
             {                
@@ -200,10 +230,17 @@ FeatureCursorOGR::readChunk()
         osg::ref_ptr<Feature> f = OgrUtils::createFeature( _nextHandleToQueue, _profile->getSRS() );
         if ( f.valid() && !_source->isBlacklisted(f->getFID()) )
         {
-            _queue.push( f );
-            
-            if ( _filters.size() > 0 )
-                preProcessList.push_back( f.release() );
+            if ( isGeometryValid( f->getGeometry() ) )
+            {
+                _queue.push( f );
+
+                if ( _filters.size() > 0 )
+                    preProcessList.push_back( f.release() );
+            }
+            else
+            {
+                OE_INFO << LC << "Skipping feature with invalid geometry: " << f->getGeoJSON() << std::endl;
+            }
         }
         OGR_F_Destroy( _nextHandleToQueue );
         _nextHandleToQueue = 0L;
@@ -220,11 +257,18 @@ FeatureCursorOGR::readChunk()
             osg::ref_ptr<Feature> f = OgrUtils::createFeature( handle, _profile->getSRS() );
             if ( f.valid() && !_source->isBlacklisted(f->getFID()) )
             {
-                _queue.push( f );
-
-                if ( _filters.size() > 0 )
-                    preProcessList.push_back( f.release() );
-            }
+                if (isGeometryValid( f->getGeometry() ) )
+                {
+                    _queue.push( f );
+
+                    if ( _filters.size() > 0 )
+                        preProcessList.push_back( f.release() );
+                }
+                else
+                {
+                    OE_INFO << LC << "Skipping feature with invalid geometry: " << f->getGeoJSON() << std::endl;
+                }
+            }            
             OGR_F_Destroy( handle );
         }
         else
@@ -238,7 +282,7 @@ FeatureCursorOGR::readChunk()
     if ( preProcessList.size() > 0 )
     {
         FilterContext cx;
-        cx.profile() = _profile.get();
+        cx.setProfile( _profile.get() );
 
         for( FeatureFilterList::const_iterator i = _filters.begin(); i != _filters.end(); ++i )
         {
diff --git a/src/osgEarthDrivers/feature_ogr/FeatureSourceOGR.cpp b/src/osgEarthDrivers/feature_ogr/FeatureSourceOGR.cpp
index 718c1a4..15cc81a 100644
--- a/src/osgEarthDrivers/feature_ogr/FeatureSourceOGR.cpp
+++ b/src/osgEarthDrivers/feature_ogr/FeatureSourceOGR.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -19,6 +19,7 @@
 
 #include <osgEarth/Registry>
 #include <osgEarth/FileUtils>
+#include <osgEarth/StringUtils>
 #include <osgEarthFeatures/FeatureSource>
 #include <osgEarthFeatures/Filter>
 #include <osgEarthFeatures/BufferFilter>
@@ -42,6 +43,22 @@ using namespace osgEarth::Drivers;
 
 #define OGR_SCOPED_LOCK GDAL_SCOPED_LOCK
 
+namespace
+{
+    // helper function.
+    OGRLayerH openLayer(OGRDataSourceH ds, const std::string& layer)
+    {
+        OGRLayerH h = OGR_DS_GetLayerByName(ds, layer.c_str());
+        if ( !h )
+        {
+            unsigned index = osgEarth::as<unsigned>(layer, 0);
+            h = OGR_DS_GetLayer(ds, index);
+        }
+        return h;
+    }
+}
+
+
 /**
  * A FeatureSource that reads features from an OGR driver.
  *
@@ -53,7 +70,6 @@ public:
     OGRFeatureSource( const OGRFeatureOptions& options ) : FeatureSource( options ),
       _dsHandle( 0L ),
       _layerHandle( 0L ),
-      _layerIndex( 0 ),
       _ogrDriverHandle( 0L ),
       _options( options ),
       _featureCount(-1),
@@ -126,11 +142,13 @@ public:
 
         if ( _geometry.valid() )
         {
-            // if the user specified explicit geometry/profile, use that:
+            // if the user specified explicit geometry, use that and the calculated
+            // extent of the geometry to derive a profile.
             GeoExtent ex;
             if ( profile.valid() )
-            {
-                ex = profile->getExtent();
+            {                
+                //ex = profile->getExtent();
+                ex = GeoExtent(profile->getSRS(), _geometry->getBounds());
             }
 
             if ( !ex.isValid() )
@@ -160,12 +178,8 @@ public:
             {
                 if (openMode == 1) _writable = true;
                 
-                if ( _options.layer().isSet() )
-                {
-                    _layerIndex = _options.layer().value();
-                }                
+                _layerHandle = openLayer(_dsHandle, _options.layer().value());
 
-                _layerHandle = OGR_DS_GetLayer( _dsHandle, _layerIndex );
                 if ( _layerHandle )
                 {                                     
                     GeoExtent extent;
@@ -201,14 +215,21 @@ public:
                     // assuming we successfully opened the layer, build a spatial index if requested.
                     if ( _options.buildSpatialIndex() == true )
                     {
-                        OE_INFO << LC << "Building spatial index for " << getName() << std::endl;
-                        std::stringstream buf;
-                        const char* name = OGR_FD_GetName( OGR_L_GetLayerDefn( _layerHandle ) );
-                        buf << "CREATE SPATIAL INDEX ON " << name; 
-                        std::string bufStr;
-                        bufStr = buf.str();
-                        OE_DEBUG << LC << "SQL: " << bufStr << std::endl;
-                        OGR_DS_ExecuteSQL( _dsHandle, bufStr.c_str(), 0L, 0L );
+                        if ( (_options.forceRebuildSpatialIndex() == true) || (OGR_L_TestCapability(_layerHandle, OLCFastSpatialFilter) == 0) )
+                        {
+                            OE_INFO << LC << "Building spatial index for " << getName() << std::endl;
+                            std::stringstream buf;
+                            const char* name = OGR_FD_GetName( OGR_L_GetLayerDefn( _layerHandle ) );
+                            buf << "CREATE SPATIAL INDEX ON " << name;
+                            std::string bufStr;
+                            bufStr = buf.str();
+                            OE_DEBUG << LC << "SQL: " << bufStr << std::endl;
+                            OGR_DS_ExecuteSQL( _dsHandle, bufStr.c_str(), 0L, 0L );
+                        }
+                        else
+                        {
+                            OE_INFO << LC << "Use existing spatial index for " << getName() << std::endl;
+                        }
                     }
 
                     //Get the feature count
@@ -278,20 +299,28 @@ public:
                 _geometry.get(),
                 getFeatureProfile(),
                 _options.filters() );
-                //getFilters() );
         }
         else
         {
-            OGR_SCOPED_LOCK;
-
-            // Each cursor requires its own DS handle so that multi-threaded access will work.
-            // The cursor impl will dispose of the new DS handle.
+            OGRDataSourceH dsHandle = 0L;
+            OGRLayerH layerHandle = 0L;
 
-            OGRDataSourceH dsHandle = OGROpenShared( _source.c_str(), 0, &_ogrDriverHandle );
-            if ( dsHandle )
+            // open the handles safely:
             {
-                OGRLayerH layerHandle = OGR_DS_GetLayer( dsHandle, _layerIndex );
+                OGR_SCOPED_LOCK;
+
+                // Each cursor requires its own DS handle so that multi-threaded access will work.
+                // The cursor impl will dispose of the new DS handle.
+                dsHandle = OGROpenShared( _source.c_str(), 0, &_ogrDriverHandle );
+                if ( dsHandle )
+                {
+                    layerHandle = openLayer(dsHandle, _options.layer().get());
+                }
+            }
 
+            if ( dsHandle && layerHandle )
+            {
+                // cursor is responsible for the OGR handles.
                 return new FeatureCursorOGR( 
                     dsHandle,
                     layerHandle, 
@@ -302,6 +331,12 @@ public:
             }
             else
             {
+                if ( dsHandle )
+                {
+                    OGR_SCOPED_LOCK;
+                    OGRReleaseDataSource( dsHandle );
+                }
+
                 return 0L;
             }
         }
@@ -311,6 +346,7 @@ public:
     {
         if (_writable && _layerHandle)
         {
+            OGR_SCOPED_LOCK;
             if (OGR_L_DeleteFeature( _layerHandle, fid ) == OGRERR_NONE)
             {
                 _needsSync = true;
@@ -469,7 +505,6 @@ private:
     std::string _source;
     OGRDataSourceH _dsHandle;
     OGRLayerH _layerHandle;
-    unsigned int _layerIndex;
     OGRSFDriverH _ogrDriverHandle;
     osg::ref_ptr<Symbology::Geometry> _geometry; // explicit geometry.
     const OGRFeatureOptions _options;
diff --git a/src/osgEarthDrivers/feature_ogr/OGRFeatureOptions b/src/osgEarthDrivers/feature_ogr/OGRFeatureOptions
index 8817962..5889715 100644
--- a/src/osgEarthDrivers/feature_ogr/OGRFeatureOptions
+++ b/src/osgEarthDrivers/feature_ogr/OGRFeatureOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -42,14 +42,17 @@ namespace osgEarth { namespace Drivers
         optional<bool>& buildSpatialIndex() { return _buildSpatialIndex; }
         const optional<bool>& buildSpatialIndex() const { return _buildSpatialIndex; }
 
+        optional<bool>& forceRebuildSpatialIndex() { return _forceRebuildSpatialIndex; }
+        const optional<bool>& forceRebuildSpatialIndex() const { return _forceRebuildSpatialIndex; }
+
         optional<Config>& geometryConfig() { return _geometryConf; }
         const optional<Config>& geometryConfig() const { return _geometryConf; }
 
         optional<std::string>& geometryUrl() { return _geometryUrl; }
         const optional<std::string>& geometryUrl() const { return _geometryUrl; }
 
-        optional<unsigned int>& layer() { return _layer; }
-        const optional<unsigned int>& layer() const { return _layer; }
+        optional<std::string>& layer() { return _layer; }
+        const optional<std::string>& layer() const { return _layer; }
 
         // does not serialize
         osg::ref_ptr<Symbology::Geometry>& geometry() { return _geometry; }
@@ -70,6 +73,7 @@ namespace osgEarth { namespace Drivers
             conf.updateIfSet( "connection", _connection );
             conf.updateIfSet( "ogr_driver", _ogrDriver );
             conf.updateIfSet( "build_spatial_index", _buildSpatialIndex );
+            conf.updateIfSet( "force_rebuild_spatial_index", _forceRebuildSpatialIndex );
             conf.updateIfSet( "geometry", _geometryConf );    
             conf.updateIfSet( "geometry_url", _geometryUrl );
             conf.updateIfSet( "layer", _layer );
@@ -89,6 +93,7 @@ namespace osgEarth { namespace Drivers
             conf.getIfSet( "connection", _connection );
             conf.getIfSet( "ogr_driver", _ogrDriver );
             conf.getIfSet( "build_spatial_index", _buildSpatialIndex );
+            conf.getIfSet( "force_rebuild_spatial_index", _forceRebuildSpatialIndex );
             conf.getIfSet( "geometry", _geometryConf );
             conf.getIfSet( "geometry_url", _geometryUrl );
             conf.getIfSet( "layer", _layer);
@@ -99,10 +104,11 @@ namespace osgEarth { namespace Drivers
         optional<std::string>             _connection;
         optional<std::string>             _ogrDriver;
         optional<bool>                    _buildSpatialIndex;
+        optional<bool>                    _forceRebuildSpatialIndex;
         optional<Config>                  _geometryConf;
         optional<Config>                  _geometryProfileConf;
         optional<std::string>             _geometryUrl;
-        optional<unsigned int >           _layer;
+        optional<std::string>             _layer;
         osg::ref_ptr<Symbology::Geometry> _geometry;
     };
 
diff --git a/src/osgEarthDrivers/feature_tfs/FeatureSourceTFS.cpp b/src/osgEarthDrivers/feature_tfs/FeatureSourceTFS.cpp
index 82e0049..0fc6d01 100644
--- a/src/osgEarthDrivers/feature_tfs/FeatureSourceTFS.cpp
+++ b/src/osgEarthDrivers/feature_tfs/FeatureSourceTFS.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -286,7 +286,7 @@ public:
             if ( features.size() > 0 )
             {
                 FilterContext cx;
-                cx.profile() = getFeatureProfile();
+                cx.setProfile( getFeatureProfile() );
 
                 for( FeatureFilterList::const_iterator i = _options.filters().begin(); i != _options.filters().end(); ++i )
                 {
diff --git a/src/osgEarthDrivers/feature_tfs/TFSFeatureOptions b/src/osgEarthDrivers/feature_tfs/TFSFeatureOptions
index 673490b..8dc6fe6 100644
--- a/src/osgEarthDrivers/feature_tfs/TFSFeatureOptions
+++ b/src/osgEarthDrivers/feature_tfs/TFSFeatureOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -43,8 +43,11 @@ namespace osgEarth { namespace Drivers
         const optional<std::string>& format() const { return _format; }        
 
     public:
-        TFSFeatureOptions( const ConfigOptions& opt =ConfigOptions() ) : FeatureSourceOptions( opt ) {
-            setDriver( "tfs" );
+        TFSFeatureOptions( const ConfigOptions& opt =ConfigOptions() ) :
+          FeatureSourceOptions( opt ),
+          _format("json")
+          {
+            setDriver( "tfs" );            
             fromConfig( _conf );
         }
 
diff --git a/src/osgEarthDrivers/feature_wfs/FeatureSourceWFS.cpp b/src/osgEarthDrivers/feature_wfs/FeatureSourceWFS.cpp
index 973a73a..02eefdd 100644
--- a/src/osgEarthDrivers/feature_wfs/FeatureSourceWFS.cpp
+++ b/src/osgEarthDrivers/feature_wfs/FeatureSourceWFS.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -83,6 +83,7 @@ public:
 
                 std::string binId = Stringify() << std::hex << hashString(optionsConf.toJSON()) << "_wfs";
                 _cacheBin = cache->addBin( binId );
+                _cacheBin->setHashKeys(true);
                 
                 // write a metadata record just for reference purposes.. we don't actually use it
                 Config metadata = _cacheBin->readMetadata();
@@ -326,9 +327,13 @@ public:
                    "&Y=" << query.tileKey().get().getTileY();
         }
         else if (query.bounds().isSet())
-        {
-            buf << "&BBOX=" << query.bounds().get().xMin() << "," << query.bounds().get().yMin() << ","
-                            << query.bounds().get().xMax() << "," << query.bounds().get().yMax();
+        {            
+            double buffer = *_options.buffer();            
+            buf << "&BBOX=" << std::setprecision(16)
+                            << query.bounds().get().xMin() - buffer << ","
+                            << query.bounds().get().yMin() - buffer << ","
+                            << query.bounds().get().xMax() + buffer << ","
+                            << query.bounds().get().yMax() + buffer;
         }
         std::string str;
         str = buf.str();
@@ -376,7 +381,7 @@ public:
             if ( features.size() > 0 )
             {
                 FilterContext cx;
-                cx.profile() = getFeatureProfile();
+                cx.setProfile( getFeatureProfile() );
 
                 for( FeatureFilterList::const_iterator i = _options.filters().begin(); i != _options.filters().end(); ++i )
                 {
diff --git a/src/osgEarthDrivers/feature_wfs/WFSFeatureOptions b/src/osgEarthDrivers/feature_wfs/WFSFeatureOptions
index 2e9de02..064dea2 100644
--- a/src/osgEarthDrivers/feature_wfs/WFSFeatureOptions
+++ b/src/osgEarthDrivers/feature_wfs/WFSFeatureOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -58,12 +58,22 @@ namespace osgEarth { namespace Drivers
         optional<bool>& disableTiling() { return _disableTiling; }
         const optional<bool>& disableTiling() const { return _disableTiling;}
 
+        /** The number of map units to buffer bounding box requests with 
+         *  to ensure that enough data is returned.  This is useful when rendering buffered lines
+         *  using the AGGLite driver.         
+         */
+        optional<double>& buffer() { return _buffer;}
+        const optional<double>& buffer() const { return _buffer;}
+
 
 
     public:
-        WFSFeatureOptions( const ConfigOptions& opt =ConfigOptions() ) : FeatureSourceOptions( opt ) {
+        WFSFeatureOptions( const ConfigOptions& opt =ConfigOptions() ) :
+          FeatureSourceOptions( opt ),
+          _buffer( 0 )
+        {
             setDriver( "wfs" );
-            fromConfig( _conf );
+            fromConfig( _conf );            
         }
 
         virtual ~WFSFeatureOptions() { }
@@ -77,6 +87,8 @@ namespace osgEarth { namespace Drivers
             conf.updateIfSet( "outputformat", _outputFormat);
             conf.updateIfSet( "maxfeatures", _maxFeatures );
             conf.updateIfSet( "disable_tiling", _disableTiling );
+            conf.updateIfSet( "request_buffer", _buffer);
+
             return conf;
         }
 
@@ -87,13 +99,14 @@ namespace osgEarth { namespace Drivers
         }
 
     private:
-        void fromConfig( const Config& conf ) {
+        void fromConfig( const Config& conf ) {            
             conf.getIfSet( "url", _url );
             conf.getIfSet( "geometry_profile", _geometryProfileConf );
             conf.getIfSet( "typename", _typename);
             conf.getIfSet( "outputformat", _outputFormat );
             conf.getIfSet( "maxfeatures", _maxFeatures );
             conf.getIfSet( "disable_tiling", _disableTiling);
+            conf.getIfSet( "request_buffer", _buffer);            
         }
 
         optional<URI>         _url;        
@@ -102,6 +115,7 @@ namespace osgEarth { namespace Drivers
         optional<std::string> _outputFormat;
         optional<unsigned>    _maxFeatures;            
         optional<bool>    _disableTiling;            
+        optional<double>  _buffer;            
     };
 
 } } // namespace osgEarth::Drivers
diff --git a/src/osgEarthDrivers/gdal/GDALOptions b/src/osgEarthDrivers/gdal/GDALOptions
index a316238..0131e93 100644
--- a/src/osgEarthDrivers/gdal/GDALOptions
+++ b/src/osgEarthDrivers/gdal/GDALOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/gdal/ReaderWriterGDAL.cpp b/src/osgEarthDrivers/gdal/ReaderWriterGDAL.cpp
index f7bbd30..c7957fc 100644
--- a/src/osgEarthDrivers/gdal/ReaderWriterGDAL.cpp
+++ b/src/osgEarthDrivers/gdal/ReaderWriterGDAL.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -22,6 +22,7 @@
 #include <osgEarth/Registry>
 #include <osgEarth/ImageUtils>
 #include <osgEarth/URI>
+#include <osgEarth/HeightFieldUtils>
 
 #include <osgDB/FileNameUtils>
 #include <osgDB/FileUtils>
@@ -42,6 +43,8 @@
 
 #define LC "[GDAL driver] "
 
+#define INDENT ""
+
 // From easyrgb.com
 float Hue_2_RGB( float v1, float v2, float vH )
 {
@@ -86,8 +89,6 @@ using namespace osgEarth::Drivers;
 #define GEOTRSFRM_ROTATION_PARAM2      4
 #define GEOTRSFRM_NS_RES               5
 
-
-
 typedef enum
 {
     LOWEST_RESOLUTION,
@@ -158,7 +159,7 @@ getFiles(const std::string &file, const std::vector<std::string> &exts, const st
 				break;
 			}
 		}
-        
+
         if (fileValid)
         {
           files.push_back(osgDB::convertFileNameToNativeStyle(file));
@@ -227,13 +228,13 @@ build_vrt(std::vector<std::string> &files, ResolutionStrategy resolutionStrategy
         {
             const char* proj = GDALGetProjectionRef(hDS);
             if (!proj || strlen(proj) == 0)
-            {                
+            {
                 std::string prjLocation = osgDB::getNameLessExtension( std::string(dsFileName) ) + std::string(".prj");
                 ReadResult r = URI(prjLocation).readString();
                 if ( r.succeeded() )
                 {
                     proj = CPLStrdup( r.getString().c_str() );
-                }                
+                }
             }
 
             GDALGetGeoTransform(hDS, psDatasetProperties[i].adfGeoTransform);
@@ -378,27 +379,27 @@ build_vrt(std::vector<std::string> &files, ResolutionStrategy resolutionStrategy
             fprintf( stderr, "Warning : can't open %s. Skipping it\n", dsFileName);
         }
     }
-    
+
     if (nCount == 0)
         goto end;
-    
+
     if (resolutionStrategy == AVERAGE_RESOLUTION)
     {
         we_res /= nCount;
         ns_res /= nCount;
     }
-    
+
     rasterXSize = (int)(0.5 + (maxX - minX) / we_res);
     rasterYSize = (int)(0.5 + (maxY - minY) / -ns_res);
-    
+
     hVRTDS = VRTCreate(rasterXSize, rasterYSize);
-    
+
     if (projectionRef)
     {
         //OE_NOTICE << "Setting projection to " << projectionRef << std::endl;
         GDALSetProjection(hVRTDS, projectionRef);
     }
-    
+
     double adfGeoTransform[6];
     adfGeoTransform[GEOTRSFRM_TOPLEFT_X] = minX;
     adfGeoTransform[GEOTRSFRM_WE_RES] = we_res;
@@ -407,7 +408,7 @@ build_vrt(std::vector<std::string> &files, ResolutionStrategy resolutionStrategy
     adfGeoTransform[GEOTRSFRM_ROTATION_PARAM2] = 0;
     adfGeoTransform[GEOTRSFRM_NS_RES] = ns_res;
     GDALSetGeoTransform(hVRTDS, adfGeoTransform);
-    
+
     for(j=0;j<nBands;j++)
     {
         GDALRasterBandH hBand;
@@ -628,11 +629,11 @@ public:
       _warpedDS(NULL),
       _options(options),
       _maxDataLevel(30)
-    {    
+    {
     }
 
     virtual ~GDALTileSource()
-    {                     
+    {
         GDAL_SCOPED_LOCK;
 
         // Close the _warpedDS dataset if :
@@ -645,11 +646,11 @@ public:
 
         // Close the _srcDS dataset if :
         // - it exists
-        // - and : 
+        // - and :
         //    -    is different from external dataset
         //    - or is equal to external dataset, but the tile source owns the external dataset
         if (_srcDS)
-        {     
+        {
             bool needClose = true;
             osg::ref_ptr<GDALOptions::ExternalDataset> pExternalDataset = _options.externalDataset();
             if (pExternalDataset != NULL)
@@ -669,7 +670,7 @@ public:
 
 
     Status initialize( const osgDB::Options* dbOptions )
-    {           
+    {
         GDAL_SCOPED_LOCK;
 
         Cache* cache = 0;
@@ -684,7 +685,7 @@ public:
             {
                 Config optionsConf = _options.getConfig();
 
-                std::string binId = Stringify() << std::hex << hashString(optionsConf.toJSON());                
+                std::string binId = Stringify() << std::hex << hashString(optionsConf.toJSON());
                 _cacheBin = cache->addBin( binId );
 
                 if ( _cacheBin.valid() )
@@ -692,7 +693,7 @@ public:
                     _cacheBin->apply( _dbOptions.get() );
                 }
             }
-        }  
+        }
 
         // Is a valid external GDAL dataset specified ?
         bool useExternalDataset = false;
@@ -714,14 +715,14 @@ public:
 
         // source connection:
         std::string source;
-        
+
         if ( _options.url().isSet() )
             source = _options.url()->full();
         else if ( _options.connection().isSet() )
             source = _options.connection().value();
 
         //URI uri = _options.url().value();
-        
+
         if (useExternalDataset == false)
         {
             std::vector<std::string> files;
@@ -751,7 +752,7 @@ public:
                 OE_INFO << LC << "Driver found " << files.size() << " files:" << std::endl;
                 for (unsigned int i = 0; i < files.size(); ++i)
                 {
-                    OE_INFO << LC << "" << files[i] << std::endl;
+                    OE_INFO << LC << INDENT << files[i] << std::endl;
                 }
             }
             else
@@ -772,29 +773,29 @@ public:
 
                 //Get the GDAL VRT driver
                 GDALDriver* vrtDriver = (GDALDriver*)GDALGetDriverByName("VRT");
-                
+
                 //Try to load the VRT file from the cache so we don't have to build it each time.
                 if (_cacheBin.valid())
                 {                
-                    ReadResult result = _cacheBin->readString( vrtKey, 0 );
+                    ReadResult result = _cacheBin->readString( vrtKey);
                     if (result.succeeded())
-                    {                        
+                    {
                         _srcDS = (GDALDataset*)GDALOpen(result.getString().c_str(), GA_ReadOnly );
                         if (_srcDS)
                         {
-                            OE_INFO << LC << "Read VRT from cache!" << std::endl;
+                            OE_INFO << LC << INDENT << "Read VRT from cache!" << std::endl;
                         }
                     }
                 }
 
                 //Build the dataset if we didn't already load it
                 if (!_srcDS)
-                {                 
+                {
                     //We couldn't get the VRT from the cache, so build it
-                    osg::Timer_t startTime = osg::Timer::instance()->tick();                    
+                    osg::Timer_t startTime = osg::Timer::instance()->tick();
                     _srcDS = (GDALDataset*)build_vrt(files, HIGHEST_RESOLUTION);
-                    osg::Timer_t endTime = osg::Timer::instance()->tick();                                                            
-                    OE_INFO << LC << "Built VRT in " << osg::Timer::instance()->delta_s(startTime, endTime) << " s" << std::endl;
+                    osg::Timer_t endTime = osg::Timer::instance()->tick();
+                    OE_INFO << LC << INDENT << "Built VRT in " << osg::Timer::instance()->delta_s(startTime, endTime) << " s" << std::endl;
 
                     if (_srcDS)
                     {
@@ -802,25 +803,25 @@ public:
                         if (_cacheBin)
                         {
                             std::string vrtFile = getTempName( "", ".vrt");
-                            OE_INFO << "Writing temp VRT to " << vrtFile << std::endl;
-                         
+                            OE_INFO << LC << INDENT << "Writing temp VRT to " << vrtFile << std::endl;
+
                             if (vrtDriver)
-                            {                    
-                                vrtDriver->CreateCopy(vrtFile.c_str(), _srcDS, 0, 0, 0, 0 );                                                        
+                            {
+                                vrtDriver->CreateCopy(vrtFile.c_str(), _srcDS, 0, 0, 0, 0 );
 
 
-                                //We created the temp file, now read the contents back                            
+                                //We created the temp file, now read the contents back
                                 std::ifstream input( vrtFile.c_str() );
                                 if ( input.is_open() )
                                 {
                                     input >> std::noskipws;
                                     std::stringstream buf;
-                                    buf << input.rdbuf();                                
-                                    std::string vrtContents = buf.str();                                
+                                    buf << input.rdbuf();
+                                    std::string vrtContents = buf.str();
                                     osg::ref_ptr< StringObject > strObject = new StringObject( vrtContents );
                                     _cacheBin->write( vrtKey, strObject.get() );
                                 }
-                            }                                                
+                            }
                             if (osgDB::fileExists( vrtFile ) )
                             {
                                 remove( vrtFile.c_str() );
@@ -834,7 +835,7 @@ public:
                 }
             }
             else
-            {            
+            {
                 //If we couldn't build a VRT, just try opening the file directly
                 //Open the dataset
                 _srcDS = (GDALDataset*)GDALOpen( files[0].c_str(), GA_ReadOnly );
@@ -847,7 +848,7 @@ public:
                     //OE_NOTICE << "There are " << numSubDatasets << " in this file " << std::endl;
 
                     if (numSubDatasets > 0)
-                    {            
+                    {
                         int subDataset = _options.subDataSet().isSet() ? *_options.subDataSet() : 1;
                         if (subDataset < 1 || subDataset > numSubDatasets) subDataset = 1;
                         std::stringstream buf;
@@ -881,7 +882,7 @@ public:
 
         if (warpProfile.valid())
         {
-            OE_NOTICE << "Created warp profile " << warpProfile->toString() <<  std::endl;
+            OE_INFO << LC << INDENT << "Created warp profile " << warpProfile->toString() <<  std::endl;
         }
 
 
@@ -890,10 +891,16 @@ public:
         //Create a spatial reference for the source.
         std::string srcProj = _srcDS->GetProjectionRef();
 
-        
+        // If the projection is empty and we have GCP's then use the GCP projection.
+        if (srcProj.empty() && _srcDS->GetGCPCount() > 0)
+        {
+            srcProj = _srcDS->GetGCPProjection();
+        }
+
+
         if ( !srcProj.empty() && getProfile() != 0L )
         {
-            OE_WARN << LC << "WARNING, overriding profile of a source that already defines its own SRS (" 
+            OE_WARN << LC << "Overriding profile of a source that already defines its own SRS ("
                 << this->getName() << ")" << std::endl;
         }
 
@@ -906,7 +913,7 @@ public:
         {
             src_srs = SpatialReference::create( srcProj );
         }
-        
+
         // assert SRS is present
         if ( !src_srs.valid() )
         {
@@ -928,7 +935,7 @@ public:
 
         //Get the initial geotransform
         _srcDS->GetGeoTransform(_geotransform);
-        
+
         bool hasGCP = _srcDS->GetGCPCount() > 0 && _srcDS->GetGCPProjection();
         bool isRotated = _geotransform[2] != 0.0 || _geotransform[4];
         if (hasGCP) OE_DEBUG << LC << source << " has GCP georeferencing" << std::endl;
@@ -978,7 +985,7 @@ public:
                     NULL);
             }
             else
-            {                                
+            {
                 _warpedDS = (GDALDataset*)GDALAutoCreateWarpedVRT(
                     _srcDS,
                     src_srs->getWKT().c_str(),
@@ -995,7 +1002,7 @@ public:
         }
         else
         {
-            _warpedDS = _srcDS;            
+            _warpedDS = _srcDS;
             warpedSRSWKT = src_srs->getWKT();
         }
 
@@ -1028,13 +1035,13 @@ public:
             double ll_lon, ll_lat, ul_lon, ul_lat, ur_lon, ur_lat, lr_lon, lr_lat;
 
             pixelToGeo(0.0, 0.0, ul_lon, ul_lat );
-            pixelToGeo(0.0, _warpedDS->GetRasterYSize(), ll_lon, ll_lat);
+            pixelToGeo(0.0, _warpedDS->GetRasterYSize() + 1, ll_lon, ll_lat);
             pixelToGeo(_warpedDS->GetRasterXSize(), _warpedDS->GetRasterYSize(), lr_lon, lr_lat);
             pixelToGeo(_warpedDS->GetRasterXSize(), 0.0, ur_lon, ur_lat);
 
             minX = osg::minimum( ll_lon, osg::minimum( ul_lon, osg::minimum( ur_lon, lr_lon ) ) );
             maxX = osg::maximum( ll_lon, osg::maximum( ul_lon, osg::maximum( ur_lon, lr_lon ) ) );
-            
+
             if ( src_srs->isNorthPolar() )
             {
                 minY = osg::minimum( ll_lat, osg::minimum( ul_lat, osg::minimum( ur_lat, lr_lat ) ) );
@@ -1052,15 +1059,15 @@ public:
             pixelToGeo(_warpedDS->GetRasterXSize(), 0.0, maxX, maxY);
         }
 
-        OE_DEBUG << LC << "Geo extents: " << minX << ", " << minY << " -> " << maxX << ", " << maxY << std::endl;
+        OE_DEBUG << LC << INDENT << "Geo extents: " << minX << ", " << minY << " -> " << maxX << ", " << maxY << std::endl;
 
         if ( !profile )
         {
-            profile = Profile::create( 
+            profile = Profile::create(
                 warpedSRSWKT,
                 minX, minY, maxX, maxY);
 
-            OE_INFO << LC << "" << source << " is projected, SRS = " 
+            OE_INFO << LC << INDENT << source << " is projected, SRS = "
                 << warpedSRSWKT << std::endl;
                 //<< _warpedDS->GetProjectionRef() << std::endl;
         }
@@ -1071,12 +1078,12 @@ public:
 
         double maxResolution = osg::minimum(resolutionX, resolutionY);
 
-        OE_INFO << LC << "Resolution= " << resolutionX << "x" << resolutionY << " max=" << maxResolution << std::endl;
+        OE_INFO << LC << INDENT << "Resolution= " << resolutionX << "x" << resolutionY << " max=" << maxResolution << std::endl;
 
         if (_options.maxDataLevelOverride().isSet())
         {
             _maxDataLevel = _options.maxDataLevelOverride().value();
-            OE_INFO << _options.url().value().full() << " using override max data level " << _maxDataLevel << std::endl;
+            OE_INFO << LC << INDENT << _options.url().value().full() << " using override max data level " << _maxDataLevel << std::endl;
         }
         else
         {
@@ -1095,7 +1102,7 @@ public:
                 }
             }
 
-            OE_INFO << LC << _options.url().value().full() << " max Data Level: " << _maxDataLevel << std::endl;
+            OE_INFO << LC << INDENT << _options.url().value().full() << " max Data Level: " << _maxDataLevel << std::endl;
         }
 
         osg::ref_ptr< SpatialReference > srs = SpatialReference::create( warpedSRSWKT );
@@ -1113,7 +1120,7 @@ public:
 
 
     /**
-    * Finds a raster band based on color interpretation 
+    * Finds a raster band based on color interpretation
     */
     static GDALRasterBand* findBandByColorInterp(GDALDataset *ds, GDALColorInterp colorInterp)
     {
@@ -1182,7 +1189,7 @@ public:
                 float R, G, B;
                 if ( S == 0 )                       //HSL values = 0 - 1
                 {
-                    R = L;                      //RGB results = 0 - 1 
+                    R = L;                      //RGB results = 0 - 1
                     G = L;
                     B = L;
                 }
@@ -1198,8 +1205,8 @@ public:
 
                     R = Hue_2_RGB( var_1, var_2, H + ( 1 / 3 ) );
                     G = Hue_2_RGB( var_1, var_2, H );
-                    B = Hue_2_RGB( var_1, var_2, H - ( 1 / 3 ) );                                
-                } 
+                    B = Hue_2_RGB( var_1, var_2, H - ( 1 / 3 ) );
+                }
                 color.r() = static_cast<unsigned char>(R*255.0f);
                 color.g() = static_cast<unsigned char>(G*255.0f);
                 color.b() = static_cast<unsigned char>(B*255.0f);
@@ -1222,9 +1229,9 @@ public:
     }
 
     void geoToPixel(double geoX, double geoY, double &x, double &y)
-    {                
+    {
         x = _invtransform[0] + _invtransform[1] * geoX + _invtransform[2] * geoY;
-        y = _invtransform[3] + _invtransform[4] * geoX + _invtransform[5] * geoY;                
+        y = _invtransform[3] + _invtransform[4] * geoX + _invtransform[5] * geoY;
 
          //Account for slight rounding errors.  If we are right on the edge of the dataset, clamp to the edge
         double eps = 0.0001;
@@ -1240,7 +1247,7 @@ public:
     {
         if (key.getLevelOfDetail() > _maxDataLevel)
         {
-            OE_DEBUG << LC << "" << getName() << ": Reached maximum data resolution key=" 
+            OE_DEBUG << LC << "" << getName() << ": Reached maximum data resolution key="
                 << key.getLevelOfDetail() << " max=" << _maxDataLevel <<  std::endl;
             return NULL;
         }
@@ -1258,12 +1265,12 @@ public:
 
             // Compute the intersection of the incoming key with the data extents of the dataset
             osgEarth::GeoExtent intersection = key.getExtent().intersectionSameSRS( _extents );
-            
+
             // Determine the read window
             double src_min_x, src_min_y, src_max_x, src_max_y;
             // Get the pixel coordiantes of the intersection
             geoToPixel( intersection.xMin(), intersection.yMax(), src_min_x, src_min_y);
-            geoToPixel( intersection.xMax(), intersection.yMin(), src_max_x, src_max_y);   
+            geoToPixel( intersection.xMax(), intersection.yMin(), src_max_x, src_max_y);
 
             // Convert the doubles to integers.  We floor the mins and ceil the maximums to give the widest window possible.
             src_min_x = floor(src_min_x);
@@ -1274,7 +1281,7 @@ public:
             int off_x = (int)( src_min_x );
             int off_y = (int)( src_min_y );
             int width  = (int)(src_max_x - src_min_x);
-            int height = (int)(src_max_y - src_min_y);      
+            int height = (int)(src_max_y - src_min_y);
 
 
             int rasterWidth = _warpedDS->GetRasterXSize();
@@ -1284,10 +1291,10 @@ public:
                 OE_WARN << LC << "Read window outside of bounds of dataset.  Source Dimensions=" << rasterWidth << "x" << rasterHeight << " Read Window=" << off_x << ", " << off_y << " " << width << "x" << height << std::endl;
             }
 
-            // Determine the destination window            
+            // Determine the destination window
 
             // Compute the offsets in geo coordinates of the intersection from the TileKey
-            double offset_left = intersection.xMin() - xmin;            
+            double offset_left = intersection.xMin() - xmin;
             double offset_top = ymax - intersection.yMax();
 
 
@@ -1295,20 +1302,20 @@ public:
             int target_height = (int)ceil((intersection.height() / key.getExtent().height())*(double)tileSize);
             int tile_offset_left = (int)floor((offset_left / key.getExtent().width()) * (double)tileSize);
             int tile_offset_top = (int)floor((offset_top / key.getExtent().height()) * (double)tileSize);
-            
+
             // Compute spacing
-            double dx       = (xmax - xmin) / (tileSize-1); 
-            double dy       = (ymax - ymin) / (tileSize-1); 
+            double dx       = (xmax - xmin) / (tileSize-1);
+            double dy       = (ymax - ymin) / (tileSize-1);
 
             OE_DEBUG << LC << "ReadWindow " << off_x << "," << off_y << " " << width << "x" << height << std::endl;
-            OE_DEBUG << LC << "DestWindow " << tile_offset_left << "," << tile_offset_top << " " << target_width << "x" << target_height << std::endl;                        
+            OE_DEBUG << LC << "DestWindow " << tile_offset_left << "," << tile_offset_top << " " << target_width << "x" << target_height << std::endl;
 
 
             //Return if parameters are out of range.
             if (width <= 0 || height <= 0 || target_width <= 0 || target_height <= 0)
             {
                 return 0;
-            }            
+            }
 
 
 
@@ -1399,14 +1406,14 @@ public:
                             unsigned char a = alpha[src_col + src_row * target_width];
                             *(image->data(dst_col, dst_row) + 0) = r;
                             *(image->data(dst_col, dst_row) + 1) = g;
-                            *(image->data(dst_col, dst_row) + 2) = b;                            
+                            *(image->data(dst_col, dst_row) + 2) = b;
                             if (!isValidValue( r, bandRed)    ||
-                                !isValidValue( g, bandGreen)  || 
+                                !isValidValue( g, bandGreen)  ||
                                 !isValidValue( b, bandBlue)   ||
                                 (bandAlpha && !isValidValue( a, bandAlpha )))
                             {
                                 a = 0.0f;
-                            }                            
+                            }
                             *(image->data(dst_col, dst_row) + 3) = a;
                         }
                     }
@@ -1418,17 +1425,17 @@ public:
                     //Sample each point exactly
                     for (unsigned int c = 0; c < (unsigned int)tileSize; ++c)
                     {
-                        double geoX = xmin + (dx * (double)c); 
+                        double geoX = xmin + (dx * (double)c);
                         for (unsigned int r = 0; r < (unsigned int)tileSize; ++r)
                         {
-                            double geoY = ymin + (dy * (double)r); 
-                            *(image->data(c,r) + 0) = (unsigned char)getInterpolatedValue(bandRed,  geoX,geoY,false); 
-                            *(image->data(c,r) + 1) = (unsigned char)getInterpolatedValue(bandGreen,geoX,geoY,false); 
-                            *(image->data(c,r) + 2) = (unsigned char)getInterpolatedValue(bandBlue, geoX,geoY,false); 
-                            if (bandAlpha != NULL) 
-                                *(image->data(c,r) + 3) = (unsigned char)getInterpolatedValue(bandAlpha,geoX, geoY, false); 
-                            else 
-                                *(image->data(c,r) + 3) = 255; 
+                            double geoY = ymin + (dy * (double)r);
+                            *(image->data(c,r) + 0) = (unsigned char)getInterpolatedValue(bandRed,  geoX,geoY,false);
+                            *(image->data(c,r) + 1) = (unsigned char)getInterpolatedValue(bandGreen,geoX,geoY,false);
+                            *(image->data(c,r) + 2) = (unsigned char)getInterpolatedValue(bandBlue, geoX,geoY,false);
+                            if (bandAlpha != NULL)
+                                *(image->data(c,r) + 3) = (unsigned char)getInterpolatedValue(bandAlpha,geoX, geoY, false);
+                            else
+                                *(image->data(c,r) + 3) = 255;
                         }
                     }
                 }
@@ -1472,7 +1479,7 @@ public:
                             unsigned char a = alpha[src_col + src_row * target_width];
                             *(image->data(dst_col, dst_row) + 0) = g;
                             *(image->data(dst_col, dst_row) + 1) = g;
-                            *(image->data(dst_col, dst_row) + 2) = g;                            
+                            *(image->data(dst_col, dst_row) + 2) = g;
                             if (!isValidValue( g, bandGray) ||
                                (bandAlpha && !isValidValue( a, bandAlpha)))
                             {
@@ -1486,22 +1493,22 @@ public:
                 }
                 else
                 {
-                    for (int c = 0; c < tileSize; ++c) 
-                    { 
-                        double geoX = xmin + (dx * (double)c); 
-
-                        for (int r = 0; r < tileSize; ++r) 
-                        { 
-                            double geoY   = ymin + (dy * (double)r); 
-                            float  color = getInterpolatedValue(bandGray,geoX,geoY,false); 
-
-                            *(image->data(c,r) + 0) = (unsigned char)color; 
-                            *(image->data(c,r) + 1) = (unsigned char)color; 
-                            *(image->data(c,r) + 2) = (unsigned char)color; 
-                            if (bandAlpha != NULL) 
-                                *(image->data(c,r) + 3) = (unsigned char)getInterpolatedValue(bandAlpha,geoX,geoY,false); 
-                            else 
-                                *(image->data(c,r) + 3) = 255; 
+                        for (int r = 0; r < tileSize; ++r)
+                        {
+                            double geoY   = ymin + (dy * (double)r);
+
+                        for (int c = 0; c < tileSize; ++c)
+                        {
+                            double geoX = xmin + (dx * (double)c);
+                            float  color = getInterpolatedValue(bandGray,geoX,geoY,false);
+
+                            *(image->data(c,r) + 0) = (unsigned char)color;
+                            *(image->data(c,r) + 1) = (unsigned char)color;
+                            *(image->data(c,r) + 2) = (unsigned char)color;
+                            if (bandAlpha != NULL)
+                                *(image->data(c,r) + 3) = (unsigned char)getInterpolatedValue(bandAlpha,geoX,geoY,false);
+                            else
+                                *(image->data(c,r) + 3) = 255;
                         }
                     }
                 }
@@ -1530,10 +1537,10 @@ public:
                         src_col < target_width;
                         ++src_col, ++dst_col)
                     {
-                        
+
                         unsigned char p = palette[src_col + src_row * target_width];
                         osg::Vec4ub color;
-                        getPalleteIndexColor( bandPalette, p, color );                        
+                        getPalleteIndexColor( bandPalette, p, color );
                         if (!isValidValue( p, bandPalette))
                         {
                             color.a() = 0.0f;
@@ -1553,7 +1560,7 @@ public:
             }
             else
             {
-                OE_WARN 
+                OE_WARN
                     << LC << "Could not find red, green and blue bands or gray bands in "
                     << _options.url()->full()
                     << ".  Cannot create image. " << std::endl;
@@ -1591,7 +1598,7 @@ public:
         //Check to see if the value is equal to the bands specified no data
         if (bandNoData == v) return false;
         //Check to see if the value is equal to the user specified nodata value
-        if (getNoDataValue() == v) return false;        
+        if (getNoDataValue() == v) return false;
 
         //Check to see if the user specified a custom min/max
         if (v < getNoDataMinValue()) return false;
@@ -1608,8 +1615,8 @@ public:
     float getInterpolatedValue(GDALRasterBand *band, double x, double y, bool applyOffset=true)
     {
         double r, c;
-        geoToPixel( x, y, c, r );        
-       
+        geoToPixel( x, y, c, r );
+
 
         if (applyOffset)
         {
@@ -1676,7 +1683,7 @@ public:
             if (!isValidValue(ulHeight, band)) ulHeight = 0.0f;
             if (!isValidValue(lrHeight, band)) lrHeight = 0.0f;
             */
-            if (!isValidValue(urHeight, band) || (!isValidValue(llHeight, band)) ||(!isValidValue(ulHeight, band)) || (!isValidValue(lrHeight, band)))
+            if ((!isValidValue(urHeight, band)) || (!isValidValue(llHeight, band)) ||(!isValidValue(ulHeight, band)) || (!isValidValue(lrHeight, band)))
             {
                 return NO_DATA_VALUE;
             }
@@ -1730,6 +1737,7 @@ public:
     }
 
 
+#if 0
     osg::HeightField* createHeightField( const TileKey&        key,
                                          ProgressCallback*     progress)
     {
@@ -1764,12 +1772,12 @@ public:
             double dx = (xmax - xmin) / (tileSize-1);
             double dy = (ymax - ymin) / (tileSize-1);
 
-            for (int c = 0; c < tileSize; ++c)
-            {
-                double geoX = xmin + (dx * (double)c);
                 for (int r = 0; r < tileSize; ++r)
                 {
                     double geoY = ymin + (dy * (double)r);
+                for (int c = 0; c < tileSize; ++c)
+                {
+                    double geoX = xmin + (dx * (double)c);
                     float h = getInterpolatedValue(band, geoX, geoY);
                     hf->setHeight(c, r, h);
                 }
@@ -1782,6 +1790,299 @@ public:
         return hf.release();
     }
 
+#else
+
+    /**
+     * Specialized version of GeoHeightField's getHeightAtLocation that just clamps values that are outside of the dataset
+     * to be within the dataset (logic in HeightFieldUtils::getHeightAtLocation).
+     * This is necessary when sampling datasets along the edges where data might actually not exist.
+     * For example, take a worldwide elevation dataset with bounds -180, -90 to 180, 90 that is 200x100 pixels.
+     * When you go to sample the western hemisphere you end up reading a heightfield of size 100x100.  The bounds of the actual data that was
+     * read in heightfield form are actually 0.5,0.5 to 99.5, 99.5 b/c the elevation sample point is in the center of the pixels, not the entire pixel.
+     * So the loop that attempts to resample the heightfield might be asking for elevation values at -180,-90 which is in pixel space 0,0.
+     * In this version of the getHeightAtLocation function it will just return the value at 0.5, 0.5.
+     */
+    float getHeightAtLocation(const GeoHeightField& hf, double x, double y, ElevationInterpolation interp)
+    {
+        double xInterval = hf.getExtent().width()  / (double)(hf.getHeightField()->getNumColumns()-1);
+        double yInterval = hf.getExtent().height() / (double)(hf.getHeightField()->getNumRows()-1);
+
+        // sample the heightfield at the input coordinates:
+        // (note: since it's sampling the HF, it will return an MSL height if applicable)
+        float height = HeightFieldUtils::getHeightAtLocation(
+            hf.getHeightField(),
+            x, y,
+            hf.getExtent().xMin(), hf.getExtent().yMin(),
+            xInterval, yInterval,
+            interp);
+
+        return height;
+    }
+
+     osg::HeightField* createHeightField( const TileKey&        key,
+                                         ProgressCallback*     progress)
+    {
+        if (key.getLevelOfDetail() > _maxDataLevel)
+        {
+            //OE_NOTICE << "Reached maximum data resolution key=" << key.getLevelOfDetail() << " max=" << _maxDataLevel <<  std::endl;
+            return NULL;
+        }
+
+        GDAL_SCOPED_LOCK;
+
+        int tileSize = _options.tileSize().value();
+
+        //Allocate the heightfield
+        osg::ref_ptr<osg::HeightField> hf = new osg::HeightField;
+        hf->allocate(tileSize, tileSize);
+        for (unsigned int i = 0; i < hf->getHeightList().size(); ++i) hf->getHeightList()[i] = NO_DATA_VALUE;
+
+        if (intersects(key))
+        {
+            //Get the extents of the tile
+            double xmin, ymin, xmax, ymax;
+            key.getExtent().getBounds(xmin, ymin, xmax, ymax);
+
+            // Compute the intersection of the incoming key with the data extents of the dataset
+            osgEarth::GeoExtent intersection = key.getExtent().intersectionSameSRS( _extents );
+
+            // Determine the read window
+            double src_min_x, src_min_y, src_max_x, src_max_y;
+            // Get the pixel coordinates of the intersection
+            geoToPixel( intersection.xMin(), intersection.yMax(), src_min_x, src_min_y);
+            geoToPixel( intersection.xMax(), intersection.yMin(), src_max_x, src_max_y);
+
+            int rasterWidth = _warpedDS->GetRasterXSize();
+            int rasterHeight = _warpedDS->GetRasterYSize();
+
+            // Convert the doubles to integers.  We floor the mins and ceil the maximums to give the widest window possible.
+            src_min_x = osg::round(src_min_x);
+            src_min_y = osg::round(src_min_y);
+            src_max_x = osg::round(src_max_x);
+            src_max_y = osg::round(src_max_y);
+
+            // We are now dealing with integer pixel values, so need to add 1 to get the width
+            int width  = (int)(src_max_x - src_min_x) + 1;
+            int height = (int)(src_max_y - src_min_y) + 1;
+
+            // Don't read anything greater than a dimension of max_read_dimensions.  If the source window is really large it will use
+            // GDAL's nearest neighbour sampling.  If the source window is < max_read_dimensions x max_read_dimensions then the exact source data is read and
+            // resampled.  This make sure that once get into high enough resolution data the verts don't move around on you due to sampling.
+            int max_read_dimensions = 256;
+
+            // Width of the GDAL target buffer. Will be modified later.
+            int target_width;
+            int target_height;
+
+            // If the source window is large, then just read a sample from it using GDALs nearest neighbour algorithm. We will then sample from the result.
+            if(width > max_read_dimensions)
+            {
+                target_width = max_read_dimensions;
+                // Figure out how many source pixels equate to half a read buffer cell.
+                double dx = ((double)width) / ((double)(target_width - 1)) * 0.5;
+                //Inflate the source read window by half a target buffer cell. There are two reasons for this:
+                // 1) Later we will deflate our read window by the same amount, so if we don't do this here there will be an apparent gap between tiles.
+                // 2) GDAL will start reading half way along the first target buffer cell and finish reading half way along the last.
+                //     We want the the nearest neighbour to that point to be right on the tile boundary so that the boundary values are the same on neighbouring tiles.
+                int x0 = (int)floor(src_min_x - dx);
+                int x1 = (int)ceil(src_max_x + dx);
+
+                bool limitx0 = false;
+                bool limitx1 = false;
+
+                // Check if the recalculated values are valid, or if they need to be limited to the edge of the data.
+                if( x0 < 0 )
+                {
+                    x0 = 0;
+                    limitx0 = true;
+                }
+
+                if( x1 > rasterWidth - 1 )
+                {
+                    x1 = rasterWidth - 1;
+                    limitx1 = true;
+                }
+
+                // If one of the recalculated values was limited, then we need to recalculate the other so that half a cell offset will land on the edge of the grid.
+                if( limitx0 && !limitx1 )
+                {
+                    // Recalculate x1
+                    double dx_rev = (src_max_x - x0) / (((double)target_width) - 0.5);
+                    x1 = (int)ceil(src_max_x + dx_rev);
+                }
+
+                if( limitx1 && !limitx0 )
+                {
+                    // Recalculate x0
+                    double dx_rev = (x1 - src_min_x) / (((double)target_width) - 0.5);
+                    x0 = (int)floor(src_min_x - dx_rev);
+                }
+
+                src_min_x = x0;
+                src_max_x = x1;
+
+                // Need to recalc width.
+                width = (int)(src_max_x - src_min_x) + 1;
+            }
+            else
+            {
+                // Inflate the source window by one cell. Source pixels are read exactly, so don't need to worry about GDAL sub sampling.
+                src_min_x = osg::maximum((int)src_min_x - 1, 0);
+                src_max_x = osg::minimum((int)src_max_x + 1, rasterWidth - 1);
+                // Need to recalc width.
+                width = (int)(src_max_x - src_min_x) + 1;
+                target_width = width;
+            }
+
+            if(height > max_read_dimensions)
+            {
+                target_height = max_read_dimensions;
+                // Figure out how many source pixels equate to half a read buffer cell.
+                double dy =  ((double)height) / ((double)(target_height - 1)) * 0.5;
+                //Inflate the source read window by half a target buffer cell. There are two reasons for this:
+                // 1) Later we will deflate our read window by the same amount, so if we don't do this here there will be an apparent gap between tiles.
+                // 2) GDAL will start reading half way along the first target buffer cell and finish reading half way along the last.
+                //     We want the the nearest neighbour to that point to be right on the tile boundary so that the boundary values are the same on neighbouring tiles.
+                int y0 = (int)floor(src_min_y - dy);
+                int y1 = (int)ceil(src_max_y + dy);
+
+                bool limity0 = false;
+                bool limity1 = false;
+
+                // Check if the recalculated values are valid, or if they need to be limited to the edge of the data.
+                if( y0 < 0 )
+                {
+                    y0 = 0;
+                    limity0 = true;
+                }
+
+                if( y1 > rasterHeight - 1 )
+                {
+                    y1 = rasterHeight - 1;
+                    limity1 = true;
+                }
+
+                // If one of the recalculated values was limited, then we need to recalculate the other so that half a cell offset will land on the edge of the grid.
+                if( limity0 && !limity1 )
+                {
+                    // Recalculate y1
+                    double dy_rev = (src_max_y - y0) / (((double)target_height) - 0.5);
+                    y1 = (int)ceil(src_max_y + dy_rev);
+                }
+
+                if( limity1 && !limity0 )
+                {
+                    // Recalculate y0
+                    double dy_rev = (y1 - src_min_y) / (((double)target_height) - 0.5);
+                    y0 = (int)floor(src_min_y - dy_rev);
+                }
+
+                src_min_y = y0;
+                src_max_y = y1;
+
+                // Need to recalc height.
+                height = (int)(src_max_y - src_min_y) + 1;
+            }
+            else
+            {
+                // Inflate the source window by one cell. Source pixels are read exactly, so don't need to worry about GDAL sub sampling.
+                src_min_y = osg::maximum((int)src_min_y - 1, 0);
+                src_max_y = osg::minimum((int)src_max_y + 1, rasterHeight - 1);
+                // Need to recalc height.
+                height = (int)(src_max_y - src_min_y) + 1;
+                target_height = height;
+            }
+
+            OE_DEBUG << LC << "Reading key " << key.str() << "   " << xmin << ", " << ymin << ", " << xmax << ", " << ymax << ", " << std::endl;
+            OE_DEBUG << LC << "ReadWindow " << src_min_x << "," << src_min_y  << "," << src_max_x << "," << src_max_y << " " << width << "x" << height << std::endl;
+            OE_DEBUG << LC << "DestWindowSize " << target_width << "x" << target_height << std::endl;
+
+            // Figure out the true pixel extents of what we read
+            double read_min_x, read_min_y, read_max_x, read_max_y;
+            pixelToGeo(src_min_x, src_min_y, read_min_x, read_max_y);
+            // True extents extends to the far side of the last pixel and line.
+            pixelToGeo(src_max_x + 1, src_max_y + 1, read_max_x, read_min_y);
+
+            // We need to deflate the size of the extents by the width of 0.5 'target buffer' pixel to get the correct extents of the heightfield since it's
+            // sampled at the center of the pixels and not the outside edges.
+            double half_dx = ((read_max_x - read_min_x)/((double)target_width)) / 2.0;
+            double half_dy = ((read_max_y - read_min_y)/((double)target_height)) / 2.0;
+            read_min_x += half_dx;
+            read_min_y += half_dy;
+            read_max_x -= half_dx;
+            read_max_y -= half_dy;
+
+
+            OE_DEBUG << LC << "Read extents " << read_min_x << ", " << read_min_y << " to " << read_max_x << ", " << read_max_y << std::endl;
+
+            // Try to find a FLOAT band
+            GDALRasterBand* band = findBandByDataType(_warpedDS, GDT_Float32);
+            if (band == NULL)
+            {
+                // Just get first band
+                band = _warpedDS->GetRasterBand(1);
+            }
+
+            float *heights = new float[target_width * target_height];
+            for (unsigned int i = 0; i < target_width * target_height; i++)
+            {
+                heights[i] = NO_DATA_VALUE;
+            }
+            band->RasterIO(GF_Read, src_min_x, src_min_y, width, height, heights, target_width, target_height, GDT_Float32, 0, 0);
+
+            // Now create a GeoHeightField that we can sample from.  This heightfield only contains the portion that was actually read from the dataset
+            osg::ref_ptr< osg::HeightField > readHF = new osg::HeightField();
+            readHF->allocate( target_width, target_height );
+            for (unsigned int c = 0; c < target_width; c++)
+            {
+                for (unsigned int r = 0; r < target_height; r++)
+                {
+                    unsigned inv_r = target_height - r -1;
+                    float h = heights[r * target_width + c];
+                    // Mark the value as nodata using the universal NO_DATA_VALUE marker.
+                    if (!isValidValue( h, band ) )
+                    {
+                        h = NO_DATA_VALUE;
+                    }
+                    readHF->setHeight(c, inv_r, h );
+                }
+            }
+
+            // Delete the heights array, it's been copied into readHF.
+            delete[] heights;
+
+
+            // Create a GeoHeightField so we can easily sample it.
+            GeoHeightField readGeoHeightField(readHF, GeoExtent(this->getProfile()->getSRS(), read_min_x, read_min_y, read_max_x, read_max_y));
+
+
+            // Iterate over the output heightfield and sample the data that was read into it.
+            double dx = (xmax - xmin) / (tileSize-1);
+            double dy = (ymax - ymin) / (tileSize-1);
+
+            for (int c = 0; c < tileSize; ++c)
+            {
+                double geoX = xmin + (dx * (double)c);
+                for (int r = 0; r < tileSize; ++r)
+                {
+                    double geoY = ymin + (dy * (double)r);
+
+                    float h = NO_DATA_VALUE;
+                    if (readGeoHeightField.getExtent().contains(geoX, geoY))
+                    {
+                        h = getHeightAtLocation( readGeoHeightField, geoX, geoY, *_options.interpolation() );
+                    }
+                    hf->setHeight(c, r, h);
+                }
+            }
+        }
+        return hf.release();
+    }
+
+#endif
+
+
+
     bool intersects(const TileKey& key)
     {
         return key.getExtent().intersects( _extents );
diff --git a/src/osgEarthDrivers/kml/KML b/src/osgEarthDrivers/kml/KML
index 01e81b6..92b3ef0 100644
--- a/src/osgEarthDrivers/kml/KML
+++ b/src/osgEarthDrivers/kml/KML
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KMLOptions b/src/osgEarthDrivers/kml/KMLOptions
index 5b0ead8..0355695 100644
--- a/src/osgEarthDrivers/kml/KMLOptions
+++ b/src/osgEarthDrivers/kml/KMLOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KMLReader b/src/osgEarthDrivers/kml/KMLReader
index 8d2a130..6d11364 100644
--- a/src/osgEarthDrivers/kml/KMLReader
+++ b/src/osgEarthDrivers/kml/KMLReader
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KMLReader.cpp b/src/osgEarthDrivers/kml/KMLReader.cpp
index fb6a052..c68a718 100644
--- a/src/osgEarthDrivers/kml/KMLReader.cpp
+++ b/src/osgEarthDrivers/kml/KMLReader.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_Common b/src/osgEarthDrivers/kml/KML_Common
index e3ba055..15cfae8 100644
--- a/src/osgEarthDrivers/kml/KML_Common
+++ b/src/osgEarthDrivers/kml/KML_Common
@@ -1,7 +1,7 @@
 
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_Container b/src/osgEarthDrivers/kml/KML_Container
index 4f96c12..0076475 100644
--- a/src/osgEarthDrivers/kml/KML_Container
+++ b/src/osgEarthDrivers/kml/KML_Container
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_Document b/src/osgEarthDrivers/kml/KML_Document
index c6c8ad3..6dc1065 100644
--- a/src/osgEarthDrivers/kml/KML_Document
+++ b/src/osgEarthDrivers/kml/KML_Document
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_Document.cpp b/src/osgEarthDrivers/kml/KML_Document.cpp
index e421a2c..9971f04 100644
--- a/src/osgEarthDrivers/kml/KML_Document.cpp
+++ b/src/osgEarthDrivers/kml/KML_Document.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_Feature b/src/osgEarthDrivers/kml/KML_Feature
index c2236a2..9042d4d 100644
--- a/src/osgEarthDrivers/kml/KML_Feature
+++ b/src/osgEarthDrivers/kml/KML_Feature
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_Feature.cpp b/src/osgEarthDrivers/kml/KML_Feature.cpp
index 9b8fcd7..4c9ebfc 100644
--- a/src/osgEarthDrivers/kml/KML_Feature.cpp
+++ b/src/osgEarthDrivers/kml/KML_Feature.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_Folder b/src/osgEarthDrivers/kml/KML_Folder
index ebc4d47..c96ee2d 100644
--- a/src/osgEarthDrivers/kml/KML_Folder
+++ b/src/osgEarthDrivers/kml/KML_Folder
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_Folder.cpp b/src/osgEarthDrivers/kml/KML_Folder.cpp
index 49560cc..bac34bb 100644
--- a/src/osgEarthDrivers/kml/KML_Folder.cpp
+++ b/src/osgEarthDrivers/kml/KML_Folder.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_Geometry b/src/osgEarthDrivers/kml/KML_Geometry
index 399dfff..166025c 100644
--- a/src/osgEarthDrivers/kml/KML_Geometry
+++ b/src/osgEarthDrivers/kml/KML_Geometry
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_Geometry.cpp b/src/osgEarthDrivers/kml/KML_Geometry.cpp
index 1eb3508..a587116 100644
--- a/src/osgEarthDrivers/kml/KML_Geometry.cpp
+++ b/src/osgEarthDrivers/kml/KML_Geometry.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_GroundOverlay b/src/osgEarthDrivers/kml/KML_GroundOverlay
index ef2769a..3b089dd 100644
--- a/src/osgEarthDrivers/kml/KML_GroundOverlay
+++ b/src/osgEarthDrivers/kml/KML_GroundOverlay
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_GroundOverlay.cpp b/src/osgEarthDrivers/kml/KML_GroundOverlay.cpp
index bf8c9ed..0e6e409 100644
--- a/src/osgEarthDrivers/kml/KML_GroundOverlay.cpp
+++ b/src/osgEarthDrivers/kml/KML_GroundOverlay.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_IconStyle b/src/osgEarthDrivers/kml/KML_IconStyle
index 87cc0bf..8e081d9 100644
--- a/src/osgEarthDrivers/kml/KML_IconStyle
+++ b/src/osgEarthDrivers/kml/KML_IconStyle
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_IconStyle.cpp b/src/osgEarthDrivers/kml/KML_IconStyle.cpp
index b11f57d..b8fa5cd 100644
--- a/src/osgEarthDrivers/kml/KML_IconStyle.cpp
+++ b/src/osgEarthDrivers/kml/KML_IconStyle.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_LabelStyle b/src/osgEarthDrivers/kml/KML_LabelStyle
index be05313..2b9e7fd 100644
--- a/src/osgEarthDrivers/kml/KML_LabelStyle
+++ b/src/osgEarthDrivers/kml/KML_LabelStyle
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_LabelStyle.cpp b/src/osgEarthDrivers/kml/KML_LabelStyle.cpp
index 20c3bbb..0d7391f 100644
--- a/src/osgEarthDrivers/kml/KML_LabelStyle.cpp
+++ b/src/osgEarthDrivers/kml/KML_LabelStyle.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_LineString b/src/osgEarthDrivers/kml/KML_LineString
index 9b58367..1a7094c 100644
--- a/src/osgEarthDrivers/kml/KML_LineString
+++ b/src/osgEarthDrivers/kml/KML_LineString
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_LineString.cpp b/src/osgEarthDrivers/kml/KML_LineString.cpp
index 632a3db..e6839cc 100644
--- a/src/osgEarthDrivers/kml/KML_LineString.cpp
+++ b/src/osgEarthDrivers/kml/KML_LineString.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_LineStyle b/src/osgEarthDrivers/kml/KML_LineStyle
index 6cda17c..ee3a474 100644
--- a/src/osgEarthDrivers/kml/KML_LineStyle
+++ b/src/osgEarthDrivers/kml/KML_LineStyle
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_LineStyle.cpp b/src/osgEarthDrivers/kml/KML_LineStyle.cpp
index 449f99a..90b84dc 100644
--- a/src/osgEarthDrivers/kml/KML_LineStyle.cpp
+++ b/src/osgEarthDrivers/kml/KML_LineStyle.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_LinearRing b/src/osgEarthDrivers/kml/KML_LinearRing
index 59bd22e..f7e302c 100644
--- a/src/osgEarthDrivers/kml/KML_LinearRing
+++ b/src/osgEarthDrivers/kml/KML_LinearRing
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_LinearRing.cpp b/src/osgEarthDrivers/kml/KML_LinearRing.cpp
index 756448c..9a21114 100644
--- a/src/osgEarthDrivers/kml/KML_LinearRing.cpp
+++ b/src/osgEarthDrivers/kml/KML_LinearRing.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_Model b/src/osgEarthDrivers/kml/KML_Model
index 75f2866..368ff97 100644
--- a/src/osgEarthDrivers/kml/KML_Model
+++ b/src/osgEarthDrivers/kml/KML_Model
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_Model.cpp b/src/osgEarthDrivers/kml/KML_Model.cpp
index 079d536..3f624ce 100644
--- a/src/osgEarthDrivers/kml/KML_Model.cpp
+++ b/src/osgEarthDrivers/kml/KML_Model.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_MultiGeometry b/src/osgEarthDrivers/kml/KML_MultiGeometry
index 38b43ac..04e813e 100644
--- a/src/osgEarthDrivers/kml/KML_MultiGeometry
+++ b/src/osgEarthDrivers/kml/KML_MultiGeometry
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_MultiGeometry.cpp b/src/osgEarthDrivers/kml/KML_MultiGeometry.cpp
index e501979..44d4fab 100644
--- a/src/osgEarthDrivers/kml/KML_MultiGeometry.cpp
+++ b/src/osgEarthDrivers/kml/KML_MultiGeometry.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_NetworkLink.cpp b/src/osgEarthDrivers/kml/KML_NetworkLink.cpp
index 39e5634..f266ca1 100644
--- a/src/osgEarthDrivers/kml/KML_NetworkLink.cpp
+++ b/src/osgEarthDrivers/kml/KML_NetworkLink.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -90,12 +90,10 @@ KML_NetworkLink::build( const Config& conf, KMLContext& cx )
         plod->setRange( 0, minRange, maxRange );
         plod->setCenter( lodCenter );
         plod->setRadius( d );
-#if OSG_MIN_VERSION_REQUIRED(3,0,0)
+
         osgDB::Options* options = Registry::instance()->cloneOrCreateOptions();
         options->setPluginData( "osgEarth::MapNode", cx._mapNode );
         plod->setDatabaseOptions( options );
-#endif
-        //plod->setNodeMask( open ? ~0 : 0 );
 
         OE_DEBUG << LC << 
             "PLOD: radius = " << d << ", minRange=" << minRange << ", maxRange=" << maxRange << std::endl;
@@ -107,12 +105,10 @@ KML_NetworkLink::build( const Config& conf, KMLContext& cx )
     {
         osg::ProxyNode* proxy = new osg::ProxyNode();
         proxy->setFileName( 0, href );                
-#if OSG_MIN_VERSION_REQUIRED(3,0,0)
+
         osgDB::Options* options = Registry::instance()->cloneOrCreateOptions();
         options->setPluginData( "osgEarth::MapNode", cx._mapNode );
         proxy->setDatabaseOptions( options );
-#endif
-        //proxy->setNodeMask( open ? ~0 : 0 );
 
         cx._groupStack.top()->addChild( proxy );
     }
diff --git a/src/osgEarthDrivers/kml/KML_NetworkLinkControl b/src/osgEarthDrivers/kml/KML_NetworkLinkControl
index 0874189..8463a18 100644
--- a/src/osgEarthDrivers/kml/KML_NetworkLinkControl
+++ b/src/osgEarthDrivers/kml/KML_NetworkLinkControl
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_NetworkLinkControl.cpp b/src/osgEarthDrivers/kml/KML_NetworkLinkControl.cpp
index ae98ad2..855550f 100644
--- a/src/osgEarthDrivers/kml/KML_NetworkLinkControl.cpp
+++ b/src/osgEarthDrivers/kml/KML_NetworkLinkControl.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_Object b/src/osgEarthDrivers/kml/KML_Object
index 6ec22b0..719dd91 100644
--- a/src/osgEarthDrivers/kml/KML_Object
+++ b/src/osgEarthDrivers/kml/KML_Object
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_Object.cpp b/src/osgEarthDrivers/kml/KML_Object.cpp
index 48589f3..dc8f7b1 100644
--- a/src/osgEarthDrivers/kml/KML_Object.cpp
+++ b/src/osgEarthDrivers/kml/KML_Object.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_Overlay b/src/osgEarthDrivers/kml/KML_Overlay
index e4669ad..53f753e 100644
--- a/src/osgEarthDrivers/kml/KML_Overlay
+++ b/src/osgEarthDrivers/kml/KML_Overlay
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_Overlay.cpp b/src/osgEarthDrivers/kml/KML_Overlay.cpp
index cb00a3a..92ec346 100644
--- a/src/osgEarthDrivers/kml/KML_Overlay.cpp
+++ b/src/osgEarthDrivers/kml/KML_Overlay.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_PhotoOverlay b/src/osgEarthDrivers/kml/KML_PhotoOverlay
index cdf0d7f..8e0d842 100644
--- a/src/osgEarthDrivers/kml/KML_PhotoOverlay
+++ b/src/osgEarthDrivers/kml/KML_PhotoOverlay
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_PhotoOverlay.cpp b/src/osgEarthDrivers/kml/KML_PhotoOverlay.cpp
index 26abdd4..5e6f8c2 100644
--- a/src/osgEarthDrivers/kml/KML_PhotoOverlay.cpp
+++ b/src/osgEarthDrivers/kml/KML_PhotoOverlay.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_Placemark.cpp b/src/osgEarthDrivers/kml/KML_Placemark.cpp
index 71bf4bc..e3f212e 100644
--- a/src/osgEarthDrivers/kml/KML_Placemark.cpp
+++ b/src/osgEarthDrivers/kml/KML_Placemark.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -37,22 +37,22 @@ using namespace osgEarth::Annotation;
 void 
 KML_Placemark::build( const Config& conf, KMLContext& cx )
 {
-    Style masterStyle;
-
-    if ( conf.hasValue("styleurl") )
-    {
-        // process a "stylesheet" style
-        const Style* ref_style = cx._sheet->getStyle( conf.value("styleurl"), false );
-        if ( ref_style )
-            masterStyle = *ref_style;
-    }
-    else if ( conf.hasChild("style") )
-    {
-        // process an "inline" style
-        KML_Style kmlStyle;
-        kmlStyle.scan( conf.child("style"), cx );
-        masterStyle = cx._activeStyle;
-    }
+	Style masterStyle;
+
+	if (conf.hasValue("styleurl"))
+	{	// process a "stylesheet" style
+		const Style* ref_style = cx._sheet->getStyle( conf.value("styleurl"), false );
+		if (ref_style)
+		{
+			masterStyle = masterStyle.combineWith(*ref_style);
+		}
+	}
+	if ( conf.hasChild("style") )
+	{	// process an "inline" style
+		KML_Style kmlStyle;
+		kmlStyle.scan(conf.child("style"), cx);
+		masterStyle = masterStyle.combineWith(cx._activeStyle);
+	}
 
     // parse the geometry. the placemark must have geometry to be valid. The 
     // geometry parse may optionally specify an altitude mode as well.
diff --git a/src/osgEarthDrivers/kml/KML_Point b/src/osgEarthDrivers/kml/KML_Point
index c3a762f..97f5be9 100644
--- a/src/osgEarthDrivers/kml/KML_Point
+++ b/src/osgEarthDrivers/kml/KML_Point
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_Point.cpp b/src/osgEarthDrivers/kml/KML_Point.cpp
index 6ead7f9..9c1555b 100644
--- a/src/osgEarthDrivers/kml/KML_Point.cpp
+++ b/src/osgEarthDrivers/kml/KML_Point.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_PolyStyle b/src/osgEarthDrivers/kml/KML_PolyStyle
index 264c43d..8eb299e 100644
--- a/src/osgEarthDrivers/kml/KML_PolyStyle
+++ b/src/osgEarthDrivers/kml/KML_PolyStyle
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_PolyStyle.cpp b/src/osgEarthDrivers/kml/KML_PolyStyle.cpp
index ccb1710..8ed3d43 100644
--- a/src/osgEarthDrivers/kml/KML_PolyStyle.cpp
+++ b/src/osgEarthDrivers/kml/KML_PolyStyle.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -23,30 +23,40 @@ using namespace osgEarth_kml;
 void
 KML_PolyStyle::scan( const Config& conf, Style& style, KMLContext& cx )
 {
-    if ( !conf.empty() )
-    {
-        bool fill = true;
-        if ( conf.hasValue("fill") ) {
-            fill = as<int>(conf.value("fill"), 1) == 1;
-        }
+	if (!conf.empty())
+	{
+		Color color(Color::White);
+		bool colorSpecified = conf.hasValue("color");
+		if (colorSpecified)
+		{
+			color = Color(Stringify() << "#" << conf.value("color"), Color::ABGR);
+		}
 
-        bool outline = false;
-        if ( conf.hasValue("outline") ) {
-            outline = as<int>(conf.value("outline"), 0) == 1;
-        }
+		bool fill = true;	// By default it is true
+		if (conf.hasValue("fill"))
+		{
+			fill = (as<int>(conf.value("fill"), 1) == 1);
+			if (!fill)
+			{
+				color.a() = 0;
+			}
+		}
 
-        Color color(Color::White);
-        if ( conf.hasValue("color") ) {
-            color = Color( Stringify() << "#" << conf.value("color"), Color::ABGR );
-        }
+		if (colorSpecified || !style.has<PolygonSymbol>())
+		{
+			PolygonSymbol* poly = style.getOrCreate<PolygonSymbol>();
+			poly->fill()->color() = color;
+		}
 
-        if ( fill ) {
-            PolygonSymbol* poly = style.getOrCreate<PolygonSymbol>();
-            poly->fill()->color() = color;
-        }
-        else {
-            LineSymbol* line = style.getOrCreate<LineSymbol>();
-            line->stroke()->color() = color;
-        }
-    }
+		bool outline = true;	// By default it is true
+		if (conf.hasValue("outline"))
+		{
+			outline = (as<int>(conf.value("outline"), 0) == 1);
+		}
+		if (!outline)
+		{
+			LineSymbol* line = style.getOrCreate<LineSymbol>();
+			line->stroke()->color().a() = 0;
+		}
+	}
 }
diff --git a/src/osgEarthDrivers/kml/KML_Polygon b/src/osgEarthDrivers/kml/KML_Polygon
index bc52885..a1ab6b2 100644
--- a/src/osgEarthDrivers/kml/KML_Polygon
+++ b/src/osgEarthDrivers/kml/KML_Polygon
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_Polygon.cpp b/src/osgEarthDrivers/kml/KML_Polygon.cpp
index e8ea29c..9cabed8 100644
--- a/src/osgEarthDrivers/kml/KML_Polygon.cpp
+++ b/src/osgEarthDrivers/kml/KML_Polygon.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_Root b/src/osgEarthDrivers/kml/KML_Root
index 6a84b27..15b1062 100644
--- a/src/osgEarthDrivers/kml/KML_Root
+++ b/src/osgEarthDrivers/kml/KML_Root
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_Root.cpp b/src/osgEarthDrivers/kml/KML_Root.cpp
index 3bbfb67..3c74085 100644
--- a/src/osgEarthDrivers/kml/KML_Root.cpp
+++ b/src/osgEarthDrivers/kml/KML_Root.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_Schema b/src/osgEarthDrivers/kml/KML_Schema
index e1641c7..00f7fb0 100644
--- a/src/osgEarthDrivers/kml/KML_Schema
+++ b/src/osgEarthDrivers/kml/KML_Schema
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_Schema.cpp b/src/osgEarthDrivers/kml/KML_Schema.cpp
index c58cadd..3f292c1 100644
--- a/src/osgEarthDrivers/kml/KML_Schema.cpp
+++ b/src/osgEarthDrivers/kml/KML_Schema.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_ScreenOverlay b/src/osgEarthDrivers/kml/KML_ScreenOverlay
index 08cab36..de4a44a 100644
--- a/src/osgEarthDrivers/kml/KML_ScreenOverlay
+++ b/src/osgEarthDrivers/kml/KML_ScreenOverlay
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_ScreenOverlay.cpp b/src/osgEarthDrivers/kml/KML_ScreenOverlay.cpp
index 62836b2..d81135f 100644
--- a/src/osgEarthDrivers/kml/KML_ScreenOverlay.cpp
+++ b/src/osgEarthDrivers/kml/KML_ScreenOverlay.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_Style b/src/osgEarthDrivers/kml/KML_Style
index 2c12bcc..5f53951 100644
--- a/src/osgEarthDrivers/kml/KML_Style
+++ b/src/osgEarthDrivers/kml/KML_Style
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_Style.cpp b/src/osgEarthDrivers/kml/KML_Style.cpp
index b6b9bab..0584a1b 100644
--- a/src/osgEarthDrivers/kml/KML_Style.cpp
+++ b/src/osgEarthDrivers/kml/KML_Style.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_StyleMap b/src/osgEarthDrivers/kml/KML_StyleMap
index a3c9dfd..201254e 100644
--- a/src/osgEarthDrivers/kml/KML_StyleMap
+++ b/src/osgEarthDrivers/kml/KML_StyleMap
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_StyleMap.cpp b/src/osgEarthDrivers/kml/KML_StyleMap.cpp
index 7a7359e..84cf032 100644
--- a/src/osgEarthDrivers/kml/KML_StyleMap.cpp
+++ b/src/osgEarthDrivers/kml/KML_StyleMap.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KML_StyleSelector b/src/osgEarthDrivers/kml/KML_StyleSelector
index 6b998bf..5f3ede5 100644
--- a/src/osgEarthDrivers/kml/KML_StyleSelector
+++ b/src/osgEarthDrivers/kml/KML_StyleSelector
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KMZArchive b/src/osgEarthDrivers/kml/KMZArchive
index f617953..f14de5a 100644
--- a/src/osgEarthDrivers/kml/KMZArchive
+++ b/src/osgEarthDrivers/kml/KMZArchive
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/KMZArchive.cpp b/src/osgEarthDrivers/kml/KMZArchive.cpp
index 2358213..23c4a51 100644
--- a/src/osgEarthDrivers/kml/KMZArchive.cpp
+++ b/src/osgEarthDrivers/kml/KMZArchive.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/kml/ReaderWriterKML.cpp b/src/osgEarthDrivers/kml/ReaderWriterKML.cpp
index 730de75..3b96823 100644
--- a/src/osgEarthDrivers/kml/ReaderWriterKML.cpp
+++ b/src/osgEarthDrivers/kml/ReaderWriterKML.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/label_annotation/AnnotationLabelSource.cpp b/src/osgEarthDrivers/label_annotation/AnnotationLabelSource.cpp
index 02c869e..4f8ecc8 100644
--- a/src/osgEarthDrivers/label_annotation/AnnotationLabelSource.cpp
+++ b/src/osgEarthDrivers/label_annotation/AnnotationLabelSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,6 +21,7 @@
 #include <osgEarthAnnotation/LabelNode>
 #include <osgEarthAnnotation/PlaceNode>
 #include <osgEarth/DepthOffset>
+#include <osgEarth/VirtualProgram>
 #include <osgDB/FileNameUtils>
 #include <osgUtil/Optimizer>
 
@@ -59,6 +60,7 @@ public:
         
         StringExpression  textContentExpr ( text ? *text->content()  : StringExpression() );
         NumericExpression textPriorityExpr( text ? *text->priority() : NumericExpression() );
+        NumericExpression textSizeExpr    ( text ? *text->size()     : NumericExpression() );
         StringExpression  iconUrlExpr     ( icon ? *icon->url()      : StringExpression() );
         NumericExpression iconScaleExpr   ( icon ? *icon->scale()    : NumericExpression() );
         NumericExpression iconHeadingExpr ( icon ? *icon->heading()  : NumericExpression() );
@@ -68,6 +70,20 @@ public:
             const Feature* feature = i->get();
             if ( !feature )
                 continue;
+            
+            // run a symbol script if present.
+            if ( text && text->script().isSet() )
+            {
+                StringExpression temp( text->script().get() );
+                feature->eval( temp, &context );
+            }
+            
+            // run a symbol script if present.
+            if ( icon && icon->script().isSet() )
+            {
+                StringExpression temp( icon->script().get() );
+                feature->eval( temp, &context );
+            }
 
             const Geometry* geom = feature->getGeometry();
             if ( !geom )
@@ -83,6 +99,9 @@ public:
             {
                 if ( text->content().isSet() )
                     tempStyle.get<TextSymbol>()->content()->setLiteral( feature->eval( textContentExpr, &context ) );
+
+                if ( text->size().isSet() )
+                    tempStyle.get<TextSymbol>()->size()->setLiteral( feature->eval(textSizeExpr, &context) );
             }
 
             if ( icon )
@@ -114,6 +133,9 @@ public:
             }
         }
 
+        VirtualProgram* vp = VirtualProgram::getOrCreate(group->getOrCreateStateSet());
+        vp->setInheritShaders( false );
+
         return group;
     }
 
diff --git a/src/osgEarthDrivers/label_overlay/OverlayLabelSource.cpp b/src/osgEarthDrivers/label_overlay/OverlayLabelSource.cpp
index a28fb24..b279b64 100644
--- a/src/osgEarthDrivers/label_overlay/OverlayLabelSource.cpp
+++ b/src/osgEarthDrivers/label_overlay/OverlayLabelSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -60,10 +60,16 @@ public:
                 label->setForeColor( symbol->fill()->color() );
             if ( symbol->halo().isSet() )
                 label->setHaloColor( symbol->halo()->color() );
-            if ( symbol->size().isSet() )
-                label->setFontSize( *symbol->size() );
+            //if ( symbol->size().isSet() )
+            //    label->setFontSize( *symbol->size() );
             if ( symbol->font().isSet() )
-                label->setFont( osgText::readFontFile(*symbol->font()) );
+            {
+                osgText::Font* font = osgText::readFontFile(*symbol->font() );
+                // mitigates mipmapping issues that cause rendering artifacts for some fonts/placement
+                if ( font )
+                    font->setGlyphImageMargin( 2 );
+                label->setFont( font );
+            }
             if ( symbol->encoding().isSet() )
             {
                 osgText::String::Encoding enc;
@@ -141,10 +147,17 @@ public:
                     label->setForeColor( text->fill()->color() );
                 if ( text->halo().isSet() )
                     label->setHaloColor( text->halo()->color() );
-                if ( text->size().isSet() )
-                    label->setFontSize( *text->size() );
+                //if ( text->size().isSet() )
+                //    label->setFontSize( *text->size() );
                 if ( text->font().isSet() )
-                    label->setFont( osgText::readFontFile(*text->font()) );
+                {
+                    // mitigates mipmapping issues that cause rendering artifacts for some fonts/placement
+                    osgText::Font* font = osgText::readFontFile(*text->font() );
+                    // mitigates mipmapping issues that cause rendering artifacts for some fonts/placement
+                    if ( font )
+                        font->setGlyphImageMargin( 2 );
+                    label->setFont( font );
+                }
 
                 Controls::ControlNode* node = new Controls::ControlNode( label, priority );
 
diff --git a/src/osgEarthDrivers/mask_feature/FeatureMaskOptions b/src/osgEarthDrivers/mask_feature/FeatureMaskOptions
index 03ee435..5346487 100644
--- a/src/osgEarthDrivers/mask_feature/FeatureMaskOptions
+++ b/src/osgEarthDrivers/mask_feature/FeatureMaskOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/mask_feature/FeatureMaskSource.cpp b/src/osgEarthDrivers/mask_feature/FeatureMaskSource.cpp
index a40bc77..722e387 100644
--- a/src/osgEarthDrivers/mask_feature/FeatureMaskSource.cpp
+++ b/src/osgEarthDrivers/mask_feature/FeatureMaskSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -61,9 +61,9 @@ public:
     const MaskSourceOptions& getOptions() const { return _options; }
 
     //override
-    void initialize( const osgDB::Options* dbOptions, const osgEarth::Map* map )
+    void initialize(const osgDB::Options* dbOptions)
     {
-        MaskSource::initialize( dbOptions, map );
+        MaskSource::initialize( dbOptions );
 
         if ( _features.valid() )
         {
@@ -75,7 +75,7 @@ public:
         }
     }
 
-    osg::Vec3dArray* createBoundary( const SpatialReference* srs, ProgressCallback* progress )
+    osg::Vec3dArray* createBoundary(const SpatialReference* srs, ProgressCallback* progress)
     {
         if ( _failed )
             return 0L;
@@ -85,27 +85,24 @@ public:
             if ( _features->getFeatureProfile() )
             {
                 osg::ref_ptr<FeatureCursor> cursor = _features->createFeatureCursor();
-                if ( cursor )
+                if ( cursor.valid() && cursor->hasMore() )
                 {
-                    if ( cursor->hasMore() )
+                    Feature* f = cursor->nextFeature();
+                    if ( f && f->getGeometry() )
                     {
-                        Feature* f = cursor->nextFeature();
-                        if ( f && f->getGeometry() )
+                        // Init a filter to tranform feature in desired SRS 
+                        if (!srs->isEquivalentTo(_features->getFeatureProfile()->getSRS()))
                         {
-                            // Init a filter to tranform feature in desired SRS 
-                            if (!srs->isEquivalentTo(_features->getFeatureProfile()->getSRS())) {
-                                FilterContext cx;
-                                cx.profile() = new FeatureProfile(_features->getFeatureProfile()->getExtent());
-                                //cx.isGeocentric() = _features->getFeatureProfile()->getSRS()->isGeographic();
-
-                                TransformFilter xform( srs );
-                                FeatureList featureList;
-                                featureList.push_back(f);
-                                cx = xform.push(featureList, cx);
-                            }
-
-                            return f->getGeometry()->toVec3dArray();
+                            FilterContext cx;
+                            cx.setProfile( new FeatureProfile(_features->getFeatureProfile()->getExtent()) );
+
+                            TransformFilter xform( srs );
+                            FeatureList featureList;
+                            featureList.push_back(f);
+                            cx = xform.push(featureList, cx);
                         }
+
+                        return f->getGeometry()->toVec3dArray();
                     }
                 }
             }
diff --git a/src/osgEarthDrivers/mbtiles/CMakeLists.txt b/src/osgEarthDrivers/mbtiles/CMakeLists.txt
index 3e20c12..3fb8748 100644
--- a/src/osgEarthDrivers/mbtiles/CMakeLists.txt
+++ b/src/osgEarthDrivers/mbtiles/CMakeLists.txt
@@ -6,12 +6,14 @@ INCLUDE_DIRECTORIES( ${SQLITE3_INCLUDE_DIR} )
 #)
 
 SET(TARGET_SRC
-    ReaderWriterMBTiles.cpp    
+    MBTilesPlugin.cpp
+	MBTilesTileSource.cpp
 )
 
 # headers to show in IDE
 SET(TARGET_H    
     MBTilesOptions
+	MBTilesTileSource
 )
 
 SET(TARGET_LIBRARIES_VARS SQLITE3_LIBRARY)
diff --git a/src/osgEarthDrivers/mbtiles/MBTilesOptions b/src/osgEarthDrivers/mbtiles/MBTilesOptions
index 24a092b..867ca9a 100644
--- a/src/osgEarthDrivers/mbtiles/MBTilesOptions
+++ b/src/osgEarthDrivers/mbtiles/MBTilesOptions
@@ -21,22 +21,45 @@
 
 #include <osgEarth/Common>
 #include <osgEarth/TileSource>
+#include <osgEarth/URI>
 
 namespace osgEarth { namespace Drivers
 {
     using namespace osgEarth;
 
-    class MBTilesOptions : public TileSourceOptions // NO EXPORT; header only
+    class MBTilesTileSourceOptions : public TileSourceOptions // NO EXPORT; header only
     {
-    public:
-        optional<std::string>& filename() { return _filename; }
-        const optional<std::string>& filename() const { return _filename; }
+    public:        
+        /**
+         * The filename of the MBTiles file
+         */
+        optional<URI>& filename() { return _filename; }
+        const optional<URI>& filename() const { return _filename; }
 
+        /**
+         * The format of the imagery in the MBTiles file (jpeg, png, etc)
+         */
         optional<std::string>& format() { return _format; }
         const optional<std::string>& format() const { return _format; }
 
+        /**
+         * Whether to compress the data. This isn't necessary for formats that
+         * are already compressed, like JPEG
+         */
+        optional<bool>& compress() { return _compress; }
+        const optional<bool>& compress() const { return _compress; }
+
+        /**
+         * Whether or not to automatically compute the valid levels of the MBTiles file.  By default this is true and will
+         * scan the table to determine the min/max.  This can take time when first loading the file so if you know the levels of your file 
+         * up front you can set this to false and just use the min_level max_level settings of the tile source.
+         */
+        optional<bool>& computeLevels() { return _computeLevels;}
+        const optional<bool>& computeLevels() const { return _computeLevels;}
     public:
-        MBTilesOptions( const TileSourceOptions& opt =TileSourceOptions() ) : TileSourceOptions( opt )
+        MBTilesTileSourceOptions(const TileSourceOptions& opt =TileSourceOptions()) :
+            TileSourceOptions( opt ),
+            _computeLevels( true )
         {
             setDriver( "mbtiles" );
             fromConfig( _conf );
@@ -47,6 +70,8 @@ namespace osgEarth { namespace Drivers
             Config conf = TileSourceOptions::getConfig();
             conf.updateIfSet("filename", _filename);            
             conf.updateIfSet("format", _format);            
+            conf.updateIfSet("compute_levels", _computeLevels);
+            conf.updateIfSet("compress", _compress);
             return conf;
         }
 
@@ -58,11 +83,15 @@ namespace osgEarth { namespace Drivers
         void fromConfig( const Config& conf ) {
             conf.getIfSet( "filename", _filename );
             conf.getIfSet( "format", _format );
+            conf.getIfSet( "compute_levels", _computeLevels );
+            conf.getIfSet( "compress", _compress );
         }
 
     private:
-        optional<std::string> _filename;        
+        optional<URI>         _filename;        
         optional<std::string> _format;
+        optional<bool>        _computeLevels;
+        optional<bool>        _compress;
     };
 
 } } // namespace osgEarth::Drivers
diff --git a/src/osgEarthDrivers/mbtiles/MBTilesPlugin.cpp b/src/osgEarthDrivers/mbtiles/MBTilesPlugin.cpp
new file mode 100644
index 0000000..f3c7329
--- /dev/null
+++ b/src/osgEarthDrivers/mbtiles/MBTilesPlugin.cpp
@@ -0,0 +1,61 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2010 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+#include "MBTilesOptions"
+#include "MBTilesTileSource"
+
+#include <osgDB/FileNameUtils>
+#include <osgDB/Registry>
+
+using namespace osgEarth;
+
+#define LC "[MBTilesPlugin] "
+
+namespace osgEarth { namespace Drivers { namespace MBTiles
+{
+    class MBTilesPlugin : public TileSourceDriver
+    {
+    public:
+        MBTilesPlugin()
+        {
+            supportsExtension( "osgearth_mbtiles", "MBTiles tile driver" );
+        }
+
+        virtual const char* className()
+        {
+            return "MBTiles Driver";
+        }
+
+        virtual ReadResult readObject(const std::string& file_name, const Options* options) const
+        {
+            if ( !acceptsExtension(osgDB::getLowerCaseFileExtension( file_name )))
+                return ReadResult::FILE_NOT_HANDLED;
+
+            std::string iname = getInterfaceName(options);
+
+            if ( iname == TileSource::INTERFACE_NAME )
+                return new MBTilesTileSource( getTileSourceOptions(options) );
+
+            return ReadResult::FILE_NOT_FOUND;
+        }
+    };
+
+    REGISTER_OSGPLUGIN(osgearth_mbtiles, MBTilesPlugin)
+
+} } } // namespace osgEarth::Drivers::MBTiles
diff --git a/src/osgEarthDrivers/mbtiles/MBTilesTileSource b/src/osgEarthDrivers/mbtiles/MBTilesTileSource
new file mode 100644
index 0000000..d2891f9
--- /dev/null
+++ b/src/osgEarthDrivers/mbtiles/MBTilesTileSource
@@ -0,0 +1,88 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2010 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+#include "MBTilesOptions"
+
+#include <osgEarth/TileSource>
+#include <osgEarth/ThreadingUtils>
+#include <osgDB/ObjectWrapper>
+
+// forward declare
+struct sqlite3;
+
+namespace osgEarth { namespace Drivers { namespace MBTiles
+{
+    /**
+     * TileSource that reads and writes the MapBox MBTiles format.
+     * https://www.mapbox.com/foundations/an-open-platform/#storing-tiles
+     */
+    class MBTilesTileSource : public TileSource
+    {
+    public:
+
+        /** Constructor */
+        MBTilesTileSource(const TileSourceOptions& options);
+
+    public: // TileSource interface
+
+        Status initialize(const osgDB::Options* dbOptions);
+
+        /** Reads and image from the mbtiles db */
+        osg::Image* createImage(
+            const TileKey&    key, 
+            ProgressCallback* progress);
+        
+        /** Stores an image to the mbtiles db */
+        bool storeImage(
+            const TileKey&    key,
+            osg::Image*       image,
+            ProgressCallback* progress);
+
+        std::string getExtension() const;
+
+        CachePolicy getCachePolicyHint(const Profile* targetProfile) const;
+
+
+    protected:
+        void computeLevels();
+
+        bool getMetaData(const std::string& name, std::string& value);
+
+        bool putMetaData(const std::string& name, const std::string& value);
+
+        bool createTables();
+
+    private:
+        const MBTilesTileSourceOptions _options;    
+        sqlite3* _database;
+        unsigned int _minLevel;
+        unsigned int _maxLevel;
+        osg::ref_ptr< osg::Image> _emptyImage;
+
+        osg::ref_ptr<osgDB::ReaderWriter> _rw;
+        osg::ref_ptr<osgDB::Options> _dbOptions;
+        osg::ref_ptr<osgDB::BaseCompressor> _compressor;
+        std::string _tileFormat;
+        bool _forceRGB;
+
+        // because no one knows if/when sqlite3 is threadsafe.
+        mutable Threading::Mutex _mutex; 
+    };
+
+} } } // namespace osgEarth::Drivers::MBTiles
diff --git a/src/osgEarthDrivers/mbtiles/MBTilesTileSource.cpp b/src/osgEarthDrivers/mbtiles/MBTilesTileSource.cpp
new file mode 100644
index 0000000..a01057e
--- /dev/null
+++ b/src/osgEarthDrivers/mbtiles/MBTilesTileSource.cpp
@@ -0,0 +1,596 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2010 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+#include "MBTilesTileSource"
+
+#include <osgEarth/Registry>
+#include <osgEarth/ImageUtils>
+#include <osgDB/FileUtils>
+
+#include <sstream>
+#include <iomanip>
+#include <algorithm>
+
+#include <sqlite3.h>
+
+#define LC "[MBTilesTileSource] "
+
+using namespace osgEarth;
+using namespace osgEarth::Drivers::MBTiles;
+
+//......................................................................
+
+namespace
+{
+    osgDB::ReaderWriter* getReaderWriter(const std::string& format)
+    {
+        osgDB::ReaderWriter* rw = 0L;
+
+        // Get a ReaderWriter for the tile format. Try both mime-type and extension.
+        rw = osgDB::Registry::instance()->getReaderWriterForMimeType( format );
+        if ( rw == 0L )
+        {
+            rw = osgDB::Registry::instance()->getReaderWriterForExtension( format ); 
+        }
+        return rw;
+    }
+}
+
+//......................................................................
+
+MBTilesTileSource::MBTilesTileSource(const TileSourceOptions& options) :
+TileSource( options ),
+_options  ( options ),      
+_database ( NULL ),
+_minLevel ( 0 ),
+_maxLevel ( 20 ),
+_forceRGB ( false )
+{
+    //nop
+}
+
+TileSource::Status
+MBTilesTileSource::initialize(const osgDB::Options* dbOptions)
+{    
+    _dbOptions = Registry::instance()->cloneOrCreateOptions( dbOptions );    
+    
+    bool readWrite = (MODE_WRITE & (int)getMode()) != 0;
+
+    std::string fullFilename = _options.filename()->full();   
+    bool isNewDatabase = readWrite && !osgDB::fileExists(fullFilename);
+
+    if ( isNewDatabase )
+    {
+        // For a NEW database, the profile MUST be set prior to initialization.
+        if ( getProfile() == 0L )
+            return Status::Error("Cannot create database; required Profile is missing");
+
+        // For a NEW database the format is required.
+        if ( _options.format().isSet() )
+        {
+            _tileFormat = _options.format().value();
+            _rw = getReaderWriter( _tileFormat );
+            if ( !_rw.valid() )
+                return Status::Error("No plugin to load format \"" + _tileFormat + "\"");
+        }
+        else
+        {
+            return Status::Error("Cannot create database; required format is missing");
+        }
+
+        OE_INFO << LC << "Database does not exist; attempting to create it." << std::endl;
+    }
+
+    // Try to open (or create) the database. We use SQLITE_OPEN_NOMUTEX to do
+    // our own mutexing.
+    int flags = readWrite 
+        ? (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX)
+        : (SQLITE_OPEN_READONLY | SQLITE_OPEN_NOMUTEX);
+     
+    int rc = sqlite3_open_v2( fullFilename.c_str(), &_database, flags, 0L );
+    if ( rc != 0 )
+    {                        
+        return Status::Error( Stringify()
+            << "Database \"" << fullFilename << "\": " << sqlite3_errmsg(_database) );
+    }
+    
+    // New database setup:
+    if ( isNewDatabase )
+    {
+        // create necessary db tables:
+        createTables();
+
+        // write profile to metadata:
+        std::string profileJSON = getProfile()->toProfileOptions().getConfig().toJSON(false);
+        putMetaData("profile", profileJSON);
+
+        // write format to metadata:
+        putMetaData("format", _tileFormat);
+
+        // compression?
+        if ( _options.compress().isSetTo(true) )
+        {
+            _compressor = osgDB::Registry::instance()->getObjectWrapperManager()->findCompressor("zlib");
+            if ( _compressor.valid() )
+            {
+                putMetaData("compression", "zlib");
+                OE_INFO << LC << "Data will be compressed (zlib)" << std::endl;
+            }
+        }
+    }
+
+    // If the database pre-existed, read in the information from the metadata.
+    else // !isNewDatabase
+    {
+        std::string profileStr;
+        getMetaData( "profile", profileStr );
+
+        // The data format (e.g., png, jpg, etc.). Any format passed in 
+        // in the options is superceded by the one in the database metadata.
+        std::string metaDataFormat;
+        getMetaData( "format", metaDataFormat );
+        if ( !metaDataFormat.empty() )
+            _tileFormat = metaDataFormat;
+
+        // By this point, we require a valid tile format.
+        if ( _tileFormat.empty() )
+            return Status::Error("Required format not in metadata, nor specified in the options.");
+
+        _rw = getReaderWriter( _tileFormat );
+        if ( !_rw.valid() )
+            return Status::Error("No plugin to load format \"" + _tileFormat + "\"");
+
+        // check for compression.
+        std::string compression;
+        getMetaData("compression", compression);
+        if ( !compression.empty() )
+        {
+            _compressor = osgDB::Registry::instance()->getObjectWrapperManager()->findCompressor(compression);
+            if ( !_compressor.valid() )
+                return Status::Error("Cannot find compressor \"" + compression + "\"");
+            else
+                OE_INFO << LC << "Data is compressed (" << compression << ")" << std::endl;
+        }
+
+        // Check for bounds and populate DataExtents.
+        std::string boundsStr;
+        if ( getMetaData("bounds", boundsStr) )
+        {
+            std::vector<std::string> tokens;
+            StringTokenizer(",").tokenize(boundsStr, tokens);
+            if (tokens.size() == 4)
+            {
+                GeoExtent extent(
+                    osgEarth::SpatialReference::get("wgs84"),
+                    osgEarth::as<double>(tokens[0], 0.0),
+                    osgEarth::as<double>(tokens[3], 0.0), // south
+                    osgEarth::as<double>(tokens[2], 0.0), // east
+                    osgEarth::as<double>(tokens[1], 0.0)  // north
+                    );
+
+                this->getDataExtents().push_back(DataExtent(extent));
+
+                OE_INFO << LC << "Bounds = " << extent.toString() << std::endl;
+            }
+        }
+
+        // Set the profile
+        const Profile* profile = getProfile();
+        if (!profile)
+        {
+            if (!profileStr.empty())
+            {
+                // try to parse it as a JSON config
+                Config pconf;
+                pconf.fromJSON(profileStr);
+                profile = Profile::create(ProfileOptions(pconf));
+
+                // if that didn't work, try parsing it directly
+                if ( !profile )
+                {
+                    profile = Profile::create(profileStr);
+                }
+
+                if ( !profile )
+                {
+                    return Status::Error( Stringify() << "Profile not recognized: " << profileStr );
+                }
+            }
+            else
+            {
+                // Spherical mercator is the MBTiles default.
+                profile = osgEarth::Registry::instance()->getSphericalMercatorProfile();
+            }
+
+            setProfile( profile );     
+            OE_INFO << LC << "Profile = " << profileStr << std::endl;
+        }
+
+        if ( _options.computeLevels() == true )
+        {
+            computeLevels();
+        }
+    }
+
+    // do we require RGB? for jpeg?
+    _forceRGB =
+        osgEarth::endsWith(_tileFormat, "jpg", false) ||
+        osgEarth::endsWith(_tileFormat, "jpeg", false);
+
+    // make an empty image.
+    int size = 256;
+    _emptyImage = new osg::Image();
+    _emptyImage->allocateImage(size, size, 1, _forceRGB? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE);
+    unsigned char *data = _emptyImage->data(0,0);
+    memset(data, 0, (_forceRGB?3:4) * size * size);
+
+    return STATUS_OK;
+}    
+
+
+CachePolicy
+MBTilesTileSource::getCachePolicyHint(const Profile* targetProfile) const
+{
+    if ( !targetProfile || targetProfile->isHorizEquivalentTo(getProfile()) )
+        return CachePolicy::NO_CACHE;
+    else
+        return CachePolicy::DEFAULT;
+}
+
+
+osg::Image*
+MBTilesTileSource::createImage(const TileKey&    key,
+                               ProgressCallback* progress)
+{
+    Threading::ScopedMutexLock exclusiveLock(_mutex);
+
+    int z = key.getLevelOfDetail();
+    int x = key.getTileX();
+    int y = key.getTileY();
+
+    if (z < (int)_minLevel)
+    {
+        return _emptyImage.get();            
+    }
+
+    if (z > (int)_maxLevel)
+    {
+        //If we're at the max level, just return NULL
+        return NULL;
+    }
+
+    unsigned int numRows, numCols;
+    key.getProfile()->getNumTiles(key.getLevelOfDetail(), numCols, numRows);
+    y  = numRows - y - 1;
+
+    //Get the image
+    sqlite3_stmt* select = NULL;
+    std::string query = "SELECT tile_data from tiles where zoom_level = ? AND tile_column = ? AND tile_row = ?";
+    int rc = sqlite3_prepare_v2( _database, query.c_str(), -1, &select, 0L );
+    if ( rc != SQLITE_OK )
+    {
+        OE_WARN << LC << "Failed to prepare SQL: " << query << "; " << sqlite3_errmsg(_database) << std::endl;
+        return NULL;
+    }
+
+    bool valid = true;        
+
+    sqlite3_bind_int( select, 1, z );
+    sqlite3_bind_int( select, 2, x );
+    sqlite3_bind_int( select, 3, y );
+
+    osg::Image* result = NULL;
+    rc = sqlite3_step( select );
+    if ( rc == SQLITE_ROW)
+    {                     
+        // the pointer returned from _blob gets freed internally by sqlite, supposedly
+        const char* data = (const char*)sqlite3_column_blob( select, 0 );
+        int dataLen = sqlite3_column_bytes( select, 0 );
+
+        std::string dataBuffer( data, dataLen );
+
+        // decompress if necessary:
+        if ( _compressor.valid() )
+        {
+            std::istringstream inputStream(dataBuffer);
+            std::string value;
+            if ( !_compressor->decompress(inputStream, value) )
+            {
+                OE_WARN << LC << "Decompression failed" << std::endl;
+                valid = false;
+            }
+            else
+            {
+                dataBuffer = value;
+            }
+        }
+
+        // decode the raw image data:
+        if ( valid )
+        {
+            std::istringstream inputStream(dataBuffer);
+            osgDB::ReaderWriter::ReadResult rr = _rw->readImage( inputStream );
+            if (rr.validImage())
+            {
+                result = rr.takeImage();                
+            }
+        }
+    }
+    else
+    {
+        OE_DEBUG << LC << "SQL QUERY failed for " << query << ": " << std::endl;
+        valid = false;
+    }
+
+    sqlite3_finalize( select );
+    return result;
+}
+
+bool 
+MBTilesTileSource::storeImage(const TileKey&    key,
+                              osg::Image*       image,
+                              ProgressCallback* progress)
+{
+    if ( (getMode() & MODE_WRITE) == 0 )
+        return false;
+
+    Threading::ScopedMutexLock exclusiveLock(_mutex);
+
+    // encode the data stream:
+    std::stringstream buf;
+    osgDB::ReaderWriter::WriteResult wr;
+    if ( _forceRGB && ImageUtils::hasAlphaChannel(image) )
+    {
+        osg::ref_ptr<osg::Image> rgb = ImageUtils::convertToRGB8(image);
+        wr = _rw->writeImage(*(rgb.get()), buf);
+    }
+    else
+    {
+        wr = _rw->writeImage(*image, buf);
+    }
+
+    if ( wr.error() )
+    {
+        OE_WARN << LC << "Image encoding failed: " << wr.message() << std::endl;
+        return false;
+    }
+
+    std::string value = buf.str();
+    
+    // compress if necessary:
+    if ( _compressor.valid() )
+    {
+        std::ostringstream output;
+        if ( !_compressor->compress(output, value) )
+        {
+            OE_WARN << LC << "Compressor failed" << std::endl;
+            return false;
+        }
+        value = output.str();
+    }
+
+    int z = key.getLOD();
+    int x = key.getTileX();
+    int y = key.getTileY();
+
+    // flip Y axis
+    unsigned int numRows, numCols;
+    key.getProfile()->getNumTiles(key.getLevelOfDetail(), numCols, numRows);
+    y  = numRows - y - 1;
+
+    // Prep the insert statement:
+    sqlite3_stmt* insert = NULL;
+    std::string query = "INSERT OR REPLACE INTO tiles (zoom_level, tile_column, tile_row, tile_data) VALUES (?, ?, ?, ?)";
+    int rc = sqlite3_prepare_v2( _database, query.c_str(), -1, &insert, 0L );
+    if ( rc != SQLITE_OK )
+    {
+        OE_WARN << LC << "Failed to prepare SQL: " << query << "; " << sqlite3_errmsg(_database) << std::endl;
+        return NULL;
+    }
+
+    // bind parameters:
+    sqlite3_bind_int( insert, 1, z );
+    sqlite3_bind_int( insert, 2, x );
+    sqlite3_bind_int( insert, 3, y );
+
+    // bind the data blob:
+    sqlite3_bind_blob( insert, 4, value.c_str(), value.length(), SQLITE_STATIC );
+
+    // run the sql.
+    bool ok = true;
+    int tries = 0;
+    do {
+        rc = sqlite3_step(insert);
+    }
+    while (++tries < 100 && (rc == SQLITE_BUSY || rc == SQLITE_LOCKED));
+
+    if (SQLITE_OK != rc && SQLITE_DONE != rc)
+    {
+#if SQLITE_VERSION_NUMBER >= 3007015
+        OE_WARN << LC << "Failed query: " << query << "(" << rc << ")" << sqlite3_errstr(rc) << "; " << sqlite3_errmsg(_database) << std::endl;
+#else
+        OE_WARN << LC << "Failed query: " << query << "(" << rc << ")" << rc << "; " << sqlite3_errmsg(_database) << std::endl;
+#endif        
+        ok = false;
+    }
+
+    sqlite3_finalize( insert );
+
+    return ok;
+}
+
+bool
+MBTilesTileSource::getMetaData(const std::string& key, std::string& value)
+{
+    Threading::ScopedMutexLock exclusiveLock(_mutex);
+
+    //get the metadata
+    sqlite3_stmt* select = NULL;
+    std::string query = "SELECT value from metadata where name = ?";
+    int rc = sqlite3_prepare_v2( _database, query.c_str(), -1, &select, 0L );
+    if ( rc != SQLITE_OK )
+    {
+        OE_WARN << LC << "Failed to prepare SQL: " << query << "; " << sqlite3_errmsg(_database) << std::endl;
+        return false;
+    }
+
+
+    bool valid = true;
+    std::string keyStr = std::string( key );
+    rc = sqlite3_bind_text( select, 1, keyStr.c_str(), keyStr.length(), SQLITE_STATIC );
+    if (rc != SQLITE_OK )
+    {
+        OE_WARN << LC << "Failed to bind text: " << query << "; " << sqlite3_errmsg(_database) << std::endl;
+        return false;
+    }
+
+    rc = sqlite3_step( select );
+    if ( rc == SQLITE_ROW)
+    {                     
+        value = (char*)sqlite3_column_text( select, 0 );
+    }
+    else
+    {
+        OE_DEBUG << LC << "SQL QUERY failed for " << query << ": " << std::endl;
+        valid = false;
+    }
+
+    sqlite3_finalize( select );
+    return valid;
+}
+
+bool
+MBTilesTileSource::putMetaData(const std::string& key, const std::string& value)
+{
+    Threading::ScopedMutexLock exclusiveLock(_mutex);
+
+    // prep the insert statement.
+    sqlite3_stmt* insert = 0L;
+    std::string query = Stringify() << "INSERT OR REPLACE INTO metadata (name,value) VALUES (?,?)";
+    if ( SQLITE_OK != sqlite3_prepare_v2(_database, query.c_str(), -1, &insert, 0L) )
+    {
+        OE_WARN << LC << "Failed to prepare SQL: " << query << "; " << sqlite3_errmsg(_database) << std::endl;
+        return false;
+    }
+
+    // bind the values:
+    if( SQLITE_OK != sqlite3_bind_text(insert, 1, key.c_str(), key.length(), SQLITE_STATIC) )
+    {
+        OE_WARN << LC << "Failed to bind text: " << query << "; " << sqlite3_errmsg(_database) << std::endl;
+        return false;
+    }
+    if ( SQLITE_OK != sqlite3_bind_text(insert, 2, value.c_str(), value.length(), SQLITE_STATIC) )
+    {
+        OE_WARN << LC << "Failed to bind text: " << query << "; " << sqlite3_errmsg(_database) << std::endl;
+        return false;
+    }
+
+    // execute the sql. no idea what a good return value should be :/
+    sqlite3_step( insert );
+    sqlite3_finalize( insert );
+    return true;
+}
+
+void
+MBTilesTileSource::computeLevels()
+{        
+    Threading::ScopedMutexLock exclusiveLock(_mutex);
+
+    osg::Timer_t startTime = osg::Timer::instance()->tick();
+    sqlite3_stmt* select = NULL;
+    std::string query = "SELECT min(zoom_level), max(zoom_level) from tiles";
+    int rc = sqlite3_prepare_v2( _database, query.c_str(), -1, &select, 0L );
+    if ( rc != SQLITE_OK )
+    {
+        OE_WARN << LC << "Failed to prepare SQL: " << query << "; " << sqlite3_errmsg(_database) << std::endl;
+    }
+
+    rc = sqlite3_step( select );
+    if ( rc == SQLITE_ROW)
+    {                     
+        _minLevel = sqlite3_column_int( select, 0 );
+        _maxLevel = sqlite3_column_int( select, 1 );
+        OE_DEBUG << LC << "Min=" << _minLevel << " Max=" << _maxLevel << std::endl;
+    }
+    else
+    {
+        OE_DEBUG << LC << "SQL QUERY failed for " << query << ": " << std::endl;
+    }        
+    sqlite3_finalize( select );        
+    osg::Timer_t endTime = osg::Timer::instance()->tick();
+    OE_DEBUG << LC << "Computing levels took " << osg::Timer::instance()->delta_s(startTime, endTime ) << " s" << std::endl;
+}
+
+bool
+MBTilesTileSource::createTables()
+{
+    Threading::ScopedMutexLock exclusiveLock(_mutex);
+
+    // https://github.com/mapbox/mbtiles-spec/blob/master/1.2/spec.md
+
+    std::string query =
+        "CREATE TABLE IF NOT EXISTS metadata ("
+        " name  text,"
+        " value text)";
+
+    if (SQLITE_OK != sqlite3_exec(_database, query.c_str(), 0L, 0L, 0L))
+    {
+        OE_WARN << LC << "Failed to create table [metadata]" << std::endl;
+        return false;
+    }
+
+    query = 
+        "CREATE TABLE IF NOT EXISTS tiles ("
+        " zoom_level integer,"
+        " tile_column integer,"
+        " tile_row integer,"
+        " tile_data blob)";
+
+    char* errorMsg = 0L;
+
+    if (SQLITE_OK != sqlite3_exec(_database, query.c_str(), 0L, 0L, &errorMsg))
+    {
+        OE_WARN << LC << "Failed to create table [tiles]: " << errorMsg << std::endl;
+        sqlite3_free( errorMsg );
+        return false;
+    }
+
+    // create an index
+    query =
+        "CREATE UNIQUE INDEX tile_index ON tiles ("
+        " zoom_level, tile_column, tile_row)";
+
+    if (SQLITE_OK != sqlite3_exec(_database, query.c_str(), 0L, 0L, &errorMsg))
+    {
+        OE_WARN << LC << "Failed to create index on table [tiles]: " << errorMsg << std::endl;
+        sqlite3_free( errorMsg );
+        // keep going...
+        // return false; 
+    }
+
+    // TODO: support "grids" and "grid_data" tables if necessary.
+
+    return true;
+}
+
+std::string
+MBTilesTileSource::getExtension() const 
+{
+    return _tileFormat;
+}
diff --git a/src/osgEarthDrivers/mbtiles/ReaderWriterMBTiles.cpp b/src/osgEarthDrivers/mbtiles/ReaderWriterMBTiles.cpp
deleted file mode 100644
index b169e93..0000000
--- a/src/osgEarthDrivers/mbtiles/ReaderWriterMBTiles.cpp
+++ /dev/null
@@ -1,310 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2010 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-* GNU Lesser General Public License for more details.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-
-#include "MBTilesOptions"
-
-#include <osgEarth/TileSource>
-#include <osgEarth/Registry>
-#include <osgEarth/FileUtils>
-#include <osgEarth/ImageUtils>
-#include <osg/Notify>
-#include <osgDB/FileNameUtils>
-#include <osgDB/FileUtils>
-#include <osgDB/Registry>
-#include <osgDB/ReadFile>
-#include <osgDB/WriteFile>
-
-#include <sstream>
-#include <iomanip>
-#include <algorithm>
-
-using namespace osgEarth;
-using namespace osgEarth::Drivers;
-
-#include <sqlite3.h>
-
-
-#define LC "[MBTilesSource] "
-
-class MBTilesSource : public TileSource
-{
-public:
-    MBTilesSource( const TileSourceOptions& options ) :
-      TileSource( options ),
-      _options( options ),      
-      _database( NULL ),
-      _minLevel( 0 ),
-      _maxLevel( 20 )
-    {
-    }
-
-    // override
-    Status initialize(const osgDB::Options* dbOptions)
-    {
-        // no caching of source tiles
-        _dbOptions = Registry::instance()->cloneOrCreateOptions( dbOptions );
-        CachePolicy::NO_CACHE.apply( _dbOptions.get() );
-
-                   
-
-        int flags = SQLITE_OPEN_READONLY;
-        int rc = sqlite3_open_v2( _options.filename()->c_str(), &_database, flags, 0L );
-        if ( rc != 0 )
-        {                        
-            std::stringstream buf;
-            buf << "Failed to open database \"" << *_options.filename() << "\": " << sqlite3_errmsg(_database);
-            return Status::Error(buf.str());
-        }
-
-        //Print out some metadata
-        std::string name, type, version, description, format, profileStr;
-        getMetaData( "name", name );
-        getMetaData( "type", type);
-        getMetaData( "version", version );
-        getMetaData( "description", description );
-        getMetaData( "format", format );
-        getMetaData( "profile", profileStr );
-        OE_NOTICE << "name=" << name << std::endl
-                  << "type=" << type << std::endl
-                  << "version=" << version << std::endl
-                  << "description=" << description << std::endl
-                  << "format=" << format << std::endl
-                  << "profile=" << profileStr << std::endl;
-
-
-
-         //Set the profile
-        const Profile* profile = getProfile();        
-        if (!profile)
-        {
-            if (!profileStr.empty())
-            {
-                profile = Profile::create(profileStr);
-            }
-            else
-            {
-                profile = osgEarth::Registry::instance()->getSphericalMercatorProfile();
-            }
-            setProfile( profile );                    
-        }
-        
-
-        //Determine the tile format and get a reader writer for it.        
-        if (_options.format().isSet())
-        {
-            //Get an explicitly defined format
-            _tileFormat = _options.format().value();
-        }
-        else if (!format.empty())
-        {
-            //Try to get it from the database metadata
-            _tileFormat = format;
-        }
-        else
-        {
-            //Assume it's PNG
-            _tileFormat = "png";
-        }
-
-        OE_DEBUG << LC <<  "_tileFormat = " << _tileFormat << std::endl;
-
-        //Get the ReaderWriter
-        _rw = osgDB::Registry::instance()->getReaderWriterForExtension( _tileFormat );                
-
-        computeLevels();
-
-        _emptyImage = ImageUtils::createEmptyImage( 256, 256 );
-        
-        return STATUS_OK;
-    }    
-
-    // override
-    osg::Image* createImage( const TileKey& key,
-                             ProgressCallback* progress)
-    {             
-        int z = key.getLevelOfDetail();
-        int x = key.getTileX();
-        int y = key.getTileY();
-
-        if (z < (int)_minLevel)
-        {
-            return _emptyImage.get();            
-        }
-
-        if (z > (int)_maxLevel)
-        {
-            //If we're at the max level, just return NULL
-            return NULL;
-        }
-
-        unsigned int numRows, numCols;
-        key.getProfile()->getNumTiles(key.getLevelOfDetail(), numCols, numRows);
-        y  = numRows - y - 1;
-
-        //Get the image
-        sqlite3_stmt* select = NULL;
-        std::string query = "SELECT tile_data from tiles where zoom_level = ? AND tile_column = ? AND tile_row = ?";
-        int rc = sqlite3_prepare_v2( _database, query.c_str(), -1, &select, 0L );
-        if ( rc != SQLITE_OK )
-        {
-            OE_WARN << LC << "Failed to prepare SQL: " << query << "; " << sqlite3_errmsg(_database) << std::endl;
-            return NULL;
-        }
-
-        bool valid = true;        
-        sqlite3_bind_int( select, 1, z );
-        sqlite3_bind_int( select, 2, x );
-        sqlite3_bind_int( select, 3, y );
-
-
-        osg::Image* result = NULL;
-        rc = sqlite3_step( select );
-        if ( rc == SQLITE_ROW)
-        {                     
-            // the pointer returned from _blob gets freed internally by sqlite, supposedly
-            const char* data = (const char*)sqlite3_column_blob( select, 0 );
-            int imageBufLen = sqlite3_column_bytes( select, 0 );
-
-            // deserialize the image from the buffer:
-            std::string imageString( data, imageBufLen );
-            std::stringstream imageBufStream( imageString );
-            osgDB::ReaderWriter::ReadResult rr = _rw->readImage( imageBufStream );
-            if (rr.validImage())
-            {
-                result = rr.takeImage();                
-            }            
-        }
-        else
-        {
-            OE_DEBUG << LC << "SQL QUERY failed for " << query << ": " << std::endl;
-            valid = false;
-        }
-
-        sqlite3_finalize( select );
-        return result;
-
-    }
-
-    bool getMetaData( const std::string& key, std::string& value )
-    {
-        //get the metadata
-        sqlite3_stmt* select = NULL;
-        std::string query = "SELECT value from metadata where name = ?";
-        int rc = sqlite3_prepare_v2( _database, query.c_str(), -1, &select, 0L );
-        if ( rc != SQLITE_OK )
-        {
-            OE_WARN << LC << "Failed to prepare SQL: " << query << "; " << sqlite3_errmsg(_database) << std::endl;
-            return false;
-        }
-
-
-        bool valid = true;
-        std::string keyStr = std::string( key );
-        rc = sqlite3_bind_text( select, 1, keyStr.c_str(), keyStr.length(), SQLITE_STATIC );
-        if (rc != SQLITE_OK )
-        {
-            OE_WARN << LC << "Failed to bind text: " << query << "; " << sqlite3_errmsg(_database) << std::endl;
-            return false;
-        }
-
-        rc = sqlite3_step( select );
-        if ( rc == SQLITE_ROW)
-        {                     
-            value = (char*)sqlite3_column_text( select, 0 );
-        }
-        else
-        {
-            OE_DEBUG << LC << "SQL QUERY failed for " << query << ": " << std::endl;
-            valid = false;
-        }
-
-        sqlite3_finalize( select );
-        return valid;
-    }
-
-    void computeLevels()
-    {        
-        sqlite3_stmt* select = NULL;
-        std::string query = "SELECT min(zoom_level), max(zoom_level) from tiles";
-        int rc = sqlite3_prepare_v2( _database, query.c_str(), -1, &select, 0L );
-        if ( rc != SQLITE_OK )
-        {
-            OE_WARN << LC << "Failed to prepare SQL: " << query << "; " << sqlite3_errmsg(_database) << std::endl;
-        }
-
-        rc = sqlite3_step( select );
-        if ( rc == SQLITE_ROW)
-        {                     
-            _minLevel = sqlite3_column_int( select, 0 );
-            _maxLevel = sqlite3_column_int( select, 1 );
-            OE_NOTICE << "Min=" << _minLevel << " Max=" << _maxLevel << std::endl;
-        }
-        else
-        {
-            OE_DEBUG << LC << "SQL QUERY failed for " << query << ": " << std::endl;
-        }
-
-        sqlite3_finalize( select );        
-    }
-
-    // override
-    virtual std::string getExtension() const 
-    {
-        return _tileFormat;
-    }
-
-private:
-    const MBTilesOptions _options;    
-    sqlite3* _database;
-    unsigned int _minLevel;
-    unsigned int _maxLevel;
-    osg::ref_ptr< osg::Image> _emptyImage;
-
-    osg::ref_ptr<osgDB::ReaderWriter> _rw;
-    osg::ref_ptr<osgDB::Options> _dbOptions;
-    std::string _tileFormat;
-
-};
-
-
-class MBTilesTileSourceFactory : public TileSourceDriver
-{
-public:
-    MBTilesTileSourceFactory()
-    {
-        supportsExtension( "osgearth_mbtiles", "MBTiles Driver" );
-    }
-
-    virtual const char* className()
-    {
-        return "MBTiles ReaderWriter";
-    }
-
-    virtual ReadResult readObject(const std::string& file_name, const Options* options) const
-    {
-        if ( !acceptsExtension(osgDB::getLowerCaseFileExtension( file_name )))
-            return ReadResult::FILE_NOT_HANDLED;
-
-        return new MBTilesSource( getTileSourceOptions(options) );
-    }
-};
-
-REGISTER_OSGPLUGIN(osgearth_mbtiles, MBTilesTileSourceFactory)
-
-
diff --git a/src/osgEarthDrivers/model_feature_geom/FeatureGeomModelOptions b/src/osgEarthDrivers/model_feature_geom/FeatureGeomModelOptions
index 095bccb..42b59c5 100644
--- a/src/osgEarthDrivers/model_feature_geom/FeatureGeomModelOptions
+++ b/src/osgEarthDrivers/model_feature_geom/FeatureGeomModelOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/model_feature_geom/FeatureGeomModelSource.cpp b/src/osgEarthDrivers/model_feature_geom/FeatureGeomModelSource.cpp
index 494d94d..abb133d 100644
--- a/src/osgEarthDrivers/model_feature_geom/FeatureGeomModelSource.cpp
+++ b/src/osgEarthDrivers/model_feature_geom/FeatureGeomModelSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/model_feature_stencil/FeatureStencilModelOptions b/src/osgEarthDrivers/model_feature_stencil/FeatureStencilModelOptions
index 45038b7..e6e6ca2 100644
--- a/src/osgEarthDrivers/model_feature_stencil/FeatureStencilModelOptions
+++ b/src/osgEarthDrivers/model_feature_stencil/FeatureStencilModelOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/model_feature_stencil/FeatureStencilModelSource.cpp b/src/osgEarthDrivers/model_feature_stencil/FeatureStencilModelSource.cpp
index 7af03a6..a60250f 100644
--- a/src/osgEarthDrivers/model_feature_stencil/FeatureStencilModelSource.cpp
+++ b/src/osgEarthDrivers/model_feature_stencil/FeatureStencilModelSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/model_simple/SimpleModelOptions b/src/osgEarthDrivers/model_simple/SimpleModelOptions
index 66380fd..388bd91 100644
--- a/src/osgEarthDrivers/model_simple/SimpleModelOptions
+++ b/src/osgEarthDrivers/model_simple/SimpleModelOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -51,6 +51,9 @@ namespace osgEarth { namespace Drivers
 
         optional<float>& loadingPriorityOffset() { return _loadingPriorityOffset; }
         const optional<float>& loadingPriorityOffset() const { return _loadingPriorityOffset; }
+
+        optional<bool>& paged() { return _paged; }
+        const optional<bool>& paged() const { return _paged; }
         
         /**
          If specified, use this node instead try to load from url
@@ -66,7 +69,8 @@ namespace osgEarth { namespace Drivers
               _lod_scale(1.0f),
               _shaderPolicy( SHADERPOLICY_GENERATE ),
               _loadingPriorityScale(1.0f),
-              _loadingPriorityOffset(0.0f)
+              _loadingPriorityOffset(0.0f),
+              _paged(false)
         {
             setDriver( "simple" );
             fromConfig( _conf );
@@ -83,6 +87,7 @@ namespace osgEarth { namespace Drivers
             conf.updateIfSet( "orientation", _orientation);
             conf.updateIfSet( "loading_priority_scale", _loadingPriorityScale );
             conf.updateIfSet( "loading_priority_offset", _loadingPriorityOffset );
+            conf.updateIfSet( "paged", _paged);
 
             conf.addIfSet( "shader_policy", "disable",  _shaderPolicy, SHADERPOLICY_DISABLE );
             conf.addIfSet( "shader_policy", "inherit",  _shaderPolicy, SHADERPOLICY_INHERIT );
@@ -106,6 +111,7 @@ namespace osgEarth { namespace Drivers
             conf.getIfSet( "orientation", _orientation);
             conf.getIfSet( "loading_priority_scale", _loadingPriorityScale );
             conf.getIfSet( "loading_priority_offset", _loadingPriorityOffset );
+            conf.getIfSet( "paged", _paged );
 
             conf.getIfSet( "shader_policy", "disable",  _shaderPolicy, SHADERPOLICY_DISABLE );
             conf.getIfSet( "shader_policy", "inherit",  _shaderPolicy, SHADERPOLICY_INHERIT );
@@ -121,6 +127,7 @@ namespace osgEarth { namespace Drivers
         optional<ShaderPolicy> _shaderPolicy;
         optional<float> _loadingPriorityScale;
         optional<float> _loadingPriorityOffset;
+        optional<bool> _paged;
         osg::ref_ptr<osg::Node> _node;
     };
 
diff --git a/src/osgEarthDrivers/model_simple/SimpleModelSource.cpp b/src/osgEarthDrivers/model_simple/SimpleModelSource.cpp
index 2ba06c2..109d117 100644
--- a/src/osgEarthDrivers/model_simple/SimpleModelSource.cpp
+++ b/src/osgEarthDrivers/model_simple/SimpleModelSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -23,7 +23,8 @@
 #include <osgEarth/Map>
 #include <osgEarth/ShaderGenerator>
 #include <osgEarth/FileUtils>
-#include <osgEarth/AutoScale>
+#include <osgEarth/StateSetCache>
+#include <osg/CullStack>
 #include <osg/LOD>
 #include <osg/ProxyNode>
 #include <osg/Notify>
@@ -116,22 +117,33 @@ public:
     osg::Node* createNodeImplementation(const Map* map, const osgDB::Options* dbOptions, ProgressCallback* progress )
     {
         osg::ref_ptr<osg::Node> result;
-
+        
+        // Only support paging if they've enabled it and provided a min/max range
+        bool usePagedLOD = *_options.paged() &&
+                          (_options.minRange().isSet() || _options.maxRange().isSet());
+        
         if (_options.node() != NULL)
         {
             result = _options.node();
         }
         else
         {
-            // required if the model includes local refs, like PagedLOD or ProxyNode:
-            osg::ref_ptr<osgDB::Options> localOptions = 
-                Registry::instance()->cloneOrCreateOptions( dbOptions );
+            // Only load the model if it's not paged or we don't have a location set.
+            if (!usePagedLOD || !_options.location().isSet())
+            {
+                // required if the model includes local refs, like PagedLOD or ProxyNode:
+                osg::ref_ptr<osgDB::Options> localOptions = 
+                    Registry::instance()->cloneOrCreateOptions( dbOptions );
 
-            localOptions->getDatabasePathList().push_back( osgDB::getFilePath(_options.url()->full()) );
+                localOptions->getDatabasePathList().push_back( osgDB::getFilePath(_options.url()->full()) );
 
-            result = _options.url()->getNode( localOptions.get(), progress );
+                result = _options.url()->getNode( localOptions.get(), progress );                
+            }
         }
 
+        // Always create a matrix transform
+        osg::MatrixTransform* mt = new osg::MatrixTransform;        
+
         if (_options.location().isSet() && map != 0L)
         {
             GeoPoint geoPoint(
@@ -153,23 +165,53 @@ public:
                     osg::DegreesToRadians((*_options.orientation()).x()), osg::Vec3(0,0,1),
                     osg::DegreesToRadians((*_options.orientation()).z()), osg::Vec3(0,1,0) );
                 matrix.preMult(rot_mat);
-            }
+            }            
+            mt->setMatrix( matrix );            
+        }
 
-            osg::MatrixTransform* mt = new osg::MatrixTransform;
-            mt->setMatrix( matrix );
-            mt->addChild( result.get() );
-            result = mt;
+        if ( _options.minRange().isSet() || _options.maxRange().isSet() )
+        {                
+            float minRange = _options.minRange().isSet() ? (*_options.minRange()) : 0.0f;
+            float maxRange = _options.maxRange().isSet() ? (*_options.maxRange()) : FLT_MAX;
 
-            if ( _options.minRange().isSet() || _options.maxRange().isSet() )
+            osg::LOD* lod = 0;
+
+            if (!usePagedLOD)
             {
-                osg::LOD* lod = new osg::LOD();
-                lod->addChild(
-                    result.release(),
-                    _options.minRange().isSet() ? (*_options.minRange()) : 0.0f,
-                    _options.maxRange().isSet() ? (*_options.maxRange()) : FLT_MAX );
-                result = lod;
+                // Just use a regular LOD
+                lod = new osg::LOD();                
+                lod->addChild(result.release(), minRange, maxRange);                                           
             }
+            else
+            {
+                // Use a PagedLOD
+                osg::PagedLOD* plod =new osg::PagedLOD();                
+                plod->setFileName(0, _options.url()->full());     
+
+                // If they want the model to be paged but haven't given us a location we have to load
+                // up the node up front and figure out what it's center and radius are or it won't page in.
+                if (!_options.location().isSet() && result.valid())
+                {                    
+                    osg::Vec3d center = result->getBound().center();
+                    OE_DEBUG << "Radius=" << result->getBound().radius() << " center=" << center.x() << "," << center.y() << "," << center.z() << std::endl;                    
+                    plod->setCenter(result->getBound().center());
+                    plod->setRadius(result->getBound().radius());                    
+                }
+                lod = plod;
+            }   
+            lod->setRange(0, minRange, maxRange);                
+            mt->addChild(lod);                
         }
+        else
+        {
+            // Simply add the node to the matrix transform
+            if (result.valid())
+            {            
+                mt->addChild( result.get() );
+            }            
+        }
+
+        result = mt;
 
         // generate a shader program to render the model.
         if ( result.valid() )
@@ -190,9 +232,12 @@ public:
 
             if ( _options.shaderPolicy() == SHADERPOLICY_GENERATE )
             {
-                ShaderGenerator gen;
-                gen.setProgramName( "osgEarth.SimpleModelSource" );
-                gen.run( result );
+                osg::ref_ptr<StateSetCache> cache = new StateSetCache();
+
+                Registry::shaderGenerator().run(
+                    result,
+                    _options.url()->base(),
+                    cache.get() );
             }
             else if ( _options.shaderPolicy() == SHADERPOLICY_DISABLE )
             {
diff --git a/src/osgEarthDrivers/noise/CMakeLists.txt b/src/osgEarthDrivers/noise/CMakeLists.txt
index e7a6129..01be7ba 100644
--- a/src/osgEarthDrivers/noise/CMakeLists.txt
+++ b/src/osgEarthDrivers/noise/CMakeLists.txt
@@ -1,9 +1,10 @@
-INCLUDE_DIRECTORIES( ${LIBNOISE_INCLUDE_DIR} )
 
-SET(TARGET_SRC ReaderWriterNoise.cpp)
-SET(TARGET_H NoiseOptions)
+SET(TARGET_SRC NoiseDriver.cpp)
+SET(TARGET_H   NoiseOptions)
 
-SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES} ${LIBNOISE_LIBRARY})
+SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES}
+    osgEarthUtil
+)
 
 SETUP_PLUGIN(osgearth_noise)
 
@@ -11,4 +12,3 @@ SETUP_PLUGIN(osgearth_noise)
 SET(LIB_NAME noise)
 SET(LIB_PUBLIC_HEADERS NoiseOptions)
 INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
-
diff --git a/src/osgEarthDrivers/noise/NoiseDriver.cpp b/src/osgEarthDrivers/noise/NoiseDriver.cpp
new file mode 100644
index 0000000..ad79a4e
--- /dev/null
+++ b/src/osgEarthDrivers/noise/NoiseDriver.cpp
@@ -0,0 +1,321 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osgEarth/TileSource>
+#include <osgEarth/Registry>
+#include <osgEarth/URI>
+#include <osgEarth/ImageUtils>
+
+#include <osgEarthUtil/SimplexNoise>
+
+#include <osg/Notify>
+#include <osgDB/FileNameUtils>
+#include <osgDB/FileUtils>
+#include <osgDB/Registry>
+#include <osgDB/ReadFile>
+#include <osgDB/WriteFile>
+#include <sstream>
+#include <cmath>
+
+#include "NoiseOptions"
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+namespace osgEarth { namespace Drivers { namespace Noise
+{
+    class NoiseSource : public TileSource
+    {
+    public:
+        NoiseSource( const TileSourceOptions& options ) : TileSource( options ), _options(options)
+        {
+            _noise.setFrequency  ( *_options.frequency() );
+            _noise.setPersistence( *_options.persistence() );
+            _noise.setLacunarity ( *_options.lacunarity() );
+            _noise.setOctaves    ( *_options.octaves() );
+            _noise.setRange      ( -1.0, 1.0 );
+        }
+
+        // Yahoo! uses spherical mercator, but the top LOD is a 2x2 tile set.
+        Status initialize(const osgDB::Options* dbOptions)
+        {            
+            _dbOptions = Registry::instance()->cloneOrCreateOptions( dbOptions );            
+            setProfile( osgEarth::Registry::instance()->getGlobalGeodeticProfile() );
+
+            // resolve frequency if the user set resolution
+            if (_options.resolution().isSet() && !_options.resolution().isSetTo(0.0))
+            {
+                _noise.setFrequency( 1.0 / *_options.resolution() );
+            }
+
+            return STATUS_OK;
+        }
+    
+        /** Tell the terrain engine not to cache tiles form this source by default. */
+        CachePolicy getCachePolicyHint(const Profile*) const
+        {
+            return CachePolicy::NO_CACHE;
+        }
+
+        inline double sample(double x, double y, double z)
+        {
+            return _noise.getValue(x, y, z);
+        }
+
+        inline double sample(const osg::Vec3d& v)
+        {
+            return _noise.getValue(v.x(), v.y(), v.z());
+        }
+
+        inline double turbulence(const osg::Vec3d& v, double f)
+        {
+            double t = -0.5;
+            for( ; f<getPixelsPerTile()/2; f *= 2 ) 
+                t += std::abs(_noise.getValue(v.x(), v.y(), v.z())/f);
+            return t;
+        }
+
+        inline double stripes(double x, double f)
+        {
+            double t = 0.5 + 0.5 * asin(f * 2*osg::PI * x);
+            return t * t - 0.5;
+        }
+
+
+        osg::Image* createImage(const TileKey&    key,
+                                ProgressCallback* progress)
+        {
+            if ( _options.normalMap() == true )
+            {
+                return createNormalMap(key, progress);
+            }
+            else
+            {
+                const SpatialReference* srs = key.getProfile()->getSRS();
+
+                double projNormX, projNormY;
+                if ( srs->isProjected() )
+                {
+                    projNormX = 1.0/key.getProfile()->getExtent().width();
+                    projNormY = 1.0/key.getProfile()->getExtent().height();
+                }
+
+                osg::Image* image = new osg::Image();
+                image->allocateImage( getPixelsPerTile(), getPixelsPerTile(), 1, GL_RGB, GL_UNSIGNED_BYTE );
+
+                double dx = key.getExtent().width()  / (double)(image->s()-1);
+                double dy = key.getExtent().height() / (double)(image->t()-1);
+
+                ImageUtils::PixelWriter write(image);
+                for(int s=0; s<image->s(); ++s)
+                {
+                    for(int t=0; t<image->t(); ++t)
+                    {
+                        double x = key.getExtent().xMin() + (double)s * dx;
+                        double y = key.getExtent().yMin() + (double)t * dy;
+                        
+                        osg::Vec3d world(x, y, 0.0);
+
+                        if ( srs->isGeographic() )
+                        {
+                            srs->transform(world, srs->getECEF(), world);
+                            world.normalize();
+                        }
+                        else
+                        {
+                            world.x() *= projNormX;
+                            world.y() *= projNormY;
+                            world.z()  = 0.0;
+                        }
+
+                        double n = sample(world); //world.x(), world.y(), world.z());
+                        //double n = 0.1 * stripes(world.x() + 2.0*turbulence(noise, world, 1.0), 1.6);
+                        //double n = -.10 * turbulence(noise, world, 0.2);
+
+                        // scale and bias from[-1..1] to [0..1] for coloring.
+                        n = osg::clampBetween( (n+1.0)*0.5, 0.0, 1.0 );
+
+                        write(osg::Vec4f(n,n,n,1), s, t);
+                    }
+                }
+
+                return image;
+            }
+        }
+
+
+        osg::HeightField* createHeightField(const TileKey&        key,
+                                            ProgressCallback*     progress )
+        {
+            const SpatialReference* srs = key.getProfile()->getSRS();
+
+            osg::HeightField* hf = new osg::HeightField();
+            hf->allocate( getPixelsPerTile(), getPixelsPerTile() );
+
+            double dx = key.getExtent().width() / (double)(hf->getNumColumns()-1);
+            double dy = key.getExtent().height() / (double)(hf->getNumRows()-1);
+
+            double bias  = _options.bias().get();
+            double scale = _options.scale().get();
+        
+            //Initialize the heightfield
+            for (unsigned int c = 0; c < hf->getNumColumns(); c++) 
+            {
+                for (unsigned int r = 0; r < hf->getNumRows(); r++)
+                {                
+                    double lon = key.getExtent().xMin() + (double)c * dx;
+                    double lat = key.getExtent().yMin() + (double)r * dy;
+
+                    osg::Vec3d world(lon, lat, 0.0);
+                    if ( srs->isGeographic() )
+                    {
+                        srs->transform(world, srs->getECEF(), world);
+                        world.normalize();
+                    }
+
+                    double n = sample( world );
+
+                    // Scale the noise value.
+                    double h = osg::clampBetween(
+                        (float)(bias + scale * n),
+                        *_options.minElevation(),
+                        *_options.maxElevation() );
+
+                    hf->setHeight( c, r, h );
+
+                    // NOTE! The elevation engine treats extreme values (>32000, etc)
+                    // as "no data" so be careful with your scale.
+                }
+            }     
+
+            return hf;
+        }
+
+
+        osg::Image* createNormalMap(const TileKey& key, ProgressCallback* progress)
+        {
+            // set up the image and prepare to write to it.
+            osg::Image* image = new osg::Image();
+            image->allocateImage( getPixelsPerTile(), getPixelsPerTile(), 1, GL_RGB, GL_UNSIGNED_BYTE );
+            ImageUtils::PixelWriter write(image);
+
+            const GeoExtent&        ex     = key.getExtent();
+            const SpatialReference* srs    = ex.getSRS();
+            bool                    isGeo  = srs->isGeographic();
+            const SpatialReference* ecef   = srs->getECEF();
+
+            double dx = ex.width()  / (double)(image->s()-1);
+            double dy = ex.height() / (double)(image->t()-1);
+
+            double scale  = _options.scale().get();
+            double bias   = _options.bias().get();
+
+            // figure out the spacing between pixels in the same units as the height value:
+            double udx = dx;
+            double udy = dy;
+            if ( isGeo )
+            {
+                udx = srs->transformUnits(dx, ecef, ex.south()+0.5*dy);
+                udy = srs->transformUnits(dy, ecef, ex.south()+0.5*dy);
+            }
+
+            double z = 0.0;
+            std::vector<osg::Vec3d> v(4);
+            double samples[4];
+
+            for(int s=0; s<image->s(); ++s)
+            {
+                for(int t=0; t<image->t(); ++t)
+                {
+                    double x = ex.xMin() + (double)s * dx;
+                    double y = ex.yMin() + (double)t * dy;
+
+                    if ( isGeo )
+                    {
+                        v[0].set(x-dx, y, z);
+                        v[1].set(x+dx, y, z);
+                        v[2].set(x, y+dy, z);
+                        v[3].set(x, y-dy, z);
+                        srs->transform(v, ecef);
+                        for(int i=0; i<4; ++i )
+                            samples[i] = bias + scale * sample(v[i]);
+                    }
+                    else
+                    {
+                        samples[0] = bias + scale * sample(x-dx, y, z);
+                        samples[1] = bias + scale * sample(x+dx, y, z);
+                        samples[2] = bias + scale * sample(x, y+dy, z);
+                        samples[3] = bias + scale * sample(x, y-dy, z);
+                    }
+
+                    osg::Vec3d west (-udx,    0, samples[0]);
+                    osg::Vec3d east ( udx,    0, samples[1]);
+                    osg::Vec3d north(   0,  udy, samples[2]);
+                    osg::Vec3d south(   0, -udy, samples[3]);
+
+                    // calculate the normal at the center point.
+                    osg::Vec3 normal = (east-west) ^ (north-south);
+                    normal.normalize();
+
+                    // encode as: xyz[-1..1]=>r[0..255]. (In reality Z will always fall 
+                    // between [0..1] but a uniform encoding makes the shader code simpler.)
+                    normal.x() = normal.x()*0.5f + 0.5f;
+                    normal.y() = normal.y()*0.5f + 0.5f;
+                    normal.z() = normal.z()*0.5f + 0.5f;
+                    normal.normalize();
+
+                    write(osg::Vec4f(normal,1), s, t);
+                }
+            }
+
+            return image;
+        }
+
+
+    private:
+        NoiseOptions                 _options;
+        osg::ref_ptr<osgDB::Options> _dbOptions;
+        SimplexNoise                 _noise;
+    };
+
+
+    class NoiseDriver : public TileSourceDriver
+    {
+        public:
+            NoiseDriver()
+            {
+                supportsExtension( "osgearth_noise", "Procedural noise generator" );
+            }
+
+            virtual const char* className()
+            {
+                return "Procedural noise generator";
+            }
+
+            virtual ReadResult readObject(const std::string& file_name, const Options* options) const
+            {
+                if ( !acceptsExtension(osgDB::getLowerCaseFileExtension( file_name )))
+                    return ReadResult::FILE_NOT_HANDLED;
+
+                return new NoiseSource( getTileSourceOptions(options) );
+            }
+    };
+
+    REGISTER_OSGPLUGIN(osgearth_noise, NoiseDriver);
+
+} } } // namespace osgEarth::Drivers::Noise
diff --git a/src/osgEarthDrivers/noise/NoiseOptions b/src/osgEarthDrivers/noise/NoiseOptions
index addca2e..a8ecb5b 100644
--- a/src/osgEarthDrivers/noise/NoiseOptions
+++ b/src/osgEarthDrivers/noise/NoiseOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,15 +21,17 @@
 
 #include <osgEarth/Common>
 #include <osgEarth/TileSource>
+#include <osgEarthUtil/SimplexNoise>
 
-namespace osgEarth { namespace Drivers
+namespace osgEarth { namespace Drivers { namespace Noise
 {
     using namespace osgEarth;
+    using namespace osgEarth::Util;
 
     /**
      * Options for the Noise driver
-     * See http://libnoise.sourceforge.net/docs/classnoise_1_1module_1_1Perlin.html for documentation
-     * on specific noise settings.
+     * See http://libnoise.sourceforge.net/docs/classnoise_1_1module_1_1Perlin.html
+     * for documentation on specific noise settings.
      */
     class NoiseOptions : public TileSourceOptions // NO EXPORT; header only
     {
@@ -150,11 +152,11 @@ namespace osgEarth { namespace Drivers
             _normalMap   (false),
             _scale       (1.0),
             _bias        (0.0),
-            _octaves     (3),
-            _resolution  (1.0),
-            _frequency   (1.0),
-            _persistence (0.5),
-            _lacunarity  (2.0)
+            _octaves     (SimplexNoise::DefaultOctaves),
+            _resolution  (1.0/SimplexNoise::DefaultFrequency),
+            _frequency   (SimplexNoise::DefaultFrequency),
+            _persistence (SimplexNoise::DefaultPersistence),
+            _lacunarity  (SimplexNoise::DefaultLacunarity)
         {
             setDriver( "noise" );
             fromConfig( _conf );
@@ -194,7 +196,7 @@ namespace osgEarth { namespace Drivers
             conf.getIfSet( "resolution", _resolution);
             conf.getIfSet( "frequency", _frequency );
             conf.getIfSet( "persistence", _persistence );
-            conf.getIfSet( "lacunarity", _lacunarity );
+            conf.getIfSet( "lacunarity", _lacunarity);
             conf.getIfSet( "seed", _seed );
             conf.getIfSet( "normal_map", _normalMap );
             conf.getIfSet( "scale", _scale );
@@ -214,7 +216,6 @@ namespace osgEarth { namespace Drivers
         optional<double> _bias;
     };
 
-} } // namespace osgEarth::Drivers
+} } } // namespace osgEarth::Drivers::Noise
 
 #endif // OSGEARTH_DRIVER_NOISE_DRIVEROPTIONS
-
diff --git a/src/osgEarthDrivers/noise/ReaderWriterNoise.cpp b/src/osgEarthDrivers/noise/ReaderWriterNoise.cpp
deleted file mode 100644
index d25857b..0000000
--- a/src/osgEarthDrivers/noise/ReaderWriterNoise.cpp
+++ /dev/null
@@ -1,318 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#include <osgEarth/TileSource>
-#include <osgEarth/Registry>
-#include <osgEarth/URI>
-#include <osgEarth/ImageUtils>
-
-#include <osg/Notify>
-#include <osgDB/FileNameUtils>
-#include <osgDB/FileUtils>
-#include <osgDB/Registry>
-#include <osgDB/ReadFile>
-#include <osgDB/WriteFile>
-#include <sstream>
-
-#include <noise/noise.h>
-
-using namespace noise;
-
-#include "NoiseOptions"
-
-using namespace osgEarth;
-using namespace osgEarth::Drivers;
-
-class NoiseSource : public TileSource
-{
-public:
-    NoiseSource( const TileSourceOptions& options ) : TileSource( options ), _options(options)
-    {
-        //nop
-    }
-
-    // Yahoo! uses spherical mercator, but the top LOD is a 2x2 tile set.
-    Status initialize(const osgDB::Options* dbOptions)
-    {
-        // no caching of source tiles (there are none..)
-        _dbOptions = Registry::instance()->cloneOrCreateOptions( dbOptions );
-        CachePolicy::NO_CACHE.apply( _dbOptions.get() );
-        setProfile( osgEarth::Registry::instance()->getGlobalGeodeticProfile() );
-
-        // resolve frequency if the user set resolution
-        if (_options.resolution().isSet() && !_options.resolution().isSetTo(0.0))
-        {
-            _options.frequency().init( 1.0 / *_options.resolution() );
-        }
-
-        return STATUS_OK;
-    }
-    
-    /** Tell the terrain engine not to cache tiles form this source by default. */
-    CachePolicy getCachePolicyHint(const Profile*) const
-    {
-        return CachePolicy::NO_CACHE;
-    }
-
-    inline double sample(module::Perlin& noise, double x, double y, double z)
-    {
-        return noise.GetValue(x, y, z);
-    }
-
-    inline double sample(module::Perlin& noise, const osg::Vec3d& v)
-    {
-        return noise.GetValue(v.x(), v.y(), v.z());
-    }
-
-    inline double turbulence(module::Perlin& noise, const osg::Vec3d& v, double f )
-    {
-        double t = -0.5;
-        for( ; f<getPixelsPerTile()/2; f *= 2 ) 
-            t += abs(noise.GetValue(v.x(), v.y(), v.z())/f);
-        return t;
-    }
-
-    inline double stripes(double x, double f)
-    {
-        double t = 0.5 + 0.5 * asin(f * 2*osg::PI * x);
-        return t * t - 0.5;
-    }
-
-
-    osg::Image* createImage(const TileKey&        key,
-                            ProgressCallback*     progress )
-    {
-        if ( _options.normalMap() == true )
-        {
-            return createNormalMap(key, progress);
-        }
-        else
-        {
-            module::Perlin noise;
-            noise.SetFrequency  ( _options.frequency().get() );
-            noise.SetPersistence( _options.persistence().get() );
-            noise.SetLacunarity ( _options.lacunarity().get() );
-            noise.SetOctaveCount( _options.octaves().get() );
-
-            const SpatialReference* srs = key.getProfile()->getSRS();
-
-            osg::Image* image = new osg::Image();
-            image->allocateImage( getPixelsPerTile(), getPixelsPerTile(), 1, GL_RGB, GL_UNSIGNED_BYTE );
-
-            double dx = key.getExtent().width()  / (double)(image->s()-1);
-            double dy = key.getExtent().height() / (double)(image->t()-1);
-
-            ImageUtils::PixelWriter write(image);
-            for(int s=0; s<image->s(); ++s)
-            {
-                for(int t=0; t<image->t(); ++t)
-                {
-                    double lon = key.getExtent().xMin() + (double)s * dx;
-                    double lat = key.getExtent().yMin() + (double)t * dy;
-
-                    osg::Vec3d world(lon, lat, 0.0);
-                    if ( srs->isGeographic() )
-                        srs->transform(world, srs->getECEF(), world);
-
-                    double n = noise.GetValue(world.x(), world.y(), world.z());
-                    //world.normalize();
-                    //double n = 0.1 * stripes(world.x() + 2.0*turbulence(noise, world, 1.0), 1.6);
-                    //double n = -.10 * turbulence(noise, world, 0.2);
-
-                    // scale and bias from[-1..1] to [0..1] for coloring. It should be noted that
-                    // the Perlin noise function can generate values outside this range, hence
-                    // the clamp!
-                    n = osg::clampBetween( (n+1.0)*0.5, 0.0, 1.0 );
-
-                    write(osg::Vec4f(n,n,n,1), s, t);
-                }
-            }
-
-            return image;
-        }
-    }
-
-
-    osg::HeightField* createHeightField(const TileKey&        key,
-                                        ProgressCallback*     progress )
-    {
-        module::Perlin noise;
-        noise.SetFrequency  ( _options.frequency().get() );
-        noise.SetPersistence( _options.persistence().get() );
-        noise.SetLacunarity ( _options.lacunarity().get() );
-        noise.SetOctaveCount( _options.octaves().get() );
-
-        const SpatialReference* srs = key.getProfile()->getSRS();
-
-        osg::HeightField* hf = new osg::HeightField();
-        hf->allocate( getPixelsPerTile(), getPixelsPerTile() );
-
-        double dx = key.getExtent().width() / (double)(hf->getNumColumns()-1);
-        double dy = key.getExtent().height() / (double)(hf->getNumRows()-1);
-
-        double bias  = _options.bias().get();
-        double scale = _options.scale().get();
-
-        //Initialize the heightfield
-        for (unsigned int c = 0; c < hf->getNumColumns(); c++) 
-        {
-            for (unsigned int r = 0; r < hf->getNumRows(); r++)
-            {                
-                double lon = key.getExtent().xMin() + (double)c * dx;
-                double lat = key.getExtent().yMin() + (double)r * dy;
-
-                osg::Vec3d world(lon, lat, 0.0);
-                if ( srs->isGeographic() )
-                    srs->transform(world, srs->getECEF(), world);
-
-                double n = noise.GetValue(world.x(), world.y(), world.z());
-
-                // Scale the noise value which is between -1 and 1...ish
-                double h = osg::clampBetween(
-                    (float)(bias + scale * n),
-                    *_options.minElevation(),
-                    *_options.maxElevation() );
-
-                hf->setHeight( c, r, h );
-
-                // NOTE! The elevation engine treats extreme values (>32000, etc)
-                // as "no data" so be careful with your scale.
-            }
-        }     
-
-        return hf;
-    }
-
-
-    osg::Image* createNormalMap(const TileKey& key, ProgressCallback* progress)
-    {
-        module::Perlin noise;
-        noise.SetFrequency  ( _options.frequency().get() );
-        noise.SetPersistence( _options.persistence().get() );
-        noise.SetLacunarity ( _options.lacunarity().get() );
-        noise.SetOctaveCount( _options.octaves().get() );
-
-        // set up the image and prepare to write to it.
-        osg::Image* image = new osg::Image();
-        image->allocateImage( getPixelsPerTile(), getPixelsPerTile(), 1, GL_RGB, GL_UNSIGNED_BYTE );
-        ImageUtils::PixelWriter write(image);
-
-        const GeoExtent&        ex     = key.getExtent();
-        const SpatialReference* srs    = ex.getSRS();
-        bool                    isGeo  = srs->isGeographic();
-        const SpatialReference* ecef   = srs->getECEF();
-
-        double dx = ex.width()  / (double)(image->s()-1);
-        double dy = ex.height() / (double)(image->t()-1);
-
-        double scale  = _options.scale().get();
-        double bias   = _options.bias().get();
-
-        // figure out the spacing between pixels in the same units as the height value:
-        double udx = dx;
-        double udy = dy;
-        if ( isGeo )
-        {
-            udx = srs->transformUnits(dx, ecef, ex.south()+0.5*dy);
-            udy = srs->transformUnits(dy, ecef, ex.south()+0.5*dy);
-        }
-
-        double z = 0.0;
-        std::vector<osg::Vec3d> v(4);
-        double samples[4];
-
-        for(int s=0; s<image->s(); ++s)
-        {
-            for(int t=0; t<image->t(); ++t)
-            {
-                double x = ex.xMin() + (double)s * dx;
-                double y = ex.yMin() + (double)t * dy;
-
-                if ( isGeo )
-                {
-                    v[0].set(x-dx, y, z);
-                    v[1].set(x+dx, y, z);
-                    v[2].set(x, y+dy, z);
-                    v[3].set(x, y-dy, z);
-                    srs->transform(v, ecef);
-                    for(int i=0; i<4; ++i )
-                        samples[i] = bias + scale * sample(noise, v[i]);
-                }
-                else
-                {
-                    samples[0] = bias + scale * sample(noise, osg::Vec3d(x-dx, y, z));
-                    samples[1] = bias + scale * sample(noise, osg::Vec3d(x+dx, y, z));
-                    samples[2] = bias + scale * sample(noise, osg::Vec3d(x, y+dy, z));
-                    samples[3] = bias + scale * sample(noise, osg::Vec3d(x, y-dy, z));
-                }
-
-                osg::Vec3d west (-udx,    0, samples[0]);
-                osg::Vec3d east ( udx,    0, samples[1]);
-                osg::Vec3d north(   0,  udy, samples[2]);
-                osg::Vec3d south(   0, -udy, samples[3]);
-
-                // calculate the normal at the center point.
-                osg::Vec3 normal = (east-west) ^ (north-south);
-                normal.normalize();
-
-                // encode as: xyz[-1..1]=>r[0..255]. (In reality Z will always fall 
-                // between [0..1] but a uniform encoding makes the shader code simpler.)
-                normal.x() = normal.x()*0.5f + 0.5f;
-                normal.y() = normal.y()*0.5f + 0.5f;
-                normal.z() = normal.z()*0.5f + 0.5f;
-                normal.normalize();
-
-                write(osg::Vec4f(normal,1), s, t);
-            }
-        }
-
-        return image;
-    }
-
-
-private:
-    NoiseOptions                 _options;
-    osg::ref_ptr<osgDB::Options> _dbOptions;
-};
-
-
-class ReaderWriterNoise : public TileSourceDriver
-{
-    public:
-        ReaderWriterNoise()
-        {
-            supportsExtension( "osgearth_noise", "Procedurally generated terrain" );
-        }
-
-        virtual const char* className()
-        {
-            return "Noise ReaderWriter";
-        }
-
-        virtual ReadResult readObject(const std::string& file_name, const Options* options) const
-        {
-            if ( !acceptsExtension(osgDB::getLowerCaseFileExtension( file_name )))
-                return ReadResult::FILE_NOT_HANDLED;
-
-            return new NoiseSource( getTileSourceOptions(options) );
-        }
-};
-
-REGISTER_OSGPLUGIN(osgearth_noise, ReaderWriterNoise)
-
diff --git a/src/osgEarthDrivers/ocean_simple/CMakeLists.txt b/src/osgEarthDrivers/ocean_simple/CMakeLists.txt
new file mode 100644
index 0000000..298b512
--- /dev/null
+++ b/src/osgEarthDrivers/ocean_simple/CMakeLists.txt
@@ -0,0 +1,25 @@
+
+SET(TARGET_SRC ElevationProxyImageLayer.cpp
+               SimpleOceanDriver.cpp
+               SimpleOceanNode.cpp
+)
+               
+SET(TARGET_H   ElevationProxyImageLayer
+               SimpleOceanOptions
+               SimpleOceanNode
+               SimpleOceanShaders
+)
+
+SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES}
+    osgEarthUtil
+    osgEarthSymbology
+)
+
+SETUP_PLUGIN(osgearth_ocean_simple)
+
+# to install public driver includes:
+SET(LIB_NAME ocean_simple)
+
+SET(LIB_PUBLIC_HEADERS SimpleOceanOptions)
+
+INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
diff --git a/src/osgEarthDrivers/ocean_surface/ElevationProxyImageLayer b/src/osgEarthDrivers/ocean_simple/ElevationProxyImageLayer
similarity index 70%
rename from src/osgEarthDrivers/ocean_surface/ElevationProxyImageLayer
rename to src/osgEarthDrivers/ocean_simple/ElevationProxyImageLayer
index 89ee3dd..f275549 100644
--- a/src/osgEarthDrivers/ocean_surface/ElevationProxyImageLayer
+++ b/src/osgEarthDrivers/ocean_simple/ElevationProxyImageLayer
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -16,13 +16,14 @@
  * You should have received a copy of the GNU Lesser General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
-#ifndef OSGEARTH_DRIVER_OCEAN_SURFACE_ELEV_PROXY_IMAGE_LAYER
-#define OSGEARTH_DRIVER_OCEAN_SURFACE_ELEV_PROXY_IMAGE_LAYER 1
+#ifndef OSGEARTH_DRIVER_SIMPLE_OCEAN_ELEV_PROXY_IMAGE_LAYER
+#define OSGEARTH_DRIVER_SIMPLE_OCEAN_ELEV_PROXY_IMAGE_LAYER 1
 
 #include <osgEarth/MapFrame>
 #include <osgEarth/ImageLayer>
+#include <osgEarth/ThreadingUtils>
 
-namespace osgEarth_ocean_surface
+namespace osgEarth { namespace Drivers { namespace SimpleOcean
 {
     using namespace osgEarth;
 
@@ -37,7 +38,9 @@ namespace osgEarth_ocean_surface
          * Constucts a proxy layer
          * @param sourceMap Map from which to read heightfields
          */
-        ElevationProxyImageLayer( Map* sourceMap, const ImageLayerOptions& options );
+        ElevationProxyImageLayer(
+            const Map* sourceMap, 
+            const ImageLayerOptions& options);
 
         /** dtor */
         virtual ~ElevationProxyImageLayer() { }
@@ -46,17 +49,17 @@ namespace osgEarth_ocean_surface
 
         virtual void initTileSource();
 
-        virtual bool isKeyValid( const TileKey& key ) const;
+        virtual bool isKeyInRange( const TileKey& key ) const;
 
         virtual bool isCached( const TileKey& key ) const;
 
-        virtual GeoImage createImage( const TileKey& key, ProgressCallback* progress, bool forceFallback );
+        virtual GeoImage createImage( const TileKey& key, ProgressCallback* progress);
 
     private:
-        osg::observer_ptr<Map> _sourceMap;
-        MapFrame               _mapf;
+        MapFrame _mapf;
+        Threading::Mutex _mapfMutex;
     };
 
-} // namespace osgEarth_ocean_surface
+} } } // namespace osgEarth::Drivers::SimpleOcean
 
-#endif // OSGEARTH_DRIVER_OCEAN_SURFACE_ELEV_PROXY_IMAGE_LAYER
+#endif // OSGEARTH_DRIVER_SIMPLE_OCEAN_ELEV_PROXY_IMAGE_LAYER
diff --git a/src/osgEarthDrivers/ocean_simple/ElevationProxyImageLayer.cpp b/src/osgEarthDrivers/ocean_simple/ElevationProxyImageLayer.cpp
new file mode 100644
index 0000000..441f887
--- /dev/null
+++ b/src/osgEarthDrivers/ocean_simple/ElevationProxyImageLayer.cpp
@@ -0,0 +1,88 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include "ElevationProxyImageLayer"
+
+using namespace osgEarth;
+using namespace osgEarth::Drivers::SimpleOcean;
+
+#define LC "[ElevationProxyImageLayer] "
+
+
+ElevationProxyImageLayer::ElevationProxyImageLayer(const Map* sourceMap,
+                                                   const ImageLayerOptions& options ) :
+ImageLayer( options ),
+_mapf     ( sourceMap )
+{
+    _runtimeOptions.cachePolicy() = CachePolicy::NO_CACHE;
+}
+
+void
+ElevationProxyImageLayer::initTileSource()
+{
+    _tileSourceInitAttempted = true;
+    _tileSourceInitFailed    = false;
+}
+
+bool
+ElevationProxyImageLayer::isKeyInRange( const TileKey& key ) const
+{
+    return key.getLevelOfDetail() <= *_runtimeOptions.maxLevel();
+}
+
+bool
+ElevationProxyImageLayer::isCached( const TileKey& key ) const
+{
+    return true;
+}
+
+GeoImage
+ElevationProxyImageLayer::createImage(const TileKey& key, ProgressCallback* progress)
+{
+    if ( _mapf.needsSync() )
+    {
+        Threading::ScopedMutexLock lock(_mapfMutex);
+        if ( _mapf.needsSync() )
+        {
+            _mapf.sync();
+        }
+    }
+
+    osg::ref_ptr<osg::HeightField> hf;
+
+    if ( _mapf.populateHeightField(hf, key, true) )
+    {
+        // encode the heightfield as a 16-bit normalized LUNIMANCE image
+        osg::Image* image = new osg::Image();
+        image->allocateImage(hf->getNumColumns(), hf->getNumRows(), 1, GL_LUMINANCE, GL_UNSIGNED_SHORT);
+        image->setInternalTextureFormat( GL_LUMINANCE16 );
+        const osg::FloatArray* floats = hf->getFloatArray();
+        for( unsigned int i = 0; i < floats->size(); ++i  )
+        {
+            int col = i % hf->getNumColumns();
+            int row = i / hf->getNumColumns();
+            *(unsigned short*)image->data( col, row ) = (unsigned short)(32768 + (short)floats->at(i));
+        }
+
+        return GeoImage( image, key.getExtent() );
+    }
+    else
+    {
+        return GeoImage::INVALID;
+    }
+}
diff --git a/src/osgEarthDrivers/ocean_simple/SimpleOceanDriver.cpp b/src/osgEarthDrivers/ocean_simple/SimpleOceanDriver.cpp
new file mode 100644
index 0000000..fd60dde
--- /dev/null
+++ b/src/osgEarthDrivers/ocean_simple/SimpleOceanDriver.cpp
@@ -0,0 +1,67 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osgDB/ReaderWriter>
+#include <osgDB/FileNameUtils>
+#include <osgDB/Registry>
+#include <osgDB/FileUtils>
+
+#include "SimpleOceanNode"
+
+#undef  LC
+#define LC "[SimpleOceanDriver] "
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+namespace osgEarth { namespace Drivers { namespace SimpleOcean
+{
+    struct SimpleOceanDriver : public OceanDriver
+    {
+        SimpleOceanDriver()
+        {
+            supportsExtension( "osgearth_ocean_simple", "Simple Ocean" );
+        }
+
+        ReadResult readObject(const std::string& url, const Options* options) const
+        {
+            return readNode( url, options );
+        }
+
+        ReadResult readNode(const std::string& url, const Options* options) const
+        {
+            std::string ext = osgDB::getLowerCaseFileExtension(url);
+            if ( !acceptsExtension(ext) )
+                return ReadResult::FILE_NOT_HANDLED;
+
+            MapNode*           mapNode( getMapNode(options) );
+            SimpleOceanOptions oceanOptions( getOceanOptions(options) );
+
+            if ( !mapNode )
+            {
+                OE_WARN << LC << "Internal error - no MapNode marshalled" << std::endl;
+                return ReadResult::ERROR_IN_READING_FILE;
+            }
+
+            return new SimpleOceanNode( oceanOptions, mapNode );
+        }
+    };
+
+    REGISTER_OSGPLUGIN( osgearth_ocean_simple, SimpleOceanDriver )
+
+} } } // namespace osgEarth::Drivers::SimpleOcean
diff --git a/src/osgEarthDrivers/ocean_surface/OceanSurfaceContainer b/src/osgEarthDrivers/ocean_simple/SimpleOceanNode
similarity index 52%
rename from src/osgEarthDrivers/ocean_surface/OceanSurfaceContainer
rename to src/osgEarthDrivers/ocean_simple/SimpleOceanNode
index 2393966..4e90c84 100644
--- a/src/osgEarthDrivers/ocean_surface/OceanSurfaceContainer
+++ b/src/osgEarthDrivers/ocean_simple/SimpleOceanNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -16,48 +16,57 @@
  * You should have received a copy of the GNU Lesser General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
-#ifndef OSGEARTH_DRIVER_OCEAN_SURFACE_CONTAINER
-#define OSGEARTH_DRIVER_OCEAN_SURFACE_CONTAINER 1
-
-#include "OceanSurface"
-#include <osgEarth/MapNode>
-#include <osgEarth/MapNodeObserver>
-#include <osgEarth/ImageLayer>
-#include <osgEarth/URI>
-#include <osg/Node>
-#include <osg/Image>
-#include <osg/Uniform>
-
-namespace osgEarth_ocean_surface
+#ifndef OSGEARTH_DRIVER_SIMPLE_OCEAN_NODE
+#define OSGEARTH_DRIVER_SIMPLE_OCEAN_NODE 1
+
+#include "SimpleOceanOptions"
+#include <osgEarthUtil/Ocean>
+
+namespace osgEarth {
+    class MapNode;
+}
+namespace osg {
+    class Uniform;
+}
+
+namespace osgEarth { namespace Drivers { namespace SimpleOcean
 {
     using namespace osgEarth;
-    using namespace osgEarth::Drivers;
+    using namespace osgEarth::Util;
 
-    class OceanSurfaceContainer : public osg::Group,
-                                  public MapNodeObserver
+    /**
+     * Node tha renders an ocean surface over a MapNode.
+     */
+    class SimpleOceanNode : public OceanNode
     {
     public:
-        OceanSurfaceContainer( MapNode* mapNode, const OceanSurfaceOptions& options );
+        /**
+         * Constructs a new ocean surface node. Add this to your scene graph somewhere
+         * alongside your MapNode.
+         */
+        SimpleOceanNode(
+            const SimpleOceanOptions& options,
+            MapNode*                  mapNode);
 
-        /** dtor */
-        virtual ~OceanSurfaceContainer() { }
+    protected: // OceanNode
 
-        void apply( const OceanSurfaceOptions& options );
+        void onSetSeaLevel();
 
-    public:
-        MapNode* getMapNode() { return _parentMapNode.get(); }
-        void setMapNode( MapNode* mapNode );
+        virtual ~SimpleOceanNode() { }
 
     private:
+
         osg::observer_ptr<MapNode> _parentMapNode;
-        OceanSurfaceOptions        _options;
+        SimpleOceanOptions         _options;
         osg::ref_ptr<osg::Uniform> _seaLevel, _lowFeather, _highFeather;
         osg::ref_ptr<osg::Uniform> _maxRange, _fadeRange;
         osg::ref_ptr<osg::Uniform> _baseColor;
 
         void rebuild();
+
+        void applyOptions();
     };
 
-} // namespace osgEarth_ocean_surface
+} } } // namespace osgEarth::Drivers::SimpleOcean
 
-#endif // OSGEARTH_DRIVER_OCEAN_SURFACE_CONTAINER
+#endif // OSGEARTH_DRIVER_SIMPLE_OCEAN_NODE
diff --git a/src/osgEarthDrivers/ocean_simple/SimpleOceanNode.cpp b/src/osgEarthDrivers/ocean_simple/SimpleOceanNode.cpp
new file mode 100644
index 0000000..24e9de6
--- /dev/null
+++ b/src/osgEarthDrivers/ocean_simple/SimpleOceanNode.cpp
@@ -0,0 +1,275 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include "SimpleOceanNode"
+#include "ElevationProxyImageLayer"
+#include "SimpleOceanShaders"
+#include <osgEarth/Map>
+#include <osgEarth/ShaderFactory>
+#include <osgEarth/TextureCompositor>
+#include <osgEarth/ImageUtils>
+#include <osgEarth/CullingUtils>
+#include <osgEarthUtil/SimplexNoise>
+#include <osgEarthDrivers/engine_mp/MPTerrainEngineOptions>
+
+#include <osg/CullFace>
+#include <osg/Depth>
+#include <osg/Texture2D>
+
+#define LC "[SimpleOceanNode] "
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+using namespace osgEarth::Drivers::SimpleOcean;
+using namespace osgEarth::Drivers::MPTerrainEngine;
+
+namespace
+{
+#define SIR 512
+
+    osg::Image* createSurfaceImage()
+    {
+        static const double twoPI = 2.0*osg::PI;
+
+        osg::Image* image = new osg::Image();
+        image->allocateImage(SIR, SIR, 1, GL_RGBA, GL_UNSIGNED_BYTE);
+
+        SimplexNoise noise;
+        noise.setFrequency(SIR*512.0);
+        noise.setOctaves(16);
+        noise.setRange(1.0, 2.6);
+
+        ImageUtils::PixelWriter write(image);
+        for(int s=0; s<image->s(); ++s)
+        {
+            for(int t=0; t<image->t(); ++t)
+            {
+                double a = (double)s / (double)image->s();
+                double b = (double)t / (double)image->t();
+
+                // trick to create tiled noise (2 ortho circles)
+                // http://www.gamedev.net/blog/33/entry-2138456-seamless-noise/
+
+                double x = cos(a*twoPI)/twoPI;
+                double y = cos(b*twoPI)/twoPI;
+                double z = sin(a*twoPI)/twoPI;
+                double w = sin(b*twoPI)/twoPI;
+
+                double n = noise.getValue(x, y, z, w);
+
+                write( osg::Vec4(0.25*n, 0.3*n, 0.35*n, 1.0), s, t );
+            }
+        }
+
+        return image;
+    }
+}
+
+
+
+
+SimpleOceanNode::SimpleOceanNode(const SimpleOceanOptions& options,
+                                 MapNode*                  mapNode) :
+OceanNode     ( options ),
+_parentMapNode( mapNode ),
+_options      ( options )
+{
+    // set the node mask so that our custom EarthManipulator will NOT find this node.
+    setNodeMask( 0xFFFFFFFE );
+    setSRS( mapNode? mapNode->getMapSRS() : 0L );
+    rebuild();
+}
+
+
+void
+SimpleOceanNode::rebuild()
+{
+    this->removeChildren( 0, this->getNumChildren() );
+
+    if ( _parentMapNode.valid() )
+    {
+        const MapOptions&     parentMapOptions     = _parentMapNode->getMap()->getMapOptions();
+        const MapNodeOptions& parentMapNodeOptions = _parentMapNode->getMapNodeOptions();
+
+        // set up the map to "match" the parent map:
+        MapOptions mo;
+        mo.coordSysType() = parentMapOptions.coordSysType();
+        mo.profile()      = _parentMapNode->getMap()->getProfile()->toProfileOptions();
+
+        // new data model for the ocean:
+        Map* oceanMap = new Map( mo );
+
+        // ditto with the map node options:
+        MapNodeOptions mno;
+        if ( mno.enableLighting().isSet() )
+            mno.enableLighting() = *mno.enableLighting();
+
+        MPTerrainEngineOptions mpoptions;
+        mpoptions.heightFieldSkirtRatio() = 0.0;      // don't want to see skirts
+        mpoptions.minLOD() = _options.maxLOD().get(); // weird, I know
+
+        // so we can the surface from underwater:
+        mpoptions.clusterCulling() = false;       // want to see underwater
+
+        mpoptions.enableBlending() = true;        // gotsta blend with the main node
+
+        mno.setTerrainOptions( mpoptions );
+
+        // make the ocean's map node:
+        MapNode* oceanMapNode = new MapNode( oceanMap, mno );
+
+        // if the caller requested a mask layer, install that now.
+        if ( _options.maskLayer().isSet() )
+        {
+            if ( !_options.maskLayer()->maxLevel().isSet() )
+            {
+                // set the max subdivision level if it's not already specified in the 
+                // mask layer options:
+                _options.maskLayer()->maxLevel() = *_options.maxLOD();
+            }
+
+            // make sure the mask is shared (so we can access it from our shader)
+            // and invisible (so we can't see it)
+            _options.maskLayer()->shared() = true;
+            _options.maskLayer()->visible() = false;
+
+            ImageLayer* maskLayer = new ImageLayer( "ocean-mask", *_options.maskLayer() );
+            oceanMap->addImageLayer( maskLayer );
+        }
+
+        // otherwise, install a "proxy layer" that will use the elevation data in the map
+        // to determine where the ocean is. This approach is limited in that it cannot
+        // detect the difference between ocean and inland areas that are below sea level.
+        else
+        {
+            // install an "elevation proxy" layer that reads elevation tiles from the
+            // parent map and turns them into encoded images for our shader to use.
+            ImageLayerOptions epo( "ocean-proxy" );
+            epo.cachePolicy() = CachePolicy::NO_CACHE;
+            //epo.maxLevel() = *_options.maxLOD();
+            oceanMap->addImageLayer( new ElevationProxyImageLayer(_parentMapNode->getMap(), epo) );
+        }
+
+        this->addChild( oceanMapNode );
+
+        // set up the shaders.
+        osg::StateSet* ss = this->getOrCreateStateSet();
+
+        // install the shaders on the ocean map node.
+        VirtualProgram* vp = VirtualProgram::getOrCreate( ss );
+        vp->setName( "osgEarth SimpleOcean" );
+
+        // use the appropriate shader for the active technique:
+        std::string vertSource = _options.maskLayer().isSet() ? source_vertMask : source_vertProxy;
+        std::string fragSource = _options.maskLayer().isSet() ? source_fragMask : source_fragProxy;
+
+        vp->setFunction( "oe_ocean_vertex",   vertSource, ShaderComp::LOCATION_VERTEX_VIEW );
+        vp->setFunction( "oe_ocean_fragment", fragSource, ShaderComp::LOCATION_FRAGMENT_COLORING );
+
+        // install the slot attribute(s)
+        ss->getOrCreateUniform( "ocean_data", osg::Uniform::SAMPLER_2D )->set( 0 );
+
+        // set up the options uniforms.
+
+        _seaLevel = new osg::Uniform(osg::Uniform::FLOAT, "ocean_seaLevel");
+        ss->addUniform( _seaLevel.get() );
+
+        _lowFeather = new osg::Uniform(osg::Uniform::FLOAT, "ocean_lowFeather");
+        ss->addUniform( _lowFeather.get() );
+
+        _highFeather = new osg::Uniform(osg::Uniform::FLOAT, "ocean_highFeather");
+        ss->addUniform( _highFeather.get() );
+
+        _baseColor = new osg::Uniform(osg::Uniform::FLOAT_VEC4, "ocean_baseColor");
+        ss->addUniform( _baseColor.get() );
+
+        _maxRange = new osg::Uniform(osg::Uniform::FLOAT, "ocean_max_range");
+        ss->addUniform( _maxRange.get() );
+
+        _fadeRange = new osg::Uniform(osg::Uniform::FLOAT, "ocean_fade_range");
+        ss->addUniform( _fadeRange.get() );
+
+        // trick to mitigate z-fighting..
+        ss->setAttributeAndModes( new osg::Depth(osg::Depth::LEQUAL, 0.0, 1.0, false) );
+        ss->setRenderBinDetails( 15, "RenderBin" );
+
+        // load up a surface texture
+        osg::ref_ptr<osg::Image> surfaceImage;
+        ss->getOrCreateUniform( "ocean_has_surface_tex", osg::Uniform::BOOL )->set( false );
+        if ( _options.textureURI().isSet() )
+        {
+            //TODO: enable cache support here?
+            surfaceImage = _options.textureURI()->getImage();
+        }
+
+        if ( !surfaceImage.valid() )
+        {
+            surfaceImage = createSurfaceImage();
+        }
+
+        if ( surfaceImage.valid() )
+        {
+            osg::Texture2D* tex = new osg::Texture2D( surfaceImage.get() );
+            tex->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR );
+            tex->setFilter( osg::Texture::MAG_FILTER, osg::Texture::LINEAR );
+            tex->setWrap  ( osg::Texture::WRAP_S, osg::Texture::REPEAT );
+            tex->setWrap  ( osg::Texture::WRAP_T, osg::Texture::REPEAT );
+
+            ss->setTextureAttributeAndModes( 2, tex, 1 );
+            ss->getOrCreateUniform( "ocean_surface_tex", osg::Uniform::SAMPLER_2D )->set( 2 );
+            ss->getOrCreateUniform( "ocean_has_surface_tex", osg::Uniform::BOOL )->set( true );
+        }
+
+        // remove backface culling so we can see underwater
+        // (use OVERRIDE since the terrain engine sets back face culling.)
+        ss->setAttributeAndModes( 
+            new osg::CullFace(), 
+            osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE );
+
+        // Material.
+        osg::Material* m = new osg::Material();
+        m->setAmbient(m->FRONT_AND_BACK, osg::Vec4(0,0,0,1));
+        m->setDiffuse(m->FRONT_AND_BACK, osg::Vec4(1,1,1,1));
+        m->setSpecular(m->FRONT_AND_BACK, osg::Vec4(0.1,0.1,0.1,1));
+        m->setEmission(m->FRONT_AND_BACK, osg::Vec4(0,0,0,1));
+        m->setShininess(m->FRONT_AND_BACK, 32.0);
+        ss->setAttributeAndModes(m, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE );
+
+        // force apply options:
+        applyOptions();
+    }
+}
+
+void
+SimpleOceanNode::applyOptions()
+{
+    setSeaLevel( *_options.seaLevel() );
+
+    _lowFeather->set( *_options.lowFeatherOffset() );
+    _highFeather->set( *_options.highFeatherOffset() );
+    _baseColor->set( *_options.baseColor() );
+    _maxRange->set( *_options.maxRange() );
+    _fadeRange->set( *_options.fadeRange() );
+
+}
+
+void
+SimpleOceanNode::onSetSeaLevel()
+{
+    _seaLevel->set( getSeaLevel() );
+}
diff --git a/src/osgEarthDrivers/ocean_surface/OceanSurface b/src/osgEarthDrivers/ocean_simple/SimpleOceanOptions
similarity index 69%
rename from src/osgEarthDrivers/ocean_surface/OceanSurface
rename to src/osgEarthDrivers/ocean_simple/SimpleOceanOptions
index f7f534a..5aa112f 100644
--- a/src/osgEarthDrivers/ocean_surface/OceanSurface
+++ b/src/osgEarthDrivers/ocean_simple/SimpleOceanOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -16,28 +16,26 @@
  * You should have received a copy of the GNU Lesser General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
-#ifndef OSGEARTH_DRIVER_OCEAN_SURFACE
-#define OSGEARTH_DRIVER_OCEAN_SURFACE 1
+#ifndef OSGEARTH_DRIVER_SIMPLE_OCEAN
+#define OSGEARTH_DRIVER_SIMPLE_OCEAN 1
 
+#include <osgEarthUtil/Ocean>
 #include <osgEarth/MapNode>
 #include <osgEarth/ImageLayer>
 #include <osgEarth/URI>
 #include <osgEarth/Registry>
 #include <osgEarthSymbology/Color>
-#include <osg/Node>
-#include <osg/Image>
-#include <osgDB/ReadFile>
-#include <osgDB/Options>
 
-namespace osgEarth { namespace Drivers
+namespace osgEarth { namespace Drivers { namespace SimpleOcean
 {
     using namespace osgEarth;
-    using namespace osgEarth::Symbology;
+    using namespace osgEarth::Util;
 
     /**
      * Options for controlling the ocean surface node.
+     * (header-only)
      */
-    class /*header-only*/ OceanSurfaceOptions : public ConfigOptions
+    class SimpleOceanOptions : public OceanOptions
     {
     public:
         /** Nominal sea level in meters (relative to ellipsoid/geoid); default is zero. */
@@ -81,13 +79,13 @@ namespace osgEarth { namespace Drivers
         const optional<ImageLayerOptions>& maskLayer() const { return _maskLayerOptions; }
 
     public:
-        OceanSurfaceOptions( const Config& conf =Config() )
-            : ConfigOptions     ( conf ),
+        SimpleOceanOptions( const ConfigOptions& conf =ConfigOptions() )
+            : OceanOptions      ( conf ),
               _seaLevel         ( 0.0f ),
               _lowFeatherOffset ( -100.0f ),
               _highFeatherOffset( -10.0f ),
               _maxRange         ( 1000000.0f ),
-              _fadeRange        ( 225000.0f ),
+              _fadeRange        ( 125000.0f ),
               _maxLOD           ( 11 ),
               _baseColor        ( osg::Vec4(0.2, 0.3, 0.5, 0.8) )
         {
@@ -95,11 +93,11 @@ namespace osgEarth { namespace Drivers
         }
 
         /** dtor */
-        virtual ~OceanSurfaceOptions() { }
+        virtual ~SimpleOceanOptions() { }
 
     public:
         Config getConfig() const {
-            Config conf = ConfigOptions::newConfig();
+            Config conf = OceanOptions::newConfig();
             conf.updateIfSet("sea_level",           _seaLevel );
             conf.updateIfSet("high_feather_offset", _highFeatherOffset );
             conf.updateIfSet("low_feather_offset",  _lowFeatherOffset );
@@ -114,7 +112,7 @@ namespace osgEarth { namespace Drivers
 
     protected:
         void mergeConfig( const Config& conf ) {
-            ConfigOptions::mergeConfig( conf );
+            OceanOptions::mergeConfig( conf );
             fromConfig( conf );
         }
 
@@ -143,59 +141,6 @@ namespace osgEarth { namespace Drivers
         optional<ImageLayerOptions> _maskLayerOptions;
     };
 
+} } } // namespace osgEarth::Drivers::SimpleOcean
 
-    /**
-     * Node tha renders an ocean surface over a MapNode.
-     */
-    class /*header-only*/ OceanSurfaceNode : public osg::Group
-    {
-    public:
-        /**
-         * Constructs a new ocean surface node. Add this to your scene graph somewhere
-         * alongside your MapNode.
-         */
-        OceanSurfaceNode( MapNode* mapNode, const OceanSurfaceOptions& initialOptions =OceanSurfaceOptions() )
-            : _mapNode( mapNode ), _options( initialOptions )
-        {
-            osg::Node* node = load();
-            if ( node )
-                this->addChild( node );
-        }
-
-        /** dtor */
-        virtual ~OceanSurfaceNode() { }
-
-        /**
-         * Options controlling the look and behavior of the ocean
-         */         
-        OceanSurfaceOptions& options() { return _options; }
-        const OceanSurfaceOptions& options() const { return _options; }
-
-        /**
-         * Call this whenever you change options and want to apply the changes.
-         */
-        void dirty() { load(); }
-
-    private:
-        osg::observer_ptr<MapNode> _mapNode;
-        OceanSurfaceOptions        _options;
-
-        osg::Node* load()
-        {
-            osg::Node* result = 0L;
-            osg::ref_ptr<MapNode> safeMapNode = _mapNode.get();
-            if ( safeMapNode.valid() )
-            {
-                osg::ref_ptr<osgDB::Options> o = Registry::instance()->cloneOrCreateOptions();
-                o->setPluginData( "mapNode", (void*)_mapNode.get() );
-                o->setPluginData( "options", (void*)&_options );
-                osgDB::ReaderWriter::ReadResult r = osgDB::readNodeFile( ".osgearth_ocean_surface", o.get() );
-                result = r.takeNode();
-            }
-            return result;
-        }
-    };
-
-} } // namespace osgEarth::Drivers
-
-#endif // OSGEARTH_DRIVER_OCEAN_SURFACE
+#endif // OSGEARTH_DRIVER_SIMPLE_OCEAN
diff --git a/src/osgEarthDrivers/ocean_surface/OceanShaders b/src/osgEarthDrivers/ocean_simple/SimpleOceanShaders
similarity index 88%
rename from src/osgEarthDrivers/ocean_surface/OceanShaders
rename to src/osgEarthDrivers/ocean_simple/SimpleOceanShaders
index c813c33..43cb296 100644
--- a/src/osgEarthDrivers/ocean_surface/OceanShaders
+++ b/src/osgEarthDrivers/ocean_simple/SimpleOceanShaders
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -16,17 +16,16 @@
  * You should have received a copy of the GNU Lesser General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
-#ifndef OSGEARTH_DRIVER_OCEAN_SURFACE_SHADERS
-#define OSGEARTH_DRIVER_OCEAN_SURFACE_SHADERS 1
+#ifndef OSGEARTH_DRIVER_SIMPLE_OCEAN_SHADERS
+#define OSGEARTH_DRIVER_SIMPLE_OCEAN_SHADERS 1
+
+#include <osgEarth/VirtualProgram>
 
 namespace
 {
     static char source_vertProxy[] =
-
         "#version " GLSL_VERSION_STR "\n"
-#ifdef OSG_GLES2_AVAILABLE
-        "precision mediump float;\n"
-#endif
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
 
         "vec2 ocean_xyz_to_spherical(in vec3 xyz) \n"
         "{ \n"
@@ -48,18 +47,21 @@ namespace
         "varying float ocean_v_range; \n"                  // distance from camera to current vertex
         "varying float ocean_v_enorm; \n"                  // normalized terrain height at vertex [0..1]
 
-        "void oe_ocean_vertex(inout vec4 VertexMODEL) \n"
+        "varying vec3 oe_Normal; \n" 
+        "uniform sampler2D ocean_surface_tex; \n"        // surface texture
+        "uniform bool ocean_has_surface_tex; \n"         // whether there's a surface texture
+
+        "void oe_ocean_vertex(inout vec4 VertexVIEW) \n"
         "{ \n"
         "   osg_FrontColor = gl_Color; \n"
 
         // adjust our vert for the sea level - extrude along the normal vector 
-        // (this must be done in modelview space to preserve precision)
-        "   vec4 mvVertex = VertexMODEL; \n"
-        "   vec3 mvNormal = gl_NormalMatrix * gl_Normal; \n"
+        // (this must be done in view space to preserve precision)
+        "   vec4 mvVertex = VertexVIEW; \n"
+        "   vec3 mvNormal = oe_Normal; \n"
         "   vec4 mvVertex2 = vec4(mvVertex.xyz + (mvNormal * ocean_seaLevel), mvVertex.w ); \n"
 
-        "   VertexMODEL = mvVertex2; \n"
-        //"   gl_Position = gl_ProjectionMatrix * mvVertex2; \n"
+        "   VertexVIEW = mvVertex2; \n"
 
         // read normalized [0..1] elevation data from the height texture:
         "   ocean_v_enorm = texture2D( ocean_data, gl_MultiTexCoord0.st ).r; \n"
@@ -78,17 +80,20 @@ namespace
         "   vec2 lonlat = ocean_xyz_to_spherical( worldVertex.xyz/worldVertex.w ); \n"
         "   ocean_surface_tex_coord.xy = lonlat / 0.0005; \n"
         "   ocean_surface_tex_coord.zw = ocean_surface_tex_coord.xy; \n"
-        "   ocean_surface_tex_coord.x += mod(osg_FrameTime,100.0)/100.0;\n"
-        "   ocean_surface_tex_coord.w -= mod(osg_FrameTime,25.0)/25.0;\n"
+        "   ocean_surface_tex_coord.w -= mod(0.1*osg_FrameTime,25.0)/25.0;\n"
+
+        // fake waves:
+        "   if (ocean_has_surface_tex) { \n"
+        "       vec4 t0 = texture2D(ocean_surface_tex, ocean_surface_tex_coord.xy); \n"
+        "       vec4 t1 = texture2D(ocean_surface_tex, ocean_surface_tex_coord.zw); \n"
+        "       oe_Normal = normalize((3.0*t0.rgb*t1.rgb)*2.0-1.0); \n"
+        "   } \n"
         "} \n";
 
 
     char source_fragProxy[] = 
-
         "#version " GLSL_VERSION_STR "\n"
-#ifdef OSG_GLES2_AVAILABLE
-        "precision mediump float;\n"
-#endif
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
 
         // clamps a value to the vmin/vmax range, then re-maps it to the r0/r1 range:
         "float ocean_remap( float val, float vmin, float vmax, float r0, float r1 ) \n"
@@ -142,7 +147,7 @@ namespace
         "    float terrainEffect = ocean_remap( terrainHeight, ocean_seaLevel+ocean_lowFeather, ocean_seaLevel+ocean_highFeather, 1.0, 0.0 ); \n" 
 
         // color it
-        "    color = vec4( color.rgb, terrainEffect * rangeEffect * color.a ); \n"
+        "    color = vec4( color.rgb, terrainEffect * rangeEffect ); \n" //* color.a ); \n"
 
         //"    color = vec4( 1, 0, 0, 1 ); \n" // debugging
         "} \n";
@@ -150,11 +155,8 @@ namespace
 
 
     char source_vertMask[] =
-
         "#version " GLSL_VERSION_STR "\n"
-#ifdef OSG_GLES2_AVAILABLE
-        "precision mediump float;\n"
-#endif
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
 
         "vec2 ocean_xyz_to_spherical(in vec3 xyz) \n"
         "{ \n"
@@ -204,16 +206,14 @@ namespace
         "   ocean_surface_tex_coord.xy = lonlat / 0.0005; \n"
         "   ocean_surface_tex_coord.zw = ocean_surface_tex_coord.xy; \n"
         "   ocean_surface_tex_coord.x += mod(osg_FrameTime,100.0)/100.0;\n"
-        "   ocean_surface_tex_coord.w -= mod(osg_FrameTime,50.0)/50.0;\n"
+        //"   ocean_surface_tex_coord.w -= mod(osg_FrameTime,50.0)/50.0;\n"
         "} \n";
 
 
     char source_fragMask[] = 
 
         "#version " GLSL_VERSION_STR "\n"
-#ifdef OSG_GLES2_AVAILABLE
-        "precision mediump float;\n"
-#endif
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
 
         // clamps a value to the vmin/vmax range, then re-maps it to the r0/r1 range:
         "float ocean_remap( float val, float vmin, float vmax, float r0, float r1 ) \n"
@@ -268,9 +268,6 @@ namespace
 
         //"    color = vec4( 1, 0, 0, 1 ); \n" // debugging
         "} \n";
-
-
-
 }
 
-#endif // OSGEARTH_DRIVER_OCEAN_SURFACE_SHADERS
+#endif // OSGEARTH_DRIVER_SIMPLE_OCEAN_SHADERS
diff --git a/src/osgEarthDrivers/ocean_surface/CMakeLists.txt b/src/osgEarthDrivers/ocean_surface/CMakeLists.txt
deleted file mode 100644
index 4ddfd28..0000000
--- a/src/osgEarthDrivers/ocean_surface/CMakeLists.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-
-SET(TARGET_SRC ElevationProxyImageLayer.cpp
-               OceanCompositor.cpp
-               OceanSurfaceContainer.cpp
-               ReaderWriterOceanSurface.cpp
-)
-               
-SET(TARGET_H   ElevationProxyImageLayer
-               OceanCompositor
-               OceanSurface
-               OceanSurfaceContainer
-               OceanShaders
-)
-
-SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES} osgEarthSymbology)
-
-SETUP_PLUGIN(osgearth_ocean_surface)
-
-# to install public driver includes:
-SET(LIB_NAME ocean_surface)
-SET(LIB_PUBLIC_HEADERS OceanSurface)
-INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
diff --git a/src/osgEarthDrivers/ocean_surface/ElevationProxyImageLayer.cpp b/src/osgEarthDrivers/ocean_surface/ElevationProxyImageLayer.cpp
deleted file mode 100644
index 2286255..0000000
--- a/src/osgEarthDrivers/ocean_surface/ElevationProxyImageLayer.cpp
+++ /dev/null
@@ -1,76 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#include "ElevationProxyImageLayer"
-
-using namespace osgEarth_ocean_surface;
-using namespace osgEarth;
-
-ElevationProxyImageLayer::ElevationProxyImageLayer( Map* sourceMap, const ImageLayerOptions& options ) :
-ImageLayer( options ),
-_sourceMap( sourceMap ),
-_mapf     ( sourceMap )
-{
-    _runtimeOptions.cachePolicy() = CachePolicy::NO_CACHE;
-}
-
-void
-ElevationProxyImageLayer::initTileSource()
-{
-    _tileSourceInitAttempted = true;
-    _tileSourceInitFailed    = true;
-}
-
-bool
-ElevationProxyImageLayer::isKeyValid( const TileKey& key ) const
-{
-    return key.getLevelOfDetail() <= *_runtimeOptions.maxLevel();
-}
-
-bool
-ElevationProxyImageLayer::isCached( const TileKey& key ) const
-{
-    return true;
-}
-
-GeoImage
-ElevationProxyImageLayer::createImage(const TileKey& key, ProgressCallback* progress, bool forceFallback)
-{
-    osg::ref_ptr<Map> map = _sourceMap.get();
-    if ( map.valid() )
-    {
-        osg::ref_ptr<osg::HeightField> hf;
-        if ( map->getHeightField( key, true, hf ) )
-        {
-            // encode the heightfield as a 16-bit normalized LUNIMANCE image
-            osg::Image* image = new osg::Image();
-            image->allocateImage(hf->getNumColumns(), hf->getNumRows(), 1, GL_LUMINANCE, GL_UNSIGNED_SHORT);
-            image->setInternalTextureFormat( GL_LUMINANCE16 );
-            const osg::FloatArray* floats = hf->getFloatArray();
-            for( unsigned int i = 0; i < floats->size(); ++i  )
-            {
-                int col = i % hf->getNumColumns();
-                int row = i / hf->getNumColumns();
-                *(unsigned short*)image->data( col, row ) = (unsigned short)(32768 + (short)floats->at(i));
-            }
-
-            return GeoImage( image, key.getExtent() );
-        }
-    }
-    return GeoImage::INVALID;
-}
diff --git a/src/osgEarthDrivers/ocean_surface/OceanCompositor b/src/osgEarthDrivers/ocean_surface/OceanCompositor
deleted file mode 100644
index 685cb1b..0000000
--- a/src/osgEarthDrivers/ocean_surface/OceanCompositor
+++ /dev/null
@@ -1,60 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#ifndef OSGEARTH_DRIVER_OCEAN_SURFACE_COMPOSITOR
-#define OSGEARTH_DRIVER_OCEAN_SURFACE_COMPOSITOR 1
-
-#include <osgEarth/TextureCompositor>
-#include "OceanSurface"
-
-namespace osgEarth_ocean_surface
-{
-    using namespace osgEarth;
-
-    /**
-     * A custom texture compositor for rendering the ocean surface.
-     */
-    class OceanCompositor : public TextureCompositorTechnique
-    {
-    public:
-        OceanCompositor(const osgEarth::Drivers::OceanSurfaceOptions& options);
-
-        /** dtor */
-        virtual ~OceanCompositor() { }
-
-        virtual bool requiresUnitTextureSpace() const { return true; }
-
-        virtual bool usesShaderComposition() const { return true; }
-
-        virtual void updateMasterStateSet( osg::StateSet* stateSet, const TextureLayout& layout ) const;
-
-        virtual void applyLayerUpdate(osg::StateSet*       stateSet,
-                                      UID                  layerUID,
-                                      const GeoImage&      preparedImage,
-                                      const TileKey&       tileKey,
-                                      const TextureLayout& layout,
-                                      osg::StateSet*       parentStateSet) const;
-
-    private:
-
-        osgEarth::Drivers::OceanSurfaceOptions _options;
-    };
-
-} // namespace osgEarth_ocean_surface
-
-#endif // OSGEARTH_DRIVER_OCEAN_SURFACE_COMPOSITOR
diff --git a/src/osgEarthDrivers/ocean_surface/OceanCompositor.cpp b/src/osgEarthDrivers/ocean_surface/OceanCompositor.cpp
deleted file mode 100644
index 1bcd43d..0000000
--- a/src/osgEarthDrivers/ocean_surface/OceanCompositor.cpp
+++ /dev/null
@@ -1,130 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#include "OceanCompositor"
-#include <osgEarth/ImageUtils>
-#include <osgEarth/Registry>
-#include <osgEarth/VirtualProgram>
-#include <osgEarth/ShaderFactory>
-#include <osg/Texture2D>
-#include "OceanShaders"
-
-#define OCEAN_DATA "ocean_data"
-#define OCEAN_TEX  "ocean_surface_tex"
-
-using namespace osgEarth_ocean_surface;
-using namespace osgEarth;
-using namespace osgEarth::Drivers;
-
-
-OceanCompositor::OceanCompositor(const OceanSurfaceOptions& options) :
-_options( options )
-{
-    //nop
-}
-
-void
-OceanCompositor::updateMasterStateSet(osg::StateSet*       stateSet, 
-                                      const TextureLayout& layout ) const
-{
-    VirtualProgram* vp = VirtualProgram::getOrCreate( stateSet );
-    vp->setName( "osgEarth.OceanCompositor" );
-
-    // install a default lighting shader
-    Registry::shaderFactory()->installLightingShaders( vp );
-
-    // use the appropriate shader for the active technique:
-    std::string vertSource = _options.maskLayer().isSet() ? source_vertMask : source_vertProxy;
-    std::string fragSource = _options.maskLayer().isSet() ? source_fragMask : source_fragProxy;
-
-    vp->setFunction( "oe_ocean_vertex",   vertSource, ShaderComp::LOCATION_VERTEX_VIEW );
-    vp->setFunction( "oe_ocean_fragment", fragSource, ShaderComp::LOCATION_FRAGMENT_COLORING );
-
-    // install the slot attribute(s)
-    stateSet->getOrCreateUniform( OCEAN_DATA, osg::Uniform::SAMPLER_2D )->set( 0 );
-    stateSet->getOrCreateUniform( OCEAN_TEX,  osg::Uniform::SAMPLER_2D )->set( 1 );
-}
-
-namespace
-{
-    // probably don't need this
-    std::string makeSamplerName(int slot)
-    {
-        if ( slot == 0 )
-            return OCEAN_DATA;
-        else 
-            return OCEAN_TEX;
-    }
-
-    osg::Texture2D*
-    s_getTexture( osg::StateSet* stateSet, UID layerUID, const TextureLayout& layout, osg::StateSet* parentStateSet)
-    {
-        int slot = layout.getSlot( layerUID, 0 );
-        if ( slot < 0 )
-            return 0L;
-
-        osg::Texture2D* tex = static_cast<osg::Texture2D*>(
-            stateSet->getTextureAttribute( slot, osg::StateAttribute::TEXTURE ) );
-
-        if ( !tex )
-        {
-            tex = new osg::Texture2D();
-
-            tex->setResizeNonPowerOfTwoHint(false);
-            tex->setFilter( osg::Texture::MAG_FILTER, osg::Texture::LINEAR );
-            tex->setFilter( osg::Texture::MIN_FILTER, osg::Texture::NEAREST );
-
-            // configure the wrapping
-            tex->setWrap( osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE );
-            tex->setWrap( osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE );
-
-            stateSet->setTextureAttributeAndModes( slot, tex, osg::StateAttribute::ON );
-        }
-        return tex;
-    }
-}
-
-
-void
-OceanCompositor::applyLayerUpdate(osg::StateSet*       stateSet,
-                                  UID                  layerUID,
-                                  const GeoImage&      preparedImage,
-                                  const TileKey&       tileKey,
-                                  const TextureLayout& layout,
-                                  osg::StateSet*       parentStateSet) const
-{
-    osg::Texture2D* tex = s_getTexture( stateSet, layerUID, layout, parentStateSet);
-    if ( tex )
-    {
-        osg::Image* image = preparedImage.getImage();
-        image->dirty(); // required for ensure the texture recognizes the image as new data
-        tex->setImage( image );
-
-        // set up proper mipmapping filters:
-        if (ImageUtils::isPowerOfTwo( image ) && 
-            !(!image->isMipmap() && ImageUtils::isCompressed(image)) )
-        {
-            if ( tex->getFilter(osg::Texture::MIN_FILTER) != osg::Texture::LINEAR_MIPMAP_LINEAR )
-                tex->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR );
-        }
-        else if ( tex->getFilter(osg::Texture::MIN_FILTER) != osg::Texture::LINEAR )
-        {
-            tex->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR );
-        }
-    }
-}
diff --git a/src/osgEarthDrivers/ocean_surface/OceanSurfaceContainer.cpp b/src/osgEarthDrivers/ocean_surface/OceanSurfaceContainer.cpp
deleted file mode 100644
index c95dd8a..0000000
--- a/src/osgEarthDrivers/ocean_surface/OceanSurfaceContainer.cpp
+++ /dev/null
@@ -1,184 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#include "OceanSurfaceContainer"
-#include "OceanCompositor"
-#include "ElevationProxyImageLayer"
-#include <osgEarth/Map>
-#include <osgEarth/TextureCompositor>
-#include <osgEarthDrivers/osg/OSGOptions>
-#include <osgEarthDrivers/engine_quadtree/QuadTreeTerrainEngineOptions>
-
-#include <osg/CullFace>
-#include <osg/Depth>
-#include <osg/Texture2D>
-
-#define LC "[OceanSurface] "
-
-using namespace osgEarth_ocean_surface;
-
-
-OceanSurfaceContainer::OceanSurfaceContainer( MapNode* mapNode, const OceanSurfaceOptions& options ) :
-_parentMapNode( mapNode ),
-_options      ( options )
-{
-    // set the node mask so that our custom EarthManipulator will NOT find this node.
-    setNodeMask( 0xFFFFFFFE );
-    rebuild();
-}
-
-
-void
-OceanSurfaceContainer::rebuild()
-{
-    this->removeChildren( 0, this->getNumChildren() );
-
-    if ( _parentMapNode.valid() )
-    {
-        const MapOptions&     parentMapOptions     = _parentMapNode->getMap()->getMapOptions();
-        const MapNodeOptions& parentMapNodeOptions = _parentMapNode->getMapNodeOptions();
-
-        // set up the map to "match" the parent map:
-        MapOptions mo;
-        mo.coordSysType() = parentMapOptions.coordSysType();
-        mo.profile()      = _parentMapNode->getMap()->getProfile()->toProfileOptions();
-
-        // new data model for the ocean:
-        Map* oceanMap = new Map( mo );
-
-        // ditto with the map node options:
-        MapNodeOptions mno;
-        if ( mno.enableLighting().isSet() )
-            mno.enableLighting() = *mno.enableLighting();
-
-        QuadTreeTerrainEngineOptions to;
-        to.heightFieldSkirtRatio() = 0.0;  // don't want to see skirts
-        to.clusterCulling() = false;       // want to see underwater
-        to.enableBlending() = true;        // gotsta blend with the main node
-        mno.setTerrainOptions( to );
-
-        // make the ocean's map node:
-        MapNode* oceanMapNode = new MapNode( oceanMap, mno );
-        
-        // install a custom compositor. Must do this before adding any image layers.
-        oceanMapNode->setCompositorTechnique( new OceanCompositor(_options) );
-
-        // if the caller requested a mask layer, install that now.
-        if ( _options.maskLayer().isSet() )
-        {
-            if ( !_options.maskLayer()->maxLevel().isSet() )
-            {
-                // set the max subdivision level if it's not already specified in the 
-                // mask layer options:
-                _options.maskLayer()->maxLevel() = *_options.maxLOD();
-            }
-
-            ImageLayer* maskLayer = new ImageLayer( "ocean-mask", *_options.maskLayer() );
-            oceanMap->addImageLayer( maskLayer );
-        }
-
-        // otherwise, install a "proxy layer" that will use the elevation data in the map
-        // to determine where the ocean is. This approach is limited in that it cannot
-        // detect the difference between ocean and inland areas that are below sea level.
-        else
-        {
-            // install an "elevation proxy" layer that reads elevation tiles from the
-            // parent map and turns them into encoded images for our shader to use.
-            ImageLayerOptions epo( "ocean-proxy" );
-            epo.cachePolicy() = CachePolicy::NO_CACHE;
-            epo.maxLevel() = *_options.maxLOD();
-            oceanMap->addImageLayer( new ElevationProxyImageLayer(_parentMapNode->getMap(), epo) );
-        }
-
-        this->addChild( oceanMapNode );
-
-        // set up the options uniforms.
-        osg::StateSet* ss = this->getOrCreateStateSet();
-
-        _seaLevel = new osg::Uniform(osg::Uniform::FLOAT, "ocean_seaLevel");
-        ss->addUniform( _seaLevel.get() );
-
-        _lowFeather = new osg::Uniform(osg::Uniform::FLOAT, "ocean_lowFeather");
-        ss->addUniform( _lowFeather.get() );
-
-        _highFeather = new osg::Uniform(osg::Uniform::FLOAT, "ocean_highFeather");
-        ss->addUniform( _highFeather.get() );
-
-        _baseColor = new osg::Uniform(osg::Uniform::FLOAT_VEC4, "ocean_baseColor");
-        ss->addUniform( _baseColor.get() );
-
-        _maxRange = new osg::Uniform(osg::Uniform::FLOAT, "ocean_max_range");
-        ss->addUniform( _maxRange.get() );
-
-        _fadeRange = new osg::Uniform(osg::Uniform::FLOAT, "ocean_fade_range");
-        ss->addUniform( _fadeRange.get() );
-
-        // trick to mitigate z-fighting..
-        ss->setAttributeAndModes( new osg::Depth(osg::Depth::LEQUAL, 0.0, 1.0, false) );
-        ss->setRenderBinDetails( 15, "RenderBin" );
-
-        // load up a surface texture
-        ss->getOrCreateUniform( "ocean_has_surface_tex", osg::Uniform::BOOL )->set( false );
-        if ( _options.textureURI().isSet() )
-        {
-            //TODO: enable cache support here:
-            osg::Image* image = _options.textureURI()->getImage();
-            if ( image )
-            {
-                osg::Texture2D* tex = new osg::Texture2D( image );
-                tex->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR );
-                tex->setFilter( osg::Texture::MAG_FILTER, osg::Texture::LINEAR );
-                tex->setWrap  ( osg::Texture::WRAP_S, osg::Texture::REPEAT );
-                tex->setWrap  ( osg::Texture::WRAP_T, osg::Texture::REPEAT );
-
-                ss->setTextureAttributeAndModes( 1, tex, 1 );
-                ss->getOrCreateUniform( "ocean_surface_tex", osg::Uniform::SAMPLER_2D )->set( 1 );
-                ss->getOrCreateUniform( "ocean_has_surface_tex", osg::Uniform::BOOL )->set( true );
-            }
-        }
-
-        // remove backface culling so we can see underwater
-        // (use OVERRIDE since the terrain engine sets back face culling.)
-        ss->setAttributeAndModes( 
-            new osg::CullFace(), 
-            osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE );
-
-        apply( _options );
-    }
-}
-
-void
-OceanSurfaceContainer::apply( const OceanSurfaceOptions& options )
-{
-    OE_DEBUG << LC << "Ocean Options = " << options.getConfig().toJSON(true) << std::endl;
-
-    _seaLevel->set( *options.seaLevel() );
-    _lowFeather->set( *options.lowFeatherOffset() );
-    _highFeather->set( *options.highFeatherOffset() );
-    _baseColor->set( *options.baseColor() );
-    _maxRange->set( *options.maxRange() );
-    _fadeRange->set( *options.fadeRange() );
-}
-
-
-void
-OceanSurfaceContainer::setMapNode( MapNode* parentMapNode )
-{
-    _parentMapNode = parentMapNode;
-    rebuild();
-}
diff --git a/src/osgEarthDrivers/ocean_surface/ReaderWriterOceanSurface.cpp b/src/osgEarthDrivers/ocean_surface/ReaderWriterOceanSurface.cpp
deleted file mode 100644
index fab5d51..0000000
--- a/src/osgEarthDrivers/ocean_surface/ReaderWriterOceanSurface.cpp
+++ /dev/null
@@ -1,88 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#include <osgDB/ReaderWriter>
-#include <osgDB/FileNameUtils>
-#include <osgDB/Registry>
-#include <osgDB/FileUtils>
-#include <osgEarth/Registry>
-#include <osgEarth/ThreadingUtils>
-
-#include "OceanSurface"
-#include "OceanSurfaceContainer"
-
-#undef  LC
-#define LC "[ReaderWriterOceanSurface] "
-
-using namespace osgEarth_ocean_surface;
-using namespace osgEarth;
-using namespace osgEarth::Drivers;
-
-//---------------------------------------------------------------------------
-
-struct ReaderWriterOceanSurface : public osgDB::ReaderWriter
-{
-    ReaderWriterOceanSurface()
-    {
-        supportsExtension( "osgearth_ocean_surface", "Ocean Surface" );
-    }
-
-    ReadResult readObject(const std::string& url, const Options* options) const
-    {
-        return readNode( url, options );
-    }
-
-    ReadResult readNode(const std::string& url, const Options* options) const
-    {
-        std::string ext = osgDB::getLowerCaseFileExtension(url);
-        if ( !acceptsExtension(ext) )
-            return ReadResult::FILE_NOT_HANDLED;
-
-        MapNode*             mapNode    = 0L;
-        OceanSurfaceOptions* osOptions  = 0L;
-
-        if ( options )
-        {
-            mapNode    = static_cast<MapNode*>( const_cast<void*>(options->getPluginData("mapNode")) );
-            osOptions  = static_cast<OceanSurfaceOptions*>( const_cast<void*>(options->getPluginData("options")) );
-        }
-
-        if ( !mapNode )
-            return ReadResult::ERROR_IN_READING_FILE;
-
-        osg::observer_ptr<OceanSurfaceContainer>& node = const_cast<ReaderWriterOceanSurface*>(this)->_oceans.get(mapNode);
-        if ( !node.valid() )
-        {
-            node = new OceanSurfaceContainer( mapNode, osOptions? *osOptions : OceanSurfaceOptions() );
-            return ReadResult( node.get() );
-        }
-        else if ( osOptions )
-        {
-            node->apply( *osOptions );
-            return ReadResult( node.get() );
-        }
-        else
-        {
-            return ReadResult();
-        }
-    }
-
-    Threading::PerObjectMap<MapNode*, osg::observer_ptr<OceanSurfaceContainer> > _oceans;
-};
-
-REGISTER_OSGPLUGIN( osgearth_ocean_surface, ReaderWriterOceanSurface )
diff --git a/src/osgEarthDrivers/ocean_triton/CMakeLists.txt b/src/osgEarthDrivers/ocean_triton/CMakeLists.txt
new file mode 100644
index 0000000..a9a519f
--- /dev/null
+++ b/src/osgEarthDrivers/ocean_triton/CMakeLists.txt
@@ -0,0 +1,30 @@
+
+SET(TARGET_SRC TritonDriver.cpp
+               TritonNode.cpp
+               TritonContext.cpp
+               TritonDrawable.cpp
+)
+               
+SET(TARGET_H   TritonNode
+               TritonOptions
+               TritonContext
+               TritonDrawable
+)
+
+INCLUDE_DIRECTORIES( 
+    ${TRITON_INCLUDE_DIR}
+)
+
+SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES}
+	${TRITON_LIBRARY}
+    osgEarthUtil
+)
+
+SETUP_PLUGIN(osgearth_ocean_triton)
+
+# to install public driver includes:
+SET(LIB_NAME ocean_triton)
+
+SET(LIB_PUBLIC_HEADERS TritonOptions)
+
+INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
diff --git a/src/osgEarthDrivers/ocean_triton/TritonContext b/src/osgEarthDrivers/ocean_triton/TritonContext
new file mode 100644
index 0000000..b0afeb7
--- /dev/null
+++ b/src/osgEarthDrivers/ocean_triton/TritonContext
@@ -0,0 +1,82 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include "TritonOptions"
+#include <osg/Referenced>
+#include <osg/Light>
+#include <osgEarth/ThreadingUtils>
+
+namespace osgEarth {
+    class SpatialReference;
+}
+
+namespace Triton {
+    class ResourceLoader;
+    class Environment;
+    class Ocean;
+}
+
+namespace osgEarth { namespace Drivers { namespace Triton
+{
+    using namespace osgEarth;
+
+    /**
+     * Contains all the Triton SDK handles.
+     */
+    class TritonContext : public osg::Referenced
+    {
+    public:
+        TritonContext(const TritonOptions& options);
+
+        /** Sets the spatial reference system of the map */
+        void setSRS(const SpatialReference* srs);
+
+    public: // accessors
+
+        bool ready() const { return _initAttempted && !_initFailed; }
+
+        /** Spatial reference of the map */
+        const SpatialReference* getSRS() const { return _srs.get(); }
+
+        void initialize(osg::RenderInfo& renderInfo);
+
+        void update(double simTime);
+
+        ::Triton::Environment* getEnvironment() { return _environment; }
+        ::Triton::Ocean* getOcean() { return _ocean; }
+
+    protected:
+
+        virtual ~TritonContext();
+
+
+    private:
+        TritonOptions    _options;
+
+        bool             _initAttempted;
+        bool             _initFailed;
+        Threading::Mutex _initMutex;
+
+        osg::ref_ptr<const SpatialReference> _srs;
+
+        ::Triton::ResourceLoader* _resourceLoader;
+        ::Triton::Environment*    _environment;
+        ::Triton::Ocean*          _ocean;
+    };
+
+} } } // namespace osgEarth::Drivers::Triton
diff --git a/src/osgEarthDrivers/ocean_triton/TritonContext.cpp b/src/osgEarthDrivers/ocean_triton/TritonContext.cpp
new file mode 100644
index 0000000..490a046
--- /dev/null
+++ b/src/osgEarthDrivers/ocean_triton/TritonContext.cpp
@@ -0,0 +1,141 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include "TritonContext"
+#include <Triton.h>
+#include <osg/GLExtensions>
+#include <osgEarth/SpatialReference>
+
+#define LC "[TritonContext] "
+
+using namespace osgEarth;
+using namespace osgEarth::Drivers::Triton;
+
+
+TritonContext::TritonContext(const TritonOptions& options) :
+_options              ( options ),
+_initAttempted        ( false ),
+_initFailed           ( false ),
+_resourceLoader       ( 0L ),
+_environment          ( 0L ),
+_ocean                ( 0L )
+{
+    //nop
+}
+
+void
+TritonContext::setSRS(const SpatialReference* srs)
+{
+    _srs = srs;
+}
+
+void
+TritonContext::initialize(osg::RenderInfo& renderInfo)
+{
+    if ( !_initAttempted && !_initFailed )
+    {
+        // lock/double-check:
+        Threading::ScopedMutexLock excl(_initMutex);
+        if ( !_initAttempted && !_initFailed )
+        {
+            _initAttempted = true;
+
+            _resourceLoader = new ::Triton::ResourceLoader(
+                _options.resourcePath()->c_str() );
+
+            _environment = new ::Triton::Environment();
+
+            _environment->SetLicenseCode(
+                _options.user()->c_str(),
+                _options.licenseCode()->c_str() );
+
+            // "WGS84" is used to represent any ellipsoid.
+            ::Triton::CoordinateSystem cs =
+                _srs->isGeographic() ? ::Triton::WGS84_ZUP :
+                ::Triton::FLAT_ZUP;
+
+            // Set the ellipsoid to match the one in our map's SRS.
+            if ( _srs->isGeographic() )
+            {
+                const osg::EllipsoidModel* ellipsoid = _srs->getEllipsoid();
+                
+                std::string eqRadius = Stringify() << ellipsoid->getRadiusEquator();
+                std::string poRadius = Stringify() << ellipsoid->getRadiusPolar();
+
+                _environment->SetConfigOption( "equatorial-earth-radius-meters", eqRadius.c_str() );
+                _environment->SetConfigOption( "polar-earth-radius-meters",      poRadius.c_str() );
+            }
+
+            float openGLVersion = osg::getGLVersionNumber();
+            enum ::Triton::Renderer tritonOpenGlVersion = ::Triton::OPENGL_2_0;
+            if( openGLVersion == 4.1 )
+                tritonOpenGlVersion = ::Triton::OPENGL_4_1;
+            else if( openGLVersion == 4.0 )
+                tritonOpenGlVersion = ::Triton::OPENGL_4_0;
+            else if( openGLVersion == 3.2 )
+                tritonOpenGlVersion = ::Triton::OPENGL_3_2;
+
+            ::Triton::EnvironmentError err = _environment->Initialize(
+                cs,
+                tritonOpenGlVersion,
+                _resourceLoader );
+
+            if ( err == ::Triton::SUCCEEDED )
+            {
+                ::Triton::WindFetch wf;
+                wf.SetWind( 10.0, 0.0 );
+                _environment->AddWindFetch( wf );
+
+                _ocean = ::Triton::Ocean::Create(
+                    _environment, 
+                    ::Triton::JONSWAP );
+            }
+
+            if ( _ocean )
+            {
+                OE_INFO << LC << "Triton initialized OK!" << std::endl;
+            }
+            else
+            {
+                _initFailed = true;
+                OE_WARN << LC << "Triton initialization failed" << std::endl;
+            }
+        }
+    }
+}
+
+void
+TritonContext::update(double simTime)
+{
+    if ( _ocean )
+    {
+        _ocean->UpdateSimulation( simTime );
+    }
+}
+
+TritonContext::~TritonContext()
+{
+    if ( _ocean )
+        delete _ocean;
+
+    if ( _environment )
+        delete _environment;
+
+    if ( _resourceLoader )
+        delete _resourceLoader;
+}
diff --git a/src/osgEarthDrivers/ocean_triton/TritonDrawable b/src/osgEarthDrivers/ocean_triton/TritonDrawable
new file mode 100644
index 0000000..4831ee7
--- /dev/null
+++ b/src/osgEarthDrivers/ocean_triton/TritonDrawable
@@ -0,0 +1,80 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osg/Drawable>
+#include <osg/RenderInfo>
+#include <osg/TextureCubeMap>
+#include <osg/Version>
+#include <osg/Texture2D>
+
+#include <osgEarth/MapNode>
+
+const unsigned int OCEAN_MASK         = 0x4; // 0100
+
+class OceanTerrainChangedCallback;
+
+namespace osgEarth { namespace Drivers { namespace Triton
+{
+    class TritonContext;
+
+    using namespace osgEarth;
+
+    /**
+     * Custom drawable for rendering the Triton ocean effects
+     */
+    class TritonDrawable : public osg::Drawable
+    {
+    public:
+        TritonDrawable(osgEarth::MapNode* mapNode=NULL, TritonContext* TRITON =0L);
+        META_Object(Triton, TritonDrawable);
+     
+    public: // osg::Drawable
+
+        // custom draw (called with an active GC)
+        void drawImplementation(osg::RenderInfo& ri) const;
+        
+        void setupHeightMap(osgEarth::MapNode* mapNode);
+
+#if OSG_VERSION_GREATER_THAN(3,3,1)
+        osg::BoundingBox computeBoundingBox() const
+#else
+        osg::BoundingBox computeBound() const
+#endif
+            { return osg::BoundingBox(); }
+            
+        void SetPlanarReflectionMap(osg::Texture2D* map) {_planarReflectionMap = map;};
+        void SetPlanarReflectionProjection(osg::RefMatrix * proj) {_planarReflectionProjection = proj;};
+
+    protected:
+        virtual ~TritonDrawable() { }
+
+        osg::observer_ptr<TritonContext>  _TRITON;
+        osg::observer_ptr<osgEarth::MapNode> _mapNode;
+        osg::ref_ptr<osg::TextureCubeMap> _cubeMap;
+        osg::BoundingBox                  _bbox;
+        osg::ref_ptr<osg::Texture2D> _heightMap;
+        osg::ref_ptr<osg::Camera> _heightCamera;
+        osg::observer_ptr<OceanTerrainChangedCallback> _terrainChangedCallback;
+        
+        osg::ref_ptr< osg::Texture2D >       _planarReflectionMap;
+        osg::ref_ptr< osg::RefMatrix >       _planarReflectionProjection;
+        
+        TritonDrawable(const TritonDrawable& copy, const osg::CopyOp& op=osg::CopyOp::SHALLOW_COPY) { }
+    };
+
+} } } // namespace osgEarth::Drivers::Triton
diff --git a/src/osgEarthDrivers/ocean_triton/TritonDrawable.cpp b/src/osgEarthDrivers/ocean_triton/TritonDrawable.cpp
new file mode 100644
index 0000000..2207636
--- /dev/null
+++ b/src/osgEarthDrivers/ocean_triton/TritonDrawable.cpp
@@ -0,0 +1,535 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include "TritonDrawable"
+#include "TritonContext"
+#include <osg/MatrixTransform>
+#include <osgEarth/SpatialReference>
+#include <osgEarth/VirtualProgram>
+#include <Triton.h>
+
+#define LC "[TritonDrawable] "
+
+//#define DEBUG_HEIGHTMAP
+
+using namespace osgEarth;
+using namespace osgEarth::Drivers::Triton;
+
+#ifdef DEBUG_HEIGHTMAP
+osg::Node*
+makeFrustumFromCamera( osg::Camera* camera )
+{
+    osg::Matrixd proj;
+    osg::Matrixd mv;
+    if (camera)
+    {
+        proj = camera->getProjectionMatrix();
+        mv = camera->getViewMatrix();
+    }
+    else
+    {
+        proj.makePerspective( 30., 1., 1., 10. );
+        // leave mv as identity
+    }
+
+    double near=0.0, far=0.0;
+    double left=0.0, right=0.0, bottom=0.0, top=0.0;
+    double fovy=0.0, aspectRatio=0.0;
+    double nLeft=0.0, nRight=0.0, nBottom=0.0, nTop=0.0;
+    double fLeft=0.0, fRight=0.0, fBottom=0.0, fTop=0.0;
+
+    osg::Geode* geode = new osg::Geode;
+
+    if( proj.getPerspective(fovy, aspectRatio, near, far) )
+    {
+        near = proj(3,2) / (proj(2,2)-1.0);
+        far = proj(3,2) / (1.0+proj(2,2));
+        
+        nLeft = near * (proj(2,0)-1.0) / proj(0,0);
+        nRight = near * (1.0+proj(2,0)) / proj(0,0);
+        nTop = near * (1.0+proj(2,1)) / proj(1,1);
+        nBottom = near * (proj(2,1)-1.0) / proj(1,1);
+
+        fLeft = far * (proj(2,0)-1.0) / proj(0,0);
+        fRight = far * (1.0+proj(2,0)) / proj(0,0);
+        fTop = far * (1.0+proj(2,1)) / proj(1,1);
+        fBottom = far * (proj(2,1)-1.0) / proj(1,1);
+
+        osg::Vec3Array* v = new osg::Vec3Array;
+        v->resize( 9 );
+        (*v)[0].set( 0., 0., 0. );
+
+        (*v)[1].set( nLeft, nBottom, -near );
+        (*v)[2].set( nRight, nBottom, -near );
+        (*v)[3].set( nRight, nTop, -near );
+        (*v)[4].set( nLeft, nTop, -near );
+        (*v)[5].set( fLeft, fBottom, -far );
+        (*v)[6].set( fRight, fBottom, -far );
+        (*v)[7].set( fRight, fTop, -far );
+        (*v)[8].set( fLeft, fTop, -far );
+
+        osg::Geometry* geom = new osg::Geometry;
+        geom->setVertexArray( v );
+
+        GLushort idxLines[8] = {
+            0, 5, 0, 6, 0, 7, 0, 8 };
+        GLushort idxLoops0[4] = {
+            1, 2, 3, 4 };
+        GLushort idxLoops1[4] = {
+            5, 6, 7, 8 };
+        geom->addPrimitiveSet( new osg::DrawElementsUShort( osg::PrimitiveSet::LINES, 8, idxLines ) );
+        geom->addPrimitiveSet( new osg::DrawElementsUShort( osg::PrimitiveSet::LINE_LOOP, 4, idxLoops0 ) );
+        geom->addPrimitiveSet( new osg::DrawElementsUShort( osg::PrimitiveSet::LINE_LOOP, 4, idxLoops1 ) );
+
+        geode->addDrawable( geom );
+    }
+    else if(proj.getOrtho( left, right, bottom, top, near, far) )
+    {
+        nLeft = left;//near * (proj(2,0)-1.0) / proj(0,0);
+        nRight = right;//near * (1.0+proj(2,0)) / proj(0,0);
+        nTop = top;//near * (1.0+proj(2,1)) / proj(1,1);
+        nBottom = bottom;//near * (proj(2,1)-1.0) / proj(1,1);
+
+        fLeft = left;//far * (proj(2,0)-1.0) / proj(0,0);
+        fRight = right;//far * (1.0+proj(2,0)) / proj(0,0);
+        fTop = top;//far * (1.0+proj(2,1)) / proj(1,1);
+        fBottom = bottom;//far * (proj(2,1)-1.0) / proj(1,1);
+
+        osg::Vec3Array* v = new osg::Vec3Array;
+        v->resize( 9 );
+        (*v)[0].set( 0., 0., 0. );
+
+        (*v)[1].set( nLeft, nBottom, -near );
+        (*v)[2].set( nRight, nBottom, -near );
+        (*v)[3].set( nRight, nTop, -near );
+        (*v)[4].set( nLeft, nTop, -near );
+        (*v)[5].set( fLeft, fBottom, -far );
+        (*v)[6].set( fRight, fBottom, -far );
+        (*v)[7].set( fRight, fTop, -far );
+        (*v)[8].set( fLeft, fTop, -far );
+
+        osg::Geometry* geom = new osg::Geometry;
+        geom->setVertexArray( v );
+
+        GLushort idxLines[8] = {
+            1, 5, 2, 6, 3, 7, 4, 8 };
+        GLushort idxLoops0[4] = {
+            1, 2, 3, 4 };
+        GLushort idxLoops1[4] = {
+            5, 6, 7, 8 };
+        geom->addPrimitiveSet( new osg::DrawElementsUShort( osg::PrimitiveSet::LINES, 8, idxLines ) );
+        geom->addPrimitiveSet( new osg::DrawElementsUShort( osg::PrimitiveSet::LINE_LOOP, 4, idxLoops0 ) );
+        geom->addPrimitiveSet( new osg::DrawElementsUShort( osg::PrimitiveSet::LINE_LOOP, 4, idxLoops1 ) );
+
+        geode->addDrawable( geom );
+    }
+
+    geode->getOrCreateStateSet()->setMode( GL_LIGHTING, osg::StateAttribute::OFF );
+
+
+
+    osg::MatrixTransform* mt = new osg::MatrixTransform;
+    osg::Matrixd mvi = osg::Matrixd::inverse( mv );
+    mt->setMatrix( mvi );
+    mt->addChild( geode );
+
+    return mt;
+}
+
+osg::Camera *
+CreateTextureQuadOverlay( osg::Texture * texture, float x, float y, float w, float h )
+{
+    osg::Camera * camera = new osg::Camera;
+
+    // set up the background color and clear mask.
+    camera->setClearMask( GL_DEPTH_BUFFER_BIT );
+
+    // set viewport
+    camera->setProjectionMatrixAsOrtho2D( 0, 1, 0, 1 );
+    camera->setViewMatrix( osg::Matrix::identity() );
+    camera->setReferenceFrame( osg::Transform::ABSOLUTE_RF );
+
+    osg::Geode * geode = new osg::Geode();
+    camera->addChild( geode );
+/*
+    osg::Geometry * background = osg::createTexturedQuadGeometry( osg::Vec3( x,y,0 ),osg::Vec3( w,0,0 ), osg::Vec3( 0,h,0 ) );
+    geode->addDrawable( background );
+
+    osg::Vec4Array* colors = new osg::Vec4Array;
+    colors->push_back( osg::Vec4( 1,1,0,0.3 ) );
+    background->setColorArray( colors );
+    background->setColorBinding( osg::Geometry::BIND_OVERALL );
+    background->getOrCreateStateSet()->setMode( GL_BLEND, osg::StateAttribute::ON );
+    background->getOrCreateStateSet()->setMode( GL_DEPTH_TEST, osg::StateAttribute::OFF );
+*/
+    osg::Geometry * quad = osg::createTexturedQuadGeometry( osg::Vec3( x,y,0 ),osg::Vec3( w,0,0 ), osg::Vec3( 0,h,0 ) );
+    geode->addDrawable( quad );
+    quad->getOrCreateStateSet()->setTextureAttributeAndModes( 0, texture, osg::StateAttribute::ON );
+    quad->getOrCreateStateSet()->setMode( GL_BLEND, osg::StateAttribute::ON );
+    quad->getOrCreateStateSet()->setMode( GL_LIGHTING, osg::StateAttribute::OFF );
+    quad->getOrCreateStateSet()->setMode( GL_DEPTH_TEST, osg::StateAttribute::OFF );
+
+
+    // set the camera to render after the main camera.
+    camera->setRenderOrder(osg::Camera::POST_RENDER);
+
+    return camera;
+}
+#endif /* DEBUG_HEIGHTMAP */
+
+struct PassHeightMapToTritonCallback : public osg::Camera::DrawCallback 
+{
+    PassHeightMapToTritonCallback(TritonContext* triton) : _TRITON(triton), _enable(false), _id(0) { _ptrEnable = const_cast<bool*> (&_enable); };
+    virtual void operator()( osg::RenderInfo& renderInfo ) const
+    {
+        if( _enable )
+        {
+            *_ptrEnable = false; // cannot directly change _enable because of const
+
+            //osg::notify( osg::ALWAYS ) << "passing heightmap to Triton" << std::endl;
+            if(_TRITON->ready())
+                _TRITON->getEnvironment()->SetHeightMap((Triton::TextureHandle)_id,_heightMapMatrix);
+        }
+    }
+
+    unsigned int _frameNumber;
+    bool _enable;
+    bool* _ptrEnable;
+    int _id;
+    Triton::Matrix4 _heightMapMatrix;
+private:
+    osg::observer_ptr<TritonContext>  _TRITON;
+};
+
+class OceanTerrainChangedCallback : public osgEarth::TerrainCallback
+{
+public:
+    OceanTerrainChangedCallback(TritonContext* triton, osgEarth::MapNode* mapNode, osg::Camera* heightCam, osg::Texture2D* heightMap )
+      : _TRITON(triton),
+        _mapNode(mapNode),
+        _heightCam(heightCam),
+        _heightMap(heightMap)
+    {
+    }
+
+    virtual void onTileAdded(const osgEarth::TileKey& tileKey, osg::Node* terrain, osgEarth::TerrainCallbackContext& context)
+    {
+        if ( !_TRITON->ready() )
+            return;
+
+        osg::Vec3d eye, center, up;
+        _viewMatrix.getLookAt(eye, center, up);
+        double fovyDEG=0.0, aspectRatio=0.0, zNear=0.0, zFar=0.0;
+        _projectionMatrix.getPerspective(fovyDEG, aspectRatio,zNear,zFar);
+
+        // aspect_ratio = tan( HFOV/2 ) / tan( VFOV/2 )
+        // tan( HFOV/2 ) = tan( VFOV/2 ) * aspect_ratio
+        // HFOV/2 = atan( tan( VFOV/2 ) * aspect_ratio )
+        // HFOV = 2.0 * atan( tan( VFOV/2 ) * aspect_ratio )
+        double fovxDEG = osg::RadiansToDegrees( 2.0 * atan( tan(osg::DegreesToRadians(fovyDEG))/2.0 * aspectRatio ));
+
+        double eyeLat=0.0, eyeLon=0.0, eyeHeight=0.0;
+        _mapNode->getMap()->getSRS()->getEllipsoid()->convertXYZToLatLongHeight(eye.x(), eye.y(), eye.z(), eyeLat, eyeLon, eyeHeight);
+        double clampedEyeX=0.0, clampedEyeY=0.0,clampedEyeZ=0.0;
+        _mapNode->getMap()->getSRS()->getEllipsoid()->convertLatLongHeightToXYZ(eyeLat, eyeLon, 0.0, clampedEyeX, clampedEyeY, clampedEyeZ);
+        osg::Vec3 mslEye(clampedEyeX,clampedEyeY,clampedEyeZ);
+        double lookAtLat=0.0, lookAtLon=0.0, lookAtHeight=0.0;
+        _mapNode->getMap()->getSRS()->getEllipsoid()->convertXYZToLatLongHeight(center.x(), center.y(), center.z(), lookAtLat, lookAtLon, lookAtHeight);
+
+        // Calculate the distance to the horizon from the eyepoint
+        double eyeLen = eye.length();
+        double radE = mslEye.length();
+        double hmax = radE + 8848.0;
+        double hmin = radE - 12262.0;
+        double hasl = osg::maximum(0.1, eyeLen - radE);
+        double radius = eyeLen - hasl;
+        double horizonDistance = osg::minimum(radE, sqrt( 2.0*radius*hasl + hasl*hasl ));
+
+        osg::Vec3d heightCamEye(eye);
+
+        double near = osg::maximum(1.0, heightCamEye.length() - hmax);
+        double far = osg::maximum(10.0, heightCamEye.length() - hmin + radE);
+        //osg::notify( osg::ALWAYS ) << "near = " << near << "; far = " << far << std::endl;
+
+        _heightCam->setProjectionMatrix(osg::Matrix::ortho(-horizonDistance,horizonDistance,-horizonDistance,horizonDistance,near,far) );
+        _heightCam->setViewMatrixAsLookAt( heightCamEye, osg::Vec3d(0.0,0.0,0.0), osg::Vec3d(0.0,0.0,1.0));
+
+        const osg::Matrixd bias(0.5, 0.0, 0.0, 0.0,
+                                0.0, 0.5, 0.0, 0.0,
+                                0.0, 0.0, 0.5, 0.0,
+                                0.5, 0.5, 0.5, 1.0);
+
+        osg::Matrix hMM = _heightCam->getViewMatrix() * _heightCam->getProjectionMatrix() * bias;
+        Triton::Matrix4 heightMapMatrix(hMM(0,0),hMM(0,1),hMM(0,2),hMM(0,3),
+                                        hMM(1,0),hMM(1,1),hMM(1,2),hMM(1,3),
+                                        hMM(2,0),hMM(2,1),hMM(2,2),hMM(2,3),
+                                        hMM(3,0),hMM(3,1),hMM(3,2),hMM(3,3));
+
+        //unsigned int contextID = _viewer->getCamera()->getGraphicsContext()->getState()->getContextID();
+        _texObj = _heightMap->getTextureObject(_contextID);
+        //osg::notify( osg::ALWAYS ) << "_contextID " << _contextID << std::endl;
+
+        if(_texObj)
+        {
+            PassHeightMapToTritonCallback* cb = dynamic_cast<PassHeightMapToTritonCallback*>(_heightCam->getFinalDrawCallback());
+            if( cb )
+            {
+                cb->_enable = true;
+                cb->_id = _texObj->id();
+                cb->_heightMapMatrix = heightMapMatrix;
+            }
+        }
+#ifdef DEBUG_HEIGHTMAP
+        _mapNode->getParent(0)->removeChild(0 ,1);
+        _mapNode->getParent(0)->insertChild(0, makeFrustumFromCamera(_heightCam));
+#endif /* DEBUG_HEIGHTMAP */
+
+    }
+
+    void setViewMatrix(const osg::Matrix& viewMatrix) { _viewMatrix = viewMatrix; };
+    void setProjectionMatrix(const osg::Matrix& projectionMatrix) { _projectionMatrix = projectionMatrix; };
+    void setContextID(unsigned int contextID) {_contextID = contextID;}
+
+private:
+    osg::observer_ptr<TritonContext> _TRITON;
+    osg::observer_ptr<osgEarth::MapNode> _mapNode;
+    osg::Camera* _heightCam;
+    osg::Texture2D* _heightMap;
+    osg::Texture::TextureObject* _texObj;
+    osg::Matrix _viewMatrix, _projectionMatrix;
+    unsigned int _contextID;
+};
+
+
+const char* vertexShader =
+    "#version " GLSL_VERSION_STR "\n"
+    GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+    "attribute vec4  oe_terrain_attr; \n" // osgearth_elevData //oe_terrain_attr
+    "varying float height;\n"
+
+    "void setupContour(inout vec4 VertexModel) \n"
+    "{ \n"
+    "    height = oe_terrain_attr[3]; \n"
+    "} \n";
+
+// The fragment shader simply takes the texture index that we generated
+// in the vertex shader and does a texture lookup. In this case we're
+// just wholesale replacing the color, so if the map had any existing
+// imagery, this will overwrite it.
+
+const char* fragmentShader =
+    "#version " GLSL_VERSION_STR "\n"
+    GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+    "varying float height;\n"
+
+    "void colorContour( inout vec4 color ) \n"
+    "{ \n"
+#ifdef DEBUG_HEIGHTMAP
+      // Map to black = -500m, white = +500m
+      "   float nHeight = clamp(height / 1000.0 + 0.5, 0.0, 1.0);\n"
+#else
+      "   float nHeight = height;\n"
+#endif
+    "    color = vec4( nHeight, 0.0, 0.0, 1.0 ); \n"
+    "} \n";
+
+TritonDrawable::TritonDrawable(osgEarth::MapNode* mapNode, TritonContext* TRITON) :
+_TRITON(TRITON),
+_mapNode(mapNode)
+{
+    // call this to ensure draw() gets called every frame.
+    setSupportsDisplayList( false );
+    setUseVertexBufferObjects( false );
+
+    // dynamic variance prevents update/cull overlap when drawing this
+    setDataVariance( osg::Object::DYNAMIC );
+
+    this->getOrCreateStateSet()->setRenderingHint( osg::StateSet::TRANSPARENT_BIN );
+    //this->getOrCreateStateSet()->setRenderBinDetails( 97, "RenderBin" );
+}
+
+void
+TritonDrawable::drawImplementation(osg::RenderInfo& renderInfo) const
+{
+    osg::State& state = *renderInfo.getState();
+    
+    state.disableAllVertexArrays();
+
+    _TRITON->initialize( renderInfo );
+    if ( !_TRITON->ready() )
+        return;
+
+    if(!_terrainChangedCallback.valid())
+        const_cast< TritonDrawable *>( this )->setupHeightMap(_mapNode.get());;
+
+    ::Triton::Environment* environment = _TRITON->getEnvironment();
+
+    // Pass the final view and projection matrices into Triton.
+    if ( environment )
+    {
+        environment->SetCameraMatrix( state.getModelViewMatrix().ptr() );
+        environment->SetProjectionMatrix( state.getProjectionMatrix().ptr() );
+    }
+
+    if(_terrainChangedCallback.valid())
+    {
+        _terrainChangedCallback->setViewMatrix( renderInfo.getView()->getCamera()->getViewMatrix() );
+        _terrainChangedCallback->setProjectionMatrix(renderInfo.getView()->getCamera()->getProjectionMatrix() );
+        _terrainChangedCallback->setContextID(renderInfo.getView()->getCamera()->getGraphicsContext()->getState()->getContextID() );
+    }
+
+    state.dirtyAllVertexArrays();
+
+    // Now light and draw the ocean:
+    if ( environment )
+    {
+        // The sun position is roughly where it is in our skybox texture:
+
+        // Since this is a simple example we will just assume that Sun is the light from View light source
+        // TODO: fix this...
+        osg::Light* light = renderInfo.getView() ? renderInfo.getView()->getLight() : NULL;
+
+        // This is the light attached to View so there are no transformations above..
+        // But in general case you would need to accumulate all transforms above the light into this matrix
+        osg::Matrix lightLocalToWorldMatrix = osg::Matrix::identity();
+
+        // If you don't know where the sun lightsource is attached and don't know its local to world matrix you may use
+        // following elaborate scheme to grab the light source while drawing Triton ocean:
+        // - Install cull callback to catch CullVisitor and record pointer to its associated RenderStage
+        //   I was hoping RenderStage can be found from renderInfo in drawImplementation but I didn't figure how ...
+        // - When TritonDrawable::drawImplementation is called all lights will be already applied to OpenGL
+        //   then just find proper infinite directional light by scanning renderStage->PositionalStateContainer.
+        // - Note that we canot scan for the lights inside cull because they may not be traversed before Triton drawable
+        // - When you found interesting ligt source that can work as Sun, read its modelview matrix and lighting params
+        //   Multiply light position by ( modelview * inverse camera view ) and pass this to Triton with lighting colors
+
+        if ( light && light->getPosition().w() == 0 )
+        {
+            osg::Vec4 ambient = light->getAmbient();
+            osg::Vec4 diffuse = light->getDiffuse();
+            osg::Vec4 position = light->getPosition();
+
+            // Compute light position/direction in the world
+            position = position * lightLocalToWorldMatrix;
+
+            // Diffuse direction and color
+            environment->SetDirectionalLight(
+                ::Triton::Vector3( position[0], position[1], position[2] ),
+                ::Triton::Vector3( diffuse[0],  diffuse[1],  diffuse[2] ) );
+
+            // Ambient color based on the zenith color in the cube map
+            environment->SetAmbientLight(
+                ::Triton::Vector3( ambient[0], ambient[1], ambient[2] ) );
+        }
+
+        // Build transform from our cube map orientation space to native Triton orientation
+        // See worldToCubeMap function used in SkyBox to orient sky texture so that sky is up and earth is down
+        osg::Matrix m = osg::Matrix::rotate( osg::PI_2, osg::X_AXIS ); // = worldToCubeMap
+
+        ::Triton::Matrix3 transformFromYUpToZUpCubeMapCoords(
+            m(0,0), m(0,1), m(0,2),
+            m(1,0), m(1,1), m(1,2),
+            m(2,0), m(2,1), m(2,2) );
+
+        // Grab the cube map from our sky box and give it to Triton to use as an _environment map
+        // GLenum texture = renderInfo.getState()->getLastAppliedTextureAttribute( _stage, osg::StateAttribute::TEXTURE );
+        if ( _cubeMap.valid() )
+        {
+            environment->SetEnvironmentMap(
+                (::Triton::TextureHandle)_cubeMap->getTextureObject( state.getContextID() )->id(), transformFromYUpToZUpCubeMapCoords );
+
+#if 1
+            if( _planarReflectionMap.valid() && _planarReflectionProjection.valid() )
+            {
+                osg::Matrix & p = *_planarReflectionProjection;
+
+                ::Triton::Matrix3 planarProjection( p(0,0), p(0,1), p(0,2),
+                                                    p(1,0), p(1,1), p(1,2),
+                                                    p(2,0), p(2,1), p(2,2) );
+
+                environment->SetPlanarReflectionMap( (::Triton::TextureHandle)
+                                                      _planarReflectionMap->getTextureObject( state.getContextID() )->id(),
+                                                      planarProjection, 0.125  );
+            }
+#endif
+        }
+
+        // Draw the ocean for the current time sample
+        if ( _TRITON->getOcean() )
+        {
+            _TRITON->getOcean()->Draw( renderInfo.getView()->getFrameStamp()->getSimulationTime() );
+        }
+    }
+
+    state.dirtyAllVertexArrays();
+}
+
+void TritonDrawable::setupHeightMap(osgEarth::MapNode* mapNode)
+{
+    int textureUnit = 0;
+    int textureSize = 1024;
+    // Create our height map texture
+    _heightMap = new osg::Texture2D;
+    _heightMap->setTextureSize(textureSize, textureSize);
+    _heightMap->setInternalFormat(GL_LUMINANCE32F_ARB);
+    _heightMap->setSourceFormat(GL_LUMINANCE);
+    _heightMap->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::LINEAR);
+    _heightMap->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::LINEAR);
+
+    // Create its camera and render to it
+    _heightCamera = new osg::Camera;
+    _heightCamera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
+    _heightCamera->setClearMask(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+    _heightCamera->setClearColor(osg::Vec4(-1000.0, -1000.0, -1000.0, 1.0f));
+    _heightCamera->setViewport(0, 0, textureSize, textureSize);
+    _heightCamera->setRenderOrder(osg::Camera::PRE_RENDER);
+    _heightCamera->setRenderTargetImplementation( osg::Camera::FRAME_BUFFER_OBJECT );
+    _heightCamera->attach(osg::Camera::COLOR_BUFFER, _heightMap);
+    _heightCamera->setCullMask( ~OCEAN_MASK );
+    _heightCamera->setAllowEventFocus(false);
+    _heightCamera->setFinalDrawCallback(new PassHeightMapToTritonCallback(_TRITON.get()));
+
+    // Install the shaders. We also bind osgEarth's elevation data attribute, which the 
+    // terrain engine automatically generates at the specified location.
+    osgEarth::VirtualProgram* heightProgram = new osgEarth::VirtualProgram();
+    heightProgram->setFunction( "setupContour", vertexShader,   osgEarth::ShaderComp::LOCATION_VERTEX_MODEL);
+    heightProgram->setFunction( "colorContour", fragmentShader, osgEarth::ShaderComp::LOCATION_FRAGMENT_COLORING);//, -1.0 );
+
+    osg::StateSet *stateSet = _heightCamera->getOrCreateStateSet();
+    stateSet->setAttributeAndModes(heightProgram, osg::StateAttribute::ON);// | osg::StateAttribute::OVERRIDE);
+    stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);// | osg::StateAttribute::OVERRIDE);
+
+//    heightProgram->addBindAttribLocation( "oe_terrain_attr", osg::Drawable::ATTRIBUTE_6 );
+
+    if( mapNode && _heightCamera )
+    {
+        _heightCamera->addChild( mapNode->getTerrainEngine() );
+        _terrainChangedCallback = new OceanTerrainChangedCallback( _TRITON.get(), mapNode, _heightCamera.get(), _heightMap.get());
+        mapNode->getTerrain()->addTerrainCallback( _terrainChangedCallback.get() );
+    }
+
+    osg::Group* root = osgEarth::findTopMostNodeOfType<osg::Group>(mapNode);
+    root->addChild(_heightCamera);
+
+#ifdef DEBUG_HEIGHTMAP
+    mapNode->getParent(0)->addChild(CreateTextureQuadOverlay(_heightMap, 0.65, 0.05, 0.3, 0.3));
+    mapNode->getParent(0)->insertChild(0, makeFrustumFromCamera(_heightCamera));
+#endif /* DEBUG_HEIGHTMAP */
+}
\ No newline at end of file
diff --git a/src/osgEarthDrivers/ocean_triton/TritonDriver.cpp b/src/osgEarthDrivers/ocean_triton/TritonDriver.cpp
new file mode 100644
index 0000000..7dd718b
--- /dev/null
+++ b/src/osgEarthDrivers/ocean_triton/TritonDriver.cpp
@@ -0,0 +1,96 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osgDB/ReaderWriter>
+#include <osgDB/FileNameUtils>
+#include <osgDB/Registry>
+#include <osgDB/FileUtils>
+#include <osgEarth/MapNode>
+#include <osgEarth/Registry>
+#include <osgEarth/ThreadingUtils>
+#include <osgEarthUtil/Ocean>
+
+#include "TritonNode"
+
+#undef  LC
+#define LC "[TritonDriver] "
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+//---------------------------------------------------------------------------
+
+namespace osgEarth { namespace Drivers { namespace Triton
+{
+    class TritonDriver : public OceanDriver
+    {
+    public:
+        TritonDriver()
+        {
+            supportsExtension(
+                "osgearth_ocean_triton",
+                "osgEarth Triton Ocean plugin" );
+        }
+
+        const char* className()
+        {
+            return "osgEarth Triton Ocean plugin";
+        }
+
+        ReadResult readNode(const std::string& file_name, const Options* options) const
+        {
+            if ( !acceptsExtension(osgDB::getLowerCaseFileExtension( file_name )))
+                return ReadResult::FILE_NOT_HANDLED;
+
+            TritonOptions tritonOptions = getOceanOptions(options);
+
+            // if the Resource Path isn't set, attempt to set it from 
+            // the SL environment variable.
+            if ( !tritonOptions.resourcePath().isSet() )
+            {
+                const char* ev = ::getenv("TRITON_PATH");
+                if ( ev )
+                {
+                    tritonOptions.resourcePath() = osgDB::concatPaths(
+                        std::string(ev),
+                        "Resources" );
+
+                    OE_INFO << LC 
+                        << "Setting resource path to << " << tritonOptions.resourcePath().get()
+                        << std::endl;
+                }
+                else
+                {
+                    OE_WARN << LC
+                        << "No resource path! Triton might not initialize properly. "
+                        << "Consider setting the TRITON_PATH environment variable."
+                        << std::endl;
+                }
+            }
+
+            MapNode* mapNode = getMapNode(options);
+            return new TritonNode( mapNode, tritonOptions );
+        }
+
+    protected:
+        virtual ~TritonDriver() { }
+    };
+
+    REGISTER_OSGPLUGIN(osgearth_ocean_triton, TritonDriver)
+
+} } } // namespace osgEarth::Drivers::Triton
diff --git a/src/osgEarthDrivers/ocean_triton/TritonNode b/src/osgEarthDrivers/ocean_triton/TritonNode
new file mode 100644
index 0000000..5a4c249
--- /dev/null
+++ b/src/osgEarthDrivers/ocean_triton/TritonNode
@@ -0,0 +1,64 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_DRIVER_TRITON_TRITON_NODE
+#define OSGEARTH_DRIVER_TRITON_TRITON_NODE 1
+
+#include "TritonOptions"
+#include <osgEarthUtil/Ocean>
+#include <osgEarth/MapNode>
+#include <osg/Drawable>
+
+namespace osgEarth { namespace Drivers { namespace Triton
+{
+    class TritonContext;
+
+    using namespace osgEarth;
+    using namespace osgEarth::Util;
+
+    /**
+     * Node that roots the Triton adapter.
+     */
+    class TritonNode : public OceanNode
+    {
+    public:
+        TritonNode(
+            MapNode*           mapNode,
+            const TritonOptions& options );
+
+    protected: // OceanNode
+
+        void onSetSeaLevel();
+
+    public: // osg::Node
+
+        osg::BoundingSphere computeBound() const;
+
+        void traverse(osg::NodeVisitor&);
+
+    protected:
+        virtual ~TritonNode();
+
+        osg::ref_ptr<TritonContext> _TRITON;
+        TritonOptions               _options;
+        osg::Drawable*              _drawable;
+    };
+
+} } } // namespace osgEarth::Drivers::Triton
+
+#endif // OSGEARTH_DRIVER_TRITON_TRITON_NODE
diff --git a/src/osgEarthDrivers/ocean_triton/TritonNode.cpp b/src/osgEarthDrivers/ocean_triton/TritonNode.cpp
new file mode 100644
index 0000000..c7df5dd
--- /dev/null
+++ b/src/osgEarthDrivers/ocean_triton/TritonNode.cpp
@@ -0,0 +1,86 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+#include "TritonNode"
+#include "TritonContext"
+#include "TritonDrawable"
+#include <osgEarth/CullingUtils>
+#include <Triton.h>
+
+#define LC "[TritonNode] "
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+using namespace osgEarth::Drivers::Triton;
+
+TritonNode::TritonNode(MapNode*           mapNode,
+                       const TritonOptions& options) :
+OceanNode( options ),
+_options ( options )
+{
+    const Map* map = mapNode->getMap();
+    if ( map )
+        setSRS( map->getSRS() );
+
+    _TRITON = new TritonContext( options );
+
+    if ( map )
+        _TRITON->setSRS( map->getSRS() );
+
+    TritonDrawable* tritonDrawable = new TritonDrawable(mapNode,_TRITON);
+    _drawable = tritonDrawable;
+    osg::Geode* geode = new osg::Geode();
+    geode->addDrawable( _drawable );
+    geode->setNodeMask( OCEAN_MASK );
+
+    this->addChild( geode );
+
+    this->setNumChildrenRequiringUpdateTraversal(1);
+}
+
+TritonNode::~TritonNode()
+{
+    //nop
+}
+
+void
+TritonNode::onSetSeaLevel()
+{
+    if ( _TRITON->ready() )
+    {
+        _TRITON->getEnvironment()->SetSeaLevel( getSeaLevel() );
+    }
+    dirtyBound();
+}
+
+osg::BoundingSphere
+TritonNode::computeBound() const
+{
+    return osg::BoundingSphere();
+}
+
+void
+TritonNode::traverse(osg::NodeVisitor& nv)
+{
+    if ( nv.getVisitorType() == nv.UPDATE_VISITOR && _TRITON->ready() )
+    {
+        _TRITON->update(nv.getFrameStamp()->getSimulationTime());
+    }
+    OceanNode::traverse(nv);
+}
diff --git a/src/osgEarthDrivers/ocean_triton/TritonOptions b/src/osgEarthDrivers/ocean_triton/TritonOptions
new file mode 100644
index 0000000..7ac3342
--- /dev/null
+++ b/src/osgEarthDrivers/ocean_triton/TritonOptions
@@ -0,0 +1,88 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_DRIVER_TRITON_TRITON_OPTIONS
+#define OSGEARTH_DRIVER_TRITON_TRITON_OPTIONS 1
+
+#include <osgEarthUtil/Ocean>
+
+namespace osgEarth { namespace Drivers { namespace Triton
+{
+    using namespace osgEarth;
+    using namespace osgEarth::Util;
+
+    /**
+     * Options for controlling the ocean surface node.
+     */
+    class /*header-only*/ TritonOptions : public OceanOptions
+    {
+    public:
+
+    public:
+        TritonOptions(const OceanOptions& conf =OceanOptions()) :
+          OceanOptions( conf )
+        {
+            setDriver( "triton" );
+            fromConfig( _conf );
+        }
+
+        virtual ~TritonOptions() { }
+
+        /* User name for license activation */
+        optional<std::string>& user() { return _user; }
+        const optional<std::string>& user() const { return _user; }
+
+        /* License code string */
+        optional<std::string>& licenseCode() { return _licenseCode; }
+        const optional<std::string>& licenseCode() const { return _licenseCode; }
+
+        /* SilverLining resource path */
+        optional<std::string>& resourcePath() { return _resourcePath; }
+        const optional<std::string>& resourcePath() const { return _resourcePath; }
+
+    public:
+        Config getConfig() const {
+            Config conf = OceanOptions::newConfig();
+            conf.addIfSet("user", _user);
+            conf.addIfSet("license_code", _licenseCode);
+            conf.addIfSet("resource_path", _resourcePath);
+            return conf;
+        }
+
+    protected:
+        void mergeConfig( const Config& conf ) {
+            OceanOptions::mergeConfig( conf );
+            fromConfig( conf );
+        }
+
+    private:
+        void fromConfig( const Config& conf ) {
+            conf.getIfSet("user", _user);
+            conf.getIfSet("license_code", _licenseCode);
+            conf.getIfSet("resource_path", _resourcePath);
+        }
+
+    private:
+        optional<std::string> _user;
+        optional<std::string> _licenseCode;
+        optional<std::string> _resourcePath;
+    };
+
+} } } // namespace osgEarth::Drivers::Triton
+
+#endif // OSGEARTH_DRIVER_TRITON_TRITON_OPTIONS
diff --git a/src/osgEarthDrivers/osg/OSGOptions b/src/osgEarthDrivers/osg/OSGOptions
index 4cc19a1..c1ef980 100644
--- a/src/osgEarthDrivers/osg/OSGOptions
+++ b/src/osgEarthDrivers/osg/OSGOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/osg/OSGTileSource.cpp b/src/osgEarthDrivers/osg/OSGTileSource.cpp
index 1cd3715..704f329 100644
--- a/src/osgEarthDrivers/osg/OSGTileSource.cpp
+++ b/src/osgEarthDrivers/osg/OSGTileSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -33,23 +33,26 @@
 using namespace osgEarth;
 using namespace osgEarth::Drivers;
 
-struct CopyAndSetAlpha
+namespace
 {
-    bool operator()( const osg::Vec4& in, osg::Vec4& out ) {
-        out = in;
-        out.a() = 0.3333*(in.r() + in.g() + in.b());
-        return true;
-    }
-};
+    struct CopyAndSetAlpha
+    {
+        bool operator()( const osg::Vec4& in, osg::Vec4& out ) {
+            out = in;
+            out.a() = 0.3333*(in.r() + in.g() + in.b());
+            return true;
+        }
+    };
 
-static
-osg::Image* makeRGBAandComputeAlpha(osg::Image* image)
-{
-    osg::Image* result = new osg::Image();
-    result->allocateImage( image->s(), image->t(), image->r(), GL_RGBA, GL_UNSIGNED_BYTE );
-    result->setInternalTextureFormat( GL_RGBA8 );
-    ImageUtils::PixelVisitor<CopyAndSetAlpha>().accept( image, result );
-    return result;
+    osg::Image* makeRGBAandComputeAlpha(osg::Image* image)
+    {
+        osg::Image* result = new osg::Image();
+        result->allocateImage( image->s(), image->t(), image->r(), GL_RGBA, GL_UNSIGNED_BYTE );
+        memset(result->data(), 0, result->getTotalSizeInBytes());
+        result->setInternalTextureFormat( GL_RGBA8 );
+        ImageUtils::PixelVisitor<CopyAndSetAlpha>().accept( image, result );
+        return result;
+    }
 }
 
 class OSGTileSource : public TileSource
@@ -65,8 +68,7 @@ public:
 
     Status initialize( const osgDB::Options* dbOptions )
     {
-        osg::ref_ptr<osgDB::Options> localOptions = Registry::instance()->cloneOrCreateOptions(dbOptions);
-        CachePolicy::NO_CACHE.apply(localOptions.get());
+        osg::ref_ptr<osgDB::Options> localOptions = Registry::instance()->cloneOrCreateOptions(dbOptions);        
 
         if ( !getProfile() )
         {
diff --git a/src/osgEarthDrivers/refresh/ReaderWriterRefresh.cpp b/src/osgEarthDrivers/refresh/ReaderWriterRefresh.cpp
index 53b8990..7db40ae 100644
--- a/src/osgEarthDrivers/refresh/ReaderWriterRefresh.cpp
+++ b/src/osgEarthDrivers/refresh/ReaderWriterRefresh.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/refresh/RefreshOptions b/src/osgEarthDrivers/refresh/RefreshOptions
index e2ef4eb..7f97cbb 100644
--- a/src/osgEarthDrivers/refresh/RefreshOptions
+++ b/src/osgEarthDrivers/refresh/RefreshOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/script_engine_duktape/CMakeLists.txt b/src/osgEarthDrivers/script_engine_duktape/CMakeLists.txt
new file mode 100644
index 0000000..eaeaf43
--- /dev/null
+++ b/src/osgEarthDrivers/script_engine_duktape/CMakeLists.txt
@@ -0,0 +1,42 @@
+
+IF(WITH_EXTERNAL_DUKTAPE)
+
+    INCLUDE_DIRECTORIES( ${DUKTAPE_INCLUDE_DIR} )
+
+    SET(TARGET_H
+        DuktapeEngine
+		JSGeometry
+    )
+
+    SET(TARGET_SRC
+        Plugin.cpp
+        DuktapeEngine.cpp
+    )
+
+    SET(TARGET_COMMON_LIBRARIES ${DUKTAPE_LIBRARY} osgEarthFeatures osgEarthSymbology)
+
+ELSE(WITH_EXTERNAL_DUKTAPE)
+
+    SET(TARGET_H
+        duktape.h
+        DuktapeEngine
+		JSGeometry
+    )
+
+    SET(TARGET_SRC
+        Plugin.cpp
+        duktape.c
+        DuktapeEngine.cpp
+    )
+
+    SET(TARGET_COMMON_LIBRARIES osgEarthFeatures osgEarthSymbology)
+
+ENDIF(WITH_EXTERNAL_DUKTAPE)
+
+SETUP_PLUGIN(osgearth_scriptengine_javascript)
+
+# to install public driver includes:
+SET(LIB_NAME scriptengine_javascript)
+SET(LIB_PUBLIC_HEADERS ${TARGET_H} )
+INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
+
diff --git a/src/osgEarthDrivers/script_engine_duktape/DuktapeEngine b/src/osgEarthDrivers/script_engine_duktape/DuktapeEngine
new file mode 100644
index 0000000..0c8b113
--- /dev/null
+++ b/src/osgEarthDrivers/script_engine_duktape/DuktapeEngine
@@ -0,0 +1,72 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+#ifndef OSGEARTHDRIVERS_DUKTAPE_ENGINE_H
+#define OSGEARTHDRIVERS_DUKTAPE_ENGINE_H 1
+
+#include <osgEarthFeatures/ScriptEngine>
+#include <osgEarthFeatures/Script>
+#include <osgEarthFeatures/Feature>
+#include <osgEarth/ThreadingUtils>
+#include "duktape.h"
+
+namespace osgEarth { namespace Drivers { namespace Duktape
+{
+    using namespace osgEarth::Features;
+
+    /**
+     * JavaScript engine built on the Duktape embeddable Javascript
+     * interpreter. http://duktape.org
+     */
+    class DuktapeEngine : public osgEarth::Features::ScriptEngine
+    {
+    public:
+        /** Construct the engine */
+        DuktapeEngine(const ScriptEngineOptions& options);
+
+        /** Report language support */
+        bool supported(std::string lang) { 
+            return osgEarth::toLower(lang).compare("javascript") == 0;
+        }
+
+        /** Run a javascript code snippet. */
+        ScriptResult run(
+            const std::string&                       code, 
+            osgEarth::Features::Feature const*       feature,
+            osgEarth::Features::FilterContext const* context);
+
+    protected:
+        virtual ~DuktapeEngine();
+
+        struct Context
+        {
+            Context();
+            ~Context();
+            void initialize(const ScriptEngineOptions&);
+            duk_context* _ctx;
+        };
+
+        Threading::PerThread<Context> _contexts;
+
+        const ScriptEngineOptions _options;
+    };
+
+} } } // namespace osgEarth::Drivers::Duktape
+
+#endif // OSGEARTHDRIVERS_DUKTAPE_ENGINE_H
diff --git a/src/osgEarthDrivers/script_engine_duktape/DuktapeEngine.cpp b/src/osgEarthDrivers/script_engine_duktape/DuktapeEngine.cpp
new file mode 100644
index 0000000..265669e
--- /dev/null
+++ b/src/osgEarthDrivers/script_engine_duktape/DuktapeEngine.cpp
@@ -0,0 +1,277 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include "DuktapeEngine"
+#include "JSGeometry"
+#include <osgEarth/JsonUtils>
+#include <osgEarth/StringUtils>
+#include <osgEarthFeatures/GeometryUtils>
+#include <sstream>
+
+#undef  LC
+#define LC "[duktape] "
+
+// defining this will setup and tear down a complete duktape heap/context
+// for each and every invocation. Good for testing memory usage until we
+// complete the feature set.
+//#define MAXIMUM_ISOLATION
+
+using namespace osgEarth;
+using namespace osgEarth::Features;
+using namespace osgEarth::Drivers::Duktape;
+
+//............................................................................
+
+namespace
+{
+    // generic logging function.
+    static duk_ret_t log( duk_context *ctx ) {
+        duk_idx_t i, n;
+
+        std::string msg;
+        for( i = 0, n = duk_get_top( ctx ); i < n; i++ ) {
+            if( i > 0 ) {
+                msg += " ";
+            }
+            msg += duk_safe_to_string( ctx, i );
+        }
+        OE_WARN << LC << msg << std::endl;
+        return 0;
+    }
+
+    static duk_ret_t oe_duk_save_feature(duk_context* ctx)
+    {
+        // stack: [ptr]
+
+        // pull the feature ptr from argument #0
+        Feature* feature = reinterpret_cast<Feature*>(duk_require_pointer(ctx, 0));
+
+        // Fetch the feature data:
+        duk_push_global_object(ctx);                    
+        // [ptr, global]
+
+        if ( !duk_get_prop_string(ctx, -1, "feature") || !duk_is_object(ctx, -1))
+            return 0;
+
+         // [ptr, global, feature]
+
+        if ( duk_get_prop_string(ctx, -1, "properties") && duk_is_object(ctx, -1) )
+        {
+            // [ptr, global, feature, props]
+            duk_enum(ctx, -1, 0);                       
+        
+            // [ptr, global, feature, props, enum]
+            while( duk_next(ctx, -1, 1/*get_value=true*/) )
+            {
+                std::string key( duk_get_string(ctx, -2) );
+                if (duk_is_string(ctx, -1))
+                {
+                    feature->set( key, std::string(duk_get_string(ctx, -1)) );
+                }
+                else if (duk_is_number(ctx, -1))
+                {
+                    feature->set( key, (double)duk_get_number(ctx, -1) );
+                }
+                else if (duk_is_boolean(ctx, -1))
+                {
+                    feature->set( key, duk_get_boolean(ctx, -1) );
+                }
+                else if( duk_is_null_or_undefined( ctx, -1 ) )
+                {
+                    feature->setNull( key );
+                }
+                 duk_pop_2(ctx);
+            }
+
+            duk_pop_2(ctx);
+            // [ptr, global, feature]
+        }
+        else
+        {   // [ptr, global, feature, undefined]
+            duk_pop(ctx);
+            // [ptr, global, feature]
+        }
+
+        // save the geometry, if set:
+        if ( duk_get_prop_string(ctx, -1, "geometry")&& duk_is_object(ctx, -1) )
+        {
+            // [ptr, global, feature, geometry]
+            std::string json( duk_json_encode(ctx, -1) ); // [ptr, global, feature, json]
+            Geometry* newGeom = GeometryUtils::geometryFromGeoJSON(json);
+            if ( newGeom )
+            {
+                feature->setGeometry( newGeom );
+            }
+            duk_pop(ctx);
+            // [ptr, global, feature]
+        }
+        else
+        {
+            // [ptr, global, feature, undefined]
+        }
+        
+        // [ptr, global, feature]
+        duk_pop_2(ctx);     // [ptr] (as we found it)
+        return 0;           // no return values.
+    }
+}
+
+//............................................................................
+
+namespace
+{
+    // Create a "feature" object in the global namespace.
+    void setFeature(duk_context* ctx, Feature const* feature)
+    {
+        std::string geojson = feature->getGeoJSON();
+        
+        duk_push_global_object(ctx);                         // [global]
+        duk_push_string(ctx, geojson.c_str());               // [global, json]
+        duk_json_decode(ctx, -1);                            // [global, feature]
+        duk_push_pointer(ctx, (void*)feature);               // [global, feature, ptr]
+        duk_put_prop_string(ctx, -2, "__ptr");               // [global, feature]
+        duk_put_prop_string(ctx, -2, "feature");             // [global]
+
+        // add the save() function and the "attributes" alias.
+        duk_eval_string_noresult(ctx,
+            "feature.save = function() {"
+            "    oe_duk_save_feature(this.__ptr);"
+            "} ");
+
+        duk_eval_string_noresult(ctx,
+            "Object.defineProperty(feature, 'attributes', {get:function() {return feature.properties;}});");
+
+        GeometryAPI::bindToFeature(ctx);
+
+        duk_pop(ctx); 
+    }
+}
+
+//............................................................................
+
+DuktapeEngine::Context::Context()
+{
+    _ctx = 0L;
+}
+
+void
+DuktapeEngine::Context::initialize(const ScriptEngineOptions& options)
+{
+    if ( _ctx == 0L )
+    {
+        // new heap + context.
+        _ctx = duk_create_heap_default();
+
+        // if there is a static script, evaluate it first. This will register
+        // any functions or objects with the EcmaScript global object.
+        if ( options.script().isSet() )
+        {
+            bool ok = (duk_peval_string(_ctx, options.script()->getCode().c_str()) == 0); // [ "result" ]
+            if ( !ok )
+            {
+                const char* err = duk_safe_to_string(_ctx, -1);
+                OE_WARN << LC << err << std::endl;
+            }
+            duk_pop(_ctx); // []
+        }
+
+        duk_push_global_object( _ctx );
+
+        // Add global log function.
+        duk_push_c_function( _ctx, log, DUK_VARARGS );
+        duk_put_prop_string( _ctx, -2, "log" );
+
+        // feature.save() callback
+        duk_push_c_function(_ctx, oe_duk_save_feature, 1/*numargs*/); // [global, function]
+        duk_put_prop_string(_ctx, -2, "oe_duk_save_feature");         // [global]
+
+        GeometryAPI::install(_ctx);
+
+        duk_pop(_ctx); // []
+    }
+}
+
+DuktapeEngine::Context::~Context()
+{
+    if ( _ctx )
+    {
+        duk_destroy_heap(_ctx);
+        _ctx = 0L;
+    }
+}
+
+//............................................................................
+
+DuktapeEngine::DuktapeEngine(const ScriptEngineOptions& options) :
+ScriptEngine( options ),
+_options    ( options )
+{
+    //nop
+}
+
+DuktapeEngine::~DuktapeEngine()
+{
+    //nop
+}
+
+ScriptResult
+DuktapeEngine::run(const std::string&   code,
+                   Feature const*       feature,
+                   FilterContext const* context)
+{
+    if (code.empty())
+        return ScriptResult(EMPTY_STRING, false, "Script is empty.");
+
+#ifdef MAXIMUM_ISOLATION
+    // brand new context every time
+    Context c;
+    c.initialize( _options );
+    duk_context* ctx = c._ctx;
+#else
+    // cache the Context on a per-thread basis
+    Context& c = _contexts.get();
+    c.initialize( _options );
+    duk_context* ctx = c._ctx;
+#endif
+
+	if(feature) {
+		// encode the feature in the global object and push a
+        // native pointer:
+		setFeature(ctx, feature);
+	}
+
+    // run the script. On error, the top of stack will hold the error
+    // message instead of the return value.
+    std::string resultString;
+    bool ok = (duk_peval_string(ctx, code.c_str()) == 0); // [ "result" ]
+    const char* resultVal = duk_to_string(ctx, -1);
+    if ( resultVal )
+        resultString = resultVal;
+
+    if ( !ok )
+    {
+        OE_WARN << LC << "Error: source =\n" << code << std::endl;
+    }
+
+    // pop the return value:
+    duk_pop(ctx); // []
+
+    return ok ?
+        ScriptResult(resultString, true) :
+        ScriptResult("", false, resultString);
+}
diff --git a/src/osgEarthDrivers/script_engine_duktape/JSGeometry b/src/osgEarthDrivers/script_engine_duktape/JSGeometry
new file mode 100644
index 0000000..fbf52db
--- /dev/null
+++ b/src/osgEarthDrivers/script_engine_duktape/JSGeometry
@@ -0,0 +1,180 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+#ifndef OSGEARTHDRIVERS_DUKTAPE_JS_GEOMETRY_H
+#define OSGEARTHDRIVERS_DUKTAPE_JS_GEOMETRY_H
+
+#include <osgEarthSymbology/Geometry>
+#include <osgEarthFeatures/GeometryUtils>
+#include "duktape.h"
+
+#define LC "[duktape] "
+
+namespace osgEarth { namespace Drivers { namespace Duktape
+{
+    using namespace osgEarth::Features;
+    using namespace osgEarth::Symbology;
+
+    struct GeometryAPI
+    {
+        static void install(duk_context* ctx)
+        {
+            duk_push_c_function(ctx, GeometryAPI::buffer, 2/*numargs*/); // [global, function]        
+            duk_put_prop_string(ctx, -2, "oe_geometry_buffer");          // [global]
+
+            duk_push_c_function(ctx, GeometryAPI::getBounds, 1);
+            duk_put_prop_string(ctx, -2, "oe_geometry_getBounds");
+
+            duk_push_c_function(ctx, GeometryAPI::cloneAs, 2);
+            duk_put_prop_string(ctx, -2, "oe_geometry_cloneAs");
+
+            duk_eval_string_noresult(ctx,
+                "oe_duk_bind_geometry_api = function(geometry) {"
+                "    geometry.getBounds = function() {"
+                "        return oe_geometry_getBounds(this);"
+                "    };"
+                "    geometry.buffer = function(distance) {"
+                "        var result = oe_geometry_buffer(this, distance);"
+                "        return oe_duk_bind_geometry_api(result);"
+                "    };"
+                "    geometry.cloneAs = function(typeName) {"
+                "        var result = oe_geometry_cloneAs(this, typeName);"
+                "        return oe_duk_bind_geometry_api(result);"
+                "    };"
+                "    return geometry;"
+                "};"
+            );
+        }
+
+        static void bindToFeature(duk_context* ctx)
+        {
+            duk_eval_string_noresult(ctx,
+                "oe_duk_bind_geometry_api(feature.geometry);" );
+        }
+        
+        /**
+         * buffer operation
+         * input:  1) geometry GeoJSON, 2) distance
+         * output: geometry GeoJSON object, or undefined on fail
+         */
+        static duk_ret_t buffer(duk_context* ctx)
+        {
+            // arg#0: geometry object
+            if ( !duk_is_object(ctx, 0) && !duk_is_number(ctx, 1) )
+            {
+                OE_WARN << LC << "geometry.buffer(): illegal arguments" << std::endl;
+                return DUK_RET_TYPE_ERROR;
+            }
+
+            // arg#0 : geometry
+            std::string geomJSON = duk_json_encode(ctx, 0);
+            osg::ref_ptr<Geometry> input = GeometryUtils::geometryFromGeoJSON(geomJSON);
+            if ( !input.valid() )
+                return DUK_RET_TYPE_ERROR;
+        
+            // arg#1 : distance
+            double distance = duk_get_number(ctx, 1);
+
+            // run the buffer op:
+            osg::ref_ptr<Geometry> output;
+            BufferParameters p;
+            p._cornerSegs = 0; // speed it up
+            p._joinStyle  = p.JOIN_ROUND;
+            p._capStyle   = p.CAP_ROUND;
+            if ( input->buffer(distance, output, p) )
+            {
+                duk_push_string(ctx, GeometryUtils::geometryToGeoJSON(output.get()).c_str());
+                duk_json_decode(ctx, -1);
+            }
+            else
+            {
+                duk_push_undefined(ctx);
+            }
+            return 1;
+        }
+
+        // input: GeoJSON geometry
+        // output: bounds object, e.g. { xmin: num, ymin: num, xmax: num, ymax: num }
+        static duk_ret_t getBounds(duk_context* ctx)
+        {
+            if ( !duk_is_object(ctx, 0) ) 
+            {
+                OE_WARN << LC << "geometry.getBounds(): illegal arguments" << std::endl;
+                return DUK_RET_TYPE_ERROR;
+            }
+
+            // arg#0 : geometry
+            std::string geomJSON = duk_json_encode(ctx, 0);
+            osg::ref_ptr<Geometry> input = GeometryUtils::geometryFromGeoJSON(geomJSON);
+            if ( !input.valid() )
+                return DUK_RET_TYPE_ERROR;
+
+            Bounds b = input->getBounds();
+
+            duk_push_object(ctx);
+            duk_push_number(ctx, b.xMin());
+            duk_put_prop_string(ctx, -2, "xmin");
+            duk_push_number(ctx, b.yMin());
+            duk_put_prop_string(ctx, -2, "ymin");
+            duk_push_number(ctx, b.xMax());
+            duk_put_prop_string(ctx, -2, "xmax");
+            duk_push_number(ctx, b.yMax());
+            duk_put_prop_string(ctx, -2, "ymax");
+
+            return 1;
+        }
+
+        // input: type name
+        // output: new geometry
+        static duk_ret_t cloneAs(duk_context* ctx)
+        {
+            // arg#0 : geometry
+            std::string geomJSON = duk_json_encode(ctx, 0);
+            osg::ref_ptr<Geometry> input = GeometryUtils::geometryFromGeoJSON(geomJSON);
+            if ( !input.valid() )
+                return DUK_RET_TYPE_ERROR;
+        
+            // arg#1 : type
+            std::string typeName = osgEarth::toLower( duk_get_string(ctx, 1) );
+            Geometry::Type type;
+            if ( typeName == "point" || typeName == "multipoint" )
+                type = Geometry::TYPE_POINTSET;
+            else if (typeName == "linestring" || typeName == "multilinestring")
+                type = Geometry::TYPE_LINESTRING;
+            else //if (typeName == "polygon")
+                type = Geometry::TYPE_POLYGON;
+
+            // run the buffer op:
+            osg::ref_ptr<Geometry> output = input->cloneAs(type);
+            if ( output.valid() )
+            {
+                duk_push_string(ctx, GeometryUtils::geometryToGeoJSON(output.get()).c_str());
+                duk_json_decode(ctx, -1);
+            }
+            else
+            {
+                duk_push_undefined(ctx);
+            }
+            return 1;
+        }
+    };
+
+} } } // namespace osgEarth::Drivers::Duktape
+
+#endif // OSGEARTHDRIVERS_DUKTAPE_JS_GEOMETRY_H
diff --git a/src/osgEarthDrivers/script_engine_duktape/Plugin.cpp b/src/osgEarthDrivers/script_engine_duktape/Plugin.cpp
new file mode 100644
index 0000000..417f3d8
--- /dev/null
+++ b/src/osgEarthDrivers/script_engine_duktape/Plugin.cpp
@@ -0,0 +1,59 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osgDB/ReaderWriter>
+#include <osgEarthFeatures/ScriptEngine>
+#include <osgEarth/Common>
+#include <osgDB/FileNameUtils>
+
+#include "DuktapeEngine"
+
+#define LC "[Duktape] "
+
+namespace osgEarth { namespace Drivers { namespace Duktape
+{
+    /**
+     * Driver plugin entry point - creates a DuktapeEngine instance.
+     */
+    class DuktapeScriptEngineDriver : public osgEarth::Features::ScriptEngineDriver
+    {
+    public:
+        DuktapeScriptEngineDriver()
+        {
+            supportsExtension(
+                "osgearth_scriptengine_javascript", "osgEarth Duktape JavaScript Engine" );
+        }
+
+        const char* className()
+        {
+            return "osgEarth Duktape JavaScript Engine";
+        }
+
+        ReadResult readObject(const std::string& filename, const osgDB::Options* dbOptions) const
+        {
+          if ( !acceptsExtension(osgDB::getLowerCaseFileExtension(filename)) )
+                return ReadResult::FILE_NOT_HANDLED;
+
+          OE_INFO << LC << "Loaded duktape JavaScript engine" << std::endl;
+          return ReadResult( new DuktapeEngine(getScriptEngineOptions(dbOptions)) );
+        }
+    };
+
+    REGISTER_OSGPLUGIN(osgearth_scriptengine_javascript, DuktapeScriptEngineDriver)
+
+} } } // namespace osgEarth::Drivers::Duktape
diff --git a/src/osgEarthDrivers/script_engine_duktape/duktape.c b/src/osgEarthDrivers/script_engine_duktape/duktape.c
new file mode 100644
index 0000000..a3a0d7e
--- /dev/null
+++ b/src/osgEarthDrivers/script_engine_duktape/duktape.c
@@ -0,0 +1,65023 @@
+/*
+ *  Single file autogenerated distributable for Duktape 0.11.0.
+ *  Git commit 621b545c474dd7a3def7aefed0557b1a825a4578 (v0.10.0-827-g621b545).
+ *
+ *  See Duktape AUTHORS.txt and LICENSE.txt for copyright and
+ *  licensing information.
+ */
+
+/* LICENSE.txt */
+/*
+*  ===============
+*  Duktape license
+*  ===============
+*
+*  (http://opensource.org/licenses/MIT)
+*
+*  Copyright (c) 2013-2014 by Duktape authors (see AUTHORS.txt)
+*
+*  Permission is hereby granted, free of charge, to any person obtaining a copy
+*  of this software and associated documentation files (the "Software"), to deal
+*  in the Software without restriction, including without limitation the rights
+*  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+*  copies of the Software, and to permit persons to whom the Software is
+*  furnished to do so, subject to the following conditions:
+*
+*  The above copyright notice and this permission notice shall be included in
+*  all copies or substantial portions of the Software.
+*
+*  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+*  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+*  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+*  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+*  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+*  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+*  THE SOFTWARE.
+*
+*/
+/* AUTHORS.txt */
+/*
+*  ===============
+*  Duktape authors
+*  ===============
+*
+*  Copyright
+*  =========
+*
+*  Duktape copyrights are held by its authors.  Each author has a copyright
+*  to their contribution, and agrees to irrevocably license the contribution
+*  under the Duktape ``LICENSE.txt``.
+*
+*  Authors
+*  =======
+*
+*  Please include an e-mail address, a link to your GitHub profile, or something
+*  similar to allow your contribution to be identified accurately.
+*
+*  The following people have contributed code and agreed to irrevocably license
+*  their contributions under the Duktape ``LICENSE.txt`` (in order of appearance):
+*
+*  * Sami Vaarala <sami.vaarala at iki.fi>
+*  * Niki Dobrev
+*  * Andreas \u00d6man <andreas at lonelycoder.com>
+*
+*  Other contributions
+*  ===================
+*
+*  The following people have contributed something other than code (e.g. reported
+*  bugs, provided ideas, etc; in order of appearance):
+*
+*  * Greg Burns
+*  * Anthony Rabine
+*  * Carlos Costa
+*  * Aur\u00e9lien Bouilland
+*  * Preet Desai (Pris Matic)
+*  * judofyr (http://www.reddit.com/user/judofyr)
+*  * Jason Woofenden
+*  * Micha\u0142 Przyby\u015b
+*  * Anthony Howe
+*  * Conrad Pankoff
+*  * Jim Schimpf
+*  * Rajaran Gaunker (https://github.com/zimbabao)
+*  * Andreas \u00d6man
+*  * Doug Sanden
+*  * Remo Eichenberger (https://github.com/remoe)
+*/
+#line 1 "duk_internal.h"
+/*
+ *  Top-level include file to be used for all (internal) source files.
+ *
+ *  Source files should not include individual header files, as they
+ *  have not been designed to be individually included.
+ */
+
+#ifndef DUK_INTERNAL_H_INCLUDED
+#define DUK_INTERNAL_H_INCLUDED
+
+/*
+ *  The 'duktape.h' header provides the public API, but also handles all
+ *  compiler and platform specific feature detection, Duktape feature
+ *  resolution, inclusion of system headers, etc.  These have been merged
+ *  because the public API is also dependent on e.g. detecting appropriate
+ *  C types which is quite platform/compiler specific especially for a non-C99
+ *  build.  The public API is also dependent on the resolved feature set.
+ *
+ *  Some actions taken by the merged header (such as including system headers)
+ *  are not appropriate for building a user application.  The define
+ *  DUK_COMPILING_DUKTAPE allows the merged header to skip/include some
+ *  sections depending on what is being built.
+ */
+
+#define DUK_COMPILING_DUKTAPE
+#include "duktape.h"
+
+/*
+ *  User declarations, e.g. prototypes for user functions used by Duktape
+ *  macros.  Concretely, if DUK_OPT_PANIC_HANDLER is used and the macro
+ *  value calls a user function, it needs to be declared for Duktape
+ *  compilation to avoid warnings.
+ */
+
+DUK_USE_USER_DECLARE()
+
+/*
+ *  Duktape includes (other than duk_features.h)
+ *
+ *  The header files expect to be included in an order which satisfies header
+ *  dependencies correctly (the headers themselves don't include any other
+ *  includes).  Forward declarations are used to break circular struct/typedef
+ *  dependencies.
+ */
+
+#line 1 "duk_replacements.h"
+#ifndef DUK_REPLACEMENTS_H_INCLUDED
+#define DUK_REPLACEMENTS_H_INCLUDED
+
+#ifdef DUK_USE_REPL_FPCLASSIFY
+int duk_repl_fpclassify(double x);
+#endif
+
+#ifdef DUK_USE_REPL_SIGNBIT
+int duk_repl_signbit(double x);
+#endif
+
+#ifdef DUK_USE_REPL_ISFINITE
+int duk_repl_isfinite(double x);
+#endif
+
+#ifdef DUK_USE_REPL_ISNAN
+int duk_repl_isnan(double x);
+#endif
+
+#ifdef DUK_USE_REPL_ISINF
+int duk_repl_isinf(double x);
+#endif
+
+#endif  /* DUK_REPLACEMENTS_H_INCLUDED */
+#line 1 "duk_jmpbuf.h"
+/*
+ *  Wrapper for jmp_buf.
+ *
+ *  This is used because jmp_buf is an array type for backward compatibility.
+ *  Wrapping jmp_buf in a struct makes pointer references, sizeof, etc,
+ *  behave more intuitively.
+ *
+ *  http://en.wikipedia.org/wiki/Setjmp.h#Member_types
+ */
+
+#ifndef DUK_JMPBUF_H_INCLUDED
+#define DUK_JMPBUF_H_INCLUDED
+
+struct duk_jmpbuf {
+	jmp_buf jb;
+};
+
+#endif  /* DUK_JMPBUF_H_INCLUDED */
+
+#line 1 "duk_forwdecl.h"
+/*
+ *  Forward declarations for all Duktape structures.
+ */
+
+#ifndef DUK_FORWDECL_H_INCLUDED
+#define DUK_FORWDECL_H_INCLUDED
+
+/*
+ *  Forward declarations
+ */
+
+struct duk_jmpbuf;
+
+/* duk_tval intentionally skipped */
+struct duk_heaphdr;
+struct duk_heaphdr_string;
+struct duk_hstring;
+struct duk_hobject;
+struct duk_hcompiledfunction;
+struct duk_hnativefunction;
+struct duk_hthread;
+struct duk_hbuffer;
+struct duk_hbuffer_fixed;
+struct duk_hbuffer_dynamic;
+
+struct duk_propaccessor;
+union duk_propvalue;
+struct duk_propdesc;
+
+struct duk_heap;
+
+struct duk_activation;
+struct duk_catcher;
+struct duk_strcache;
+struct duk_ljstate;
+
+#ifdef DUK_USE_DEBUG
+struct duk_fixedbuffer;
+#endif
+
+struct duk_bitdecoder_ctx;
+struct duk_bitencoder_ctx;
+
+struct duk_token;
+struct duk_re_token;
+struct duk_lexer_point;
+struct duk_lexer_ctx;
+
+struct duk_compiler_instr;
+struct duk_compiler_func;
+struct duk_compiler_ctx;
+
+struct duk_re_matcher_ctx;
+struct duk_re_compiler_ctx;
+
+typedef struct duk_jmpbuf duk_jmpbuf;
+
+/* duk_tval intentionally skipped */
+typedef struct duk_heaphdr duk_heaphdr;
+typedef struct duk_heaphdr_string duk_heaphdr_string;
+typedef struct duk_hstring duk_hstring;
+typedef struct duk_hobject duk_hobject;
+typedef struct duk_hcompiledfunction duk_hcompiledfunction;
+typedef struct duk_hnativefunction duk_hnativefunction;
+typedef struct duk_hthread duk_hthread;
+typedef struct duk_hbuffer duk_hbuffer;
+typedef struct duk_hbuffer_fixed duk_hbuffer_fixed;
+typedef struct duk_hbuffer_dynamic duk_hbuffer_dynamic;
+
+typedef struct duk_propaccessor duk_propaccessor;
+typedef union duk_propvalue duk_propvalue;
+typedef struct duk_propdesc duk_propdesc;
+ 
+typedef struct duk_heap duk_heap;
+
+typedef struct duk_activation duk_activation;
+typedef struct duk_catcher duk_catcher;
+typedef struct duk_strcache duk_strcache;
+typedef struct duk_ljstate duk_ljstate;
+
+#ifdef DUK_USE_DEBUG
+typedef struct duk_fixedbuffer duk_fixedbuffer;
+#endif
+
+typedef struct duk_bitdecoder_ctx duk_bitdecoder_ctx;
+typedef struct duk_bitencoder_ctx duk_bitencoder_ctx;
+
+typedef struct duk_token duk_token;
+typedef struct duk_re_token duk_re_token;
+typedef struct duk_lexer_point duk_lexer_point;
+typedef struct duk_lexer_ctx duk_lexer_ctx;
+
+typedef struct duk_compiler_instr duk_compiler_instr;
+typedef struct duk_compiler_func duk_compiler_func;
+typedef struct duk_compiler_ctx duk_compiler_ctx;
+
+typedef struct duk_re_matcher_ctx duk_re_matcher_ctx;
+typedef struct duk_re_compiler_ctx duk_re_compiler_ctx;
+	
+#endif  /* DUK_FORWDECL_H_INCLUDED */
+#line 1 "duk_builtins.h"
+/*
+ *  Automatically generated by genbuiltins.py, do not edit!
+ */
+
+#ifndef DUK_BUILTINS_H_INCLUDED
+#define DUK_BUILTINS_H_INCLUDED
+
+#if defined(DUK_USE_DOUBLE_LE)
+extern const duk_uint8_t duk_strings_data[];
+
+#define DUK_STRDATA_DATA_LENGTH                                       1931
+#define DUK_STRDATA_MAX_STRLEN                                        24
+
+#define DUK_STRIDX_UC_LOGGER                                          0                              /* 'Logger' */
+#define DUK_STRIDX_UC_THREAD                                          1                              /* 'Thread' */
+#define DUK_STRIDX_UC_POINTER                                         2                              /* 'Pointer' */
+#define DUK_STRIDX_UC_BUFFER                                          3                              /* 'Buffer' */
+#define DUK_STRIDX_DEC_ENV                                            4                              /* 'DecEnv' */
+#define DUK_STRIDX_OBJ_ENV                                            5                              /* 'ObjEnv' */
+#define DUK_STRIDX_EMPTY_STRING                                       6                              /* '' */
+#define DUK_STRIDX_GLOBAL                                             7                              /* 'global' */
+#define DUK_STRIDX_UC_ARGUMENTS                                       8                              /* 'Arguments' */
+#define DUK_STRIDX_JSON                                               9                              /* 'JSON' */
+#define DUK_STRIDX_MATH                                               10                             /* 'Math' */
+#define DUK_STRIDX_UC_ERROR                                           11                             /* 'Error' */
+#define DUK_STRIDX_REG_EXP                                            12                             /* 'RegExp' */
+#define DUK_STRIDX_DATE                                               13                             /* 'Date' */
+#define DUK_STRIDX_UC_NUMBER                                          14                             /* 'Number' */
+#define DUK_STRIDX_UC_BOOLEAN                                         15                             /* 'Boolean' */
+#define DUK_STRIDX_UC_STRING                                          16                             /* 'String' */
+#define DUK_STRIDX_ARRAY                                              17                             /* 'Array' */
+#define DUK_STRIDX_UC_FUNCTION                                        18                             /* 'Function' */
+#define DUK_STRIDX_UC_OBJECT                                          19                             /* 'Object' */
+#define DUK_STRIDX_UC_NULL                                            20                             /* 'Null' */
+#define DUK_STRIDX_UC_UNDEFINED                                       21                             /* 'Undefined' */
+#define DUK_STRIDX_JSON_EXT_FUNCTION2                                 22                             /* '{_func:true}' */
+#define DUK_STRIDX_JSON_EXT_FUNCTION1                                 23                             /* '{"_func":true}' */
+#define DUK_STRIDX_JSON_EXT_NEGINF                                    24                             /* '{"_ninf":true}' */
+#define DUK_STRIDX_JSON_EXT_POSINF                                    25                             /* '{"_inf":true}' */
+#define DUK_STRIDX_JSON_EXT_NAN                                       26                             /* '{"_nan":true}' */
+#define DUK_STRIDX_JSON_EXT_UNDEFINED                                 27                             /* '{"_undef":true}' */
+#define DUK_STRIDX_TO_LOG_STRING                                      28                             /* 'toLogString' */
+#define DUK_STRIDX_CLOG                                               29                             /* 'clog' */
+#define DUK_STRIDX_LC_L                                               30                             /* 'l' */
+#define DUK_STRIDX_LC_N                                               31                             /* 'n' */
+#define DUK_STRIDX_LC_FATAL                                           32                             /* 'fatal' */
+#define DUK_STRIDX_LC_ERROR                                           33                             /* 'error' */
+#define DUK_STRIDX_LC_WARN                                            34                             /* 'warn' */
+#define DUK_STRIDX_LC_DEBUG                                           35                             /* 'debug' */
+#define DUK_STRIDX_LC_TRACE                                           36                             /* 'trace' */
+#define DUK_STRIDX_RAW                                                37                             /* 'raw' */
+#define DUK_STRIDX_FMT                                                38                             /* 'fmt' */
+#define DUK_STRIDX_CURRENT                                            39                             /* 'current' */
+#define DUK_STRIDX_RESUME                                             40                             /* 'resume' */
+#define DUK_STRIDX_COMPACT                                            41                             /* 'compact' */
+#define DUK_STRIDX_JC                                                 42                             /* 'jc' */
+#define DUK_STRIDX_JX                                                 43                             /* 'jx' */
+#define DUK_STRIDX_BASE64                                             44                             /* 'base64' */
+#define DUK_STRIDX_HEX                                                45                             /* 'hex' */
+#define DUK_STRIDX_DEC                                                46                             /* 'dec' */
+#define DUK_STRIDX_ENC                                                47                             /* 'enc' */
+#define DUK_STRIDX_FIN                                                48                             /* 'fin' */
+#define DUK_STRIDX_GC                                                 49                             /* 'gc' */
+#define DUK_STRIDX_ACT                                                50                             /* 'act' */
+#define DUK_STRIDX_LC_INFO                                            51                             /* 'info' */
+#define DUK_STRIDX_VERSION                                            52                             /* 'version' */
+#define DUK_STRIDX_ENV                                                53                             /* 'env' */
+#define DUK_STRIDX_MOD_LOADED                                         54                             /* 'modLoaded' */
+#define DUK_STRIDX_MOD_SEARCH                                         55                             /* 'modSearch' */
+#define DUK_STRIDX_ERR_THROW                                          56                             /* 'errThrow' */
+#define DUK_STRIDX_ERR_CREATE                                         57                             /* 'errCreate' */
+#define DUK_STRIDX_COMPILE                                            58                             /* 'compile' */
+#define DUK_STRIDX_INT_REGBASE                                        59                             /* '\x00regbase' */
+#define DUK_STRIDX_INT_THREAD                                         60                             /* '\x00thread' */
+#define DUK_STRIDX_INT_HANDLER                                        61                             /* '\x00handler' */
+#define DUK_STRIDX_INT_FINALIZER                                      62                             /* '\x00finalizer' */
+#define DUK_STRIDX_INT_CALLEE                                         63                             /* '\x00callee' */
+#define DUK_STRIDX_INT_MAP                                            64                             /* '\x00map' */
+#define DUK_STRIDX_INT_ARGS                                           65                             /* '\x00args' */
+#define DUK_STRIDX_INT_THIS                                           66                             /* '\x00this' */
+#define DUK_STRIDX_INT_PC2LINE                                        67                             /* '\x00pc2line' */
+#define DUK_STRIDX_INT_SOURCE                                         68                             /* '\x00source' */
+#define DUK_STRIDX_INT_VARENV                                         69                             /* '\x00varenv' */
+#define DUK_STRIDX_INT_LEXENV                                         70                             /* '\x00lexenv' */
+#define DUK_STRIDX_INT_VARMAP                                         71                             /* '\x00varmap' */
+#define DUK_STRIDX_INT_FORMALS                                        72                             /* '\x00formals' */
+#define DUK_STRIDX_INT_BYTECODE                                       73                             /* '\x00bytecode' */
+#define DUK_STRIDX_INT_NEXT                                           74                             /* '\x00next' */
+#define DUK_STRIDX_INT_TARGET                                         75                             /* '\x00target' */
+#define DUK_STRIDX_INT_VALUE                                          76                             /* '\x00value' */
+#define DUK_STRIDX_LC_POINTER                                         77                             /* 'pointer' */
+#define DUK_STRIDX_LC_BUFFER                                          78                             /* 'buffer' */
+#define DUK_STRIDX_TRACEDATA                                          79                             /* 'tracedata' */
+#define DUK_STRIDX_LINE_NUMBER                                        80                             /* 'lineNumber' */
+#define DUK_STRIDX_FILE_NAME                                          81                             /* 'fileName' */
+#define DUK_STRIDX_PC                                                 82                             /* 'pc' */
+#define DUK_STRIDX_STACK                                              83                             /* 'stack' */
+#define DUK_STRIDX_THROW_TYPE_ERROR                                   84                             /* 'ThrowTypeError' */
+#define DUK_STRIDX_DUKTAPE                                            85                             /* 'Duktape' */
+#define DUK_STRIDX_ID                                                 86                             /* 'id' */
+#define DUK_STRIDX_REQUIRE                                            87                             /* 'require' */
+#define DUK_STRIDX___PROTO__                                          88                             /* '__proto__' */
+#define DUK_STRIDX_SET_PROTOTYPE_OF                                   89                             /* 'setPrototypeOf' */
+#define DUK_STRIDX_OWN_KEYS                                           90                             /* 'ownKeys' */
+#define DUK_STRIDX_ENUMERATE                                          91                             /* 'enumerate' */
+#define DUK_STRIDX_DELETE_PROPERTY                                    92                             /* 'deleteProperty' */
+#define DUK_STRIDX_HAS                                                93                             /* 'has' */
+#define DUK_STRIDX_PROXY                                              94                             /* 'Proxy' */
+#define DUK_STRIDX_CALLEE                                             95                             /* 'callee' */
+#define DUK_STRIDX_INVALID_DATE                                       96                             /* 'Invalid Date' */
+#define DUK_STRIDX_BRACKETED_ELLIPSIS                                 97                             /* '[...]' */
+#define DUK_STRIDX_NEWLINE_TAB                                        98                             /* '\n\t' */
+#define DUK_STRIDX_SPACE                                              99                             /* ' ' */
+#define DUK_STRIDX_COMMA                                              100                            /* ',' */
+#define DUK_STRIDX_MINUS_ZERO                                         101                            /* '-0' */
+#define DUK_STRIDX_PLUS_ZERO                                          102                            /* '+0' */
+#define DUK_STRIDX_ZERO                                               103                            /* '0' */
+#define DUK_STRIDX_MINUS_INFINITY                                     104                            /* '-Infinity' */
+#define DUK_STRIDX_PLUS_INFINITY                                      105                            /* '+Infinity' */
+#define DUK_STRIDX_INFINITY                                           106                            /* 'Infinity' */
+#define DUK_STRIDX_LC_OBJECT                                          107                            /* 'object' */
+#define DUK_STRIDX_LC_STRING                                          108                            /* 'string' */
+#define DUK_STRIDX_LC_NUMBER                                          109                            /* 'number' */
+#define DUK_STRIDX_LC_BOOLEAN                                         110                            /* 'boolean' */
+#define DUK_STRIDX_LC_UNDEFINED                                       111                            /* 'undefined' */
+#define DUK_STRIDX_STRINGIFY                                          112                            /* 'stringify' */
+#define DUK_STRIDX_TAN                                                113                            /* 'tan' */
+#define DUK_STRIDX_SQRT                                               114                            /* 'sqrt' */
+#define DUK_STRIDX_SIN                                                115                            /* 'sin' */
+#define DUK_STRIDX_ROUND                                              116                            /* 'round' */
+#define DUK_STRIDX_RANDOM                                             117                            /* 'random' */
+#define DUK_STRIDX_POW                                                118                            /* 'pow' */
+#define DUK_STRIDX_MIN                                                119                            /* 'min' */
+#define DUK_STRIDX_MAX                                                120                            /* 'max' */
+#define DUK_STRIDX_LOG                                                121                            /* 'log' */
+#define DUK_STRIDX_FLOOR                                              122                            /* 'floor' */
+#define DUK_STRIDX_EXP                                                123                            /* 'exp' */
+#define DUK_STRIDX_COS                                                124                            /* 'cos' */
+#define DUK_STRIDX_CEIL                                               125                            /* 'ceil' */
+#define DUK_STRIDX_ATAN2                                              126                            /* 'atan2' */
+#define DUK_STRIDX_ATAN                                               127                            /* 'atan' */
+#define DUK_STRIDX_ASIN                                               128                            /* 'asin' */
+#define DUK_STRIDX_ACOS                                               129                            /* 'acos' */
+#define DUK_STRIDX_ABS                                                130                            /* 'abs' */
+#define DUK_STRIDX_SQRT2                                              131                            /* 'SQRT2' */
+#define DUK_STRIDX_SQRT1_2                                            132                            /* 'SQRT1_2' */
+#define DUK_STRIDX_PI                                                 133                            /* 'PI' */
+#define DUK_STRIDX_LOG10E                                             134                            /* 'LOG10E' */
+#define DUK_STRIDX_LOG2E                                              135                            /* 'LOG2E' */
+#define DUK_STRIDX_LN2                                                136                            /* 'LN2' */
+#define DUK_STRIDX_LN10                                               137                            /* 'LN10' */
+#define DUK_STRIDX_E                                                  138                            /* 'E' */
+#define DUK_STRIDX_MESSAGE                                            139                            /* 'message' */
+#define DUK_STRIDX_NAME                                               140                            /* 'name' */
+#define DUK_STRIDX_INPUT                                              141                            /* 'input' */
+#define DUK_STRIDX_INDEX                                              142                            /* 'index' */
+#define DUK_STRIDX_ESCAPED_EMPTY_REGEXP                               143                            /* '(?:)' */
+#define DUK_STRIDX_LAST_INDEX                                         144                            /* 'lastIndex' */
+#define DUK_STRIDX_MULTILINE                                          145                            /* 'multiline' */
+#define DUK_STRIDX_IGNORE_CASE                                        146                            /* 'ignoreCase' */
+#define DUK_STRIDX_SOURCE                                             147                            /* 'source' */
+#define DUK_STRIDX_TEST                                               148                            /* 'test' */
+#define DUK_STRIDX_EXEC                                               149                            /* 'exec' */
+#define DUK_STRIDX_TO_GMT_STRING                                      150                            /* 'toGMTString' */
+#define DUK_STRIDX_SET_YEAR                                           151                            /* 'setYear' */
+#define DUK_STRIDX_GET_YEAR                                           152                            /* 'getYear' */
+#define DUK_STRIDX_TO_JSON                                            153                            /* 'toJSON' */
+#define DUK_STRIDX_TO_ISO_STRING                                      154                            /* 'toISOString' */
+#define DUK_STRIDX_TO_UTC_STRING                                      155                            /* 'toUTCString' */
+#define DUK_STRIDX_SET_UTC_FULL_YEAR                                  156                            /* 'setUTCFullYear' */
+#define DUK_STRIDX_SET_FULL_YEAR                                      157                            /* 'setFullYear' */
+#define DUK_STRIDX_SET_UTC_MONTH                                      158                            /* 'setUTCMonth' */
+#define DUK_STRIDX_SET_MONTH                                          159                            /* 'setMonth' */
+#define DUK_STRIDX_SET_UTC_DATE                                       160                            /* 'setUTCDate' */
+#define DUK_STRIDX_SET_DATE                                           161                            /* 'setDate' */
+#define DUK_STRIDX_SET_UTC_HOURS                                      162                            /* 'setUTCHours' */
+#define DUK_STRIDX_SET_HOURS                                          163                            /* 'setHours' */
+#define DUK_STRIDX_SET_UTC_MINUTES                                    164                            /* 'setUTCMinutes' */
+#define DUK_STRIDX_SET_MINUTES                                        165                            /* 'setMinutes' */
+#define DUK_STRIDX_SET_UTC_SECONDS                                    166                            /* 'setUTCSeconds' */
+#define DUK_STRIDX_SET_SECONDS                                        167                            /* 'setSeconds' */
+#define DUK_STRIDX_SET_UTC_MILLISECONDS                               168                            /* 'setUTCMilliseconds' */
+#define DUK_STRIDX_SET_MILLISECONDS                                   169                            /* 'setMilliseconds' */
+#define DUK_STRIDX_SET_TIME                                           170                            /* 'setTime' */
+#define DUK_STRIDX_GET_TIMEZONE_OFFSET                                171                            /* 'getTimezoneOffset' */
+#define DUK_STRIDX_GET_UTC_MILLISECONDS                               172                            /* 'getUTCMilliseconds' */
+#define DUK_STRIDX_GET_MILLISECONDS                                   173                            /* 'getMilliseconds' */
+#define DUK_STRIDX_GET_UTC_SECONDS                                    174                            /* 'getUTCSeconds' */
+#define DUK_STRIDX_GET_SECONDS                                        175                            /* 'getSeconds' */
+#define DUK_STRIDX_GET_UTC_MINUTES                                    176                            /* 'getUTCMinutes' */
+#define DUK_STRIDX_GET_MINUTES                                        177                            /* 'getMinutes' */
+#define DUK_STRIDX_GET_UTC_HOURS                                      178                            /* 'getUTCHours' */
+#define DUK_STRIDX_GET_HOURS                                          179                            /* 'getHours' */
+#define DUK_STRIDX_GET_UTC_DAY                                        180                            /* 'getUTCDay' */
+#define DUK_STRIDX_GET_DAY                                            181                            /* 'getDay' */
+#define DUK_STRIDX_GET_UTC_DATE                                       182                            /* 'getUTCDate' */
+#define DUK_STRIDX_GET_DATE                                           183                            /* 'getDate' */
+#define DUK_STRIDX_GET_UTC_MONTH                                      184                            /* 'getUTCMonth' */
+#define DUK_STRIDX_GET_MONTH                                          185                            /* 'getMonth' */
+#define DUK_STRIDX_GET_UTC_FULL_YEAR                                  186                            /* 'getUTCFullYear' */
+#define DUK_STRIDX_GET_FULL_YEAR                                      187                            /* 'getFullYear' */
+#define DUK_STRIDX_GET_TIME                                           188                            /* 'getTime' */
+#define DUK_STRIDX_TO_LOCALE_TIME_STRING                              189                            /* 'toLocaleTimeString' */
+#define DUK_STRIDX_TO_LOCALE_DATE_STRING                              190                            /* 'toLocaleDateString' */
+#define DUK_STRIDX_TO_TIME_STRING                                     191                            /* 'toTimeString' */
+#define DUK_STRIDX_TO_DATE_STRING                                     192                            /* 'toDateString' */
+#define DUK_STRIDX_NOW                                                193                            /* 'now' */
+#define DUK_STRIDX_UTC                                                194                            /* 'UTC' */
+#define DUK_STRIDX_PARSE                                              195                            /* 'parse' */
+#define DUK_STRIDX_TO_PRECISION                                       196                            /* 'toPrecision' */
+#define DUK_STRIDX_TO_EXPONENTIAL                                     197                            /* 'toExponential' */
+#define DUK_STRIDX_TO_FIXED                                           198                            /* 'toFixed' */
+#define DUK_STRIDX_POSITIVE_INFINITY                                  199                            /* 'POSITIVE_INFINITY' */
+#define DUK_STRIDX_NEGATIVE_INFINITY                                  200                            /* 'NEGATIVE_INFINITY' */
+#define DUK_STRIDX_NAN                                                201                            /* 'NaN' */
+#define DUK_STRIDX_MIN_VALUE                                          202                            /* 'MIN_VALUE' */
+#define DUK_STRIDX_MAX_VALUE                                          203                            /* 'MAX_VALUE' */
+#define DUK_STRIDX_SUBSTR                                             204                            /* 'substr' */
+#define DUK_STRIDX_TRIM                                               205                            /* 'trim' */
+#define DUK_STRIDX_TO_LOCALE_UPPER_CASE                               206                            /* 'toLocaleUpperCase' */
+#define DUK_STRIDX_TO_UPPER_CASE                                      207                            /* 'toUpperCase' */
+#define DUK_STRIDX_TO_LOCALE_LOWER_CASE                               208                            /* 'toLocaleLowerCase' */
+#define DUK_STRIDX_TO_LOWER_CASE                                      209                            /* 'toLowerCase' */
+#define DUK_STRIDX_SUBSTRING                                          210                            /* 'substring' */
+#define DUK_STRIDX_SPLIT                                              211                            /* 'split' */
+#define DUK_STRIDX_SEARCH                                             212                            /* 'search' */
+#define DUK_STRIDX_REPLACE                                            213                            /* 'replace' */
+#define DUK_STRIDX_MATCH                                              214                            /* 'match' */
+#define DUK_STRIDX_LOCALE_COMPARE                                     215                            /* 'localeCompare' */
+#define DUK_STRIDX_CHAR_CODE_AT                                       216                            /* 'charCodeAt' */
+#define DUK_STRIDX_CHAR_AT                                            217                            /* 'charAt' */
+#define DUK_STRIDX_FROM_CHAR_CODE                                     218                            /* 'fromCharCode' */
+#define DUK_STRIDX_REDUCE_RIGHT                                       219                            /* 'reduceRight' */
+#define DUK_STRIDX_REDUCE                                             220                            /* 'reduce' */
+#define DUK_STRIDX_FILTER                                             221                            /* 'filter' */
+#define DUK_STRIDX_MAP                                                222                            /* 'map' */
+#define DUK_STRIDX_FOR_EACH                                           223                            /* 'forEach' */
+#define DUK_STRIDX_SOME                                               224                            /* 'some' */
+#define DUK_STRIDX_EVERY                                              225                            /* 'every' */
+#define DUK_STRIDX_LAST_INDEX_OF                                      226                            /* 'lastIndexOf' */
+#define DUK_STRIDX_INDEX_OF                                           227                            /* 'indexOf' */
+#define DUK_STRIDX_UNSHIFT                                            228                            /* 'unshift' */
+#define DUK_STRIDX_SPLICE                                             229                            /* 'splice' */
+#define DUK_STRIDX_SORT                                               230                            /* 'sort' */
+#define DUK_STRIDX_SLICE                                              231                            /* 'slice' */
+#define DUK_STRIDX_SHIFT                                              232                            /* 'shift' */
+#define DUK_STRIDX_REVERSE                                            233                            /* 'reverse' */
+#define DUK_STRIDX_PUSH                                               234                            /* 'push' */
+#define DUK_STRIDX_POP                                                235                            /* 'pop' */
+#define DUK_STRIDX_JOIN                                               236                            /* 'join' */
+#define DUK_STRIDX_CONCAT                                             237                            /* 'concat' */
+#define DUK_STRIDX_IS_ARRAY                                           238                            /* 'isArray' */
+#define DUK_STRIDX_LC_ARGUMENTS                                       239                            /* 'arguments' */
+#define DUK_STRIDX_CALLER                                             240                            /* 'caller' */
+#define DUK_STRIDX_BIND                                               241                            /* 'bind' */
+#define DUK_STRIDX_CALL                                               242                            /* 'call' */
+#define DUK_STRIDX_APPLY                                              243                            /* 'apply' */
+#define DUK_STRIDX_PROPERTY_IS_ENUMERABLE                             244                            /* 'propertyIsEnumerable' */
+#define DUK_STRIDX_IS_PROTOTYPE_OF                                    245                            /* 'isPrototypeOf' */
+#define DUK_STRIDX_HAS_OWN_PROPERTY                                   246                            /* 'hasOwnProperty' */
+#define DUK_STRIDX_VALUE_OF                                           247                            /* 'valueOf' */
+#define DUK_STRIDX_TO_LOCALE_STRING                                   248                            /* 'toLocaleString' */
+#define DUK_STRIDX_TO_STRING                                          249                            /* 'toString' */
+#define DUK_STRIDX_CONSTRUCTOR                                        250                            /* 'constructor' */
+#define DUK_STRIDX_SET                                                251                            /* 'set' */
+#define DUK_STRIDX_GET                                                252                            /* 'get' */
+#define DUK_STRIDX_ENUMERABLE                                         253                            /* 'enumerable' */
+#define DUK_STRIDX_CONFIGURABLE                                       254                            /* 'configurable' */
+#define DUK_STRIDX_WRITABLE                                           255                            /* 'writable' */
+#define DUK_STRIDX_VALUE                                              256                            /* 'value' */
+#define DUK_STRIDX_KEYS                                               257                            /* 'keys' */
+#define DUK_STRIDX_IS_EXTENSIBLE                                      258                            /* 'isExtensible' */
+#define DUK_STRIDX_IS_FROZEN                                          259                            /* 'isFrozen' */
+#define DUK_STRIDX_IS_SEALED                                          260                            /* 'isSealed' */
+#define DUK_STRIDX_PREVENT_EXTENSIONS                                 261                            /* 'preventExtensions' */
+#define DUK_STRIDX_FREEZE                                             262                            /* 'freeze' */
+#define DUK_STRIDX_SEAL                                               263                            /* 'seal' */
+#define DUK_STRIDX_DEFINE_PROPERTIES                                  264                            /* 'defineProperties' */
+#define DUK_STRIDX_DEFINE_PROPERTY                                    265                            /* 'defineProperty' */
+#define DUK_STRIDX_CREATE                                             266                            /* 'create' */
+#define DUK_STRIDX_GET_OWN_PROPERTY_NAMES                             267                            /* 'getOwnPropertyNames' */
+#define DUK_STRIDX_GET_OWN_PROPERTY_DESCRIPTOR                        268                            /* 'getOwnPropertyDescriptor' */
+#define DUK_STRIDX_GET_PROTOTYPE_OF                                   269                            /* 'getPrototypeOf' */
+#define DUK_STRIDX_PROTOTYPE                                          270                            /* 'prototype' */
+#define DUK_STRIDX_LENGTH                                             271                            /* 'length' */
+#define DUK_STRIDX_ALERT                                              272                            /* 'alert' */
+#define DUK_STRIDX_PRINT                                              273                            /* 'print' */
+#define DUK_STRIDX_UNESCAPE                                           274                            /* 'unescape' */
+#define DUK_STRIDX_ESCAPE                                             275                            /* 'escape' */
+#define DUK_STRIDX_ENCODE_URI_COMPONENT                               276                            /* 'encodeURIComponent' */
+#define DUK_STRIDX_ENCODE_URI                                         277                            /* 'encodeURI' */
+#define DUK_STRIDX_DECODE_URI_COMPONENT                               278                            /* 'decodeURIComponent' */
+#define DUK_STRIDX_DECODE_URI                                         279                            /* 'decodeURI' */
+#define DUK_STRIDX_IS_FINITE                                          280                            /* 'isFinite' */
+#define DUK_STRIDX_IS_NAN                                             281                            /* 'isNaN' */
+#define DUK_STRIDX_PARSE_FLOAT                                        282                            /* 'parseFloat' */
+#define DUK_STRIDX_PARSE_INT                                          283                            /* 'parseInt' */
+#define DUK_STRIDX_EVAL                                               284                            /* 'eval' */
+#define DUK_STRIDX_URI_ERROR                                          285                            /* 'URIError' */
+#define DUK_STRIDX_TYPE_ERROR                                         286                            /* 'TypeError' */
+#define DUK_STRIDX_SYNTAX_ERROR                                       287                            /* 'SyntaxError' */
+#define DUK_STRIDX_REFERENCE_ERROR                                    288                            /* 'ReferenceError' */
+#define DUK_STRIDX_RANGE_ERROR                                        289                            /* 'RangeError' */
+#define DUK_STRIDX_EVAL_ERROR                                         290                            /* 'EvalError' */
+#define DUK_STRIDX_BREAK                                              291                            /* 'break' */
+#define DUK_STRIDX_CASE                                               292                            /* 'case' */
+#define DUK_STRIDX_CATCH                                              293                            /* 'catch' */
+#define DUK_STRIDX_CONTINUE                                           294                            /* 'continue' */
+#define DUK_STRIDX_DEBUGGER                                           295                            /* 'debugger' */
+#define DUK_STRIDX_DEFAULT                                            296                            /* 'default' */
+#define DUK_STRIDX_DELETE                                             297                            /* 'delete' */
+#define DUK_STRIDX_DO                                                 298                            /* 'do' */
+#define DUK_STRIDX_ELSE                                               299                            /* 'else' */
+#define DUK_STRIDX_FINALLY                                            300                            /* 'finally' */
+#define DUK_STRIDX_FOR                                                301                            /* 'for' */
+#define DUK_STRIDX_LC_FUNCTION                                        302                            /* 'function' */
+#define DUK_STRIDX_IF                                                 303                            /* 'if' */
+#define DUK_STRIDX_IN                                                 304                            /* 'in' */
+#define DUK_STRIDX_INSTANCEOF                                         305                            /* 'instanceof' */
+#define DUK_STRIDX_NEW                                                306                            /* 'new' */
+#define DUK_STRIDX_RETURN                                             307                            /* 'return' */
+#define DUK_STRIDX_SWITCH                                             308                            /* 'switch' */
+#define DUK_STRIDX_THIS                                               309                            /* 'this' */
+#define DUK_STRIDX_THROW                                              310                            /* 'throw' */
+#define DUK_STRIDX_TRY                                                311                            /* 'try' */
+#define DUK_STRIDX_TYPEOF                                             312                            /* 'typeof' */
+#define DUK_STRIDX_VAR                                                313                            /* 'var' */
+#define DUK_STRIDX_VOID                                               314                            /* 'void' */
+#define DUK_STRIDX_WHILE                                              315                            /* 'while' */
+#define DUK_STRIDX_WITH                                               316                            /* 'with' */
+#define DUK_STRIDX_CLASS                                              317                            /* 'class' */
+#define DUK_STRIDX_CONST                                              318                            /* 'const' */
+#define DUK_STRIDX_ENUM                                               319                            /* 'enum' */
+#define DUK_STRIDX_EXPORT                                             320                            /* 'export' */
+#define DUK_STRIDX_EXTENDS                                            321                            /* 'extends' */
+#define DUK_STRIDX_IMPORT                                             322                            /* 'import' */
+#define DUK_STRIDX_SUPER                                              323                            /* 'super' */
+#define DUK_STRIDX_LC_NULL                                            324                            /* 'null' */
+#define DUK_STRIDX_TRUE                                               325                            /* 'true' */
+#define DUK_STRIDX_FALSE                                              326                            /* 'false' */
+#define DUK_STRIDX_IMPLEMENTS                                         327                            /* 'implements' */
+#define DUK_STRIDX_INTERFACE                                          328                            /* 'interface' */
+#define DUK_STRIDX_LET                                                329                            /* 'let' */
+#define DUK_STRIDX_PACKAGE                                            330                            /* 'package' */
+#define DUK_STRIDX_PRIVATE                                            331                            /* 'private' */
+#define DUK_STRIDX_PROTECTED                                          332                            /* 'protected' */
+#define DUK_STRIDX_PUBLIC                                             333                            /* 'public' */
+#define DUK_STRIDX_STATIC                                             334                            /* 'static' */
+#define DUK_STRIDX_YIELD                                              335                            /* 'yield' */
+
+#define DUK_HEAP_STRING_UC_LOGGER(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_LOGGER)
+#define DUK_HTHREAD_STRING_UC_LOGGER(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_LOGGER)
+#define DUK_HEAP_STRING_UC_THREAD(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_THREAD)
+#define DUK_HTHREAD_STRING_UC_THREAD(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_THREAD)
+#define DUK_HEAP_STRING_UC_POINTER(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_POINTER)
+#define DUK_HTHREAD_STRING_UC_POINTER(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_POINTER)
+#define DUK_HEAP_STRING_UC_BUFFER(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_BUFFER)
+#define DUK_HTHREAD_STRING_UC_BUFFER(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_BUFFER)
+#define DUK_HEAP_STRING_DEC_ENV(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DEC_ENV)
+#define DUK_HTHREAD_STRING_DEC_ENV(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DEC_ENV)
+#define DUK_HEAP_STRING_OBJ_ENV(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_OBJ_ENV)
+#define DUK_HTHREAD_STRING_OBJ_ENV(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_OBJ_ENV)
+#define DUK_HEAP_STRING_EMPTY_STRING(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EMPTY_STRING)
+#define DUK_HTHREAD_STRING_EMPTY_STRING(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EMPTY_STRING)
+#define DUK_HEAP_STRING_GLOBAL(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GLOBAL)
+#define DUK_HTHREAD_STRING_GLOBAL(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GLOBAL)
+#define DUK_HEAP_STRING_UC_ARGUMENTS(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_ARGUMENTS)
+#define DUK_HTHREAD_STRING_UC_ARGUMENTS(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_ARGUMENTS)
+#define DUK_HEAP_STRING_JSON(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON)
+#define DUK_HTHREAD_STRING_JSON(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON)
+#define DUK_HEAP_STRING_MATH(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MATH)
+#define DUK_HTHREAD_STRING_MATH(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MATH)
+#define DUK_HEAP_STRING_UC_ERROR(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_ERROR)
+#define DUK_HTHREAD_STRING_UC_ERROR(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_ERROR)
+#define DUK_HEAP_STRING_REG_EXP(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REG_EXP)
+#define DUK_HTHREAD_STRING_REG_EXP(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REG_EXP)
+#define DUK_HEAP_STRING_DATE(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DATE)
+#define DUK_HTHREAD_STRING_DATE(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DATE)
+#define DUK_HEAP_STRING_UC_NUMBER(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_NUMBER)
+#define DUK_HTHREAD_STRING_UC_NUMBER(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_NUMBER)
+#define DUK_HEAP_STRING_UC_BOOLEAN(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_BOOLEAN)
+#define DUK_HTHREAD_STRING_UC_BOOLEAN(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_BOOLEAN)
+#define DUK_HEAP_STRING_UC_STRING(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_STRING)
+#define DUK_HTHREAD_STRING_UC_STRING(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_STRING)
+#define DUK_HEAP_STRING_ARRAY(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ARRAY)
+#define DUK_HTHREAD_STRING_ARRAY(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ARRAY)
+#define DUK_HEAP_STRING_UC_FUNCTION(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_FUNCTION)
+#define DUK_HTHREAD_STRING_UC_FUNCTION(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_FUNCTION)
+#define DUK_HEAP_STRING_UC_OBJECT(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_OBJECT)
+#define DUK_HTHREAD_STRING_UC_OBJECT(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_OBJECT)
+#define DUK_HEAP_STRING_UC_NULL(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_NULL)
+#define DUK_HTHREAD_STRING_UC_NULL(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_NULL)
+#define DUK_HEAP_STRING_UC_UNDEFINED(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_UNDEFINED)
+#define DUK_HTHREAD_STRING_UC_UNDEFINED(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_UNDEFINED)
+#define DUK_HEAP_STRING_JSON_EXT_FUNCTION2(heap)                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_FUNCTION2)
+#define DUK_HTHREAD_STRING_JSON_EXT_FUNCTION2(thr)                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_FUNCTION2)
+#define DUK_HEAP_STRING_JSON_EXT_FUNCTION1(heap)                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_FUNCTION1)
+#define DUK_HTHREAD_STRING_JSON_EXT_FUNCTION1(thr)                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_FUNCTION1)
+#define DUK_HEAP_STRING_JSON_EXT_NEGINF(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_NEGINF)
+#define DUK_HTHREAD_STRING_JSON_EXT_NEGINF(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_NEGINF)
+#define DUK_HEAP_STRING_JSON_EXT_POSINF(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_POSINF)
+#define DUK_HTHREAD_STRING_JSON_EXT_POSINF(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_POSINF)
+#define DUK_HEAP_STRING_JSON_EXT_NAN(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_NAN)
+#define DUK_HTHREAD_STRING_JSON_EXT_NAN(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_NAN)
+#define DUK_HEAP_STRING_JSON_EXT_UNDEFINED(heap)                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_UNDEFINED)
+#define DUK_HTHREAD_STRING_JSON_EXT_UNDEFINED(thr)                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_UNDEFINED)
+#define DUK_HEAP_STRING_TO_LOG_STRING(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_LOG_STRING)
+#define DUK_HTHREAD_STRING_TO_LOG_STRING(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_LOG_STRING)
+#define DUK_HEAP_STRING_CLOG(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CLOG)
+#define DUK_HTHREAD_STRING_CLOG(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CLOG)
+#define DUK_HEAP_STRING_LC_L(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_L)
+#define DUK_HTHREAD_STRING_LC_L(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_L)
+#define DUK_HEAP_STRING_LC_N(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_N)
+#define DUK_HTHREAD_STRING_LC_N(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_N)
+#define DUK_HEAP_STRING_LC_FATAL(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_FATAL)
+#define DUK_HTHREAD_STRING_LC_FATAL(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_FATAL)
+#define DUK_HEAP_STRING_LC_ERROR(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_ERROR)
+#define DUK_HTHREAD_STRING_LC_ERROR(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_ERROR)
+#define DUK_HEAP_STRING_LC_WARN(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_WARN)
+#define DUK_HTHREAD_STRING_LC_WARN(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_WARN)
+#define DUK_HEAP_STRING_LC_DEBUG(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_DEBUG)
+#define DUK_HTHREAD_STRING_LC_DEBUG(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_DEBUG)
+#define DUK_HEAP_STRING_LC_TRACE(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_TRACE)
+#define DUK_HTHREAD_STRING_LC_TRACE(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_TRACE)
+#define DUK_HEAP_STRING_RAW(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_RAW)
+#define DUK_HTHREAD_STRING_RAW(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_RAW)
+#define DUK_HEAP_STRING_FMT(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FMT)
+#define DUK_HTHREAD_STRING_FMT(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FMT)
+#define DUK_HEAP_STRING_CURRENT(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CURRENT)
+#define DUK_HTHREAD_STRING_CURRENT(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CURRENT)
+#define DUK_HEAP_STRING_RESUME(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_RESUME)
+#define DUK_HTHREAD_STRING_RESUME(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_RESUME)
+#define DUK_HEAP_STRING_COMPACT(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_COMPACT)
+#define DUK_HTHREAD_STRING_COMPACT(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_COMPACT)
+#define DUK_HEAP_STRING_JC(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JC)
+#define DUK_HTHREAD_STRING_JC(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JC)
+#define DUK_HEAP_STRING_JX(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JX)
+#define DUK_HTHREAD_STRING_JX(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JX)
+#define DUK_HEAP_STRING_BASE64(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_BASE64)
+#define DUK_HTHREAD_STRING_BASE64(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_BASE64)
+#define DUK_HEAP_STRING_HEX(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_HEX)
+#define DUK_HTHREAD_STRING_HEX(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_HEX)
+#define DUK_HEAP_STRING_DEC(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DEC)
+#define DUK_HTHREAD_STRING_DEC(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DEC)
+#define DUK_HEAP_STRING_ENC(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENC)
+#define DUK_HTHREAD_STRING_ENC(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENC)
+#define DUK_HEAP_STRING_FIN(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FIN)
+#define DUK_HTHREAD_STRING_FIN(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FIN)
+#define DUK_HEAP_STRING_GC(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GC)
+#define DUK_HTHREAD_STRING_GC(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GC)
+#define DUK_HEAP_STRING_ACT(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ACT)
+#define DUK_HTHREAD_STRING_ACT(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ACT)
+#define DUK_HEAP_STRING_LC_INFO(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_INFO)
+#define DUK_HTHREAD_STRING_LC_INFO(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_INFO)
+#define DUK_HEAP_STRING_VERSION(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_VERSION)
+#define DUK_HTHREAD_STRING_VERSION(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_VERSION)
+#define DUK_HEAP_STRING_ENV(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENV)
+#define DUK_HTHREAD_STRING_ENV(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENV)
+#define DUK_HEAP_STRING_MOD_LOADED(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MOD_LOADED)
+#define DUK_HTHREAD_STRING_MOD_LOADED(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MOD_LOADED)
+#define DUK_HEAP_STRING_MOD_SEARCH(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MOD_SEARCH)
+#define DUK_HTHREAD_STRING_MOD_SEARCH(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MOD_SEARCH)
+#define DUK_HEAP_STRING_ERR_THROW(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ERR_THROW)
+#define DUK_HTHREAD_STRING_ERR_THROW(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ERR_THROW)
+#define DUK_HEAP_STRING_ERR_CREATE(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ERR_CREATE)
+#define DUK_HTHREAD_STRING_ERR_CREATE(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ERR_CREATE)
+#define DUK_HEAP_STRING_COMPILE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_COMPILE)
+#define DUK_HTHREAD_STRING_COMPILE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_COMPILE)
+#define DUK_HEAP_STRING_INT_REGBASE(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_REGBASE)
+#define DUK_HTHREAD_STRING_INT_REGBASE(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_REGBASE)
+#define DUK_HEAP_STRING_INT_THREAD(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_THREAD)
+#define DUK_HTHREAD_STRING_INT_THREAD(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_THREAD)
+#define DUK_HEAP_STRING_INT_HANDLER(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_HANDLER)
+#define DUK_HTHREAD_STRING_INT_HANDLER(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_HANDLER)
+#define DUK_HEAP_STRING_INT_FINALIZER(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_FINALIZER)
+#define DUK_HTHREAD_STRING_INT_FINALIZER(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_FINALIZER)
+#define DUK_HEAP_STRING_INT_CALLEE(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_CALLEE)
+#define DUK_HTHREAD_STRING_INT_CALLEE(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_CALLEE)
+#define DUK_HEAP_STRING_INT_MAP(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_MAP)
+#define DUK_HTHREAD_STRING_INT_MAP(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_MAP)
+#define DUK_HEAP_STRING_INT_ARGS(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_ARGS)
+#define DUK_HTHREAD_STRING_INT_ARGS(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_ARGS)
+#define DUK_HEAP_STRING_INT_THIS(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_THIS)
+#define DUK_HTHREAD_STRING_INT_THIS(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_THIS)
+#define DUK_HEAP_STRING_INT_PC2LINE(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_PC2LINE)
+#define DUK_HTHREAD_STRING_INT_PC2LINE(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_PC2LINE)
+#define DUK_HEAP_STRING_INT_SOURCE(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_SOURCE)
+#define DUK_HTHREAD_STRING_INT_SOURCE(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_SOURCE)
+#define DUK_HEAP_STRING_INT_VARENV(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_VARENV)
+#define DUK_HTHREAD_STRING_INT_VARENV(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_VARENV)
+#define DUK_HEAP_STRING_INT_LEXENV(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_LEXENV)
+#define DUK_HTHREAD_STRING_INT_LEXENV(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_LEXENV)
+#define DUK_HEAP_STRING_INT_VARMAP(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_VARMAP)
+#define DUK_HTHREAD_STRING_INT_VARMAP(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_VARMAP)
+#define DUK_HEAP_STRING_INT_FORMALS(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_FORMALS)
+#define DUK_HTHREAD_STRING_INT_FORMALS(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_FORMALS)
+#define DUK_HEAP_STRING_INT_BYTECODE(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_BYTECODE)
+#define DUK_HTHREAD_STRING_INT_BYTECODE(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_BYTECODE)
+#define DUK_HEAP_STRING_INT_NEXT(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_NEXT)
+#define DUK_HTHREAD_STRING_INT_NEXT(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_NEXT)
+#define DUK_HEAP_STRING_INT_TARGET(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_TARGET)
+#define DUK_HTHREAD_STRING_INT_TARGET(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_TARGET)
+#define DUK_HEAP_STRING_INT_VALUE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_VALUE)
+#define DUK_HTHREAD_STRING_INT_VALUE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_VALUE)
+#define DUK_HEAP_STRING_LC_POINTER(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_POINTER)
+#define DUK_HTHREAD_STRING_LC_POINTER(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_POINTER)
+#define DUK_HEAP_STRING_LC_BUFFER(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_BUFFER)
+#define DUK_HTHREAD_STRING_LC_BUFFER(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_BUFFER)
+#define DUK_HEAP_STRING_TRACEDATA(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TRACEDATA)
+#define DUK_HTHREAD_STRING_TRACEDATA(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TRACEDATA)
+#define DUK_HEAP_STRING_LINE_NUMBER(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LINE_NUMBER)
+#define DUK_HTHREAD_STRING_LINE_NUMBER(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LINE_NUMBER)
+#define DUK_HEAP_STRING_FILE_NAME(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FILE_NAME)
+#define DUK_HTHREAD_STRING_FILE_NAME(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FILE_NAME)
+#define DUK_HEAP_STRING_PC(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PC)
+#define DUK_HTHREAD_STRING_PC(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PC)
+#define DUK_HEAP_STRING_STACK(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_STACK)
+#define DUK_HTHREAD_STRING_STACK(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_STACK)
+#define DUK_HEAP_STRING_THROW_TYPE_ERROR(heap)                        DUK_HEAP_GET_STRING((heap),DUK_STRIDX_THROW_TYPE_ERROR)
+#define DUK_HTHREAD_STRING_THROW_TYPE_ERROR(thr)                      DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_THROW_TYPE_ERROR)
+#define DUK_HEAP_STRING_DUKTAPE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DUKTAPE)
+#define DUK_HTHREAD_STRING_DUKTAPE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DUKTAPE)
+#define DUK_HEAP_STRING_ID(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ID)
+#define DUK_HTHREAD_STRING_ID(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ID)
+#define DUK_HEAP_STRING_REQUIRE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REQUIRE)
+#define DUK_HTHREAD_STRING_REQUIRE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REQUIRE)
+#define DUK_HEAP_STRING___PROTO__(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX___PROTO__)
+#define DUK_HTHREAD_STRING___PROTO__(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX___PROTO__)
+#define DUK_HEAP_STRING_SET_PROTOTYPE_OF(heap)                        DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_PROTOTYPE_OF)
+#define DUK_HTHREAD_STRING_SET_PROTOTYPE_OF(thr)                      DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_PROTOTYPE_OF)
+#define DUK_HEAP_STRING_OWN_KEYS(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_OWN_KEYS)
+#define DUK_HTHREAD_STRING_OWN_KEYS(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_OWN_KEYS)
+#define DUK_HEAP_STRING_ENUMERATE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENUMERATE)
+#define DUK_HTHREAD_STRING_ENUMERATE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENUMERATE)
+#define DUK_HEAP_STRING_DELETE_PROPERTY(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DELETE_PROPERTY)
+#define DUK_HTHREAD_STRING_DELETE_PROPERTY(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DELETE_PROPERTY)
+#define DUK_HEAP_STRING_HAS(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_HAS)
+#define DUK_HTHREAD_STRING_HAS(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_HAS)
+#define DUK_HEAP_STRING_PROXY(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PROXY)
+#define DUK_HTHREAD_STRING_PROXY(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PROXY)
+#define DUK_HEAP_STRING_CALLEE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CALLEE)
+#define DUK_HTHREAD_STRING_CALLEE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CALLEE)
+#define DUK_HEAP_STRING_INVALID_DATE(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INVALID_DATE)
+#define DUK_HTHREAD_STRING_INVALID_DATE(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INVALID_DATE)
+#define DUK_HEAP_STRING_BRACKETED_ELLIPSIS(heap)                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_BRACKETED_ELLIPSIS)
+#define DUK_HTHREAD_STRING_BRACKETED_ELLIPSIS(thr)                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_BRACKETED_ELLIPSIS)
+#define DUK_HEAP_STRING_NEWLINE_TAB(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NEWLINE_TAB)
+#define DUK_HTHREAD_STRING_NEWLINE_TAB(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NEWLINE_TAB)
+#define DUK_HEAP_STRING_SPACE(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SPACE)
+#define DUK_HTHREAD_STRING_SPACE(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SPACE)
+#define DUK_HEAP_STRING_COMMA(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_COMMA)
+#define DUK_HTHREAD_STRING_COMMA(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_COMMA)
+#define DUK_HEAP_STRING_MINUS_ZERO(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MINUS_ZERO)
+#define DUK_HTHREAD_STRING_MINUS_ZERO(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MINUS_ZERO)
+#define DUK_HEAP_STRING_PLUS_ZERO(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PLUS_ZERO)
+#define DUK_HTHREAD_STRING_PLUS_ZERO(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PLUS_ZERO)
+#define DUK_HEAP_STRING_ZERO(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ZERO)
+#define DUK_HTHREAD_STRING_ZERO(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ZERO)
+#define DUK_HEAP_STRING_MINUS_INFINITY(heap)                          DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MINUS_INFINITY)
+#define DUK_HTHREAD_STRING_MINUS_INFINITY(thr)                        DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MINUS_INFINITY)
+#define DUK_HEAP_STRING_PLUS_INFINITY(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PLUS_INFINITY)
+#define DUK_HTHREAD_STRING_PLUS_INFINITY(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PLUS_INFINITY)
+#define DUK_HEAP_STRING_INFINITY(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INFINITY)
+#define DUK_HTHREAD_STRING_INFINITY(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INFINITY)
+#define DUK_HEAP_STRING_LC_OBJECT(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_OBJECT)
+#define DUK_HTHREAD_STRING_LC_OBJECT(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_OBJECT)
+#define DUK_HEAP_STRING_LC_STRING(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_STRING)
+#define DUK_HTHREAD_STRING_LC_STRING(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_STRING)
+#define DUK_HEAP_STRING_LC_NUMBER(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_NUMBER)
+#define DUK_HTHREAD_STRING_LC_NUMBER(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_NUMBER)
+#define DUK_HEAP_STRING_LC_BOOLEAN(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_BOOLEAN)
+#define DUK_HTHREAD_STRING_LC_BOOLEAN(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_BOOLEAN)
+#define DUK_HEAP_STRING_LC_UNDEFINED(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_UNDEFINED)
+#define DUK_HTHREAD_STRING_LC_UNDEFINED(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_UNDEFINED)
+#define DUK_HEAP_STRING_STRINGIFY(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_STRINGIFY)
+#define DUK_HTHREAD_STRING_STRINGIFY(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_STRINGIFY)
+#define DUK_HEAP_STRING_TAN(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TAN)
+#define DUK_HTHREAD_STRING_TAN(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TAN)
+#define DUK_HEAP_STRING_SQRT(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SQRT)
+#define DUK_HTHREAD_STRING_SQRT(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SQRT)
+#define DUK_HEAP_STRING_SIN(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SIN)
+#define DUK_HTHREAD_STRING_SIN(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SIN)
+#define DUK_HEAP_STRING_ROUND(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ROUND)
+#define DUK_HTHREAD_STRING_ROUND(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ROUND)
+#define DUK_HEAP_STRING_RANDOM(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_RANDOM)
+#define DUK_HTHREAD_STRING_RANDOM(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_RANDOM)
+#define DUK_HEAP_STRING_POW(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_POW)
+#define DUK_HTHREAD_STRING_POW(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_POW)
+#define DUK_HEAP_STRING_MIN(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MIN)
+#define DUK_HTHREAD_STRING_MIN(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MIN)
+#define DUK_HEAP_STRING_MAX(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MAX)
+#define DUK_HTHREAD_STRING_MAX(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MAX)
+#define DUK_HEAP_STRING_LOG(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LOG)
+#define DUK_HTHREAD_STRING_LOG(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LOG)
+#define DUK_HEAP_STRING_FLOOR(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FLOOR)
+#define DUK_HTHREAD_STRING_FLOOR(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FLOOR)
+#define DUK_HEAP_STRING_EXP(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EXP)
+#define DUK_HTHREAD_STRING_EXP(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EXP)
+#define DUK_HEAP_STRING_COS(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_COS)
+#define DUK_HTHREAD_STRING_COS(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_COS)
+#define DUK_HEAP_STRING_CEIL(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CEIL)
+#define DUK_HTHREAD_STRING_CEIL(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CEIL)
+#define DUK_HEAP_STRING_ATAN2(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ATAN2)
+#define DUK_HTHREAD_STRING_ATAN2(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ATAN2)
+#define DUK_HEAP_STRING_ATAN(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ATAN)
+#define DUK_HTHREAD_STRING_ATAN(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ATAN)
+#define DUK_HEAP_STRING_ASIN(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ASIN)
+#define DUK_HTHREAD_STRING_ASIN(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ASIN)
+#define DUK_HEAP_STRING_ACOS(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ACOS)
+#define DUK_HTHREAD_STRING_ACOS(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ACOS)
+#define DUK_HEAP_STRING_ABS(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ABS)
+#define DUK_HTHREAD_STRING_ABS(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ABS)
+#define DUK_HEAP_STRING_SQRT2(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SQRT2)
+#define DUK_HTHREAD_STRING_SQRT2(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SQRT2)
+#define DUK_HEAP_STRING_SQRT1_2(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SQRT1_2)
+#define DUK_HTHREAD_STRING_SQRT1_2(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SQRT1_2)
+#define DUK_HEAP_STRING_PI(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PI)
+#define DUK_HTHREAD_STRING_PI(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PI)
+#define DUK_HEAP_STRING_LOG10E(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LOG10E)
+#define DUK_HTHREAD_STRING_LOG10E(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LOG10E)
+#define DUK_HEAP_STRING_LOG2E(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LOG2E)
+#define DUK_HTHREAD_STRING_LOG2E(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LOG2E)
+#define DUK_HEAP_STRING_LN2(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LN2)
+#define DUK_HTHREAD_STRING_LN2(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LN2)
+#define DUK_HEAP_STRING_LN10(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LN10)
+#define DUK_HTHREAD_STRING_LN10(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LN10)
+#define DUK_HEAP_STRING_E(heap)                                       DUK_HEAP_GET_STRING((heap),DUK_STRIDX_E)
+#define DUK_HTHREAD_STRING_E(thr)                                     DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_E)
+#define DUK_HEAP_STRING_MESSAGE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MESSAGE)
+#define DUK_HTHREAD_STRING_MESSAGE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MESSAGE)
+#define DUK_HEAP_STRING_NAME(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NAME)
+#define DUK_HTHREAD_STRING_NAME(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NAME)
+#define DUK_HEAP_STRING_INPUT(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INPUT)
+#define DUK_HTHREAD_STRING_INPUT(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INPUT)
+#define DUK_HEAP_STRING_INDEX(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INDEX)
+#define DUK_HTHREAD_STRING_INDEX(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INDEX)
+#define DUK_HEAP_STRING_ESCAPED_EMPTY_REGEXP(heap)                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ESCAPED_EMPTY_REGEXP)
+#define DUK_HTHREAD_STRING_ESCAPED_EMPTY_REGEXP(thr)                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ESCAPED_EMPTY_REGEXP)
+#define DUK_HEAP_STRING_LAST_INDEX(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LAST_INDEX)
+#define DUK_HTHREAD_STRING_LAST_INDEX(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LAST_INDEX)
+#define DUK_HEAP_STRING_MULTILINE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MULTILINE)
+#define DUK_HTHREAD_STRING_MULTILINE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MULTILINE)
+#define DUK_HEAP_STRING_IGNORE_CASE(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IGNORE_CASE)
+#define DUK_HTHREAD_STRING_IGNORE_CASE(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IGNORE_CASE)
+#define DUK_HEAP_STRING_SOURCE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SOURCE)
+#define DUK_HTHREAD_STRING_SOURCE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SOURCE)
+#define DUK_HEAP_STRING_TEST(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TEST)
+#define DUK_HTHREAD_STRING_TEST(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TEST)
+#define DUK_HEAP_STRING_EXEC(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EXEC)
+#define DUK_HTHREAD_STRING_EXEC(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EXEC)
+#define DUK_HEAP_STRING_TO_GMT_STRING(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_GMT_STRING)
+#define DUK_HTHREAD_STRING_TO_GMT_STRING(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_GMT_STRING)
+#define DUK_HEAP_STRING_SET_YEAR(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_YEAR)
+#define DUK_HTHREAD_STRING_SET_YEAR(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_YEAR)
+#define DUK_HEAP_STRING_GET_YEAR(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_YEAR)
+#define DUK_HTHREAD_STRING_GET_YEAR(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_YEAR)
+#define DUK_HEAP_STRING_TO_JSON(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_JSON)
+#define DUK_HTHREAD_STRING_TO_JSON(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_JSON)
+#define DUK_HEAP_STRING_TO_ISO_STRING(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_ISO_STRING)
+#define DUK_HTHREAD_STRING_TO_ISO_STRING(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_ISO_STRING)
+#define DUK_HEAP_STRING_TO_UTC_STRING(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_UTC_STRING)
+#define DUK_HTHREAD_STRING_TO_UTC_STRING(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_UTC_STRING)
+#define DUK_HEAP_STRING_SET_UTC_FULL_YEAR(heap)                       DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_UTC_FULL_YEAR)
+#define DUK_HTHREAD_STRING_SET_UTC_FULL_YEAR(thr)                     DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_UTC_FULL_YEAR)
+#define DUK_HEAP_STRING_SET_FULL_YEAR(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_FULL_YEAR)
+#define DUK_HTHREAD_STRING_SET_FULL_YEAR(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_FULL_YEAR)
+#define DUK_HEAP_STRING_SET_UTC_MONTH(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_UTC_MONTH)
+#define DUK_HTHREAD_STRING_SET_UTC_MONTH(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_UTC_MONTH)
+#define DUK_HEAP_STRING_SET_MONTH(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_MONTH)
+#define DUK_HTHREAD_STRING_SET_MONTH(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_MONTH)
+#define DUK_HEAP_STRING_SET_UTC_DATE(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_UTC_DATE)
+#define DUK_HTHREAD_STRING_SET_UTC_DATE(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_UTC_DATE)
+#define DUK_HEAP_STRING_SET_DATE(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_DATE)
+#define DUK_HTHREAD_STRING_SET_DATE(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_DATE)
+#define DUK_HEAP_STRING_SET_UTC_HOURS(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_UTC_HOURS)
+#define DUK_HTHREAD_STRING_SET_UTC_HOURS(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_UTC_HOURS)
+#define DUK_HEAP_STRING_SET_HOURS(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_HOURS)
+#define DUK_HTHREAD_STRING_SET_HOURS(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_HOURS)
+#define DUK_HEAP_STRING_SET_UTC_MINUTES(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_UTC_MINUTES)
+#define DUK_HTHREAD_STRING_SET_UTC_MINUTES(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_UTC_MINUTES)
+#define DUK_HEAP_STRING_SET_MINUTES(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_MINUTES)
+#define DUK_HTHREAD_STRING_SET_MINUTES(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_MINUTES)
+#define DUK_HEAP_STRING_SET_UTC_SECONDS(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_UTC_SECONDS)
+#define DUK_HTHREAD_STRING_SET_UTC_SECONDS(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_UTC_SECONDS)
+#define DUK_HEAP_STRING_SET_SECONDS(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_SECONDS)
+#define DUK_HTHREAD_STRING_SET_SECONDS(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_SECONDS)
+#define DUK_HEAP_STRING_SET_UTC_MILLISECONDS(heap)                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_UTC_MILLISECONDS)
+#define DUK_HTHREAD_STRING_SET_UTC_MILLISECONDS(thr)                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_UTC_MILLISECONDS)
+#define DUK_HEAP_STRING_SET_MILLISECONDS(heap)                        DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_MILLISECONDS)
+#define DUK_HTHREAD_STRING_SET_MILLISECONDS(thr)                      DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_MILLISECONDS)
+#define DUK_HEAP_STRING_SET_TIME(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_TIME)
+#define DUK_HTHREAD_STRING_SET_TIME(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_TIME)
+#define DUK_HEAP_STRING_GET_TIMEZONE_OFFSET(heap)                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_TIMEZONE_OFFSET)
+#define DUK_HTHREAD_STRING_GET_TIMEZONE_OFFSET(thr)                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_TIMEZONE_OFFSET)
+#define DUK_HEAP_STRING_GET_UTC_MILLISECONDS(heap)                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_MILLISECONDS)
+#define DUK_HTHREAD_STRING_GET_UTC_MILLISECONDS(thr)                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_MILLISECONDS)
+#define DUK_HEAP_STRING_GET_MILLISECONDS(heap)                        DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_MILLISECONDS)
+#define DUK_HTHREAD_STRING_GET_MILLISECONDS(thr)                      DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_MILLISECONDS)
+#define DUK_HEAP_STRING_GET_UTC_SECONDS(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_SECONDS)
+#define DUK_HTHREAD_STRING_GET_UTC_SECONDS(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_SECONDS)
+#define DUK_HEAP_STRING_GET_SECONDS(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_SECONDS)
+#define DUK_HTHREAD_STRING_GET_SECONDS(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_SECONDS)
+#define DUK_HEAP_STRING_GET_UTC_MINUTES(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_MINUTES)
+#define DUK_HTHREAD_STRING_GET_UTC_MINUTES(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_MINUTES)
+#define DUK_HEAP_STRING_GET_MINUTES(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_MINUTES)
+#define DUK_HTHREAD_STRING_GET_MINUTES(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_MINUTES)
+#define DUK_HEAP_STRING_GET_UTC_HOURS(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_HOURS)
+#define DUK_HTHREAD_STRING_GET_UTC_HOURS(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_HOURS)
+#define DUK_HEAP_STRING_GET_HOURS(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_HOURS)
+#define DUK_HTHREAD_STRING_GET_HOURS(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_HOURS)
+#define DUK_HEAP_STRING_GET_UTC_DAY(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_DAY)
+#define DUK_HTHREAD_STRING_GET_UTC_DAY(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_DAY)
+#define DUK_HEAP_STRING_GET_DAY(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_DAY)
+#define DUK_HTHREAD_STRING_GET_DAY(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_DAY)
+#define DUK_HEAP_STRING_GET_UTC_DATE(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_DATE)
+#define DUK_HTHREAD_STRING_GET_UTC_DATE(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_DATE)
+#define DUK_HEAP_STRING_GET_DATE(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_DATE)
+#define DUK_HTHREAD_STRING_GET_DATE(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_DATE)
+#define DUK_HEAP_STRING_GET_UTC_MONTH(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_MONTH)
+#define DUK_HTHREAD_STRING_GET_UTC_MONTH(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_MONTH)
+#define DUK_HEAP_STRING_GET_MONTH(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_MONTH)
+#define DUK_HTHREAD_STRING_GET_MONTH(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_MONTH)
+#define DUK_HEAP_STRING_GET_UTC_FULL_YEAR(heap)                       DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_FULL_YEAR)
+#define DUK_HTHREAD_STRING_GET_UTC_FULL_YEAR(thr)                     DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_FULL_YEAR)
+#define DUK_HEAP_STRING_GET_FULL_YEAR(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_FULL_YEAR)
+#define DUK_HTHREAD_STRING_GET_FULL_YEAR(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_FULL_YEAR)
+#define DUK_HEAP_STRING_GET_TIME(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_TIME)
+#define DUK_HTHREAD_STRING_GET_TIME(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_TIME)
+#define DUK_HEAP_STRING_TO_LOCALE_TIME_STRING(heap)                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_LOCALE_TIME_STRING)
+#define DUK_HTHREAD_STRING_TO_LOCALE_TIME_STRING(thr)                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_LOCALE_TIME_STRING)
+#define DUK_HEAP_STRING_TO_LOCALE_DATE_STRING(heap)                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_LOCALE_DATE_STRING)
+#define DUK_HTHREAD_STRING_TO_LOCALE_DATE_STRING(thr)                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_LOCALE_DATE_STRING)
+#define DUK_HEAP_STRING_TO_TIME_STRING(heap)                          DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_TIME_STRING)
+#define DUK_HTHREAD_STRING_TO_TIME_STRING(thr)                        DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_TIME_STRING)
+#define DUK_HEAP_STRING_TO_DATE_STRING(heap)                          DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_DATE_STRING)
+#define DUK_HTHREAD_STRING_TO_DATE_STRING(thr)                        DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_DATE_STRING)
+#define DUK_HEAP_STRING_NOW(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NOW)
+#define DUK_HTHREAD_STRING_NOW(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NOW)
+#define DUK_HEAP_STRING_UTC(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UTC)
+#define DUK_HTHREAD_STRING_UTC(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UTC)
+#define DUK_HEAP_STRING_PARSE(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PARSE)
+#define DUK_HTHREAD_STRING_PARSE(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PARSE)
+#define DUK_HEAP_STRING_TO_PRECISION(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_PRECISION)
+#define DUK_HTHREAD_STRING_TO_PRECISION(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_PRECISION)
+#define DUK_HEAP_STRING_TO_EXPONENTIAL(heap)                          DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_EXPONENTIAL)
+#define DUK_HTHREAD_STRING_TO_EXPONENTIAL(thr)                        DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_EXPONENTIAL)
+#define DUK_HEAP_STRING_TO_FIXED(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_FIXED)
+#define DUK_HTHREAD_STRING_TO_FIXED(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_FIXED)
+#define DUK_HEAP_STRING_POSITIVE_INFINITY(heap)                       DUK_HEAP_GET_STRING((heap),DUK_STRIDX_POSITIVE_INFINITY)
+#define DUK_HTHREAD_STRING_POSITIVE_INFINITY(thr)                     DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_POSITIVE_INFINITY)
+#define DUK_HEAP_STRING_NEGATIVE_INFINITY(heap)                       DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NEGATIVE_INFINITY)
+#define DUK_HTHREAD_STRING_NEGATIVE_INFINITY(thr)                     DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NEGATIVE_INFINITY)
+#define DUK_HEAP_STRING_NAN(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NAN)
+#define DUK_HTHREAD_STRING_NAN(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NAN)
+#define DUK_HEAP_STRING_MIN_VALUE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MIN_VALUE)
+#define DUK_HTHREAD_STRING_MIN_VALUE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MIN_VALUE)
+#define DUK_HEAP_STRING_MAX_VALUE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MAX_VALUE)
+#define DUK_HTHREAD_STRING_MAX_VALUE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MAX_VALUE)
+#define DUK_HEAP_STRING_SUBSTR(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SUBSTR)
+#define DUK_HTHREAD_STRING_SUBSTR(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SUBSTR)
+#define DUK_HEAP_STRING_TRIM(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TRIM)
+#define DUK_HTHREAD_STRING_TRIM(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TRIM)
+#define DUK_HEAP_STRING_TO_LOCALE_UPPER_CASE(heap)                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_LOCALE_UPPER_CASE)
+#define DUK_HTHREAD_STRING_TO_LOCALE_UPPER_CASE(thr)                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_LOCALE_UPPER_CASE)
+#define DUK_HEAP_STRING_TO_UPPER_CASE(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_UPPER_CASE)
+#define DUK_HTHREAD_STRING_TO_UPPER_CASE(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_UPPER_CASE)
+#define DUK_HEAP_STRING_TO_LOCALE_LOWER_CASE(heap)                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_LOCALE_LOWER_CASE)
+#define DUK_HTHREAD_STRING_TO_LOCALE_LOWER_CASE(thr)                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_LOCALE_LOWER_CASE)
+#define DUK_HEAP_STRING_TO_LOWER_CASE(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_LOWER_CASE)
+#define DUK_HTHREAD_STRING_TO_LOWER_CASE(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_LOWER_CASE)
+#define DUK_HEAP_STRING_SUBSTRING(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SUBSTRING)
+#define DUK_HTHREAD_STRING_SUBSTRING(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SUBSTRING)
+#define DUK_HEAP_STRING_SPLIT(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SPLIT)
+#define DUK_HTHREAD_STRING_SPLIT(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SPLIT)
+#define DUK_HEAP_STRING_SEARCH(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SEARCH)
+#define DUK_HTHREAD_STRING_SEARCH(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SEARCH)
+#define DUK_HEAP_STRING_REPLACE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REPLACE)
+#define DUK_HTHREAD_STRING_REPLACE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REPLACE)
+#define DUK_HEAP_STRING_MATCH(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MATCH)
+#define DUK_HTHREAD_STRING_MATCH(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MATCH)
+#define DUK_HEAP_STRING_LOCALE_COMPARE(heap)                          DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LOCALE_COMPARE)
+#define DUK_HTHREAD_STRING_LOCALE_COMPARE(thr)                        DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LOCALE_COMPARE)
+#define DUK_HEAP_STRING_CHAR_CODE_AT(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CHAR_CODE_AT)
+#define DUK_HTHREAD_STRING_CHAR_CODE_AT(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CHAR_CODE_AT)
+#define DUK_HEAP_STRING_CHAR_AT(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CHAR_AT)
+#define DUK_HTHREAD_STRING_CHAR_AT(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CHAR_AT)
+#define DUK_HEAP_STRING_FROM_CHAR_CODE(heap)                          DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FROM_CHAR_CODE)
+#define DUK_HTHREAD_STRING_FROM_CHAR_CODE(thr)                        DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FROM_CHAR_CODE)
+#define DUK_HEAP_STRING_REDUCE_RIGHT(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REDUCE_RIGHT)
+#define DUK_HTHREAD_STRING_REDUCE_RIGHT(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REDUCE_RIGHT)
+#define DUK_HEAP_STRING_REDUCE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REDUCE)
+#define DUK_HTHREAD_STRING_REDUCE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REDUCE)
+#define DUK_HEAP_STRING_FILTER(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FILTER)
+#define DUK_HTHREAD_STRING_FILTER(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FILTER)
+#define DUK_HEAP_STRING_MAP(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MAP)
+#define DUK_HTHREAD_STRING_MAP(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MAP)
+#define DUK_HEAP_STRING_FOR_EACH(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FOR_EACH)
+#define DUK_HTHREAD_STRING_FOR_EACH(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FOR_EACH)
+#define DUK_HEAP_STRING_SOME(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SOME)
+#define DUK_HTHREAD_STRING_SOME(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SOME)
+#define DUK_HEAP_STRING_EVERY(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EVERY)
+#define DUK_HTHREAD_STRING_EVERY(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EVERY)
+#define DUK_HEAP_STRING_LAST_INDEX_OF(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LAST_INDEX_OF)
+#define DUK_HTHREAD_STRING_LAST_INDEX_OF(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LAST_INDEX_OF)
+#define DUK_HEAP_STRING_INDEX_OF(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INDEX_OF)
+#define DUK_HTHREAD_STRING_INDEX_OF(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INDEX_OF)
+#define DUK_HEAP_STRING_UNSHIFT(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UNSHIFT)
+#define DUK_HTHREAD_STRING_UNSHIFT(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UNSHIFT)
+#define DUK_HEAP_STRING_SPLICE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SPLICE)
+#define DUK_HTHREAD_STRING_SPLICE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SPLICE)
+#define DUK_HEAP_STRING_SORT(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SORT)
+#define DUK_HTHREAD_STRING_SORT(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SORT)
+#define DUK_HEAP_STRING_SLICE(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SLICE)
+#define DUK_HTHREAD_STRING_SLICE(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SLICE)
+#define DUK_HEAP_STRING_SHIFT(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SHIFT)
+#define DUK_HTHREAD_STRING_SHIFT(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SHIFT)
+#define DUK_HEAP_STRING_REVERSE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REVERSE)
+#define DUK_HTHREAD_STRING_REVERSE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REVERSE)
+#define DUK_HEAP_STRING_PUSH(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PUSH)
+#define DUK_HTHREAD_STRING_PUSH(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PUSH)
+#define DUK_HEAP_STRING_POP(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_POP)
+#define DUK_HTHREAD_STRING_POP(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_POP)
+#define DUK_HEAP_STRING_JOIN(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JOIN)
+#define DUK_HTHREAD_STRING_JOIN(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JOIN)
+#define DUK_HEAP_STRING_CONCAT(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CONCAT)
+#define DUK_HTHREAD_STRING_CONCAT(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CONCAT)
+#define DUK_HEAP_STRING_IS_ARRAY(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_ARRAY)
+#define DUK_HTHREAD_STRING_IS_ARRAY(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_ARRAY)
+#define DUK_HEAP_STRING_LC_ARGUMENTS(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_ARGUMENTS)
+#define DUK_HTHREAD_STRING_LC_ARGUMENTS(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_ARGUMENTS)
+#define DUK_HEAP_STRING_CALLER(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CALLER)
+#define DUK_HTHREAD_STRING_CALLER(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CALLER)
+#define DUK_HEAP_STRING_BIND(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_BIND)
+#define DUK_HTHREAD_STRING_BIND(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_BIND)
+#define DUK_HEAP_STRING_CALL(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CALL)
+#define DUK_HTHREAD_STRING_CALL(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CALL)
+#define DUK_HEAP_STRING_APPLY(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_APPLY)
+#define DUK_HTHREAD_STRING_APPLY(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_APPLY)
+#define DUK_HEAP_STRING_PROPERTY_IS_ENUMERABLE(heap)                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PROPERTY_IS_ENUMERABLE)
+#define DUK_HTHREAD_STRING_PROPERTY_IS_ENUMERABLE(thr)                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PROPERTY_IS_ENUMERABLE)
+#define DUK_HEAP_STRING_IS_PROTOTYPE_OF(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_PROTOTYPE_OF)
+#define DUK_HTHREAD_STRING_IS_PROTOTYPE_OF(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_PROTOTYPE_OF)
+#define DUK_HEAP_STRING_HAS_OWN_PROPERTY(heap)                        DUK_HEAP_GET_STRING((heap),DUK_STRIDX_HAS_OWN_PROPERTY)
+#define DUK_HTHREAD_STRING_HAS_OWN_PROPERTY(thr)                      DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_HAS_OWN_PROPERTY)
+#define DUK_HEAP_STRING_VALUE_OF(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_VALUE_OF)
+#define DUK_HTHREAD_STRING_VALUE_OF(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_VALUE_OF)
+#define DUK_HEAP_STRING_TO_LOCALE_STRING(heap)                        DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_LOCALE_STRING)
+#define DUK_HTHREAD_STRING_TO_LOCALE_STRING(thr)                      DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_LOCALE_STRING)
+#define DUK_HEAP_STRING_TO_STRING(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_STRING)
+#define DUK_HTHREAD_STRING_TO_STRING(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_STRING)
+#define DUK_HEAP_STRING_CONSTRUCTOR(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CONSTRUCTOR)
+#define DUK_HTHREAD_STRING_CONSTRUCTOR(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CONSTRUCTOR)
+#define DUK_HEAP_STRING_SET(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET)
+#define DUK_HTHREAD_STRING_SET(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET)
+#define DUK_HEAP_STRING_GET(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET)
+#define DUK_HTHREAD_STRING_GET(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET)
+#define DUK_HEAP_STRING_ENUMERABLE(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENUMERABLE)
+#define DUK_HTHREAD_STRING_ENUMERABLE(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENUMERABLE)
+#define DUK_HEAP_STRING_CONFIGURABLE(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CONFIGURABLE)
+#define DUK_HTHREAD_STRING_CONFIGURABLE(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CONFIGURABLE)
+#define DUK_HEAP_STRING_WRITABLE(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_WRITABLE)
+#define DUK_HTHREAD_STRING_WRITABLE(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_WRITABLE)
+#define DUK_HEAP_STRING_VALUE(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_VALUE)
+#define DUK_HTHREAD_STRING_VALUE(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_VALUE)
+#define DUK_HEAP_STRING_KEYS(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_KEYS)
+#define DUK_HTHREAD_STRING_KEYS(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_KEYS)
+#define DUK_HEAP_STRING_IS_EXTENSIBLE(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_EXTENSIBLE)
+#define DUK_HTHREAD_STRING_IS_EXTENSIBLE(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_EXTENSIBLE)
+#define DUK_HEAP_STRING_IS_FROZEN(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_FROZEN)
+#define DUK_HTHREAD_STRING_IS_FROZEN(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_FROZEN)
+#define DUK_HEAP_STRING_IS_SEALED(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_SEALED)
+#define DUK_HTHREAD_STRING_IS_SEALED(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_SEALED)
+#define DUK_HEAP_STRING_PREVENT_EXTENSIONS(heap)                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PREVENT_EXTENSIONS)
+#define DUK_HTHREAD_STRING_PREVENT_EXTENSIONS(thr)                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PREVENT_EXTENSIONS)
+#define DUK_HEAP_STRING_FREEZE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FREEZE)
+#define DUK_HTHREAD_STRING_FREEZE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FREEZE)
+#define DUK_HEAP_STRING_SEAL(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SEAL)
+#define DUK_HTHREAD_STRING_SEAL(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SEAL)
+#define DUK_HEAP_STRING_DEFINE_PROPERTIES(heap)                       DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DEFINE_PROPERTIES)
+#define DUK_HTHREAD_STRING_DEFINE_PROPERTIES(thr)                     DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DEFINE_PROPERTIES)
+#define DUK_HEAP_STRING_DEFINE_PROPERTY(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DEFINE_PROPERTY)
+#define DUK_HTHREAD_STRING_DEFINE_PROPERTY(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DEFINE_PROPERTY)
+#define DUK_HEAP_STRING_CREATE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CREATE)
+#define DUK_HTHREAD_STRING_CREATE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CREATE)
+#define DUK_HEAP_STRING_GET_OWN_PROPERTY_NAMES(heap)                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_OWN_PROPERTY_NAMES)
+#define DUK_HTHREAD_STRING_GET_OWN_PROPERTY_NAMES(thr)                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_OWN_PROPERTY_NAMES)
+#define DUK_HEAP_STRING_GET_OWN_PROPERTY_DESCRIPTOR(heap)             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_OWN_PROPERTY_DESCRIPTOR)
+#define DUK_HTHREAD_STRING_GET_OWN_PROPERTY_DESCRIPTOR(thr)           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_OWN_PROPERTY_DESCRIPTOR)
+#define DUK_HEAP_STRING_GET_PROTOTYPE_OF(heap)                        DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_PROTOTYPE_OF)
+#define DUK_HTHREAD_STRING_GET_PROTOTYPE_OF(thr)                      DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_PROTOTYPE_OF)
+#define DUK_HEAP_STRING_PROTOTYPE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PROTOTYPE)
+#define DUK_HTHREAD_STRING_PROTOTYPE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PROTOTYPE)
+#define DUK_HEAP_STRING_LENGTH(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LENGTH)
+#define DUK_HTHREAD_STRING_LENGTH(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LENGTH)
+#define DUK_HEAP_STRING_ALERT(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ALERT)
+#define DUK_HTHREAD_STRING_ALERT(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ALERT)
+#define DUK_HEAP_STRING_PRINT(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PRINT)
+#define DUK_HTHREAD_STRING_PRINT(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PRINT)
+#define DUK_HEAP_STRING_UNESCAPE(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UNESCAPE)
+#define DUK_HTHREAD_STRING_UNESCAPE(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UNESCAPE)
+#define DUK_HEAP_STRING_ESCAPE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ESCAPE)
+#define DUK_HTHREAD_STRING_ESCAPE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ESCAPE)
+#define DUK_HEAP_STRING_ENCODE_URI_COMPONENT(heap)                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENCODE_URI_COMPONENT)
+#define DUK_HTHREAD_STRING_ENCODE_URI_COMPONENT(thr)                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENCODE_URI_COMPONENT)
+#define DUK_HEAP_STRING_ENCODE_URI(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENCODE_URI)
+#define DUK_HTHREAD_STRING_ENCODE_URI(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENCODE_URI)
+#define DUK_HEAP_STRING_DECODE_URI_COMPONENT(heap)                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DECODE_URI_COMPONENT)
+#define DUK_HTHREAD_STRING_DECODE_URI_COMPONENT(thr)                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DECODE_URI_COMPONENT)
+#define DUK_HEAP_STRING_DECODE_URI(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DECODE_URI)
+#define DUK_HTHREAD_STRING_DECODE_URI(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DECODE_URI)
+#define DUK_HEAP_STRING_IS_FINITE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_FINITE)
+#define DUK_HTHREAD_STRING_IS_FINITE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_FINITE)
+#define DUK_HEAP_STRING_IS_NAN(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_NAN)
+#define DUK_HTHREAD_STRING_IS_NAN(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_NAN)
+#define DUK_HEAP_STRING_PARSE_FLOAT(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PARSE_FLOAT)
+#define DUK_HTHREAD_STRING_PARSE_FLOAT(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PARSE_FLOAT)
+#define DUK_HEAP_STRING_PARSE_INT(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PARSE_INT)
+#define DUK_HTHREAD_STRING_PARSE_INT(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PARSE_INT)
+#define DUK_HEAP_STRING_EVAL(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EVAL)
+#define DUK_HTHREAD_STRING_EVAL(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EVAL)
+#define DUK_HEAP_STRING_URI_ERROR(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_URI_ERROR)
+#define DUK_HTHREAD_STRING_URI_ERROR(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_URI_ERROR)
+#define DUK_HEAP_STRING_TYPE_ERROR(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TYPE_ERROR)
+#define DUK_HTHREAD_STRING_TYPE_ERROR(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TYPE_ERROR)
+#define DUK_HEAP_STRING_SYNTAX_ERROR(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SYNTAX_ERROR)
+#define DUK_HTHREAD_STRING_SYNTAX_ERROR(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SYNTAX_ERROR)
+#define DUK_HEAP_STRING_REFERENCE_ERROR(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REFERENCE_ERROR)
+#define DUK_HTHREAD_STRING_REFERENCE_ERROR(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REFERENCE_ERROR)
+#define DUK_HEAP_STRING_RANGE_ERROR(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_RANGE_ERROR)
+#define DUK_HTHREAD_STRING_RANGE_ERROR(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_RANGE_ERROR)
+#define DUK_HEAP_STRING_EVAL_ERROR(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EVAL_ERROR)
+#define DUK_HTHREAD_STRING_EVAL_ERROR(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EVAL_ERROR)
+#define DUK_HEAP_STRING_BREAK(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_BREAK)
+#define DUK_HTHREAD_STRING_BREAK(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_BREAK)
+#define DUK_HEAP_STRING_CASE(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CASE)
+#define DUK_HTHREAD_STRING_CASE(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CASE)
+#define DUK_HEAP_STRING_CATCH(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CATCH)
+#define DUK_HTHREAD_STRING_CATCH(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CATCH)
+#define DUK_HEAP_STRING_CONTINUE(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CONTINUE)
+#define DUK_HTHREAD_STRING_CONTINUE(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CONTINUE)
+#define DUK_HEAP_STRING_DEBUGGER(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DEBUGGER)
+#define DUK_HTHREAD_STRING_DEBUGGER(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DEBUGGER)
+#define DUK_HEAP_STRING_DEFAULT(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DEFAULT)
+#define DUK_HTHREAD_STRING_DEFAULT(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DEFAULT)
+#define DUK_HEAP_STRING_DELETE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DELETE)
+#define DUK_HTHREAD_STRING_DELETE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DELETE)
+#define DUK_HEAP_STRING_DO(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DO)
+#define DUK_HTHREAD_STRING_DO(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DO)
+#define DUK_HEAP_STRING_ELSE(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ELSE)
+#define DUK_HTHREAD_STRING_ELSE(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ELSE)
+#define DUK_HEAP_STRING_FINALLY(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FINALLY)
+#define DUK_HTHREAD_STRING_FINALLY(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FINALLY)
+#define DUK_HEAP_STRING_FOR(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FOR)
+#define DUK_HTHREAD_STRING_FOR(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FOR)
+#define DUK_HEAP_STRING_LC_FUNCTION(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_FUNCTION)
+#define DUK_HTHREAD_STRING_LC_FUNCTION(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_FUNCTION)
+#define DUK_HEAP_STRING_IF(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IF)
+#define DUK_HTHREAD_STRING_IF(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IF)
+#define DUK_HEAP_STRING_IN(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IN)
+#define DUK_HTHREAD_STRING_IN(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IN)
+#define DUK_HEAP_STRING_INSTANCEOF(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INSTANCEOF)
+#define DUK_HTHREAD_STRING_INSTANCEOF(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INSTANCEOF)
+#define DUK_HEAP_STRING_NEW(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NEW)
+#define DUK_HTHREAD_STRING_NEW(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NEW)
+#define DUK_HEAP_STRING_RETURN(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_RETURN)
+#define DUK_HTHREAD_STRING_RETURN(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_RETURN)
+#define DUK_HEAP_STRING_SWITCH(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SWITCH)
+#define DUK_HTHREAD_STRING_SWITCH(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SWITCH)
+#define DUK_HEAP_STRING_THIS(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_THIS)
+#define DUK_HTHREAD_STRING_THIS(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_THIS)
+#define DUK_HEAP_STRING_THROW(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_THROW)
+#define DUK_HTHREAD_STRING_THROW(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_THROW)
+#define DUK_HEAP_STRING_TRY(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TRY)
+#define DUK_HTHREAD_STRING_TRY(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TRY)
+#define DUK_HEAP_STRING_TYPEOF(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TYPEOF)
+#define DUK_HTHREAD_STRING_TYPEOF(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TYPEOF)
+#define DUK_HEAP_STRING_VAR(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_VAR)
+#define DUK_HTHREAD_STRING_VAR(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_VAR)
+#define DUK_HEAP_STRING_VOID(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_VOID)
+#define DUK_HTHREAD_STRING_VOID(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_VOID)
+#define DUK_HEAP_STRING_WHILE(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_WHILE)
+#define DUK_HTHREAD_STRING_WHILE(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_WHILE)
+#define DUK_HEAP_STRING_WITH(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_WITH)
+#define DUK_HTHREAD_STRING_WITH(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_WITH)
+#define DUK_HEAP_STRING_CLASS(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CLASS)
+#define DUK_HTHREAD_STRING_CLASS(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CLASS)
+#define DUK_HEAP_STRING_CONST(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CONST)
+#define DUK_HTHREAD_STRING_CONST(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CONST)
+#define DUK_HEAP_STRING_ENUM(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENUM)
+#define DUK_HTHREAD_STRING_ENUM(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENUM)
+#define DUK_HEAP_STRING_EXPORT(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EXPORT)
+#define DUK_HTHREAD_STRING_EXPORT(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EXPORT)
+#define DUK_HEAP_STRING_EXTENDS(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EXTENDS)
+#define DUK_HTHREAD_STRING_EXTENDS(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EXTENDS)
+#define DUK_HEAP_STRING_IMPORT(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IMPORT)
+#define DUK_HTHREAD_STRING_IMPORT(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IMPORT)
+#define DUK_HEAP_STRING_SUPER(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SUPER)
+#define DUK_HTHREAD_STRING_SUPER(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SUPER)
+#define DUK_HEAP_STRING_LC_NULL(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_NULL)
+#define DUK_HTHREAD_STRING_LC_NULL(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_NULL)
+#define DUK_HEAP_STRING_TRUE(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TRUE)
+#define DUK_HTHREAD_STRING_TRUE(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TRUE)
+#define DUK_HEAP_STRING_FALSE(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FALSE)
+#define DUK_HTHREAD_STRING_FALSE(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FALSE)
+#define DUK_HEAP_STRING_IMPLEMENTS(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IMPLEMENTS)
+#define DUK_HTHREAD_STRING_IMPLEMENTS(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IMPLEMENTS)
+#define DUK_HEAP_STRING_INTERFACE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INTERFACE)
+#define DUK_HTHREAD_STRING_INTERFACE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INTERFACE)
+#define DUK_HEAP_STRING_LET(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LET)
+#define DUK_HTHREAD_STRING_LET(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LET)
+#define DUK_HEAP_STRING_PACKAGE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PACKAGE)
+#define DUK_HTHREAD_STRING_PACKAGE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PACKAGE)
+#define DUK_HEAP_STRING_PRIVATE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PRIVATE)
+#define DUK_HTHREAD_STRING_PRIVATE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PRIVATE)
+#define DUK_HEAP_STRING_PROTECTED(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PROTECTED)
+#define DUK_HTHREAD_STRING_PROTECTED(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PROTECTED)
+#define DUK_HEAP_STRING_PUBLIC(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PUBLIC)
+#define DUK_HTHREAD_STRING_PUBLIC(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PUBLIC)
+#define DUK_HEAP_STRING_STATIC(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_STATIC)
+#define DUK_HTHREAD_STRING_STATIC(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_STATIC)
+#define DUK_HEAP_STRING_YIELD(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_YIELD)
+#define DUK_HTHREAD_STRING_YIELD(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_YIELD)
+
+#define DUK_HEAP_NUM_STRINGS                                          336
+
+#define DUK_STRIDX_START_RESERVED                                     291
+#define DUK_STRIDX_START_STRICT_RESERVED                              327
+#define DUK_STRIDX_END_RESERVED                                       336                            /* exclusive endpoint */
+
+extern const duk_c_function duk_bi_native_functions[];
+extern const duk_uint8_t duk_builtins_data[];
+#ifdef DUK_USE_BUILTIN_INITJS
+extern const duk_uint8_t duk_initjs_data[];
+#endif  /* DUK_USE_BUILTIN_INITJS */
+
+#define DUK_BUILTINS_DATA_LENGTH                                      1336
+#ifdef DUK_USE_BUILTIN_INITJS
+#define DUK_BUILTIN_INITJS_DATA_LENGTH                                187
+#endif  /* DUK_USE_BUILTIN_INITJS */
+
+#define DUK_BIDX_GLOBAL                                               0
+#define DUK_BIDX_GLOBAL_ENV                                           1
+#define DUK_BIDX_OBJECT_CONSTRUCTOR                                   2
+#define DUK_BIDX_OBJECT_PROTOTYPE                                     3
+#define DUK_BIDX_FUNCTION_CONSTRUCTOR                                 4
+#define DUK_BIDX_FUNCTION_PROTOTYPE                                   5
+#define DUK_BIDX_ARRAY_CONSTRUCTOR                                    6
+#define DUK_BIDX_ARRAY_PROTOTYPE                                      7
+#define DUK_BIDX_STRING_CONSTRUCTOR                                   8
+#define DUK_BIDX_STRING_PROTOTYPE                                     9
+#define DUK_BIDX_BOOLEAN_CONSTRUCTOR                                  10
+#define DUK_BIDX_BOOLEAN_PROTOTYPE                                    11
+#define DUK_BIDX_NUMBER_CONSTRUCTOR                                   12
+#define DUK_BIDX_NUMBER_PROTOTYPE                                     13
+#define DUK_BIDX_DATE_CONSTRUCTOR                                     14
+#define DUK_BIDX_DATE_PROTOTYPE                                       15
+#define DUK_BIDX_REGEXP_CONSTRUCTOR                                   16
+#define DUK_BIDX_REGEXP_PROTOTYPE                                     17
+#define DUK_BIDX_ERROR_CONSTRUCTOR                                    18
+#define DUK_BIDX_ERROR_PROTOTYPE                                      19
+#define DUK_BIDX_EVAL_ERROR_CONSTRUCTOR                               20
+#define DUK_BIDX_EVAL_ERROR_PROTOTYPE                                 21
+#define DUK_BIDX_RANGE_ERROR_CONSTRUCTOR                              22
+#define DUK_BIDX_RANGE_ERROR_PROTOTYPE                                23
+#define DUK_BIDX_REFERENCE_ERROR_CONSTRUCTOR                          24
+#define DUK_BIDX_REFERENCE_ERROR_PROTOTYPE                            25
+#define DUK_BIDX_SYNTAX_ERROR_CONSTRUCTOR                             26
+#define DUK_BIDX_SYNTAX_ERROR_PROTOTYPE                               27
+#define DUK_BIDX_TYPE_ERROR_CONSTRUCTOR                               28
+#define DUK_BIDX_TYPE_ERROR_PROTOTYPE                                 29
+#define DUK_BIDX_URI_ERROR_CONSTRUCTOR                                30
+#define DUK_BIDX_URI_ERROR_PROTOTYPE                                  31
+#define DUK_BIDX_MATH                                                 32
+#define DUK_BIDX_JSON                                                 33
+#define DUK_BIDX_TYPE_ERROR_THROWER                                   34
+#define DUK_BIDX_PROXY_CONSTRUCTOR                                    35
+#define DUK_BIDX_DUKTAPE                                              36
+#define DUK_BIDX_THREAD_CONSTRUCTOR                                   37
+#define DUK_BIDX_THREAD_PROTOTYPE                                     38
+#define DUK_BIDX_BUFFER_CONSTRUCTOR                                   39
+#define DUK_BIDX_BUFFER_PROTOTYPE                                     40
+#define DUK_BIDX_POINTER_CONSTRUCTOR                                  41
+#define DUK_BIDX_POINTER_PROTOTYPE                                    42
+#define DUK_BIDX_LOGGER_CONSTRUCTOR                                   43
+#define DUK_BIDX_LOGGER_PROTOTYPE                                     44
+#define DUK_BIDX_DOUBLE_ERROR                                         45
+
+#define DUK_NUM_BUILTINS                                              46
+
+#elif defined(DUK_USE_DOUBLE_BE)
+extern const duk_uint8_t duk_strings_data[];
+
+#define DUK_STRDATA_DATA_LENGTH                                       1931
+#define DUK_STRDATA_MAX_STRLEN                                        24
+
+#define DUK_STRIDX_UC_LOGGER                                          0                              /* 'Logger' */
+#define DUK_STRIDX_UC_THREAD                                          1                              /* 'Thread' */
+#define DUK_STRIDX_UC_POINTER                                         2                              /* 'Pointer' */
+#define DUK_STRIDX_UC_BUFFER                                          3                              /* 'Buffer' */
+#define DUK_STRIDX_DEC_ENV                                            4                              /* 'DecEnv' */
+#define DUK_STRIDX_OBJ_ENV                                            5                              /* 'ObjEnv' */
+#define DUK_STRIDX_EMPTY_STRING                                       6                              /* '' */
+#define DUK_STRIDX_GLOBAL                                             7                              /* 'global' */
+#define DUK_STRIDX_UC_ARGUMENTS                                       8                              /* 'Arguments' */
+#define DUK_STRIDX_JSON                                               9                              /* 'JSON' */
+#define DUK_STRIDX_MATH                                               10                             /* 'Math' */
+#define DUK_STRIDX_UC_ERROR                                           11                             /* 'Error' */
+#define DUK_STRIDX_REG_EXP                                            12                             /* 'RegExp' */
+#define DUK_STRIDX_DATE                                               13                             /* 'Date' */
+#define DUK_STRIDX_UC_NUMBER                                          14                             /* 'Number' */
+#define DUK_STRIDX_UC_BOOLEAN                                         15                             /* 'Boolean' */
+#define DUK_STRIDX_UC_STRING                                          16                             /* 'String' */
+#define DUK_STRIDX_ARRAY                                              17                             /* 'Array' */
+#define DUK_STRIDX_UC_FUNCTION                                        18                             /* 'Function' */
+#define DUK_STRIDX_UC_OBJECT                                          19                             /* 'Object' */
+#define DUK_STRIDX_UC_NULL                                            20                             /* 'Null' */
+#define DUK_STRIDX_UC_UNDEFINED                                       21                             /* 'Undefined' */
+#define DUK_STRIDX_JSON_EXT_FUNCTION2                                 22                             /* '{_func:true}' */
+#define DUK_STRIDX_JSON_EXT_FUNCTION1                                 23                             /* '{"_func":true}' */
+#define DUK_STRIDX_JSON_EXT_NEGINF                                    24                             /* '{"_ninf":true}' */
+#define DUK_STRIDX_JSON_EXT_POSINF                                    25                             /* '{"_inf":true}' */
+#define DUK_STRIDX_JSON_EXT_NAN                                       26                             /* '{"_nan":true}' */
+#define DUK_STRIDX_JSON_EXT_UNDEFINED                                 27                             /* '{"_undef":true}' */
+#define DUK_STRIDX_TO_LOG_STRING                                      28                             /* 'toLogString' */
+#define DUK_STRIDX_CLOG                                               29                             /* 'clog' */
+#define DUK_STRIDX_LC_L                                               30                             /* 'l' */
+#define DUK_STRIDX_LC_N                                               31                             /* 'n' */
+#define DUK_STRIDX_LC_FATAL                                           32                             /* 'fatal' */
+#define DUK_STRIDX_LC_ERROR                                           33                             /* 'error' */
+#define DUK_STRIDX_LC_WARN                                            34                             /* 'warn' */
+#define DUK_STRIDX_LC_DEBUG                                           35                             /* 'debug' */
+#define DUK_STRIDX_LC_TRACE                                           36                             /* 'trace' */
+#define DUK_STRIDX_RAW                                                37                             /* 'raw' */
+#define DUK_STRIDX_FMT                                                38                             /* 'fmt' */
+#define DUK_STRIDX_CURRENT                                            39                             /* 'current' */
+#define DUK_STRIDX_RESUME                                             40                             /* 'resume' */
+#define DUK_STRIDX_COMPACT                                            41                             /* 'compact' */
+#define DUK_STRIDX_JC                                                 42                             /* 'jc' */
+#define DUK_STRIDX_JX                                                 43                             /* 'jx' */
+#define DUK_STRIDX_BASE64                                             44                             /* 'base64' */
+#define DUK_STRIDX_HEX                                                45                             /* 'hex' */
+#define DUK_STRIDX_DEC                                                46                             /* 'dec' */
+#define DUK_STRIDX_ENC                                                47                             /* 'enc' */
+#define DUK_STRIDX_FIN                                                48                             /* 'fin' */
+#define DUK_STRIDX_GC                                                 49                             /* 'gc' */
+#define DUK_STRIDX_ACT                                                50                             /* 'act' */
+#define DUK_STRIDX_LC_INFO                                            51                             /* 'info' */
+#define DUK_STRIDX_VERSION                                            52                             /* 'version' */
+#define DUK_STRIDX_ENV                                                53                             /* 'env' */
+#define DUK_STRIDX_MOD_LOADED                                         54                             /* 'modLoaded' */
+#define DUK_STRIDX_MOD_SEARCH                                         55                             /* 'modSearch' */
+#define DUK_STRIDX_ERR_THROW                                          56                             /* 'errThrow' */
+#define DUK_STRIDX_ERR_CREATE                                         57                             /* 'errCreate' */
+#define DUK_STRIDX_COMPILE                                            58                             /* 'compile' */
+#define DUK_STRIDX_INT_REGBASE                                        59                             /* '\x00regbase' */
+#define DUK_STRIDX_INT_THREAD                                         60                             /* '\x00thread' */
+#define DUK_STRIDX_INT_HANDLER                                        61                             /* '\x00handler' */
+#define DUK_STRIDX_INT_FINALIZER                                      62                             /* '\x00finalizer' */
+#define DUK_STRIDX_INT_CALLEE                                         63                             /* '\x00callee' */
+#define DUK_STRIDX_INT_MAP                                            64                             /* '\x00map' */
+#define DUK_STRIDX_INT_ARGS                                           65                             /* '\x00args' */
+#define DUK_STRIDX_INT_THIS                                           66                             /* '\x00this' */
+#define DUK_STRIDX_INT_PC2LINE                                        67                             /* '\x00pc2line' */
+#define DUK_STRIDX_INT_SOURCE                                         68                             /* '\x00source' */
+#define DUK_STRIDX_INT_VARENV                                         69                             /* '\x00varenv' */
+#define DUK_STRIDX_INT_LEXENV                                         70                             /* '\x00lexenv' */
+#define DUK_STRIDX_INT_VARMAP                                         71                             /* '\x00varmap' */
+#define DUK_STRIDX_INT_FORMALS                                        72                             /* '\x00formals' */
+#define DUK_STRIDX_INT_BYTECODE                                       73                             /* '\x00bytecode' */
+#define DUK_STRIDX_INT_NEXT                                           74                             /* '\x00next' */
+#define DUK_STRIDX_INT_TARGET                                         75                             /* '\x00target' */
+#define DUK_STRIDX_INT_VALUE                                          76                             /* '\x00value' */
+#define DUK_STRIDX_LC_POINTER                                         77                             /* 'pointer' */
+#define DUK_STRIDX_LC_BUFFER                                          78                             /* 'buffer' */
+#define DUK_STRIDX_TRACEDATA                                          79                             /* 'tracedata' */
+#define DUK_STRIDX_LINE_NUMBER                                        80                             /* 'lineNumber' */
+#define DUK_STRIDX_FILE_NAME                                          81                             /* 'fileName' */
+#define DUK_STRIDX_PC                                                 82                             /* 'pc' */
+#define DUK_STRIDX_STACK                                              83                             /* 'stack' */
+#define DUK_STRIDX_THROW_TYPE_ERROR                                   84                             /* 'ThrowTypeError' */
+#define DUK_STRIDX_DUKTAPE                                            85                             /* 'Duktape' */
+#define DUK_STRIDX_ID                                                 86                             /* 'id' */
+#define DUK_STRIDX_REQUIRE                                            87                             /* 'require' */
+#define DUK_STRIDX___PROTO__                                          88                             /* '__proto__' */
+#define DUK_STRIDX_SET_PROTOTYPE_OF                                   89                             /* 'setPrototypeOf' */
+#define DUK_STRIDX_OWN_KEYS                                           90                             /* 'ownKeys' */
+#define DUK_STRIDX_ENUMERATE                                          91                             /* 'enumerate' */
+#define DUK_STRIDX_DELETE_PROPERTY                                    92                             /* 'deleteProperty' */
+#define DUK_STRIDX_HAS                                                93                             /* 'has' */
+#define DUK_STRIDX_PROXY                                              94                             /* 'Proxy' */
+#define DUK_STRIDX_CALLEE                                             95                             /* 'callee' */
+#define DUK_STRIDX_INVALID_DATE                                       96                             /* 'Invalid Date' */
+#define DUK_STRIDX_BRACKETED_ELLIPSIS                                 97                             /* '[...]' */
+#define DUK_STRIDX_NEWLINE_TAB                                        98                             /* '\n\t' */
+#define DUK_STRIDX_SPACE                                              99                             /* ' ' */
+#define DUK_STRIDX_COMMA                                              100                            /* ',' */
+#define DUK_STRIDX_MINUS_ZERO                                         101                            /* '-0' */
+#define DUK_STRIDX_PLUS_ZERO                                          102                            /* '+0' */
+#define DUK_STRIDX_ZERO                                               103                            /* '0' */
+#define DUK_STRIDX_MINUS_INFINITY                                     104                            /* '-Infinity' */
+#define DUK_STRIDX_PLUS_INFINITY                                      105                            /* '+Infinity' */
+#define DUK_STRIDX_INFINITY                                           106                            /* 'Infinity' */
+#define DUK_STRIDX_LC_OBJECT                                          107                            /* 'object' */
+#define DUK_STRIDX_LC_STRING                                          108                            /* 'string' */
+#define DUK_STRIDX_LC_NUMBER                                          109                            /* 'number' */
+#define DUK_STRIDX_LC_BOOLEAN                                         110                            /* 'boolean' */
+#define DUK_STRIDX_LC_UNDEFINED                                       111                            /* 'undefined' */
+#define DUK_STRIDX_STRINGIFY                                          112                            /* 'stringify' */
+#define DUK_STRIDX_TAN                                                113                            /* 'tan' */
+#define DUK_STRIDX_SQRT                                               114                            /* 'sqrt' */
+#define DUK_STRIDX_SIN                                                115                            /* 'sin' */
+#define DUK_STRIDX_ROUND                                              116                            /* 'round' */
+#define DUK_STRIDX_RANDOM                                             117                            /* 'random' */
+#define DUK_STRIDX_POW                                                118                            /* 'pow' */
+#define DUK_STRIDX_MIN                                                119                            /* 'min' */
+#define DUK_STRIDX_MAX                                                120                            /* 'max' */
+#define DUK_STRIDX_LOG                                                121                            /* 'log' */
+#define DUK_STRIDX_FLOOR                                              122                            /* 'floor' */
+#define DUK_STRIDX_EXP                                                123                            /* 'exp' */
+#define DUK_STRIDX_COS                                                124                            /* 'cos' */
+#define DUK_STRIDX_CEIL                                               125                            /* 'ceil' */
+#define DUK_STRIDX_ATAN2                                              126                            /* 'atan2' */
+#define DUK_STRIDX_ATAN                                               127                            /* 'atan' */
+#define DUK_STRIDX_ASIN                                               128                            /* 'asin' */
+#define DUK_STRIDX_ACOS                                               129                            /* 'acos' */
+#define DUK_STRIDX_ABS                                                130                            /* 'abs' */
+#define DUK_STRIDX_SQRT2                                              131                            /* 'SQRT2' */
+#define DUK_STRIDX_SQRT1_2                                            132                            /* 'SQRT1_2' */
+#define DUK_STRIDX_PI                                                 133                            /* 'PI' */
+#define DUK_STRIDX_LOG10E                                             134                            /* 'LOG10E' */
+#define DUK_STRIDX_LOG2E                                              135                            /* 'LOG2E' */
+#define DUK_STRIDX_LN2                                                136                            /* 'LN2' */
+#define DUK_STRIDX_LN10                                               137                            /* 'LN10' */
+#define DUK_STRIDX_E                                                  138                            /* 'E' */
+#define DUK_STRIDX_MESSAGE                                            139                            /* 'message' */
+#define DUK_STRIDX_NAME                                               140                            /* 'name' */
+#define DUK_STRIDX_INPUT                                              141                            /* 'input' */
+#define DUK_STRIDX_INDEX                                              142                            /* 'index' */
+#define DUK_STRIDX_ESCAPED_EMPTY_REGEXP                               143                            /* '(?:)' */
+#define DUK_STRIDX_LAST_INDEX                                         144                            /* 'lastIndex' */
+#define DUK_STRIDX_MULTILINE                                          145                            /* 'multiline' */
+#define DUK_STRIDX_IGNORE_CASE                                        146                            /* 'ignoreCase' */
+#define DUK_STRIDX_SOURCE                                             147                            /* 'source' */
+#define DUK_STRIDX_TEST                                               148                            /* 'test' */
+#define DUK_STRIDX_EXEC                                               149                            /* 'exec' */
+#define DUK_STRIDX_TO_GMT_STRING                                      150                            /* 'toGMTString' */
+#define DUK_STRIDX_SET_YEAR                                           151                            /* 'setYear' */
+#define DUK_STRIDX_GET_YEAR                                           152                            /* 'getYear' */
+#define DUK_STRIDX_TO_JSON                                            153                            /* 'toJSON' */
+#define DUK_STRIDX_TO_ISO_STRING                                      154                            /* 'toISOString' */
+#define DUK_STRIDX_TO_UTC_STRING                                      155                            /* 'toUTCString' */
+#define DUK_STRIDX_SET_UTC_FULL_YEAR                                  156                            /* 'setUTCFullYear' */
+#define DUK_STRIDX_SET_FULL_YEAR                                      157                            /* 'setFullYear' */
+#define DUK_STRIDX_SET_UTC_MONTH                                      158                            /* 'setUTCMonth' */
+#define DUK_STRIDX_SET_MONTH                                          159                            /* 'setMonth' */
+#define DUK_STRIDX_SET_UTC_DATE                                       160                            /* 'setUTCDate' */
+#define DUK_STRIDX_SET_DATE                                           161                            /* 'setDate' */
+#define DUK_STRIDX_SET_UTC_HOURS                                      162                            /* 'setUTCHours' */
+#define DUK_STRIDX_SET_HOURS                                          163                            /* 'setHours' */
+#define DUK_STRIDX_SET_UTC_MINUTES                                    164                            /* 'setUTCMinutes' */
+#define DUK_STRIDX_SET_MINUTES                                        165                            /* 'setMinutes' */
+#define DUK_STRIDX_SET_UTC_SECONDS                                    166                            /* 'setUTCSeconds' */
+#define DUK_STRIDX_SET_SECONDS                                        167                            /* 'setSeconds' */
+#define DUK_STRIDX_SET_UTC_MILLISECONDS                               168                            /* 'setUTCMilliseconds' */
+#define DUK_STRIDX_SET_MILLISECONDS                                   169                            /* 'setMilliseconds' */
+#define DUK_STRIDX_SET_TIME                                           170                            /* 'setTime' */
+#define DUK_STRIDX_GET_TIMEZONE_OFFSET                                171                            /* 'getTimezoneOffset' */
+#define DUK_STRIDX_GET_UTC_MILLISECONDS                               172                            /* 'getUTCMilliseconds' */
+#define DUK_STRIDX_GET_MILLISECONDS                                   173                            /* 'getMilliseconds' */
+#define DUK_STRIDX_GET_UTC_SECONDS                                    174                            /* 'getUTCSeconds' */
+#define DUK_STRIDX_GET_SECONDS                                        175                            /* 'getSeconds' */
+#define DUK_STRIDX_GET_UTC_MINUTES                                    176                            /* 'getUTCMinutes' */
+#define DUK_STRIDX_GET_MINUTES                                        177                            /* 'getMinutes' */
+#define DUK_STRIDX_GET_UTC_HOURS                                      178                            /* 'getUTCHours' */
+#define DUK_STRIDX_GET_HOURS                                          179                            /* 'getHours' */
+#define DUK_STRIDX_GET_UTC_DAY                                        180                            /* 'getUTCDay' */
+#define DUK_STRIDX_GET_DAY                                            181                            /* 'getDay' */
+#define DUK_STRIDX_GET_UTC_DATE                                       182                            /* 'getUTCDate' */
+#define DUK_STRIDX_GET_DATE                                           183                            /* 'getDate' */
+#define DUK_STRIDX_GET_UTC_MONTH                                      184                            /* 'getUTCMonth' */
+#define DUK_STRIDX_GET_MONTH                                          185                            /* 'getMonth' */
+#define DUK_STRIDX_GET_UTC_FULL_YEAR                                  186                            /* 'getUTCFullYear' */
+#define DUK_STRIDX_GET_FULL_YEAR                                      187                            /* 'getFullYear' */
+#define DUK_STRIDX_GET_TIME                                           188                            /* 'getTime' */
+#define DUK_STRIDX_TO_LOCALE_TIME_STRING                              189                            /* 'toLocaleTimeString' */
+#define DUK_STRIDX_TO_LOCALE_DATE_STRING                              190                            /* 'toLocaleDateString' */
+#define DUK_STRIDX_TO_TIME_STRING                                     191                            /* 'toTimeString' */
+#define DUK_STRIDX_TO_DATE_STRING                                     192                            /* 'toDateString' */
+#define DUK_STRIDX_NOW                                                193                            /* 'now' */
+#define DUK_STRIDX_UTC                                                194                            /* 'UTC' */
+#define DUK_STRIDX_PARSE                                              195                            /* 'parse' */
+#define DUK_STRIDX_TO_PRECISION                                       196                            /* 'toPrecision' */
+#define DUK_STRIDX_TO_EXPONENTIAL                                     197                            /* 'toExponential' */
+#define DUK_STRIDX_TO_FIXED                                           198                            /* 'toFixed' */
+#define DUK_STRIDX_POSITIVE_INFINITY                                  199                            /* 'POSITIVE_INFINITY' */
+#define DUK_STRIDX_NEGATIVE_INFINITY                                  200                            /* 'NEGATIVE_INFINITY' */
+#define DUK_STRIDX_NAN                                                201                            /* 'NaN' */
+#define DUK_STRIDX_MIN_VALUE                                          202                            /* 'MIN_VALUE' */
+#define DUK_STRIDX_MAX_VALUE                                          203                            /* 'MAX_VALUE' */
+#define DUK_STRIDX_SUBSTR                                             204                            /* 'substr' */
+#define DUK_STRIDX_TRIM                                               205                            /* 'trim' */
+#define DUK_STRIDX_TO_LOCALE_UPPER_CASE                               206                            /* 'toLocaleUpperCase' */
+#define DUK_STRIDX_TO_UPPER_CASE                                      207                            /* 'toUpperCase' */
+#define DUK_STRIDX_TO_LOCALE_LOWER_CASE                               208                            /* 'toLocaleLowerCase' */
+#define DUK_STRIDX_TO_LOWER_CASE                                      209                            /* 'toLowerCase' */
+#define DUK_STRIDX_SUBSTRING                                          210                            /* 'substring' */
+#define DUK_STRIDX_SPLIT                                              211                            /* 'split' */
+#define DUK_STRIDX_SEARCH                                             212                            /* 'search' */
+#define DUK_STRIDX_REPLACE                                            213                            /* 'replace' */
+#define DUK_STRIDX_MATCH                                              214                            /* 'match' */
+#define DUK_STRIDX_LOCALE_COMPARE                                     215                            /* 'localeCompare' */
+#define DUK_STRIDX_CHAR_CODE_AT                                       216                            /* 'charCodeAt' */
+#define DUK_STRIDX_CHAR_AT                                            217                            /* 'charAt' */
+#define DUK_STRIDX_FROM_CHAR_CODE                                     218                            /* 'fromCharCode' */
+#define DUK_STRIDX_REDUCE_RIGHT                                       219                            /* 'reduceRight' */
+#define DUK_STRIDX_REDUCE                                             220                            /* 'reduce' */
+#define DUK_STRIDX_FILTER                                             221                            /* 'filter' */
+#define DUK_STRIDX_MAP                                                222                            /* 'map' */
+#define DUK_STRIDX_FOR_EACH                                           223                            /* 'forEach' */
+#define DUK_STRIDX_SOME                                               224                            /* 'some' */
+#define DUK_STRIDX_EVERY                                              225                            /* 'every' */
+#define DUK_STRIDX_LAST_INDEX_OF                                      226                            /* 'lastIndexOf' */
+#define DUK_STRIDX_INDEX_OF                                           227                            /* 'indexOf' */
+#define DUK_STRIDX_UNSHIFT                                            228                            /* 'unshift' */
+#define DUK_STRIDX_SPLICE                                             229                            /* 'splice' */
+#define DUK_STRIDX_SORT                                               230                            /* 'sort' */
+#define DUK_STRIDX_SLICE                                              231                            /* 'slice' */
+#define DUK_STRIDX_SHIFT                                              232                            /* 'shift' */
+#define DUK_STRIDX_REVERSE                                            233                            /* 'reverse' */
+#define DUK_STRIDX_PUSH                                               234                            /* 'push' */
+#define DUK_STRIDX_POP                                                235                            /* 'pop' */
+#define DUK_STRIDX_JOIN                                               236                            /* 'join' */
+#define DUK_STRIDX_CONCAT                                             237                            /* 'concat' */
+#define DUK_STRIDX_IS_ARRAY                                           238                            /* 'isArray' */
+#define DUK_STRIDX_LC_ARGUMENTS                                       239                            /* 'arguments' */
+#define DUK_STRIDX_CALLER                                             240                            /* 'caller' */
+#define DUK_STRIDX_BIND                                               241                            /* 'bind' */
+#define DUK_STRIDX_CALL                                               242                            /* 'call' */
+#define DUK_STRIDX_APPLY                                              243                            /* 'apply' */
+#define DUK_STRIDX_PROPERTY_IS_ENUMERABLE                             244                            /* 'propertyIsEnumerable' */
+#define DUK_STRIDX_IS_PROTOTYPE_OF                                    245                            /* 'isPrototypeOf' */
+#define DUK_STRIDX_HAS_OWN_PROPERTY                                   246                            /* 'hasOwnProperty' */
+#define DUK_STRIDX_VALUE_OF                                           247                            /* 'valueOf' */
+#define DUK_STRIDX_TO_LOCALE_STRING                                   248                            /* 'toLocaleString' */
+#define DUK_STRIDX_TO_STRING                                          249                            /* 'toString' */
+#define DUK_STRIDX_CONSTRUCTOR                                        250                            /* 'constructor' */
+#define DUK_STRIDX_SET                                                251                            /* 'set' */
+#define DUK_STRIDX_GET                                                252                            /* 'get' */
+#define DUK_STRIDX_ENUMERABLE                                         253                            /* 'enumerable' */
+#define DUK_STRIDX_CONFIGURABLE                                       254                            /* 'configurable' */
+#define DUK_STRIDX_WRITABLE                                           255                            /* 'writable' */
+#define DUK_STRIDX_VALUE                                              256                            /* 'value' */
+#define DUK_STRIDX_KEYS                                               257                            /* 'keys' */
+#define DUK_STRIDX_IS_EXTENSIBLE                                      258                            /* 'isExtensible' */
+#define DUK_STRIDX_IS_FROZEN                                          259                            /* 'isFrozen' */
+#define DUK_STRIDX_IS_SEALED                                          260                            /* 'isSealed' */
+#define DUK_STRIDX_PREVENT_EXTENSIONS                                 261                            /* 'preventExtensions' */
+#define DUK_STRIDX_FREEZE                                             262                            /* 'freeze' */
+#define DUK_STRIDX_SEAL                                               263                            /* 'seal' */
+#define DUK_STRIDX_DEFINE_PROPERTIES                                  264                            /* 'defineProperties' */
+#define DUK_STRIDX_DEFINE_PROPERTY                                    265                            /* 'defineProperty' */
+#define DUK_STRIDX_CREATE                                             266                            /* 'create' */
+#define DUK_STRIDX_GET_OWN_PROPERTY_NAMES                             267                            /* 'getOwnPropertyNames' */
+#define DUK_STRIDX_GET_OWN_PROPERTY_DESCRIPTOR                        268                            /* 'getOwnPropertyDescriptor' */
+#define DUK_STRIDX_GET_PROTOTYPE_OF                                   269                            /* 'getPrototypeOf' */
+#define DUK_STRIDX_PROTOTYPE                                          270                            /* 'prototype' */
+#define DUK_STRIDX_LENGTH                                             271                            /* 'length' */
+#define DUK_STRIDX_ALERT                                              272                            /* 'alert' */
+#define DUK_STRIDX_PRINT                                              273                            /* 'print' */
+#define DUK_STRIDX_UNESCAPE                                           274                            /* 'unescape' */
+#define DUK_STRIDX_ESCAPE                                             275                            /* 'escape' */
+#define DUK_STRIDX_ENCODE_URI_COMPONENT                               276                            /* 'encodeURIComponent' */
+#define DUK_STRIDX_ENCODE_URI                                         277                            /* 'encodeURI' */
+#define DUK_STRIDX_DECODE_URI_COMPONENT                               278                            /* 'decodeURIComponent' */
+#define DUK_STRIDX_DECODE_URI                                         279                            /* 'decodeURI' */
+#define DUK_STRIDX_IS_FINITE                                          280                            /* 'isFinite' */
+#define DUK_STRIDX_IS_NAN                                             281                            /* 'isNaN' */
+#define DUK_STRIDX_PARSE_FLOAT                                        282                            /* 'parseFloat' */
+#define DUK_STRIDX_PARSE_INT                                          283                            /* 'parseInt' */
+#define DUK_STRIDX_EVAL                                               284                            /* 'eval' */
+#define DUK_STRIDX_URI_ERROR                                          285                            /* 'URIError' */
+#define DUK_STRIDX_TYPE_ERROR                                         286                            /* 'TypeError' */
+#define DUK_STRIDX_SYNTAX_ERROR                                       287                            /* 'SyntaxError' */
+#define DUK_STRIDX_REFERENCE_ERROR                                    288                            /* 'ReferenceError' */
+#define DUK_STRIDX_RANGE_ERROR                                        289                            /* 'RangeError' */
+#define DUK_STRIDX_EVAL_ERROR                                         290                            /* 'EvalError' */
+#define DUK_STRIDX_BREAK                                              291                            /* 'break' */
+#define DUK_STRIDX_CASE                                               292                            /* 'case' */
+#define DUK_STRIDX_CATCH                                              293                            /* 'catch' */
+#define DUK_STRIDX_CONTINUE                                           294                            /* 'continue' */
+#define DUK_STRIDX_DEBUGGER                                           295                            /* 'debugger' */
+#define DUK_STRIDX_DEFAULT                                            296                            /* 'default' */
+#define DUK_STRIDX_DELETE                                             297                            /* 'delete' */
+#define DUK_STRIDX_DO                                                 298                            /* 'do' */
+#define DUK_STRIDX_ELSE                                               299                            /* 'else' */
+#define DUK_STRIDX_FINALLY                                            300                            /* 'finally' */
+#define DUK_STRIDX_FOR                                                301                            /* 'for' */
+#define DUK_STRIDX_LC_FUNCTION                                        302                            /* 'function' */
+#define DUK_STRIDX_IF                                                 303                            /* 'if' */
+#define DUK_STRIDX_IN                                                 304                            /* 'in' */
+#define DUK_STRIDX_INSTANCEOF                                         305                            /* 'instanceof' */
+#define DUK_STRIDX_NEW                                                306                            /* 'new' */
+#define DUK_STRIDX_RETURN                                             307                            /* 'return' */
+#define DUK_STRIDX_SWITCH                                             308                            /* 'switch' */
+#define DUK_STRIDX_THIS                                               309                            /* 'this' */
+#define DUK_STRIDX_THROW                                              310                            /* 'throw' */
+#define DUK_STRIDX_TRY                                                311                            /* 'try' */
+#define DUK_STRIDX_TYPEOF                                             312                            /* 'typeof' */
+#define DUK_STRIDX_VAR                                                313                            /* 'var' */
+#define DUK_STRIDX_VOID                                               314                            /* 'void' */
+#define DUK_STRIDX_WHILE                                              315                            /* 'while' */
+#define DUK_STRIDX_WITH                                               316                            /* 'with' */
+#define DUK_STRIDX_CLASS                                              317                            /* 'class' */
+#define DUK_STRIDX_CONST                                              318                            /* 'const' */
+#define DUK_STRIDX_ENUM                                               319                            /* 'enum' */
+#define DUK_STRIDX_EXPORT                                             320                            /* 'export' */
+#define DUK_STRIDX_EXTENDS                                            321                            /* 'extends' */
+#define DUK_STRIDX_IMPORT                                             322                            /* 'import' */
+#define DUK_STRIDX_SUPER                                              323                            /* 'super' */
+#define DUK_STRIDX_LC_NULL                                            324                            /* 'null' */
+#define DUK_STRIDX_TRUE                                               325                            /* 'true' */
+#define DUK_STRIDX_FALSE                                              326                            /* 'false' */
+#define DUK_STRIDX_IMPLEMENTS                                         327                            /* 'implements' */
+#define DUK_STRIDX_INTERFACE                                          328                            /* 'interface' */
+#define DUK_STRIDX_LET                                                329                            /* 'let' */
+#define DUK_STRIDX_PACKAGE                                            330                            /* 'package' */
+#define DUK_STRIDX_PRIVATE                                            331                            /* 'private' */
+#define DUK_STRIDX_PROTECTED                                          332                            /* 'protected' */
+#define DUK_STRIDX_PUBLIC                                             333                            /* 'public' */
+#define DUK_STRIDX_STATIC                                             334                            /* 'static' */
+#define DUK_STRIDX_YIELD                                              335                            /* 'yield' */
+
+#define DUK_HEAP_STRING_UC_LOGGER(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_LOGGER)
+#define DUK_HTHREAD_STRING_UC_LOGGER(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_LOGGER)
+#define DUK_HEAP_STRING_UC_THREAD(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_THREAD)
+#define DUK_HTHREAD_STRING_UC_THREAD(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_THREAD)
+#define DUK_HEAP_STRING_UC_POINTER(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_POINTER)
+#define DUK_HTHREAD_STRING_UC_POINTER(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_POINTER)
+#define DUK_HEAP_STRING_UC_BUFFER(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_BUFFER)
+#define DUK_HTHREAD_STRING_UC_BUFFER(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_BUFFER)
+#define DUK_HEAP_STRING_DEC_ENV(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DEC_ENV)
+#define DUK_HTHREAD_STRING_DEC_ENV(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DEC_ENV)
+#define DUK_HEAP_STRING_OBJ_ENV(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_OBJ_ENV)
+#define DUK_HTHREAD_STRING_OBJ_ENV(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_OBJ_ENV)
+#define DUK_HEAP_STRING_EMPTY_STRING(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EMPTY_STRING)
+#define DUK_HTHREAD_STRING_EMPTY_STRING(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EMPTY_STRING)
+#define DUK_HEAP_STRING_GLOBAL(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GLOBAL)
+#define DUK_HTHREAD_STRING_GLOBAL(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GLOBAL)
+#define DUK_HEAP_STRING_UC_ARGUMENTS(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_ARGUMENTS)
+#define DUK_HTHREAD_STRING_UC_ARGUMENTS(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_ARGUMENTS)
+#define DUK_HEAP_STRING_JSON(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON)
+#define DUK_HTHREAD_STRING_JSON(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON)
+#define DUK_HEAP_STRING_MATH(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MATH)
+#define DUK_HTHREAD_STRING_MATH(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MATH)
+#define DUK_HEAP_STRING_UC_ERROR(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_ERROR)
+#define DUK_HTHREAD_STRING_UC_ERROR(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_ERROR)
+#define DUK_HEAP_STRING_REG_EXP(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REG_EXP)
+#define DUK_HTHREAD_STRING_REG_EXP(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REG_EXP)
+#define DUK_HEAP_STRING_DATE(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DATE)
+#define DUK_HTHREAD_STRING_DATE(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DATE)
+#define DUK_HEAP_STRING_UC_NUMBER(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_NUMBER)
+#define DUK_HTHREAD_STRING_UC_NUMBER(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_NUMBER)
+#define DUK_HEAP_STRING_UC_BOOLEAN(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_BOOLEAN)
+#define DUK_HTHREAD_STRING_UC_BOOLEAN(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_BOOLEAN)
+#define DUK_HEAP_STRING_UC_STRING(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_STRING)
+#define DUK_HTHREAD_STRING_UC_STRING(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_STRING)
+#define DUK_HEAP_STRING_ARRAY(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ARRAY)
+#define DUK_HTHREAD_STRING_ARRAY(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ARRAY)
+#define DUK_HEAP_STRING_UC_FUNCTION(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_FUNCTION)
+#define DUK_HTHREAD_STRING_UC_FUNCTION(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_FUNCTION)
+#define DUK_HEAP_STRING_UC_OBJECT(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_OBJECT)
+#define DUK_HTHREAD_STRING_UC_OBJECT(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_OBJECT)
+#define DUK_HEAP_STRING_UC_NULL(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_NULL)
+#define DUK_HTHREAD_STRING_UC_NULL(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_NULL)
+#define DUK_HEAP_STRING_UC_UNDEFINED(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_UNDEFINED)
+#define DUK_HTHREAD_STRING_UC_UNDEFINED(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_UNDEFINED)
+#define DUK_HEAP_STRING_JSON_EXT_FUNCTION2(heap)                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_FUNCTION2)
+#define DUK_HTHREAD_STRING_JSON_EXT_FUNCTION2(thr)                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_FUNCTION2)
+#define DUK_HEAP_STRING_JSON_EXT_FUNCTION1(heap)                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_FUNCTION1)
+#define DUK_HTHREAD_STRING_JSON_EXT_FUNCTION1(thr)                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_FUNCTION1)
+#define DUK_HEAP_STRING_JSON_EXT_NEGINF(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_NEGINF)
+#define DUK_HTHREAD_STRING_JSON_EXT_NEGINF(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_NEGINF)
+#define DUK_HEAP_STRING_JSON_EXT_POSINF(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_POSINF)
+#define DUK_HTHREAD_STRING_JSON_EXT_POSINF(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_POSINF)
+#define DUK_HEAP_STRING_JSON_EXT_NAN(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_NAN)
+#define DUK_HTHREAD_STRING_JSON_EXT_NAN(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_NAN)
+#define DUK_HEAP_STRING_JSON_EXT_UNDEFINED(heap)                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_UNDEFINED)
+#define DUK_HTHREAD_STRING_JSON_EXT_UNDEFINED(thr)                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_UNDEFINED)
+#define DUK_HEAP_STRING_TO_LOG_STRING(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_LOG_STRING)
+#define DUK_HTHREAD_STRING_TO_LOG_STRING(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_LOG_STRING)
+#define DUK_HEAP_STRING_CLOG(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CLOG)
+#define DUK_HTHREAD_STRING_CLOG(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CLOG)
+#define DUK_HEAP_STRING_LC_L(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_L)
+#define DUK_HTHREAD_STRING_LC_L(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_L)
+#define DUK_HEAP_STRING_LC_N(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_N)
+#define DUK_HTHREAD_STRING_LC_N(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_N)
+#define DUK_HEAP_STRING_LC_FATAL(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_FATAL)
+#define DUK_HTHREAD_STRING_LC_FATAL(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_FATAL)
+#define DUK_HEAP_STRING_LC_ERROR(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_ERROR)
+#define DUK_HTHREAD_STRING_LC_ERROR(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_ERROR)
+#define DUK_HEAP_STRING_LC_WARN(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_WARN)
+#define DUK_HTHREAD_STRING_LC_WARN(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_WARN)
+#define DUK_HEAP_STRING_LC_DEBUG(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_DEBUG)
+#define DUK_HTHREAD_STRING_LC_DEBUG(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_DEBUG)
+#define DUK_HEAP_STRING_LC_TRACE(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_TRACE)
+#define DUK_HTHREAD_STRING_LC_TRACE(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_TRACE)
+#define DUK_HEAP_STRING_RAW(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_RAW)
+#define DUK_HTHREAD_STRING_RAW(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_RAW)
+#define DUK_HEAP_STRING_FMT(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FMT)
+#define DUK_HTHREAD_STRING_FMT(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FMT)
+#define DUK_HEAP_STRING_CURRENT(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CURRENT)
+#define DUK_HTHREAD_STRING_CURRENT(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CURRENT)
+#define DUK_HEAP_STRING_RESUME(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_RESUME)
+#define DUK_HTHREAD_STRING_RESUME(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_RESUME)
+#define DUK_HEAP_STRING_COMPACT(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_COMPACT)
+#define DUK_HTHREAD_STRING_COMPACT(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_COMPACT)
+#define DUK_HEAP_STRING_JC(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JC)
+#define DUK_HTHREAD_STRING_JC(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JC)
+#define DUK_HEAP_STRING_JX(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JX)
+#define DUK_HTHREAD_STRING_JX(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JX)
+#define DUK_HEAP_STRING_BASE64(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_BASE64)
+#define DUK_HTHREAD_STRING_BASE64(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_BASE64)
+#define DUK_HEAP_STRING_HEX(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_HEX)
+#define DUK_HTHREAD_STRING_HEX(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_HEX)
+#define DUK_HEAP_STRING_DEC(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DEC)
+#define DUK_HTHREAD_STRING_DEC(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DEC)
+#define DUK_HEAP_STRING_ENC(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENC)
+#define DUK_HTHREAD_STRING_ENC(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENC)
+#define DUK_HEAP_STRING_FIN(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FIN)
+#define DUK_HTHREAD_STRING_FIN(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FIN)
+#define DUK_HEAP_STRING_GC(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GC)
+#define DUK_HTHREAD_STRING_GC(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GC)
+#define DUK_HEAP_STRING_ACT(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ACT)
+#define DUK_HTHREAD_STRING_ACT(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ACT)
+#define DUK_HEAP_STRING_LC_INFO(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_INFO)
+#define DUK_HTHREAD_STRING_LC_INFO(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_INFO)
+#define DUK_HEAP_STRING_VERSION(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_VERSION)
+#define DUK_HTHREAD_STRING_VERSION(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_VERSION)
+#define DUK_HEAP_STRING_ENV(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENV)
+#define DUK_HTHREAD_STRING_ENV(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENV)
+#define DUK_HEAP_STRING_MOD_LOADED(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MOD_LOADED)
+#define DUK_HTHREAD_STRING_MOD_LOADED(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MOD_LOADED)
+#define DUK_HEAP_STRING_MOD_SEARCH(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MOD_SEARCH)
+#define DUK_HTHREAD_STRING_MOD_SEARCH(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MOD_SEARCH)
+#define DUK_HEAP_STRING_ERR_THROW(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ERR_THROW)
+#define DUK_HTHREAD_STRING_ERR_THROW(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ERR_THROW)
+#define DUK_HEAP_STRING_ERR_CREATE(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ERR_CREATE)
+#define DUK_HTHREAD_STRING_ERR_CREATE(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ERR_CREATE)
+#define DUK_HEAP_STRING_COMPILE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_COMPILE)
+#define DUK_HTHREAD_STRING_COMPILE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_COMPILE)
+#define DUK_HEAP_STRING_INT_REGBASE(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_REGBASE)
+#define DUK_HTHREAD_STRING_INT_REGBASE(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_REGBASE)
+#define DUK_HEAP_STRING_INT_THREAD(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_THREAD)
+#define DUK_HTHREAD_STRING_INT_THREAD(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_THREAD)
+#define DUK_HEAP_STRING_INT_HANDLER(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_HANDLER)
+#define DUK_HTHREAD_STRING_INT_HANDLER(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_HANDLER)
+#define DUK_HEAP_STRING_INT_FINALIZER(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_FINALIZER)
+#define DUK_HTHREAD_STRING_INT_FINALIZER(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_FINALIZER)
+#define DUK_HEAP_STRING_INT_CALLEE(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_CALLEE)
+#define DUK_HTHREAD_STRING_INT_CALLEE(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_CALLEE)
+#define DUK_HEAP_STRING_INT_MAP(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_MAP)
+#define DUK_HTHREAD_STRING_INT_MAP(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_MAP)
+#define DUK_HEAP_STRING_INT_ARGS(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_ARGS)
+#define DUK_HTHREAD_STRING_INT_ARGS(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_ARGS)
+#define DUK_HEAP_STRING_INT_THIS(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_THIS)
+#define DUK_HTHREAD_STRING_INT_THIS(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_THIS)
+#define DUK_HEAP_STRING_INT_PC2LINE(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_PC2LINE)
+#define DUK_HTHREAD_STRING_INT_PC2LINE(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_PC2LINE)
+#define DUK_HEAP_STRING_INT_SOURCE(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_SOURCE)
+#define DUK_HTHREAD_STRING_INT_SOURCE(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_SOURCE)
+#define DUK_HEAP_STRING_INT_VARENV(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_VARENV)
+#define DUK_HTHREAD_STRING_INT_VARENV(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_VARENV)
+#define DUK_HEAP_STRING_INT_LEXENV(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_LEXENV)
+#define DUK_HTHREAD_STRING_INT_LEXENV(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_LEXENV)
+#define DUK_HEAP_STRING_INT_VARMAP(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_VARMAP)
+#define DUK_HTHREAD_STRING_INT_VARMAP(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_VARMAP)
+#define DUK_HEAP_STRING_INT_FORMALS(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_FORMALS)
+#define DUK_HTHREAD_STRING_INT_FORMALS(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_FORMALS)
+#define DUK_HEAP_STRING_INT_BYTECODE(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_BYTECODE)
+#define DUK_HTHREAD_STRING_INT_BYTECODE(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_BYTECODE)
+#define DUK_HEAP_STRING_INT_NEXT(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_NEXT)
+#define DUK_HTHREAD_STRING_INT_NEXT(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_NEXT)
+#define DUK_HEAP_STRING_INT_TARGET(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_TARGET)
+#define DUK_HTHREAD_STRING_INT_TARGET(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_TARGET)
+#define DUK_HEAP_STRING_INT_VALUE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_VALUE)
+#define DUK_HTHREAD_STRING_INT_VALUE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_VALUE)
+#define DUK_HEAP_STRING_LC_POINTER(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_POINTER)
+#define DUK_HTHREAD_STRING_LC_POINTER(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_POINTER)
+#define DUK_HEAP_STRING_LC_BUFFER(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_BUFFER)
+#define DUK_HTHREAD_STRING_LC_BUFFER(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_BUFFER)
+#define DUK_HEAP_STRING_TRACEDATA(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TRACEDATA)
+#define DUK_HTHREAD_STRING_TRACEDATA(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TRACEDATA)
+#define DUK_HEAP_STRING_LINE_NUMBER(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LINE_NUMBER)
+#define DUK_HTHREAD_STRING_LINE_NUMBER(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LINE_NUMBER)
+#define DUK_HEAP_STRING_FILE_NAME(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FILE_NAME)
+#define DUK_HTHREAD_STRING_FILE_NAME(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FILE_NAME)
+#define DUK_HEAP_STRING_PC(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PC)
+#define DUK_HTHREAD_STRING_PC(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PC)
+#define DUK_HEAP_STRING_STACK(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_STACK)
+#define DUK_HTHREAD_STRING_STACK(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_STACK)
+#define DUK_HEAP_STRING_THROW_TYPE_ERROR(heap)                        DUK_HEAP_GET_STRING((heap),DUK_STRIDX_THROW_TYPE_ERROR)
+#define DUK_HTHREAD_STRING_THROW_TYPE_ERROR(thr)                      DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_THROW_TYPE_ERROR)
+#define DUK_HEAP_STRING_DUKTAPE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DUKTAPE)
+#define DUK_HTHREAD_STRING_DUKTAPE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DUKTAPE)
+#define DUK_HEAP_STRING_ID(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ID)
+#define DUK_HTHREAD_STRING_ID(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ID)
+#define DUK_HEAP_STRING_REQUIRE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REQUIRE)
+#define DUK_HTHREAD_STRING_REQUIRE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REQUIRE)
+#define DUK_HEAP_STRING___PROTO__(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX___PROTO__)
+#define DUK_HTHREAD_STRING___PROTO__(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX___PROTO__)
+#define DUK_HEAP_STRING_SET_PROTOTYPE_OF(heap)                        DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_PROTOTYPE_OF)
+#define DUK_HTHREAD_STRING_SET_PROTOTYPE_OF(thr)                      DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_PROTOTYPE_OF)
+#define DUK_HEAP_STRING_OWN_KEYS(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_OWN_KEYS)
+#define DUK_HTHREAD_STRING_OWN_KEYS(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_OWN_KEYS)
+#define DUK_HEAP_STRING_ENUMERATE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENUMERATE)
+#define DUK_HTHREAD_STRING_ENUMERATE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENUMERATE)
+#define DUK_HEAP_STRING_DELETE_PROPERTY(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DELETE_PROPERTY)
+#define DUK_HTHREAD_STRING_DELETE_PROPERTY(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DELETE_PROPERTY)
+#define DUK_HEAP_STRING_HAS(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_HAS)
+#define DUK_HTHREAD_STRING_HAS(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_HAS)
+#define DUK_HEAP_STRING_PROXY(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PROXY)
+#define DUK_HTHREAD_STRING_PROXY(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PROXY)
+#define DUK_HEAP_STRING_CALLEE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CALLEE)
+#define DUK_HTHREAD_STRING_CALLEE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CALLEE)
+#define DUK_HEAP_STRING_INVALID_DATE(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INVALID_DATE)
+#define DUK_HTHREAD_STRING_INVALID_DATE(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INVALID_DATE)
+#define DUK_HEAP_STRING_BRACKETED_ELLIPSIS(heap)                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_BRACKETED_ELLIPSIS)
+#define DUK_HTHREAD_STRING_BRACKETED_ELLIPSIS(thr)                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_BRACKETED_ELLIPSIS)
+#define DUK_HEAP_STRING_NEWLINE_TAB(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NEWLINE_TAB)
+#define DUK_HTHREAD_STRING_NEWLINE_TAB(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NEWLINE_TAB)
+#define DUK_HEAP_STRING_SPACE(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SPACE)
+#define DUK_HTHREAD_STRING_SPACE(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SPACE)
+#define DUK_HEAP_STRING_COMMA(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_COMMA)
+#define DUK_HTHREAD_STRING_COMMA(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_COMMA)
+#define DUK_HEAP_STRING_MINUS_ZERO(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MINUS_ZERO)
+#define DUK_HTHREAD_STRING_MINUS_ZERO(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MINUS_ZERO)
+#define DUK_HEAP_STRING_PLUS_ZERO(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PLUS_ZERO)
+#define DUK_HTHREAD_STRING_PLUS_ZERO(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PLUS_ZERO)
+#define DUK_HEAP_STRING_ZERO(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ZERO)
+#define DUK_HTHREAD_STRING_ZERO(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ZERO)
+#define DUK_HEAP_STRING_MINUS_INFINITY(heap)                          DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MINUS_INFINITY)
+#define DUK_HTHREAD_STRING_MINUS_INFINITY(thr)                        DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MINUS_INFINITY)
+#define DUK_HEAP_STRING_PLUS_INFINITY(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PLUS_INFINITY)
+#define DUK_HTHREAD_STRING_PLUS_INFINITY(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PLUS_INFINITY)
+#define DUK_HEAP_STRING_INFINITY(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INFINITY)
+#define DUK_HTHREAD_STRING_INFINITY(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INFINITY)
+#define DUK_HEAP_STRING_LC_OBJECT(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_OBJECT)
+#define DUK_HTHREAD_STRING_LC_OBJECT(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_OBJECT)
+#define DUK_HEAP_STRING_LC_STRING(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_STRING)
+#define DUK_HTHREAD_STRING_LC_STRING(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_STRING)
+#define DUK_HEAP_STRING_LC_NUMBER(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_NUMBER)
+#define DUK_HTHREAD_STRING_LC_NUMBER(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_NUMBER)
+#define DUK_HEAP_STRING_LC_BOOLEAN(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_BOOLEAN)
+#define DUK_HTHREAD_STRING_LC_BOOLEAN(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_BOOLEAN)
+#define DUK_HEAP_STRING_LC_UNDEFINED(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_UNDEFINED)
+#define DUK_HTHREAD_STRING_LC_UNDEFINED(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_UNDEFINED)
+#define DUK_HEAP_STRING_STRINGIFY(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_STRINGIFY)
+#define DUK_HTHREAD_STRING_STRINGIFY(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_STRINGIFY)
+#define DUK_HEAP_STRING_TAN(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TAN)
+#define DUK_HTHREAD_STRING_TAN(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TAN)
+#define DUK_HEAP_STRING_SQRT(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SQRT)
+#define DUK_HTHREAD_STRING_SQRT(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SQRT)
+#define DUK_HEAP_STRING_SIN(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SIN)
+#define DUK_HTHREAD_STRING_SIN(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SIN)
+#define DUK_HEAP_STRING_ROUND(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ROUND)
+#define DUK_HTHREAD_STRING_ROUND(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ROUND)
+#define DUK_HEAP_STRING_RANDOM(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_RANDOM)
+#define DUK_HTHREAD_STRING_RANDOM(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_RANDOM)
+#define DUK_HEAP_STRING_POW(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_POW)
+#define DUK_HTHREAD_STRING_POW(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_POW)
+#define DUK_HEAP_STRING_MIN(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MIN)
+#define DUK_HTHREAD_STRING_MIN(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MIN)
+#define DUK_HEAP_STRING_MAX(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MAX)
+#define DUK_HTHREAD_STRING_MAX(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MAX)
+#define DUK_HEAP_STRING_LOG(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LOG)
+#define DUK_HTHREAD_STRING_LOG(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LOG)
+#define DUK_HEAP_STRING_FLOOR(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FLOOR)
+#define DUK_HTHREAD_STRING_FLOOR(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FLOOR)
+#define DUK_HEAP_STRING_EXP(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EXP)
+#define DUK_HTHREAD_STRING_EXP(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EXP)
+#define DUK_HEAP_STRING_COS(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_COS)
+#define DUK_HTHREAD_STRING_COS(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_COS)
+#define DUK_HEAP_STRING_CEIL(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CEIL)
+#define DUK_HTHREAD_STRING_CEIL(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CEIL)
+#define DUK_HEAP_STRING_ATAN2(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ATAN2)
+#define DUK_HTHREAD_STRING_ATAN2(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ATAN2)
+#define DUK_HEAP_STRING_ATAN(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ATAN)
+#define DUK_HTHREAD_STRING_ATAN(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ATAN)
+#define DUK_HEAP_STRING_ASIN(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ASIN)
+#define DUK_HTHREAD_STRING_ASIN(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ASIN)
+#define DUK_HEAP_STRING_ACOS(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ACOS)
+#define DUK_HTHREAD_STRING_ACOS(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ACOS)
+#define DUK_HEAP_STRING_ABS(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ABS)
+#define DUK_HTHREAD_STRING_ABS(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ABS)
+#define DUK_HEAP_STRING_SQRT2(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SQRT2)
+#define DUK_HTHREAD_STRING_SQRT2(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SQRT2)
+#define DUK_HEAP_STRING_SQRT1_2(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SQRT1_2)
+#define DUK_HTHREAD_STRING_SQRT1_2(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SQRT1_2)
+#define DUK_HEAP_STRING_PI(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PI)
+#define DUK_HTHREAD_STRING_PI(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PI)
+#define DUK_HEAP_STRING_LOG10E(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LOG10E)
+#define DUK_HTHREAD_STRING_LOG10E(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LOG10E)
+#define DUK_HEAP_STRING_LOG2E(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LOG2E)
+#define DUK_HTHREAD_STRING_LOG2E(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LOG2E)
+#define DUK_HEAP_STRING_LN2(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LN2)
+#define DUK_HTHREAD_STRING_LN2(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LN2)
+#define DUK_HEAP_STRING_LN10(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LN10)
+#define DUK_HTHREAD_STRING_LN10(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LN10)
+#define DUK_HEAP_STRING_E(heap)                                       DUK_HEAP_GET_STRING((heap),DUK_STRIDX_E)
+#define DUK_HTHREAD_STRING_E(thr)                                     DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_E)
+#define DUK_HEAP_STRING_MESSAGE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MESSAGE)
+#define DUK_HTHREAD_STRING_MESSAGE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MESSAGE)
+#define DUK_HEAP_STRING_NAME(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NAME)
+#define DUK_HTHREAD_STRING_NAME(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NAME)
+#define DUK_HEAP_STRING_INPUT(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INPUT)
+#define DUK_HTHREAD_STRING_INPUT(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INPUT)
+#define DUK_HEAP_STRING_INDEX(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INDEX)
+#define DUK_HTHREAD_STRING_INDEX(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INDEX)
+#define DUK_HEAP_STRING_ESCAPED_EMPTY_REGEXP(heap)                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ESCAPED_EMPTY_REGEXP)
+#define DUK_HTHREAD_STRING_ESCAPED_EMPTY_REGEXP(thr)                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ESCAPED_EMPTY_REGEXP)
+#define DUK_HEAP_STRING_LAST_INDEX(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LAST_INDEX)
+#define DUK_HTHREAD_STRING_LAST_INDEX(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LAST_INDEX)
+#define DUK_HEAP_STRING_MULTILINE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MULTILINE)
+#define DUK_HTHREAD_STRING_MULTILINE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MULTILINE)
+#define DUK_HEAP_STRING_IGNORE_CASE(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IGNORE_CASE)
+#define DUK_HTHREAD_STRING_IGNORE_CASE(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IGNORE_CASE)
+#define DUK_HEAP_STRING_SOURCE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SOURCE)
+#define DUK_HTHREAD_STRING_SOURCE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SOURCE)
+#define DUK_HEAP_STRING_TEST(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TEST)
+#define DUK_HTHREAD_STRING_TEST(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TEST)
+#define DUK_HEAP_STRING_EXEC(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EXEC)
+#define DUK_HTHREAD_STRING_EXEC(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EXEC)
+#define DUK_HEAP_STRING_TO_GMT_STRING(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_GMT_STRING)
+#define DUK_HTHREAD_STRING_TO_GMT_STRING(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_GMT_STRING)
+#define DUK_HEAP_STRING_SET_YEAR(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_YEAR)
+#define DUK_HTHREAD_STRING_SET_YEAR(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_YEAR)
+#define DUK_HEAP_STRING_GET_YEAR(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_YEAR)
+#define DUK_HTHREAD_STRING_GET_YEAR(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_YEAR)
+#define DUK_HEAP_STRING_TO_JSON(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_JSON)
+#define DUK_HTHREAD_STRING_TO_JSON(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_JSON)
+#define DUK_HEAP_STRING_TO_ISO_STRING(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_ISO_STRING)
+#define DUK_HTHREAD_STRING_TO_ISO_STRING(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_ISO_STRING)
+#define DUK_HEAP_STRING_TO_UTC_STRING(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_UTC_STRING)
+#define DUK_HTHREAD_STRING_TO_UTC_STRING(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_UTC_STRING)
+#define DUK_HEAP_STRING_SET_UTC_FULL_YEAR(heap)                       DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_UTC_FULL_YEAR)
+#define DUK_HTHREAD_STRING_SET_UTC_FULL_YEAR(thr)                     DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_UTC_FULL_YEAR)
+#define DUK_HEAP_STRING_SET_FULL_YEAR(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_FULL_YEAR)
+#define DUK_HTHREAD_STRING_SET_FULL_YEAR(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_FULL_YEAR)
+#define DUK_HEAP_STRING_SET_UTC_MONTH(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_UTC_MONTH)
+#define DUK_HTHREAD_STRING_SET_UTC_MONTH(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_UTC_MONTH)
+#define DUK_HEAP_STRING_SET_MONTH(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_MONTH)
+#define DUK_HTHREAD_STRING_SET_MONTH(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_MONTH)
+#define DUK_HEAP_STRING_SET_UTC_DATE(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_UTC_DATE)
+#define DUK_HTHREAD_STRING_SET_UTC_DATE(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_UTC_DATE)
+#define DUK_HEAP_STRING_SET_DATE(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_DATE)
+#define DUK_HTHREAD_STRING_SET_DATE(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_DATE)
+#define DUK_HEAP_STRING_SET_UTC_HOURS(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_UTC_HOURS)
+#define DUK_HTHREAD_STRING_SET_UTC_HOURS(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_UTC_HOURS)
+#define DUK_HEAP_STRING_SET_HOURS(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_HOURS)
+#define DUK_HTHREAD_STRING_SET_HOURS(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_HOURS)
+#define DUK_HEAP_STRING_SET_UTC_MINUTES(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_UTC_MINUTES)
+#define DUK_HTHREAD_STRING_SET_UTC_MINUTES(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_UTC_MINUTES)
+#define DUK_HEAP_STRING_SET_MINUTES(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_MINUTES)
+#define DUK_HTHREAD_STRING_SET_MINUTES(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_MINUTES)
+#define DUK_HEAP_STRING_SET_UTC_SECONDS(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_UTC_SECONDS)
+#define DUK_HTHREAD_STRING_SET_UTC_SECONDS(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_UTC_SECONDS)
+#define DUK_HEAP_STRING_SET_SECONDS(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_SECONDS)
+#define DUK_HTHREAD_STRING_SET_SECONDS(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_SECONDS)
+#define DUK_HEAP_STRING_SET_UTC_MILLISECONDS(heap)                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_UTC_MILLISECONDS)
+#define DUK_HTHREAD_STRING_SET_UTC_MILLISECONDS(thr)                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_UTC_MILLISECONDS)
+#define DUK_HEAP_STRING_SET_MILLISECONDS(heap)                        DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_MILLISECONDS)
+#define DUK_HTHREAD_STRING_SET_MILLISECONDS(thr)                      DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_MILLISECONDS)
+#define DUK_HEAP_STRING_SET_TIME(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_TIME)
+#define DUK_HTHREAD_STRING_SET_TIME(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_TIME)
+#define DUK_HEAP_STRING_GET_TIMEZONE_OFFSET(heap)                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_TIMEZONE_OFFSET)
+#define DUK_HTHREAD_STRING_GET_TIMEZONE_OFFSET(thr)                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_TIMEZONE_OFFSET)
+#define DUK_HEAP_STRING_GET_UTC_MILLISECONDS(heap)                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_MILLISECONDS)
+#define DUK_HTHREAD_STRING_GET_UTC_MILLISECONDS(thr)                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_MILLISECONDS)
+#define DUK_HEAP_STRING_GET_MILLISECONDS(heap)                        DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_MILLISECONDS)
+#define DUK_HTHREAD_STRING_GET_MILLISECONDS(thr)                      DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_MILLISECONDS)
+#define DUK_HEAP_STRING_GET_UTC_SECONDS(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_SECONDS)
+#define DUK_HTHREAD_STRING_GET_UTC_SECONDS(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_SECONDS)
+#define DUK_HEAP_STRING_GET_SECONDS(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_SECONDS)
+#define DUK_HTHREAD_STRING_GET_SECONDS(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_SECONDS)
+#define DUK_HEAP_STRING_GET_UTC_MINUTES(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_MINUTES)
+#define DUK_HTHREAD_STRING_GET_UTC_MINUTES(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_MINUTES)
+#define DUK_HEAP_STRING_GET_MINUTES(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_MINUTES)
+#define DUK_HTHREAD_STRING_GET_MINUTES(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_MINUTES)
+#define DUK_HEAP_STRING_GET_UTC_HOURS(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_HOURS)
+#define DUK_HTHREAD_STRING_GET_UTC_HOURS(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_HOURS)
+#define DUK_HEAP_STRING_GET_HOURS(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_HOURS)
+#define DUK_HTHREAD_STRING_GET_HOURS(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_HOURS)
+#define DUK_HEAP_STRING_GET_UTC_DAY(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_DAY)
+#define DUK_HTHREAD_STRING_GET_UTC_DAY(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_DAY)
+#define DUK_HEAP_STRING_GET_DAY(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_DAY)
+#define DUK_HTHREAD_STRING_GET_DAY(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_DAY)
+#define DUK_HEAP_STRING_GET_UTC_DATE(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_DATE)
+#define DUK_HTHREAD_STRING_GET_UTC_DATE(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_DATE)
+#define DUK_HEAP_STRING_GET_DATE(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_DATE)
+#define DUK_HTHREAD_STRING_GET_DATE(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_DATE)
+#define DUK_HEAP_STRING_GET_UTC_MONTH(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_MONTH)
+#define DUK_HTHREAD_STRING_GET_UTC_MONTH(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_MONTH)
+#define DUK_HEAP_STRING_GET_MONTH(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_MONTH)
+#define DUK_HTHREAD_STRING_GET_MONTH(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_MONTH)
+#define DUK_HEAP_STRING_GET_UTC_FULL_YEAR(heap)                       DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_FULL_YEAR)
+#define DUK_HTHREAD_STRING_GET_UTC_FULL_YEAR(thr)                     DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_FULL_YEAR)
+#define DUK_HEAP_STRING_GET_FULL_YEAR(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_FULL_YEAR)
+#define DUK_HTHREAD_STRING_GET_FULL_YEAR(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_FULL_YEAR)
+#define DUK_HEAP_STRING_GET_TIME(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_TIME)
+#define DUK_HTHREAD_STRING_GET_TIME(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_TIME)
+#define DUK_HEAP_STRING_TO_LOCALE_TIME_STRING(heap)                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_LOCALE_TIME_STRING)
+#define DUK_HTHREAD_STRING_TO_LOCALE_TIME_STRING(thr)                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_LOCALE_TIME_STRING)
+#define DUK_HEAP_STRING_TO_LOCALE_DATE_STRING(heap)                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_LOCALE_DATE_STRING)
+#define DUK_HTHREAD_STRING_TO_LOCALE_DATE_STRING(thr)                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_LOCALE_DATE_STRING)
+#define DUK_HEAP_STRING_TO_TIME_STRING(heap)                          DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_TIME_STRING)
+#define DUK_HTHREAD_STRING_TO_TIME_STRING(thr)                        DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_TIME_STRING)
+#define DUK_HEAP_STRING_TO_DATE_STRING(heap)                          DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_DATE_STRING)
+#define DUK_HTHREAD_STRING_TO_DATE_STRING(thr)                        DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_DATE_STRING)
+#define DUK_HEAP_STRING_NOW(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NOW)
+#define DUK_HTHREAD_STRING_NOW(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NOW)
+#define DUK_HEAP_STRING_UTC(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UTC)
+#define DUK_HTHREAD_STRING_UTC(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UTC)
+#define DUK_HEAP_STRING_PARSE(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PARSE)
+#define DUK_HTHREAD_STRING_PARSE(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PARSE)
+#define DUK_HEAP_STRING_TO_PRECISION(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_PRECISION)
+#define DUK_HTHREAD_STRING_TO_PRECISION(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_PRECISION)
+#define DUK_HEAP_STRING_TO_EXPONENTIAL(heap)                          DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_EXPONENTIAL)
+#define DUK_HTHREAD_STRING_TO_EXPONENTIAL(thr)                        DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_EXPONENTIAL)
+#define DUK_HEAP_STRING_TO_FIXED(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_FIXED)
+#define DUK_HTHREAD_STRING_TO_FIXED(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_FIXED)
+#define DUK_HEAP_STRING_POSITIVE_INFINITY(heap)                       DUK_HEAP_GET_STRING((heap),DUK_STRIDX_POSITIVE_INFINITY)
+#define DUK_HTHREAD_STRING_POSITIVE_INFINITY(thr)                     DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_POSITIVE_INFINITY)
+#define DUK_HEAP_STRING_NEGATIVE_INFINITY(heap)                       DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NEGATIVE_INFINITY)
+#define DUK_HTHREAD_STRING_NEGATIVE_INFINITY(thr)                     DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NEGATIVE_INFINITY)
+#define DUK_HEAP_STRING_NAN(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NAN)
+#define DUK_HTHREAD_STRING_NAN(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NAN)
+#define DUK_HEAP_STRING_MIN_VALUE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MIN_VALUE)
+#define DUK_HTHREAD_STRING_MIN_VALUE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MIN_VALUE)
+#define DUK_HEAP_STRING_MAX_VALUE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MAX_VALUE)
+#define DUK_HTHREAD_STRING_MAX_VALUE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MAX_VALUE)
+#define DUK_HEAP_STRING_SUBSTR(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SUBSTR)
+#define DUK_HTHREAD_STRING_SUBSTR(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SUBSTR)
+#define DUK_HEAP_STRING_TRIM(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TRIM)
+#define DUK_HTHREAD_STRING_TRIM(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TRIM)
+#define DUK_HEAP_STRING_TO_LOCALE_UPPER_CASE(heap)                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_LOCALE_UPPER_CASE)
+#define DUK_HTHREAD_STRING_TO_LOCALE_UPPER_CASE(thr)                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_LOCALE_UPPER_CASE)
+#define DUK_HEAP_STRING_TO_UPPER_CASE(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_UPPER_CASE)
+#define DUK_HTHREAD_STRING_TO_UPPER_CASE(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_UPPER_CASE)
+#define DUK_HEAP_STRING_TO_LOCALE_LOWER_CASE(heap)                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_LOCALE_LOWER_CASE)
+#define DUK_HTHREAD_STRING_TO_LOCALE_LOWER_CASE(thr)                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_LOCALE_LOWER_CASE)
+#define DUK_HEAP_STRING_TO_LOWER_CASE(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_LOWER_CASE)
+#define DUK_HTHREAD_STRING_TO_LOWER_CASE(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_LOWER_CASE)
+#define DUK_HEAP_STRING_SUBSTRING(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SUBSTRING)
+#define DUK_HTHREAD_STRING_SUBSTRING(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SUBSTRING)
+#define DUK_HEAP_STRING_SPLIT(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SPLIT)
+#define DUK_HTHREAD_STRING_SPLIT(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SPLIT)
+#define DUK_HEAP_STRING_SEARCH(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SEARCH)
+#define DUK_HTHREAD_STRING_SEARCH(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SEARCH)
+#define DUK_HEAP_STRING_REPLACE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REPLACE)
+#define DUK_HTHREAD_STRING_REPLACE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REPLACE)
+#define DUK_HEAP_STRING_MATCH(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MATCH)
+#define DUK_HTHREAD_STRING_MATCH(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MATCH)
+#define DUK_HEAP_STRING_LOCALE_COMPARE(heap)                          DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LOCALE_COMPARE)
+#define DUK_HTHREAD_STRING_LOCALE_COMPARE(thr)                        DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LOCALE_COMPARE)
+#define DUK_HEAP_STRING_CHAR_CODE_AT(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CHAR_CODE_AT)
+#define DUK_HTHREAD_STRING_CHAR_CODE_AT(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CHAR_CODE_AT)
+#define DUK_HEAP_STRING_CHAR_AT(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CHAR_AT)
+#define DUK_HTHREAD_STRING_CHAR_AT(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CHAR_AT)
+#define DUK_HEAP_STRING_FROM_CHAR_CODE(heap)                          DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FROM_CHAR_CODE)
+#define DUK_HTHREAD_STRING_FROM_CHAR_CODE(thr)                        DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FROM_CHAR_CODE)
+#define DUK_HEAP_STRING_REDUCE_RIGHT(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REDUCE_RIGHT)
+#define DUK_HTHREAD_STRING_REDUCE_RIGHT(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REDUCE_RIGHT)
+#define DUK_HEAP_STRING_REDUCE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REDUCE)
+#define DUK_HTHREAD_STRING_REDUCE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REDUCE)
+#define DUK_HEAP_STRING_FILTER(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FILTER)
+#define DUK_HTHREAD_STRING_FILTER(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FILTER)
+#define DUK_HEAP_STRING_MAP(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MAP)
+#define DUK_HTHREAD_STRING_MAP(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MAP)
+#define DUK_HEAP_STRING_FOR_EACH(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FOR_EACH)
+#define DUK_HTHREAD_STRING_FOR_EACH(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FOR_EACH)
+#define DUK_HEAP_STRING_SOME(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SOME)
+#define DUK_HTHREAD_STRING_SOME(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SOME)
+#define DUK_HEAP_STRING_EVERY(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EVERY)
+#define DUK_HTHREAD_STRING_EVERY(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EVERY)
+#define DUK_HEAP_STRING_LAST_INDEX_OF(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LAST_INDEX_OF)
+#define DUK_HTHREAD_STRING_LAST_INDEX_OF(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LAST_INDEX_OF)
+#define DUK_HEAP_STRING_INDEX_OF(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INDEX_OF)
+#define DUK_HTHREAD_STRING_INDEX_OF(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INDEX_OF)
+#define DUK_HEAP_STRING_UNSHIFT(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UNSHIFT)
+#define DUK_HTHREAD_STRING_UNSHIFT(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UNSHIFT)
+#define DUK_HEAP_STRING_SPLICE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SPLICE)
+#define DUK_HTHREAD_STRING_SPLICE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SPLICE)
+#define DUK_HEAP_STRING_SORT(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SORT)
+#define DUK_HTHREAD_STRING_SORT(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SORT)
+#define DUK_HEAP_STRING_SLICE(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SLICE)
+#define DUK_HTHREAD_STRING_SLICE(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SLICE)
+#define DUK_HEAP_STRING_SHIFT(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SHIFT)
+#define DUK_HTHREAD_STRING_SHIFT(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SHIFT)
+#define DUK_HEAP_STRING_REVERSE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REVERSE)
+#define DUK_HTHREAD_STRING_REVERSE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REVERSE)
+#define DUK_HEAP_STRING_PUSH(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PUSH)
+#define DUK_HTHREAD_STRING_PUSH(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PUSH)
+#define DUK_HEAP_STRING_POP(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_POP)
+#define DUK_HTHREAD_STRING_POP(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_POP)
+#define DUK_HEAP_STRING_JOIN(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JOIN)
+#define DUK_HTHREAD_STRING_JOIN(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JOIN)
+#define DUK_HEAP_STRING_CONCAT(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CONCAT)
+#define DUK_HTHREAD_STRING_CONCAT(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CONCAT)
+#define DUK_HEAP_STRING_IS_ARRAY(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_ARRAY)
+#define DUK_HTHREAD_STRING_IS_ARRAY(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_ARRAY)
+#define DUK_HEAP_STRING_LC_ARGUMENTS(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_ARGUMENTS)
+#define DUK_HTHREAD_STRING_LC_ARGUMENTS(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_ARGUMENTS)
+#define DUK_HEAP_STRING_CALLER(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CALLER)
+#define DUK_HTHREAD_STRING_CALLER(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CALLER)
+#define DUK_HEAP_STRING_BIND(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_BIND)
+#define DUK_HTHREAD_STRING_BIND(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_BIND)
+#define DUK_HEAP_STRING_CALL(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CALL)
+#define DUK_HTHREAD_STRING_CALL(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CALL)
+#define DUK_HEAP_STRING_APPLY(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_APPLY)
+#define DUK_HTHREAD_STRING_APPLY(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_APPLY)
+#define DUK_HEAP_STRING_PROPERTY_IS_ENUMERABLE(heap)                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PROPERTY_IS_ENUMERABLE)
+#define DUK_HTHREAD_STRING_PROPERTY_IS_ENUMERABLE(thr)                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PROPERTY_IS_ENUMERABLE)
+#define DUK_HEAP_STRING_IS_PROTOTYPE_OF(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_PROTOTYPE_OF)
+#define DUK_HTHREAD_STRING_IS_PROTOTYPE_OF(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_PROTOTYPE_OF)
+#define DUK_HEAP_STRING_HAS_OWN_PROPERTY(heap)                        DUK_HEAP_GET_STRING((heap),DUK_STRIDX_HAS_OWN_PROPERTY)
+#define DUK_HTHREAD_STRING_HAS_OWN_PROPERTY(thr)                      DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_HAS_OWN_PROPERTY)
+#define DUK_HEAP_STRING_VALUE_OF(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_VALUE_OF)
+#define DUK_HTHREAD_STRING_VALUE_OF(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_VALUE_OF)
+#define DUK_HEAP_STRING_TO_LOCALE_STRING(heap)                        DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_LOCALE_STRING)
+#define DUK_HTHREAD_STRING_TO_LOCALE_STRING(thr)                      DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_LOCALE_STRING)
+#define DUK_HEAP_STRING_TO_STRING(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_STRING)
+#define DUK_HTHREAD_STRING_TO_STRING(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_STRING)
+#define DUK_HEAP_STRING_CONSTRUCTOR(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CONSTRUCTOR)
+#define DUK_HTHREAD_STRING_CONSTRUCTOR(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CONSTRUCTOR)
+#define DUK_HEAP_STRING_SET(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET)
+#define DUK_HTHREAD_STRING_SET(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET)
+#define DUK_HEAP_STRING_GET(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET)
+#define DUK_HTHREAD_STRING_GET(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET)
+#define DUK_HEAP_STRING_ENUMERABLE(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENUMERABLE)
+#define DUK_HTHREAD_STRING_ENUMERABLE(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENUMERABLE)
+#define DUK_HEAP_STRING_CONFIGURABLE(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CONFIGURABLE)
+#define DUK_HTHREAD_STRING_CONFIGURABLE(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CONFIGURABLE)
+#define DUK_HEAP_STRING_WRITABLE(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_WRITABLE)
+#define DUK_HTHREAD_STRING_WRITABLE(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_WRITABLE)
+#define DUK_HEAP_STRING_VALUE(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_VALUE)
+#define DUK_HTHREAD_STRING_VALUE(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_VALUE)
+#define DUK_HEAP_STRING_KEYS(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_KEYS)
+#define DUK_HTHREAD_STRING_KEYS(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_KEYS)
+#define DUK_HEAP_STRING_IS_EXTENSIBLE(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_EXTENSIBLE)
+#define DUK_HTHREAD_STRING_IS_EXTENSIBLE(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_EXTENSIBLE)
+#define DUK_HEAP_STRING_IS_FROZEN(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_FROZEN)
+#define DUK_HTHREAD_STRING_IS_FROZEN(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_FROZEN)
+#define DUK_HEAP_STRING_IS_SEALED(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_SEALED)
+#define DUK_HTHREAD_STRING_IS_SEALED(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_SEALED)
+#define DUK_HEAP_STRING_PREVENT_EXTENSIONS(heap)                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PREVENT_EXTENSIONS)
+#define DUK_HTHREAD_STRING_PREVENT_EXTENSIONS(thr)                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PREVENT_EXTENSIONS)
+#define DUK_HEAP_STRING_FREEZE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FREEZE)
+#define DUK_HTHREAD_STRING_FREEZE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FREEZE)
+#define DUK_HEAP_STRING_SEAL(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SEAL)
+#define DUK_HTHREAD_STRING_SEAL(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SEAL)
+#define DUK_HEAP_STRING_DEFINE_PROPERTIES(heap)                       DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DEFINE_PROPERTIES)
+#define DUK_HTHREAD_STRING_DEFINE_PROPERTIES(thr)                     DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DEFINE_PROPERTIES)
+#define DUK_HEAP_STRING_DEFINE_PROPERTY(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DEFINE_PROPERTY)
+#define DUK_HTHREAD_STRING_DEFINE_PROPERTY(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DEFINE_PROPERTY)
+#define DUK_HEAP_STRING_CREATE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CREATE)
+#define DUK_HTHREAD_STRING_CREATE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CREATE)
+#define DUK_HEAP_STRING_GET_OWN_PROPERTY_NAMES(heap)                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_OWN_PROPERTY_NAMES)
+#define DUK_HTHREAD_STRING_GET_OWN_PROPERTY_NAMES(thr)                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_OWN_PROPERTY_NAMES)
+#define DUK_HEAP_STRING_GET_OWN_PROPERTY_DESCRIPTOR(heap)             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_OWN_PROPERTY_DESCRIPTOR)
+#define DUK_HTHREAD_STRING_GET_OWN_PROPERTY_DESCRIPTOR(thr)           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_OWN_PROPERTY_DESCRIPTOR)
+#define DUK_HEAP_STRING_GET_PROTOTYPE_OF(heap)                        DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_PROTOTYPE_OF)
+#define DUK_HTHREAD_STRING_GET_PROTOTYPE_OF(thr)                      DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_PROTOTYPE_OF)
+#define DUK_HEAP_STRING_PROTOTYPE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PROTOTYPE)
+#define DUK_HTHREAD_STRING_PROTOTYPE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PROTOTYPE)
+#define DUK_HEAP_STRING_LENGTH(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LENGTH)
+#define DUK_HTHREAD_STRING_LENGTH(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LENGTH)
+#define DUK_HEAP_STRING_ALERT(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ALERT)
+#define DUK_HTHREAD_STRING_ALERT(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ALERT)
+#define DUK_HEAP_STRING_PRINT(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PRINT)
+#define DUK_HTHREAD_STRING_PRINT(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PRINT)
+#define DUK_HEAP_STRING_UNESCAPE(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UNESCAPE)
+#define DUK_HTHREAD_STRING_UNESCAPE(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UNESCAPE)
+#define DUK_HEAP_STRING_ESCAPE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ESCAPE)
+#define DUK_HTHREAD_STRING_ESCAPE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ESCAPE)
+#define DUK_HEAP_STRING_ENCODE_URI_COMPONENT(heap)                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENCODE_URI_COMPONENT)
+#define DUK_HTHREAD_STRING_ENCODE_URI_COMPONENT(thr)                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENCODE_URI_COMPONENT)
+#define DUK_HEAP_STRING_ENCODE_URI(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENCODE_URI)
+#define DUK_HTHREAD_STRING_ENCODE_URI(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENCODE_URI)
+#define DUK_HEAP_STRING_DECODE_URI_COMPONENT(heap)                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DECODE_URI_COMPONENT)
+#define DUK_HTHREAD_STRING_DECODE_URI_COMPONENT(thr)                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DECODE_URI_COMPONENT)
+#define DUK_HEAP_STRING_DECODE_URI(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DECODE_URI)
+#define DUK_HTHREAD_STRING_DECODE_URI(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DECODE_URI)
+#define DUK_HEAP_STRING_IS_FINITE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_FINITE)
+#define DUK_HTHREAD_STRING_IS_FINITE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_FINITE)
+#define DUK_HEAP_STRING_IS_NAN(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_NAN)
+#define DUK_HTHREAD_STRING_IS_NAN(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_NAN)
+#define DUK_HEAP_STRING_PARSE_FLOAT(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PARSE_FLOAT)
+#define DUK_HTHREAD_STRING_PARSE_FLOAT(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PARSE_FLOAT)
+#define DUK_HEAP_STRING_PARSE_INT(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PARSE_INT)
+#define DUK_HTHREAD_STRING_PARSE_INT(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PARSE_INT)
+#define DUK_HEAP_STRING_EVAL(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EVAL)
+#define DUK_HTHREAD_STRING_EVAL(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EVAL)
+#define DUK_HEAP_STRING_URI_ERROR(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_URI_ERROR)
+#define DUK_HTHREAD_STRING_URI_ERROR(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_URI_ERROR)
+#define DUK_HEAP_STRING_TYPE_ERROR(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TYPE_ERROR)
+#define DUK_HTHREAD_STRING_TYPE_ERROR(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TYPE_ERROR)
+#define DUK_HEAP_STRING_SYNTAX_ERROR(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SYNTAX_ERROR)
+#define DUK_HTHREAD_STRING_SYNTAX_ERROR(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SYNTAX_ERROR)
+#define DUK_HEAP_STRING_REFERENCE_ERROR(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REFERENCE_ERROR)
+#define DUK_HTHREAD_STRING_REFERENCE_ERROR(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REFERENCE_ERROR)
+#define DUK_HEAP_STRING_RANGE_ERROR(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_RANGE_ERROR)
+#define DUK_HTHREAD_STRING_RANGE_ERROR(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_RANGE_ERROR)
+#define DUK_HEAP_STRING_EVAL_ERROR(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EVAL_ERROR)
+#define DUK_HTHREAD_STRING_EVAL_ERROR(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EVAL_ERROR)
+#define DUK_HEAP_STRING_BREAK(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_BREAK)
+#define DUK_HTHREAD_STRING_BREAK(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_BREAK)
+#define DUK_HEAP_STRING_CASE(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CASE)
+#define DUK_HTHREAD_STRING_CASE(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CASE)
+#define DUK_HEAP_STRING_CATCH(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CATCH)
+#define DUK_HTHREAD_STRING_CATCH(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CATCH)
+#define DUK_HEAP_STRING_CONTINUE(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CONTINUE)
+#define DUK_HTHREAD_STRING_CONTINUE(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CONTINUE)
+#define DUK_HEAP_STRING_DEBUGGER(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DEBUGGER)
+#define DUK_HTHREAD_STRING_DEBUGGER(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DEBUGGER)
+#define DUK_HEAP_STRING_DEFAULT(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DEFAULT)
+#define DUK_HTHREAD_STRING_DEFAULT(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DEFAULT)
+#define DUK_HEAP_STRING_DELETE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DELETE)
+#define DUK_HTHREAD_STRING_DELETE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DELETE)
+#define DUK_HEAP_STRING_DO(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DO)
+#define DUK_HTHREAD_STRING_DO(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DO)
+#define DUK_HEAP_STRING_ELSE(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ELSE)
+#define DUK_HTHREAD_STRING_ELSE(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ELSE)
+#define DUK_HEAP_STRING_FINALLY(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FINALLY)
+#define DUK_HTHREAD_STRING_FINALLY(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FINALLY)
+#define DUK_HEAP_STRING_FOR(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FOR)
+#define DUK_HTHREAD_STRING_FOR(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FOR)
+#define DUK_HEAP_STRING_LC_FUNCTION(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_FUNCTION)
+#define DUK_HTHREAD_STRING_LC_FUNCTION(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_FUNCTION)
+#define DUK_HEAP_STRING_IF(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IF)
+#define DUK_HTHREAD_STRING_IF(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IF)
+#define DUK_HEAP_STRING_IN(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IN)
+#define DUK_HTHREAD_STRING_IN(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IN)
+#define DUK_HEAP_STRING_INSTANCEOF(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INSTANCEOF)
+#define DUK_HTHREAD_STRING_INSTANCEOF(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INSTANCEOF)
+#define DUK_HEAP_STRING_NEW(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NEW)
+#define DUK_HTHREAD_STRING_NEW(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NEW)
+#define DUK_HEAP_STRING_RETURN(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_RETURN)
+#define DUK_HTHREAD_STRING_RETURN(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_RETURN)
+#define DUK_HEAP_STRING_SWITCH(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SWITCH)
+#define DUK_HTHREAD_STRING_SWITCH(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SWITCH)
+#define DUK_HEAP_STRING_THIS(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_THIS)
+#define DUK_HTHREAD_STRING_THIS(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_THIS)
+#define DUK_HEAP_STRING_THROW(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_THROW)
+#define DUK_HTHREAD_STRING_THROW(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_THROW)
+#define DUK_HEAP_STRING_TRY(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TRY)
+#define DUK_HTHREAD_STRING_TRY(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TRY)
+#define DUK_HEAP_STRING_TYPEOF(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TYPEOF)
+#define DUK_HTHREAD_STRING_TYPEOF(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TYPEOF)
+#define DUK_HEAP_STRING_VAR(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_VAR)
+#define DUK_HTHREAD_STRING_VAR(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_VAR)
+#define DUK_HEAP_STRING_VOID(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_VOID)
+#define DUK_HTHREAD_STRING_VOID(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_VOID)
+#define DUK_HEAP_STRING_WHILE(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_WHILE)
+#define DUK_HTHREAD_STRING_WHILE(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_WHILE)
+#define DUK_HEAP_STRING_WITH(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_WITH)
+#define DUK_HTHREAD_STRING_WITH(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_WITH)
+#define DUK_HEAP_STRING_CLASS(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CLASS)
+#define DUK_HTHREAD_STRING_CLASS(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CLASS)
+#define DUK_HEAP_STRING_CONST(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CONST)
+#define DUK_HTHREAD_STRING_CONST(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CONST)
+#define DUK_HEAP_STRING_ENUM(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENUM)
+#define DUK_HTHREAD_STRING_ENUM(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENUM)
+#define DUK_HEAP_STRING_EXPORT(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EXPORT)
+#define DUK_HTHREAD_STRING_EXPORT(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EXPORT)
+#define DUK_HEAP_STRING_EXTENDS(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EXTENDS)
+#define DUK_HTHREAD_STRING_EXTENDS(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EXTENDS)
+#define DUK_HEAP_STRING_IMPORT(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IMPORT)
+#define DUK_HTHREAD_STRING_IMPORT(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IMPORT)
+#define DUK_HEAP_STRING_SUPER(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SUPER)
+#define DUK_HTHREAD_STRING_SUPER(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SUPER)
+#define DUK_HEAP_STRING_LC_NULL(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_NULL)
+#define DUK_HTHREAD_STRING_LC_NULL(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_NULL)
+#define DUK_HEAP_STRING_TRUE(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TRUE)
+#define DUK_HTHREAD_STRING_TRUE(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TRUE)
+#define DUK_HEAP_STRING_FALSE(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FALSE)
+#define DUK_HTHREAD_STRING_FALSE(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FALSE)
+#define DUK_HEAP_STRING_IMPLEMENTS(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IMPLEMENTS)
+#define DUK_HTHREAD_STRING_IMPLEMENTS(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IMPLEMENTS)
+#define DUK_HEAP_STRING_INTERFACE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INTERFACE)
+#define DUK_HTHREAD_STRING_INTERFACE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INTERFACE)
+#define DUK_HEAP_STRING_LET(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LET)
+#define DUK_HTHREAD_STRING_LET(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LET)
+#define DUK_HEAP_STRING_PACKAGE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PACKAGE)
+#define DUK_HTHREAD_STRING_PACKAGE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PACKAGE)
+#define DUK_HEAP_STRING_PRIVATE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PRIVATE)
+#define DUK_HTHREAD_STRING_PRIVATE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PRIVATE)
+#define DUK_HEAP_STRING_PROTECTED(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PROTECTED)
+#define DUK_HTHREAD_STRING_PROTECTED(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PROTECTED)
+#define DUK_HEAP_STRING_PUBLIC(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PUBLIC)
+#define DUK_HTHREAD_STRING_PUBLIC(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PUBLIC)
+#define DUK_HEAP_STRING_STATIC(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_STATIC)
+#define DUK_HTHREAD_STRING_STATIC(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_STATIC)
+#define DUK_HEAP_STRING_YIELD(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_YIELD)
+#define DUK_HTHREAD_STRING_YIELD(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_YIELD)
+
+#define DUK_HEAP_NUM_STRINGS                                          336
+
+#define DUK_STRIDX_START_RESERVED                                     291
+#define DUK_STRIDX_START_STRICT_RESERVED                              327
+#define DUK_STRIDX_END_RESERVED                                       336                            /* exclusive endpoint */
+
+extern const duk_c_function duk_bi_native_functions[];
+extern const duk_uint8_t duk_builtins_data[];
+#ifdef DUK_USE_BUILTIN_INITJS
+extern const duk_uint8_t duk_initjs_data[];
+#endif  /* DUK_USE_BUILTIN_INITJS */
+
+#define DUK_BUILTINS_DATA_LENGTH                                      1336
+#ifdef DUK_USE_BUILTIN_INITJS
+#define DUK_BUILTIN_INITJS_DATA_LENGTH                                187
+#endif  /* DUK_USE_BUILTIN_INITJS */
+
+#define DUK_BIDX_GLOBAL                                               0
+#define DUK_BIDX_GLOBAL_ENV                                           1
+#define DUK_BIDX_OBJECT_CONSTRUCTOR                                   2
+#define DUK_BIDX_OBJECT_PROTOTYPE                                     3
+#define DUK_BIDX_FUNCTION_CONSTRUCTOR                                 4
+#define DUK_BIDX_FUNCTION_PROTOTYPE                                   5
+#define DUK_BIDX_ARRAY_CONSTRUCTOR                                    6
+#define DUK_BIDX_ARRAY_PROTOTYPE                                      7
+#define DUK_BIDX_STRING_CONSTRUCTOR                                   8
+#define DUK_BIDX_STRING_PROTOTYPE                                     9
+#define DUK_BIDX_BOOLEAN_CONSTRUCTOR                                  10
+#define DUK_BIDX_BOOLEAN_PROTOTYPE                                    11
+#define DUK_BIDX_NUMBER_CONSTRUCTOR                                   12
+#define DUK_BIDX_NUMBER_PROTOTYPE                                     13
+#define DUK_BIDX_DATE_CONSTRUCTOR                                     14
+#define DUK_BIDX_DATE_PROTOTYPE                                       15
+#define DUK_BIDX_REGEXP_CONSTRUCTOR                                   16
+#define DUK_BIDX_REGEXP_PROTOTYPE                                     17
+#define DUK_BIDX_ERROR_CONSTRUCTOR                                    18
+#define DUK_BIDX_ERROR_PROTOTYPE                                      19
+#define DUK_BIDX_EVAL_ERROR_CONSTRUCTOR                               20
+#define DUK_BIDX_EVAL_ERROR_PROTOTYPE                                 21
+#define DUK_BIDX_RANGE_ERROR_CONSTRUCTOR                              22
+#define DUK_BIDX_RANGE_ERROR_PROTOTYPE                                23
+#define DUK_BIDX_REFERENCE_ERROR_CONSTRUCTOR                          24
+#define DUK_BIDX_REFERENCE_ERROR_PROTOTYPE                            25
+#define DUK_BIDX_SYNTAX_ERROR_CONSTRUCTOR                             26
+#define DUK_BIDX_SYNTAX_ERROR_PROTOTYPE                               27
+#define DUK_BIDX_TYPE_ERROR_CONSTRUCTOR                               28
+#define DUK_BIDX_TYPE_ERROR_PROTOTYPE                                 29
+#define DUK_BIDX_URI_ERROR_CONSTRUCTOR                                30
+#define DUK_BIDX_URI_ERROR_PROTOTYPE                                  31
+#define DUK_BIDX_MATH                                                 32
+#define DUK_BIDX_JSON                                                 33
+#define DUK_BIDX_TYPE_ERROR_THROWER                                   34
+#define DUK_BIDX_PROXY_CONSTRUCTOR                                    35
+#define DUK_BIDX_DUKTAPE                                              36
+#define DUK_BIDX_THREAD_CONSTRUCTOR                                   37
+#define DUK_BIDX_THREAD_PROTOTYPE                                     38
+#define DUK_BIDX_BUFFER_CONSTRUCTOR                                   39
+#define DUK_BIDX_BUFFER_PROTOTYPE                                     40
+#define DUK_BIDX_POINTER_CONSTRUCTOR                                  41
+#define DUK_BIDX_POINTER_PROTOTYPE                                    42
+#define DUK_BIDX_LOGGER_CONSTRUCTOR                                   43
+#define DUK_BIDX_LOGGER_PROTOTYPE                                     44
+#define DUK_BIDX_DOUBLE_ERROR                                         45
+
+#define DUK_NUM_BUILTINS                                              46
+
+#elif defined(DUK_USE_DOUBLE_ME)
+extern const duk_uint8_t duk_strings_data[];
+
+#define DUK_STRDATA_DATA_LENGTH                                       1931
+#define DUK_STRDATA_MAX_STRLEN                                        24
+
+#define DUK_STRIDX_UC_LOGGER                                          0                              /* 'Logger' */
+#define DUK_STRIDX_UC_THREAD                                          1                              /* 'Thread' */
+#define DUK_STRIDX_UC_POINTER                                         2                              /* 'Pointer' */
+#define DUK_STRIDX_UC_BUFFER                                          3                              /* 'Buffer' */
+#define DUK_STRIDX_DEC_ENV                                            4                              /* 'DecEnv' */
+#define DUK_STRIDX_OBJ_ENV                                            5                              /* 'ObjEnv' */
+#define DUK_STRIDX_EMPTY_STRING                                       6                              /* '' */
+#define DUK_STRIDX_GLOBAL                                             7                              /* 'global' */
+#define DUK_STRIDX_UC_ARGUMENTS                                       8                              /* 'Arguments' */
+#define DUK_STRIDX_JSON                                               9                              /* 'JSON' */
+#define DUK_STRIDX_MATH                                               10                             /* 'Math' */
+#define DUK_STRIDX_UC_ERROR                                           11                             /* 'Error' */
+#define DUK_STRIDX_REG_EXP                                            12                             /* 'RegExp' */
+#define DUK_STRIDX_DATE                                               13                             /* 'Date' */
+#define DUK_STRIDX_UC_NUMBER                                          14                             /* 'Number' */
+#define DUK_STRIDX_UC_BOOLEAN                                         15                             /* 'Boolean' */
+#define DUK_STRIDX_UC_STRING                                          16                             /* 'String' */
+#define DUK_STRIDX_ARRAY                                              17                             /* 'Array' */
+#define DUK_STRIDX_UC_FUNCTION                                        18                             /* 'Function' */
+#define DUK_STRIDX_UC_OBJECT                                          19                             /* 'Object' */
+#define DUK_STRIDX_UC_NULL                                            20                             /* 'Null' */
+#define DUK_STRIDX_UC_UNDEFINED                                       21                             /* 'Undefined' */
+#define DUK_STRIDX_JSON_EXT_FUNCTION2                                 22                             /* '{_func:true}' */
+#define DUK_STRIDX_JSON_EXT_FUNCTION1                                 23                             /* '{"_func":true}' */
+#define DUK_STRIDX_JSON_EXT_NEGINF                                    24                             /* '{"_ninf":true}' */
+#define DUK_STRIDX_JSON_EXT_POSINF                                    25                             /* '{"_inf":true}' */
+#define DUK_STRIDX_JSON_EXT_NAN                                       26                             /* '{"_nan":true}' */
+#define DUK_STRIDX_JSON_EXT_UNDEFINED                                 27                             /* '{"_undef":true}' */
+#define DUK_STRIDX_TO_LOG_STRING                                      28                             /* 'toLogString' */
+#define DUK_STRIDX_CLOG                                               29                             /* 'clog' */
+#define DUK_STRIDX_LC_L                                               30                             /* 'l' */
+#define DUK_STRIDX_LC_N                                               31                             /* 'n' */
+#define DUK_STRIDX_LC_FATAL                                           32                             /* 'fatal' */
+#define DUK_STRIDX_LC_ERROR                                           33                             /* 'error' */
+#define DUK_STRIDX_LC_WARN                                            34                             /* 'warn' */
+#define DUK_STRIDX_LC_DEBUG                                           35                             /* 'debug' */
+#define DUK_STRIDX_LC_TRACE                                           36                             /* 'trace' */
+#define DUK_STRIDX_RAW                                                37                             /* 'raw' */
+#define DUK_STRIDX_FMT                                                38                             /* 'fmt' */
+#define DUK_STRIDX_CURRENT                                            39                             /* 'current' */
+#define DUK_STRIDX_RESUME                                             40                             /* 'resume' */
+#define DUK_STRIDX_COMPACT                                            41                             /* 'compact' */
+#define DUK_STRIDX_JC                                                 42                             /* 'jc' */
+#define DUK_STRIDX_JX                                                 43                             /* 'jx' */
+#define DUK_STRIDX_BASE64                                             44                             /* 'base64' */
+#define DUK_STRIDX_HEX                                                45                             /* 'hex' */
+#define DUK_STRIDX_DEC                                                46                             /* 'dec' */
+#define DUK_STRIDX_ENC                                                47                             /* 'enc' */
+#define DUK_STRIDX_FIN                                                48                             /* 'fin' */
+#define DUK_STRIDX_GC                                                 49                             /* 'gc' */
+#define DUK_STRIDX_ACT                                                50                             /* 'act' */
+#define DUK_STRIDX_LC_INFO                                            51                             /* 'info' */
+#define DUK_STRIDX_VERSION                                            52                             /* 'version' */
+#define DUK_STRIDX_ENV                                                53                             /* 'env' */
+#define DUK_STRIDX_MOD_LOADED                                         54                             /* 'modLoaded' */
+#define DUK_STRIDX_MOD_SEARCH                                         55                             /* 'modSearch' */
+#define DUK_STRIDX_ERR_THROW                                          56                             /* 'errThrow' */
+#define DUK_STRIDX_ERR_CREATE                                         57                             /* 'errCreate' */
+#define DUK_STRIDX_COMPILE                                            58                             /* 'compile' */
+#define DUK_STRIDX_INT_REGBASE                                        59                             /* '\x00regbase' */
+#define DUK_STRIDX_INT_THREAD                                         60                             /* '\x00thread' */
+#define DUK_STRIDX_INT_HANDLER                                        61                             /* '\x00handler' */
+#define DUK_STRIDX_INT_FINALIZER                                      62                             /* '\x00finalizer' */
+#define DUK_STRIDX_INT_CALLEE                                         63                             /* '\x00callee' */
+#define DUK_STRIDX_INT_MAP                                            64                             /* '\x00map' */
+#define DUK_STRIDX_INT_ARGS                                           65                             /* '\x00args' */
+#define DUK_STRIDX_INT_THIS                                           66                             /* '\x00this' */
+#define DUK_STRIDX_INT_PC2LINE                                        67                             /* '\x00pc2line' */
+#define DUK_STRIDX_INT_SOURCE                                         68                             /* '\x00source' */
+#define DUK_STRIDX_INT_VARENV                                         69                             /* '\x00varenv' */
+#define DUK_STRIDX_INT_LEXENV                                         70                             /* '\x00lexenv' */
+#define DUK_STRIDX_INT_VARMAP                                         71                             /* '\x00varmap' */
+#define DUK_STRIDX_INT_FORMALS                                        72                             /* '\x00formals' */
+#define DUK_STRIDX_INT_BYTECODE                                       73                             /* '\x00bytecode' */
+#define DUK_STRIDX_INT_NEXT                                           74                             /* '\x00next' */
+#define DUK_STRIDX_INT_TARGET                                         75                             /* '\x00target' */
+#define DUK_STRIDX_INT_VALUE                                          76                             /* '\x00value' */
+#define DUK_STRIDX_LC_POINTER                                         77                             /* 'pointer' */
+#define DUK_STRIDX_LC_BUFFER                                          78                             /* 'buffer' */
+#define DUK_STRIDX_TRACEDATA                                          79                             /* 'tracedata' */
+#define DUK_STRIDX_LINE_NUMBER                                        80                             /* 'lineNumber' */
+#define DUK_STRIDX_FILE_NAME                                          81                             /* 'fileName' */
+#define DUK_STRIDX_PC                                                 82                             /* 'pc' */
+#define DUK_STRIDX_STACK                                              83                             /* 'stack' */
+#define DUK_STRIDX_THROW_TYPE_ERROR                                   84                             /* 'ThrowTypeError' */
+#define DUK_STRIDX_DUKTAPE                                            85                             /* 'Duktape' */
+#define DUK_STRIDX_ID                                                 86                             /* 'id' */
+#define DUK_STRIDX_REQUIRE                                            87                             /* 'require' */
+#define DUK_STRIDX___PROTO__                                          88                             /* '__proto__' */
+#define DUK_STRIDX_SET_PROTOTYPE_OF                                   89                             /* 'setPrototypeOf' */
+#define DUK_STRIDX_OWN_KEYS                                           90                             /* 'ownKeys' */
+#define DUK_STRIDX_ENUMERATE                                          91                             /* 'enumerate' */
+#define DUK_STRIDX_DELETE_PROPERTY                                    92                             /* 'deleteProperty' */
+#define DUK_STRIDX_HAS                                                93                             /* 'has' */
+#define DUK_STRIDX_PROXY                                              94                             /* 'Proxy' */
+#define DUK_STRIDX_CALLEE                                             95                             /* 'callee' */
+#define DUK_STRIDX_INVALID_DATE                                       96                             /* 'Invalid Date' */
+#define DUK_STRIDX_BRACKETED_ELLIPSIS                                 97                             /* '[...]' */
+#define DUK_STRIDX_NEWLINE_TAB                                        98                             /* '\n\t' */
+#define DUK_STRIDX_SPACE                                              99                             /* ' ' */
+#define DUK_STRIDX_COMMA                                              100                            /* ',' */
+#define DUK_STRIDX_MINUS_ZERO                                         101                            /* '-0' */
+#define DUK_STRIDX_PLUS_ZERO                                          102                            /* '+0' */
+#define DUK_STRIDX_ZERO                                               103                            /* '0' */
+#define DUK_STRIDX_MINUS_INFINITY                                     104                            /* '-Infinity' */
+#define DUK_STRIDX_PLUS_INFINITY                                      105                            /* '+Infinity' */
+#define DUK_STRIDX_INFINITY                                           106                            /* 'Infinity' */
+#define DUK_STRIDX_LC_OBJECT                                          107                            /* 'object' */
+#define DUK_STRIDX_LC_STRING                                          108                            /* 'string' */
+#define DUK_STRIDX_LC_NUMBER                                          109                            /* 'number' */
+#define DUK_STRIDX_LC_BOOLEAN                                         110                            /* 'boolean' */
+#define DUK_STRIDX_LC_UNDEFINED                                       111                            /* 'undefined' */
+#define DUK_STRIDX_STRINGIFY                                          112                            /* 'stringify' */
+#define DUK_STRIDX_TAN                                                113                            /* 'tan' */
+#define DUK_STRIDX_SQRT                                               114                            /* 'sqrt' */
+#define DUK_STRIDX_SIN                                                115                            /* 'sin' */
+#define DUK_STRIDX_ROUND                                              116                            /* 'round' */
+#define DUK_STRIDX_RANDOM                                             117                            /* 'random' */
+#define DUK_STRIDX_POW                                                118                            /* 'pow' */
+#define DUK_STRIDX_MIN                                                119                            /* 'min' */
+#define DUK_STRIDX_MAX                                                120                            /* 'max' */
+#define DUK_STRIDX_LOG                                                121                            /* 'log' */
+#define DUK_STRIDX_FLOOR                                              122                            /* 'floor' */
+#define DUK_STRIDX_EXP                                                123                            /* 'exp' */
+#define DUK_STRIDX_COS                                                124                            /* 'cos' */
+#define DUK_STRIDX_CEIL                                               125                            /* 'ceil' */
+#define DUK_STRIDX_ATAN2                                              126                            /* 'atan2' */
+#define DUK_STRIDX_ATAN                                               127                            /* 'atan' */
+#define DUK_STRIDX_ASIN                                               128                            /* 'asin' */
+#define DUK_STRIDX_ACOS                                               129                            /* 'acos' */
+#define DUK_STRIDX_ABS                                                130                            /* 'abs' */
+#define DUK_STRIDX_SQRT2                                              131                            /* 'SQRT2' */
+#define DUK_STRIDX_SQRT1_2                                            132                            /* 'SQRT1_2' */
+#define DUK_STRIDX_PI                                                 133                            /* 'PI' */
+#define DUK_STRIDX_LOG10E                                             134                            /* 'LOG10E' */
+#define DUK_STRIDX_LOG2E                                              135                            /* 'LOG2E' */
+#define DUK_STRIDX_LN2                                                136                            /* 'LN2' */
+#define DUK_STRIDX_LN10                                               137                            /* 'LN10' */
+#define DUK_STRIDX_E                                                  138                            /* 'E' */
+#define DUK_STRIDX_MESSAGE                                            139                            /* 'message' */
+#define DUK_STRIDX_NAME                                               140                            /* 'name' */
+#define DUK_STRIDX_INPUT                                              141                            /* 'input' */
+#define DUK_STRIDX_INDEX                                              142                            /* 'index' */
+#define DUK_STRIDX_ESCAPED_EMPTY_REGEXP                               143                            /* '(?:)' */
+#define DUK_STRIDX_LAST_INDEX                                         144                            /* 'lastIndex' */
+#define DUK_STRIDX_MULTILINE                                          145                            /* 'multiline' */
+#define DUK_STRIDX_IGNORE_CASE                                        146                            /* 'ignoreCase' */
+#define DUK_STRIDX_SOURCE                                             147                            /* 'source' */
+#define DUK_STRIDX_TEST                                               148                            /* 'test' */
+#define DUK_STRIDX_EXEC                                               149                            /* 'exec' */
+#define DUK_STRIDX_TO_GMT_STRING                                      150                            /* 'toGMTString' */
+#define DUK_STRIDX_SET_YEAR                                           151                            /* 'setYear' */
+#define DUK_STRIDX_GET_YEAR                                           152                            /* 'getYear' */
+#define DUK_STRIDX_TO_JSON                                            153                            /* 'toJSON' */
+#define DUK_STRIDX_TO_ISO_STRING                                      154                            /* 'toISOString' */
+#define DUK_STRIDX_TO_UTC_STRING                                      155                            /* 'toUTCString' */
+#define DUK_STRIDX_SET_UTC_FULL_YEAR                                  156                            /* 'setUTCFullYear' */
+#define DUK_STRIDX_SET_FULL_YEAR                                      157                            /* 'setFullYear' */
+#define DUK_STRIDX_SET_UTC_MONTH                                      158                            /* 'setUTCMonth' */
+#define DUK_STRIDX_SET_MONTH                                          159                            /* 'setMonth' */
+#define DUK_STRIDX_SET_UTC_DATE                                       160                            /* 'setUTCDate' */
+#define DUK_STRIDX_SET_DATE                                           161                            /* 'setDate' */
+#define DUK_STRIDX_SET_UTC_HOURS                                      162                            /* 'setUTCHours' */
+#define DUK_STRIDX_SET_HOURS                                          163                            /* 'setHours' */
+#define DUK_STRIDX_SET_UTC_MINUTES                                    164                            /* 'setUTCMinutes' */
+#define DUK_STRIDX_SET_MINUTES                                        165                            /* 'setMinutes' */
+#define DUK_STRIDX_SET_UTC_SECONDS                                    166                            /* 'setUTCSeconds' */
+#define DUK_STRIDX_SET_SECONDS                                        167                            /* 'setSeconds' */
+#define DUK_STRIDX_SET_UTC_MILLISECONDS                               168                            /* 'setUTCMilliseconds' */
+#define DUK_STRIDX_SET_MILLISECONDS                                   169                            /* 'setMilliseconds' */
+#define DUK_STRIDX_SET_TIME                                           170                            /* 'setTime' */
+#define DUK_STRIDX_GET_TIMEZONE_OFFSET                                171                            /* 'getTimezoneOffset' */
+#define DUK_STRIDX_GET_UTC_MILLISECONDS                               172                            /* 'getUTCMilliseconds' */
+#define DUK_STRIDX_GET_MILLISECONDS                                   173                            /* 'getMilliseconds' */
+#define DUK_STRIDX_GET_UTC_SECONDS                                    174                            /* 'getUTCSeconds' */
+#define DUK_STRIDX_GET_SECONDS                                        175                            /* 'getSeconds' */
+#define DUK_STRIDX_GET_UTC_MINUTES                                    176                            /* 'getUTCMinutes' */
+#define DUK_STRIDX_GET_MINUTES                                        177                            /* 'getMinutes' */
+#define DUK_STRIDX_GET_UTC_HOURS                                      178                            /* 'getUTCHours' */
+#define DUK_STRIDX_GET_HOURS                                          179                            /* 'getHours' */
+#define DUK_STRIDX_GET_UTC_DAY                                        180                            /* 'getUTCDay' */
+#define DUK_STRIDX_GET_DAY                                            181                            /* 'getDay' */
+#define DUK_STRIDX_GET_UTC_DATE                                       182                            /* 'getUTCDate' */
+#define DUK_STRIDX_GET_DATE                                           183                            /* 'getDate' */
+#define DUK_STRIDX_GET_UTC_MONTH                                      184                            /* 'getUTCMonth' */
+#define DUK_STRIDX_GET_MONTH                                          185                            /* 'getMonth' */
+#define DUK_STRIDX_GET_UTC_FULL_YEAR                                  186                            /* 'getUTCFullYear' */
+#define DUK_STRIDX_GET_FULL_YEAR                                      187                            /* 'getFullYear' */
+#define DUK_STRIDX_GET_TIME                                           188                            /* 'getTime' */
+#define DUK_STRIDX_TO_LOCALE_TIME_STRING                              189                            /* 'toLocaleTimeString' */
+#define DUK_STRIDX_TO_LOCALE_DATE_STRING                              190                            /* 'toLocaleDateString' */
+#define DUK_STRIDX_TO_TIME_STRING                                     191                            /* 'toTimeString' */
+#define DUK_STRIDX_TO_DATE_STRING                                     192                            /* 'toDateString' */
+#define DUK_STRIDX_NOW                                                193                            /* 'now' */
+#define DUK_STRIDX_UTC                                                194                            /* 'UTC' */
+#define DUK_STRIDX_PARSE                                              195                            /* 'parse' */
+#define DUK_STRIDX_TO_PRECISION                                       196                            /* 'toPrecision' */
+#define DUK_STRIDX_TO_EXPONENTIAL                                     197                            /* 'toExponential' */
+#define DUK_STRIDX_TO_FIXED                                           198                            /* 'toFixed' */
+#define DUK_STRIDX_POSITIVE_INFINITY                                  199                            /* 'POSITIVE_INFINITY' */
+#define DUK_STRIDX_NEGATIVE_INFINITY                                  200                            /* 'NEGATIVE_INFINITY' */
+#define DUK_STRIDX_NAN                                                201                            /* 'NaN' */
+#define DUK_STRIDX_MIN_VALUE                                          202                            /* 'MIN_VALUE' */
+#define DUK_STRIDX_MAX_VALUE                                          203                            /* 'MAX_VALUE' */
+#define DUK_STRIDX_SUBSTR                                             204                            /* 'substr' */
+#define DUK_STRIDX_TRIM                                               205                            /* 'trim' */
+#define DUK_STRIDX_TO_LOCALE_UPPER_CASE                               206                            /* 'toLocaleUpperCase' */
+#define DUK_STRIDX_TO_UPPER_CASE                                      207                            /* 'toUpperCase' */
+#define DUK_STRIDX_TO_LOCALE_LOWER_CASE                               208                            /* 'toLocaleLowerCase' */
+#define DUK_STRIDX_TO_LOWER_CASE                                      209                            /* 'toLowerCase' */
+#define DUK_STRIDX_SUBSTRING                                          210                            /* 'substring' */
+#define DUK_STRIDX_SPLIT                                              211                            /* 'split' */
+#define DUK_STRIDX_SEARCH                                             212                            /* 'search' */
+#define DUK_STRIDX_REPLACE                                            213                            /* 'replace' */
+#define DUK_STRIDX_MATCH                                              214                            /* 'match' */
+#define DUK_STRIDX_LOCALE_COMPARE                                     215                            /* 'localeCompare' */
+#define DUK_STRIDX_CHAR_CODE_AT                                       216                            /* 'charCodeAt' */
+#define DUK_STRIDX_CHAR_AT                                            217                            /* 'charAt' */
+#define DUK_STRIDX_FROM_CHAR_CODE                                     218                            /* 'fromCharCode' */
+#define DUK_STRIDX_REDUCE_RIGHT                                       219                            /* 'reduceRight' */
+#define DUK_STRIDX_REDUCE                                             220                            /* 'reduce' */
+#define DUK_STRIDX_FILTER                                             221                            /* 'filter' */
+#define DUK_STRIDX_MAP                                                222                            /* 'map' */
+#define DUK_STRIDX_FOR_EACH                                           223                            /* 'forEach' */
+#define DUK_STRIDX_SOME                                               224                            /* 'some' */
+#define DUK_STRIDX_EVERY                                              225                            /* 'every' */
+#define DUK_STRIDX_LAST_INDEX_OF                                      226                            /* 'lastIndexOf' */
+#define DUK_STRIDX_INDEX_OF                                           227                            /* 'indexOf' */
+#define DUK_STRIDX_UNSHIFT                                            228                            /* 'unshift' */
+#define DUK_STRIDX_SPLICE                                             229                            /* 'splice' */
+#define DUK_STRIDX_SORT                                               230                            /* 'sort' */
+#define DUK_STRIDX_SLICE                                              231                            /* 'slice' */
+#define DUK_STRIDX_SHIFT                                              232                            /* 'shift' */
+#define DUK_STRIDX_REVERSE                                            233                            /* 'reverse' */
+#define DUK_STRIDX_PUSH                                               234                            /* 'push' */
+#define DUK_STRIDX_POP                                                235                            /* 'pop' */
+#define DUK_STRIDX_JOIN                                               236                            /* 'join' */
+#define DUK_STRIDX_CONCAT                                             237                            /* 'concat' */
+#define DUK_STRIDX_IS_ARRAY                                           238                            /* 'isArray' */
+#define DUK_STRIDX_LC_ARGUMENTS                                       239                            /* 'arguments' */
+#define DUK_STRIDX_CALLER                                             240                            /* 'caller' */
+#define DUK_STRIDX_BIND                                               241                            /* 'bind' */
+#define DUK_STRIDX_CALL                                               242                            /* 'call' */
+#define DUK_STRIDX_APPLY                                              243                            /* 'apply' */
+#define DUK_STRIDX_PROPERTY_IS_ENUMERABLE                             244                            /* 'propertyIsEnumerable' */
+#define DUK_STRIDX_IS_PROTOTYPE_OF                                    245                            /* 'isPrototypeOf' */
+#define DUK_STRIDX_HAS_OWN_PROPERTY                                   246                            /* 'hasOwnProperty' */
+#define DUK_STRIDX_VALUE_OF                                           247                            /* 'valueOf' */
+#define DUK_STRIDX_TO_LOCALE_STRING                                   248                            /* 'toLocaleString' */
+#define DUK_STRIDX_TO_STRING                                          249                            /* 'toString' */
+#define DUK_STRIDX_CONSTRUCTOR                                        250                            /* 'constructor' */
+#define DUK_STRIDX_SET                                                251                            /* 'set' */
+#define DUK_STRIDX_GET                                                252                            /* 'get' */
+#define DUK_STRIDX_ENUMERABLE                                         253                            /* 'enumerable' */
+#define DUK_STRIDX_CONFIGURABLE                                       254                            /* 'configurable' */
+#define DUK_STRIDX_WRITABLE                                           255                            /* 'writable' */
+#define DUK_STRIDX_VALUE                                              256                            /* 'value' */
+#define DUK_STRIDX_KEYS                                               257                            /* 'keys' */
+#define DUK_STRIDX_IS_EXTENSIBLE                                      258                            /* 'isExtensible' */
+#define DUK_STRIDX_IS_FROZEN                                          259                            /* 'isFrozen' */
+#define DUK_STRIDX_IS_SEALED                                          260                            /* 'isSealed' */
+#define DUK_STRIDX_PREVENT_EXTENSIONS                                 261                            /* 'preventExtensions' */
+#define DUK_STRIDX_FREEZE                                             262                            /* 'freeze' */
+#define DUK_STRIDX_SEAL                                               263                            /* 'seal' */
+#define DUK_STRIDX_DEFINE_PROPERTIES                                  264                            /* 'defineProperties' */
+#define DUK_STRIDX_DEFINE_PROPERTY                                    265                            /* 'defineProperty' */
+#define DUK_STRIDX_CREATE                                             266                            /* 'create' */
+#define DUK_STRIDX_GET_OWN_PROPERTY_NAMES                             267                            /* 'getOwnPropertyNames' */
+#define DUK_STRIDX_GET_OWN_PROPERTY_DESCRIPTOR                        268                            /* 'getOwnPropertyDescriptor' */
+#define DUK_STRIDX_GET_PROTOTYPE_OF                                   269                            /* 'getPrototypeOf' */
+#define DUK_STRIDX_PROTOTYPE                                          270                            /* 'prototype' */
+#define DUK_STRIDX_LENGTH                                             271                            /* 'length' */
+#define DUK_STRIDX_ALERT                                              272                            /* 'alert' */
+#define DUK_STRIDX_PRINT                                              273                            /* 'print' */
+#define DUK_STRIDX_UNESCAPE                                           274                            /* 'unescape' */
+#define DUK_STRIDX_ESCAPE                                             275                            /* 'escape' */
+#define DUK_STRIDX_ENCODE_URI_COMPONENT                               276                            /* 'encodeURIComponent' */
+#define DUK_STRIDX_ENCODE_URI                                         277                            /* 'encodeURI' */
+#define DUK_STRIDX_DECODE_URI_COMPONENT                               278                            /* 'decodeURIComponent' */
+#define DUK_STRIDX_DECODE_URI                                         279                            /* 'decodeURI' */
+#define DUK_STRIDX_IS_FINITE                                          280                            /* 'isFinite' */
+#define DUK_STRIDX_IS_NAN                                             281                            /* 'isNaN' */
+#define DUK_STRIDX_PARSE_FLOAT                                        282                            /* 'parseFloat' */
+#define DUK_STRIDX_PARSE_INT                                          283                            /* 'parseInt' */
+#define DUK_STRIDX_EVAL                                               284                            /* 'eval' */
+#define DUK_STRIDX_URI_ERROR                                          285                            /* 'URIError' */
+#define DUK_STRIDX_TYPE_ERROR                                         286                            /* 'TypeError' */
+#define DUK_STRIDX_SYNTAX_ERROR                                       287                            /* 'SyntaxError' */
+#define DUK_STRIDX_REFERENCE_ERROR                                    288                            /* 'ReferenceError' */
+#define DUK_STRIDX_RANGE_ERROR                                        289                            /* 'RangeError' */
+#define DUK_STRIDX_EVAL_ERROR                                         290                            /* 'EvalError' */
+#define DUK_STRIDX_BREAK                                              291                            /* 'break' */
+#define DUK_STRIDX_CASE                                               292                            /* 'case' */
+#define DUK_STRIDX_CATCH                                              293                            /* 'catch' */
+#define DUK_STRIDX_CONTINUE                                           294                            /* 'continue' */
+#define DUK_STRIDX_DEBUGGER                                           295                            /* 'debugger' */
+#define DUK_STRIDX_DEFAULT                                            296                            /* 'default' */
+#define DUK_STRIDX_DELETE                                             297                            /* 'delete' */
+#define DUK_STRIDX_DO                                                 298                            /* 'do' */
+#define DUK_STRIDX_ELSE                                               299                            /* 'else' */
+#define DUK_STRIDX_FINALLY                                            300                            /* 'finally' */
+#define DUK_STRIDX_FOR                                                301                            /* 'for' */
+#define DUK_STRIDX_LC_FUNCTION                                        302                            /* 'function' */
+#define DUK_STRIDX_IF                                                 303                            /* 'if' */
+#define DUK_STRIDX_IN                                                 304                            /* 'in' */
+#define DUK_STRIDX_INSTANCEOF                                         305                            /* 'instanceof' */
+#define DUK_STRIDX_NEW                                                306                            /* 'new' */
+#define DUK_STRIDX_RETURN                                             307                            /* 'return' */
+#define DUK_STRIDX_SWITCH                                             308                            /* 'switch' */
+#define DUK_STRIDX_THIS                                               309                            /* 'this' */
+#define DUK_STRIDX_THROW                                              310                            /* 'throw' */
+#define DUK_STRIDX_TRY                                                311                            /* 'try' */
+#define DUK_STRIDX_TYPEOF                                             312                            /* 'typeof' */
+#define DUK_STRIDX_VAR                                                313                            /* 'var' */
+#define DUK_STRIDX_VOID                                               314                            /* 'void' */
+#define DUK_STRIDX_WHILE                                              315                            /* 'while' */
+#define DUK_STRIDX_WITH                                               316                            /* 'with' */
+#define DUK_STRIDX_CLASS                                              317                            /* 'class' */
+#define DUK_STRIDX_CONST                                              318                            /* 'const' */
+#define DUK_STRIDX_ENUM                                               319                            /* 'enum' */
+#define DUK_STRIDX_EXPORT                                             320                            /* 'export' */
+#define DUK_STRIDX_EXTENDS                                            321                            /* 'extends' */
+#define DUK_STRIDX_IMPORT                                             322                            /* 'import' */
+#define DUK_STRIDX_SUPER                                              323                            /* 'super' */
+#define DUK_STRIDX_LC_NULL                                            324                            /* 'null' */
+#define DUK_STRIDX_TRUE                                               325                            /* 'true' */
+#define DUK_STRIDX_FALSE                                              326                            /* 'false' */
+#define DUK_STRIDX_IMPLEMENTS                                         327                            /* 'implements' */
+#define DUK_STRIDX_INTERFACE                                          328                            /* 'interface' */
+#define DUK_STRIDX_LET                                                329                            /* 'let' */
+#define DUK_STRIDX_PACKAGE                                            330                            /* 'package' */
+#define DUK_STRIDX_PRIVATE                                            331                            /* 'private' */
+#define DUK_STRIDX_PROTECTED                                          332                            /* 'protected' */
+#define DUK_STRIDX_PUBLIC                                             333                            /* 'public' */
+#define DUK_STRIDX_STATIC                                             334                            /* 'static' */
+#define DUK_STRIDX_YIELD                                              335                            /* 'yield' */
+
+#define DUK_HEAP_STRING_UC_LOGGER(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_LOGGER)
+#define DUK_HTHREAD_STRING_UC_LOGGER(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_LOGGER)
+#define DUK_HEAP_STRING_UC_THREAD(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_THREAD)
+#define DUK_HTHREAD_STRING_UC_THREAD(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_THREAD)
+#define DUK_HEAP_STRING_UC_POINTER(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_POINTER)
+#define DUK_HTHREAD_STRING_UC_POINTER(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_POINTER)
+#define DUK_HEAP_STRING_UC_BUFFER(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_BUFFER)
+#define DUK_HTHREAD_STRING_UC_BUFFER(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_BUFFER)
+#define DUK_HEAP_STRING_DEC_ENV(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DEC_ENV)
+#define DUK_HTHREAD_STRING_DEC_ENV(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DEC_ENV)
+#define DUK_HEAP_STRING_OBJ_ENV(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_OBJ_ENV)
+#define DUK_HTHREAD_STRING_OBJ_ENV(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_OBJ_ENV)
+#define DUK_HEAP_STRING_EMPTY_STRING(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EMPTY_STRING)
+#define DUK_HTHREAD_STRING_EMPTY_STRING(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EMPTY_STRING)
+#define DUK_HEAP_STRING_GLOBAL(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GLOBAL)
+#define DUK_HTHREAD_STRING_GLOBAL(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GLOBAL)
+#define DUK_HEAP_STRING_UC_ARGUMENTS(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_ARGUMENTS)
+#define DUK_HTHREAD_STRING_UC_ARGUMENTS(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_ARGUMENTS)
+#define DUK_HEAP_STRING_JSON(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON)
+#define DUK_HTHREAD_STRING_JSON(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON)
+#define DUK_HEAP_STRING_MATH(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MATH)
+#define DUK_HTHREAD_STRING_MATH(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MATH)
+#define DUK_HEAP_STRING_UC_ERROR(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_ERROR)
+#define DUK_HTHREAD_STRING_UC_ERROR(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_ERROR)
+#define DUK_HEAP_STRING_REG_EXP(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REG_EXP)
+#define DUK_HTHREAD_STRING_REG_EXP(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REG_EXP)
+#define DUK_HEAP_STRING_DATE(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DATE)
+#define DUK_HTHREAD_STRING_DATE(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DATE)
+#define DUK_HEAP_STRING_UC_NUMBER(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_NUMBER)
+#define DUK_HTHREAD_STRING_UC_NUMBER(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_NUMBER)
+#define DUK_HEAP_STRING_UC_BOOLEAN(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_BOOLEAN)
+#define DUK_HTHREAD_STRING_UC_BOOLEAN(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_BOOLEAN)
+#define DUK_HEAP_STRING_UC_STRING(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_STRING)
+#define DUK_HTHREAD_STRING_UC_STRING(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_STRING)
+#define DUK_HEAP_STRING_ARRAY(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ARRAY)
+#define DUK_HTHREAD_STRING_ARRAY(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ARRAY)
+#define DUK_HEAP_STRING_UC_FUNCTION(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_FUNCTION)
+#define DUK_HTHREAD_STRING_UC_FUNCTION(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_FUNCTION)
+#define DUK_HEAP_STRING_UC_OBJECT(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_OBJECT)
+#define DUK_HTHREAD_STRING_UC_OBJECT(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_OBJECT)
+#define DUK_HEAP_STRING_UC_NULL(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_NULL)
+#define DUK_HTHREAD_STRING_UC_NULL(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_NULL)
+#define DUK_HEAP_STRING_UC_UNDEFINED(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_UNDEFINED)
+#define DUK_HTHREAD_STRING_UC_UNDEFINED(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_UNDEFINED)
+#define DUK_HEAP_STRING_JSON_EXT_FUNCTION2(heap)                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_FUNCTION2)
+#define DUK_HTHREAD_STRING_JSON_EXT_FUNCTION2(thr)                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_FUNCTION2)
+#define DUK_HEAP_STRING_JSON_EXT_FUNCTION1(heap)                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_FUNCTION1)
+#define DUK_HTHREAD_STRING_JSON_EXT_FUNCTION1(thr)                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_FUNCTION1)
+#define DUK_HEAP_STRING_JSON_EXT_NEGINF(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_NEGINF)
+#define DUK_HTHREAD_STRING_JSON_EXT_NEGINF(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_NEGINF)
+#define DUK_HEAP_STRING_JSON_EXT_POSINF(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_POSINF)
+#define DUK_HTHREAD_STRING_JSON_EXT_POSINF(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_POSINF)
+#define DUK_HEAP_STRING_JSON_EXT_NAN(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_NAN)
+#define DUK_HTHREAD_STRING_JSON_EXT_NAN(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_NAN)
+#define DUK_HEAP_STRING_JSON_EXT_UNDEFINED(heap)                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_UNDEFINED)
+#define DUK_HTHREAD_STRING_JSON_EXT_UNDEFINED(thr)                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_UNDEFINED)
+#define DUK_HEAP_STRING_TO_LOG_STRING(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_LOG_STRING)
+#define DUK_HTHREAD_STRING_TO_LOG_STRING(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_LOG_STRING)
+#define DUK_HEAP_STRING_CLOG(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CLOG)
+#define DUK_HTHREAD_STRING_CLOG(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CLOG)
+#define DUK_HEAP_STRING_LC_L(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_L)
+#define DUK_HTHREAD_STRING_LC_L(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_L)
+#define DUK_HEAP_STRING_LC_N(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_N)
+#define DUK_HTHREAD_STRING_LC_N(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_N)
+#define DUK_HEAP_STRING_LC_FATAL(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_FATAL)
+#define DUK_HTHREAD_STRING_LC_FATAL(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_FATAL)
+#define DUK_HEAP_STRING_LC_ERROR(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_ERROR)
+#define DUK_HTHREAD_STRING_LC_ERROR(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_ERROR)
+#define DUK_HEAP_STRING_LC_WARN(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_WARN)
+#define DUK_HTHREAD_STRING_LC_WARN(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_WARN)
+#define DUK_HEAP_STRING_LC_DEBUG(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_DEBUG)
+#define DUK_HTHREAD_STRING_LC_DEBUG(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_DEBUG)
+#define DUK_HEAP_STRING_LC_TRACE(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_TRACE)
+#define DUK_HTHREAD_STRING_LC_TRACE(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_TRACE)
+#define DUK_HEAP_STRING_RAW(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_RAW)
+#define DUK_HTHREAD_STRING_RAW(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_RAW)
+#define DUK_HEAP_STRING_FMT(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FMT)
+#define DUK_HTHREAD_STRING_FMT(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FMT)
+#define DUK_HEAP_STRING_CURRENT(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CURRENT)
+#define DUK_HTHREAD_STRING_CURRENT(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CURRENT)
+#define DUK_HEAP_STRING_RESUME(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_RESUME)
+#define DUK_HTHREAD_STRING_RESUME(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_RESUME)
+#define DUK_HEAP_STRING_COMPACT(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_COMPACT)
+#define DUK_HTHREAD_STRING_COMPACT(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_COMPACT)
+#define DUK_HEAP_STRING_JC(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JC)
+#define DUK_HTHREAD_STRING_JC(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JC)
+#define DUK_HEAP_STRING_JX(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JX)
+#define DUK_HTHREAD_STRING_JX(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JX)
+#define DUK_HEAP_STRING_BASE64(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_BASE64)
+#define DUK_HTHREAD_STRING_BASE64(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_BASE64)
+#define DUK_HEAP_STRING_HEX(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_HEX)
+#define DUK_HTHREAD_STRING_HEX(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_HEX)
+#define DUK_HEAP_STRING_DEC(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DEC)
+#define DUK_HTHREAD_STRING_DEC(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DEC)
+#define DUK_HEAP_STRING_ENC(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENC)
+#define DUK_HTHREAD_STRING_ENC(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENC)
+#define DUK_HEAP_STRING_FIN(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FIN)
+#define DUK_HTHREAD_STRING_FIN(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FIN)
+#define DUK_HEAP_STRING_GC(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GC)
+#define DUK_HTHREAD_STRING_GC(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GC)
+#define DUK_HEAP_STRING_ACT(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ACT)
+#define DUK_HTHREAD_STRING_ACT(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ACT)
+#define DUK_HEAP_STRING_LC_INFO(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_INFO)
+#define DUK_HTHREAD_STRING_LC_INFO(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_INFO)
+#define DUK_HEAP_STRING_VERSION(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_VERSION)
+#define DUK_HTHREAD_STRING_VERSION(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_VERSION)
+#define DUK_HEAP_STRING_ENV(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENV)
+#define DUK_HTHREAD_STRING_ENV(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENV)
+#define DUK_HEAP_STRING_MOD_LOADED(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MOD_LOADED)
+#define DUK_HTHREAD_STRING_MOD_LOADED(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MOD_LOADED)
+#define DUK_HEAP_STRING_MOD_SEARCH(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MOD_SEARCH)
+#define DUK_HTHREAD_STRING_MOD_SEARCH(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MOD_SEARCH)
+#define DUK_HEAP_STRING_ERR_THROW(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ERR_THROW)
+#define DUK_HTHREAD_STRING_ERR_THROW(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ERR_THROW)
+#define DUK_HEAP_STRING_ERR_CREATE(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ERR_CREATE)
+#define DUK_HTHREAD_STRING_ERR_CREATE(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ERR_CREATE)
+#define DUK_HEAP_STRING_COMPILE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_COMPILE)
+#define DUK_HTHREAD_STRING_COMPILE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_COMPILE)
+#define DUK_HEAP_STRING_INT_REGBASE(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_REGBASE)
+#define DUK_HTHREAD_STRING_INT_REGBASE(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_REGBASE)
+#define DUK_HEAP_STRING_INT_THREAD(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_THREAD)
+#define DUK_HTHREAD_STRING_INT_THREAD(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_THREAD)
+#define DUK_HEAP_STRING_INT_HANDLER(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_HANDLER)
+#define DUK_HTHREAD_STRING_INT_HANDLER(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_HANDLER)
+#define DUK_HEAP_STRING_INT_FINALIZER(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_FINALIZER)
+#define DUK_HTHREAD_STRING_INT_FINALIZER(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_FINALIZER)
+#define DUK_HEAP_STRING_INT_CALLEE(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_CALLEE)
+#define DUK_HTHREAD_STRING_INT_CALLEE(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_CALLEE)
+#define DUK_HEAP_STRING_INT_MAP(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_MAP)
+#define DUK_HTHREAD_STRING_INT_MAP(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_MAP)
+#define DUK_HEAP_STRING_INT_ARGS(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_ARGS)
+#define DUK_HTHREAD_STRING_INT_ARGS(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_ARGS)
+#define DUK_HEAP_STRING_INT_THIS(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_THIS)
+#define DUK_HTHREAD_STRING_INT_THIS(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_THIS)
+#define DUK_HEAP_STRING_INT_PC2LINE(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_PC2LINE)
+#define DUK_HTHREAD_STRING_INT_PC2LINE(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_PC2LINE)
+#define DUK_HEAP_STRING_INT_SOURCE(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_SOURCE)
+#define DUK_HTHREAD_STRING_INT_SOURCE(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_SOURCE)
+#define DUK_HEAP_STRING_INT_VARENV(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_VARENV)
+#define DUK_HTHREAD_STRING_INT_VARENV(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_VARENV)
+#define DUK_HEAP_STRING_INT_LEXENV(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_LEXENV)
+#define DUK_HTHREAD_STRING_INT_LEXENV(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_LEXENV)
+#define DUK_HEAP_STRING_INT_VARMAP(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_VARMAP)
+#define DUK_HTHREAD_STRING_INT_VARMAP(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_VARMAP)
+#define DUK_HEAP_STRING_INT_FORMALS(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_FORMALS)
+#define DUK_HTHREAD_STRING_INT_FORMALS(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_FORMALS)
+#define DUK_HEAP_STRING_INT_BYTECODE(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_BYTECODE)
+#define DUK_HTHREAD_STRING_INT_BYTECODE(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_BYTECODE)
+#define DUK_HEAP_STRING_INT_NEXT(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_NEXT)
+#define DUK_HTHREAD_STRING_INT_NEXT(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_NEXT)
+#define DUK_HEAP_STRING_INT_TARGET(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_TARGET)
+#define DUK_HTHREAD_STRING_INT_TARGET(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_TARGET)
+#define DUK_HEAP_STRING_INT_VALUE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_VALUE)
+#define DUK_HTHREAD_STRING_INT_VALUE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_VALUE)
+#define DUK_HEAP_STRING_LC_POINTER(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_POINTER)
+#define DUK_HTHREAD_STRING_LC_POINTER(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_POINTER)
+#define DUK_HEAP_STRING_LC_BUFFER(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_BUFFER)
+#define DUK_HTHREAD_STRING_LC_BUFFER(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_BUFFER)
+#define DUK_HEAP_STRING_TRACEDATA(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TRACEDATA)
+#define DUK_HTHREAD_STRING_TRACEDATA(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TRACEDATA)
+#define DUK_HEAP_STRING_LINE_NUMBER(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LINE_NUMBER)
+#define DUK_HTHREAD_STRING_LINE_NUMBER(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LINE_NUMBER)
+#define DUK_HEAP_STRING_FILE_NAME(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FILE_NAME)
+#define DUK_HTHREAD_STRING_FILE_NAME(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FILE_NAME)
+#define DUK_HEAP_STRING_PC(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PC)
+#define DUK_HTHREAD_STRING_PC(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PC)
+#define DUK_HEAP_STRING_STACK(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_STACK)
+#define DUK_HTHREAD_STRING_STACK(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_STACK)
+#define DUK_HEAP_STRING_THROW_TYPE_ERROR(heap)                        DUK_HEAP_GET_STRING((heap),DUK_STRIDX_THROW_TYPE_ERROR)
+#define DUK_HTHREAD_STRING_THROW_TYPE_ERROR(thr)                      DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_THROW_TYPE_ERROR)
+#define DUK_HEAP_STRING_DUKTAPE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DUKTAPE)
+#define DUK_HTHREAD_STRING_DUKTAPE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DUKTAPE)
+#define DUK_HEAP_STRING_ID(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ID)
+#define DUK_HTHREAD_STRING_ID(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ID)
+#define DUK_HEAP_STRING_REQUIRE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REQUIRE)
+#define DUK_HTHREAD_STRING_REQUIRE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REQUIRE)
+#define DUK_HEAP_STRING___PROTO__(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX___PROTO__)
+#define DUK_HTHREAD_STRING___PROTO__(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX___PROTO__)
+#define DUK_HEAP_STRING_SET_PROTOTYPE_OF(heap)                        DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_PROTOTYPE_OF)
+#define DUK_HTHREAD_STRING_SET_PROTOTYPE_OF(thr)                      DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_PROTOTYPE_OF)
+#define DUK_HEAP_STRING_OWN_KEYS(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_OWN_KEYS)
+#define DUK_HTHREAD_STRING_OWN_KEYS(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_OWN_KEYS)
+#define DUK_HEAP_STRING_ENUMERATE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENUMERATE)
+#define DUK_HTHREAD_STRING_ENUMERATE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENUMERATE)
+#define DUK_HEAP_STRING_DELETE_PROPERTY(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DELETE_PROPERTY)
+#define DUK_HTHREAD_STRING_DELETE_PROPERTY(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DELETE_PROPERTY)
+#define DUK_HEAP_STRING_HAS(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_HAS)
+#define DUK_HTHREAD_STRING_HAS(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_HAS)
+#define DUK_HEAP_STRING_PROXY(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PROXY)
+#define DUK_HTHREAD_STRING_PROXY(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PROXY)
+#define DUK_HEAP_STRING_CALLEE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CALLEE)
+#define DUK_HTHREAD_STRING_CALLEE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CALLEE)
+#define DUK_HEAP_STRING_INVALID_DATE(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INVALID_DATE)
+#define DUK_HTHREAD_STRING_INVALID_DATE(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INVALID_DATE)
+#define DUK_HEAP_STRING_BRACKETED_ELLIPSIS(heap)                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_BRACKETED_ELLIPSIS)
+#define DUK_HTHREAD_STRING_BRACKETED_ELLIPSIS(thr)                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_BRACKETED_ELLIPSIS)
+#define DUK_HEAP_STRING_NEWLINE_TAB(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NEWLINE_TAB)
+#define DUK_HTHREAD_STRING_NEWLINE_TAB(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NEWLINE_TAB)
+#define DUK_HEAP_STRING_SPACE(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SPACE)
+#define DUK_HTHREAD_STRING_SPACE(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SPACE)
+#define DUK_HEAP_STRING_COMMA(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_COMMA)
+#define DUK_HTHREAD_STRING_COMMA(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_COMMA)
+#define DUK_HEAP_STRING_MINUS_ZERO(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MINUS_ZERO)
+#define DUK_HTHREAD_STRING_MINUS_ZERO(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MINUS_ZERO)
+#define DUK_HEAP_STRING_PLUS_ZERO(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PLUS_ZERO)
+#define DUK_HTHREAD_STRING_PLUS_ZERO(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PLUS_ZERO)
+#define DUK_HEAP_STRING_ZERO(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ZERO)
+#define DUK_HTHREAD_STRING_ZERO(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ZERO)
+#define DUK_HEAP_STRING_MINUS_INFINITY(heap)                          DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MINUS_INFINITY)
+#define DUK_HTHREAD_STRING_MINUS_INFINITY(thr)                        DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MINUS_INFINITY)
+#define DUK_HEAP_STRING_PLUS_INFINITY(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PLUS_INFINITY)
+#define DUK_HTHREAD_STRING_PLUS_INFINITY(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PLUS_INFINITY)
+#define DUK_HEAP_STRING_INFINITY(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INFINITY)
+#define DUK_HTHREAD_STRING_INFINITY(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INFINITY)
+#define DUK_HEAP_STRING_LC_OBJECT(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_OBJECT)
+#define DUK_HTHREAD_STRING_LC_OBJECT(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_OBJECT)
+#define DUK_HEAP_STRING_LC_STRING(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_STRING)
+#define DUK_HTHREAD_STRING_LC_STRING(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_STRING)
+#define DUK_HEAP_STRING_LC_NUMBER(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_NUMBER)
+#define DUK_HTHREAD_STRING_LC_NUMBER(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_NUMBER)
+#define DUK_HEAP_STRING_LC_BOOLEAN(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_BOOLEAN)
+#define DUK_HTHREAD_STRING_LC_BOOLEAN(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_BOOLEAN)
+#define DUK_HEAP_STRING_LC_UNDEFINED(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_UNDEFINED)
+#define DUK_HTHREAD_STRING_LC_UNDEFINED(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_UNDEFINED)
+#define DUK_HEAP_STRING_STRINGIFY(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_STRINGIFY)
+#define DUK_HTHREAD_STRING_STRINGIFY(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_STRINGIFY)
+#define DUK_HEAP_STRING_TAN(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TAN)
+#define DUK_HTHREAD_STRING_TAN(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TAN)
+#define DUK_HEAP_STRING_SQRT(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SQRT)
+#define DUK_HTHREAD_STRING_SQRT(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SQRT)
+#define DUK_HEAP_STRING_SIN(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SIN)
+#define DUK_HTHREAD_STRING_SIN(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SIN)
+#define DUK_HEAP_STRING_ROUND(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ROUND)
+#define DUK_HTHREAD_STRING_ROUND(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ROUND)
+#define DUK_HEAP_STRING_RANDOM(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_RANDOM)
+#define DUK_HTHREAD_STRING_RANDOM(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_RANDOM)
+#define DUK_HEAP_STRING_POW(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_POW)
+#define DUK_HTHREAD_STRING_POW(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_POW)
+#define DUK_HEAP_STRING_MIN(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MIN)
+#define DUK_HTHREAD_STRING_MIN(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MIN)
+#define DUK_HEAP_STRING_MAX(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MAX)
+#define DUK_HTHREAD_STRING_MAX(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MAX)
+#define DUK_HEAP_STRING_LOG(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LOG)
+#define DUK_HTHREAD_STRING_LOG(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LOG)
+#define DUK_HEAP_STRING_FLOOR(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FLOOR)
+#define DUK_HTHREAD_STRING_FLOOR(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FLOOR)
+#define DUK_HEAP_STRING_EXP(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EXP)
+#define DUK_HTHREAD_STRING_EXP(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EXP)
+#define DUK_HEAP_STRING_COS(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_COS)
+#define DUK_HTHREAD_STRING_COS(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_COS)
+#define DUK_HEAP_STRING_CEIL(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CEIL)
+#define DUK_HTHREAD_STRING_CEIL(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CEIL)
+#define DUK_HEAP_STRING_ATAN2(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ATAN2)
+#define DUK_HTHREAD_STRING_ATAN2(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ATAN2)
+#define DUK_HEAP_STRING_ATAN(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ATAN)
+#define DUK_HTHREAD_STRING_ATAN(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ATAN)
+#define DUK_HEAP_STRING_ASIN(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ASIN)
+#define DUK_HTHREAD_STRING_ASIN(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ASIN)
+#define DUK_HEAP_STRING_ACOS(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ACOS)
+#define DUK_HTHREAD_STRING_ACOS(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ACOS)
+#define DUK_HEAP_STRING_ABS(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ABS)
+#define DUK_HTHREAD_STRING_ABS(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ABS)
+#define DUK_HEAP_STRING_SQRT2(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SQRT2)
+#define DUK_HTHREAD_STRING_SQRT2(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SQRT2)
+#define DUK_HEAP_STRING_SQRT1_2(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SQRT1_2)
+#define DUK_HTHREAD_STRING_SQRT1_2(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SQRT1_2)
+#define DUK_HEAP_STRING_PI(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PI)
+#define DUK_HTHREAD_STRING_PI(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PI)
+#define DUK_HEAP_STRING_LOG10E(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LOG10E)
+#define DUK_HTHREAD_STRING_LOG10E(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LOG10E)
+#define DUK_HEAP_STRING_LOG2E(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LOG2E)
+#define DUK_HTHREAD_STRING_LOG2E(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LOG2E)
+#define DUK_HEAP_STRING_LN2(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LN2)
+#define DUK_HTHREAD_STRING_LN2(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LN2)
+#define DUK_HEAP_STRING_LN10(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LN10)
+#define DUK_HTHREAD_STRING_LN10(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LN10)
+#define DUK_HEAP_STRING_E(heap)                                       DUK_HEAP_GET_STRING((heap),DUK_STRIDX_E)
+#define DUK_HTHREAD_STRING_E(thr)                                     DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_E)
+#define DUK_HEAP_STRING_MESSAGE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MESSAGE)
+#define DUK_HTHREAD_STRING_MESSAGE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MESSAGE)
+#define DUK_HEAP_STRING_NAME(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NAME)
+#define DUK_HTHREAD_STRING_NAME(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NAME)
+#define DUK_HEAP_STRING_INPUT(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INPUT)
+#define DUK_HTHREAD_STRING_INPUT(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INPUT)
+#define DUK_HEAP_STRING_INDEX(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INDEX)
+#define DUK_HTHREAD_STRING_INDEX(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INDEX)
+#define DUK_HEAP_STRING_ESCAPED_EMPTY_REGEXP(heap)                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ESCAPED_EMPTY_REGEXP)
+#define DUK_HTHREAD_STRING_ESCAPED_EMPTY_REGEXP(thr)                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ESCAPED_EMPTY_REGEXP)
+#define DUK_HEAP_STRING_LAST_INDEX(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LAST_INDEX)
+#define DUK_HTHREAD_STRING_LAST_INDEX(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LAST_INDEX)
+#define DUK_HEAP_STRING_MULTILINE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MULTILINE)
+#define DUK_HTHREAD_STRING_MULTILINE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MULTILINE)
+#define DUK_HEAP_STRING_IGNORE_CASE(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IGNORE_CASE)
+#define DUK_HTHREAD_STRING_IGNORE_CASE(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IGNORE_CASE)
+#define DUK_HEAP_STRING_SOURCE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SOURCE)
+#define DUK_HTHREAD_STRING_SOURCE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SOURCE)
+#define DUK_HEAP_STRING_TEST(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TEST)
+#define DUK_HTHREAD_STRING_TEST(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TEST)
+#define DUK_HEAP_STRING_EXEC(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EXEC)
+#define DUK_HTHREAD_STRING_EXEC(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EXEC)
+#define DUK_HEAP_STRING_TO_GMT_STRING(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_GMT_STRING)
+#define DUK_HTHREAD_STRING_TO_GMT_STRING(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_GMT_STRING)
+#define DUK_HEAP_STRING_SET_YEAR(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_YEAR)
+#define DUK_HTHREAD_STRING_SET_YEAR(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_YEAR)
+#define DUK_HEAP_STRING_GET_YEAR(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_YEAR)
+#define DUK_HTHREAD_STRING_GET_YEAR(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_YEAR)
+#define DUK_HEAP_STRING_TO_JSON(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_JSON)
+#define DUK_HTHREAD_STRING_TO_JSON(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_JSON)
+#define DUK_HEAP_STRING_TO_ISO_STRING(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_ISO_STRING)
+#define DUK_HTHREAD_STRING_TO_ISO_STRING(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_ISO_STRING)
+#define DUK_HEAP_STRING_TO_UTC_STRING(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_UTC_STRING)
+#define DUK_HTHREAD_STRING_TO_UTC_STRING(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_UTC_STRING)
+#define DUK_HEAP_STRING_SET_UTC_FULL_YEAR(heap)                       DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_UTC_FULL_YEAR)
+#define DUK_HTHREAD_STRING_SET_UTC_FULL_YEAR(thr)                     DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_UTC_FULL_YEAR)
+#define DUK_HEAP_STRING_SET_FULL_YEAR(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_FULL_YEAR)
+#define DUK_HTHREAD_STRING_SET_FULL_YEAR(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_FULL_YEAR)
+#define DUK_HEAP_STRING_SET_UTC_MONTH(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_UTC_MONTH)
+#define DUK_HTHREAD_STRING_SET_UTC_MONTH(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_UTC_MONTH)
+#define DUK_HEAP_STRING_SET_MONTH(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_MONTH)
+#define DUK_HTHREAD_STRING_SET_MONTH(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_MONTH)
+#define DUK_HEAP_STRING_SET_UTC_DATE(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_UTC_DATE)
+#define DUK_HTHREAD_STRING_SET_UTC_DATE(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_UTC_DATE)
+#define DUK_HEAP_STRING_SET_DATE(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_DATE)
+#define DUK_HTHREAD_STRING_SET_DATE(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_DATE)
+#define DUK_HEAP_STRING_SET_UTC_HOURS(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_UTC_HOURS)
+#define DUK_HTHREAD_STRING_SET_UTC_HOURS(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_UTC_HOURS)
+#define DUK_HEAP_STRING_SET_HOURS(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_HOURS)
+#define DUK_HTHREAD_STRING_SET_HOURS(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_HOURS)
+#define DUK_HEAP_STRING_SET_UTC_MINUTES(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_UTC_MINUTES)
+#define DUK_HTHREAD_STRING_SET_UTC_MINUTES(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_UTC_MINUTES)
+#define DUK_HEAP_STRING_SET_MINUTES(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_MINUTES)
+#define DUK_HTHREAD_STRING_SET_MINUTES(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_MINUTES)
+#define DUK_HEAP_STRING_SET_UTC_SECONDS(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_UTC_SECONDS)
+#define DUK_HTHREAD_STRING_SET_UTC_SECONDS(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_UTC_SECONDS)
+#define DUK_HEAP_STRING_SET_SECONDS(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_SECONDS)
+#define DUK_HTHREAD_STRING_SET_SECONDS(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_SECONDS)
+#define DUK_HEAP_STRING_SET_UTC_MILLISECONDS(heap)                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_UTC_MILLISECONDS)
+#define DUK_HTHREAD_STRING_SET_UTC_MILLISECONDS(thr)                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_UTC_MILLISECONDS)
+#define DUK_HEAP_STRING_SET_MILLISECONDS(heap)                        DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_MILLISECONDS)
+#define DUK_HTHREAD_STRING_SET_MILLISECONDS(thr)                      DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_MILLISECONDS)
+#define DUK_HEAP_STRING_SET_TIME(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_TIME)
+#define DUK_HTHREAD_STRING_SET_TIME(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_TIME)
+#define DUK_HEAP_STRING_GET_TIMEZONE_OFFSET(heap)                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_TIMEZONE_OFFSET)
+#define DUK_HTHREAD_STRING_GET_TIMEZONE_OFFSET(thr)                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_TIMEZONE_OFFSET)
+#define DUK_HEAP_STRING_GET_UTC_MILLISECONDS(heap)                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_MILLISECONDS)
+#define DUK_HTHREAD_STRING_GET_UTC_MILLISECONDS(thr)                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_MILLISECONDS)
+#define DUK_HEAP_STRING_GET_MILLISECONDS(heap)                        DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_MILLISECONDS)
+#define DUK_HTHREAD_STRING_GET_MILLISECONDS(thr)                      DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_MILLISECONDS)
+#define DUK_HEAP_STRING_GET_UTC_SECONDS(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_SECONDS)
+#define DUK_HTHREAD_STRING_GET_UTC_SECONDS(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_SECONDS)
+#define DUK_HEAP_STRING_GET_SECONDS(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_SECONDS)
+#define DUK_HTHREAD_STRING_GET_SECONDS(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_SECONDS)
+#define DUK_HEAP_STRING_GET_UTC_MINUTES(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_MINUTES)
+#define DUK_HTHREAD_STRING_GET_UTC_MINUTES(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_MINUTES)
+#define DUK_HEAP_STRING_GET_MINUTES(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_MINUTES)
+#define DUK_HTHREAD_STRING_GET_MINUTES(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_MINUTES)
+#define DUK_HEAP_STRING_GET_UTC_HOURS(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_HOURS)
+#define DUK_HTHREAD_STRING_GET_UTC_HOURS(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_HOURS)
+#define DUK_HEAP_STRING_GET_HOURS(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_HOURS)
+#define DUK_HTHREAD_STRING_GET_HOURS(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_HOURS)
+#define DUK_HEAP_STRING_GET_UTC_DAY(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_DAY)
+#define DUK_HTHREAD_STRING_GET_UTC_DAY(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_DAY)
+#define DUK_HEAP_STRING_GET_DAY(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_DAY)
+#define DUK_HTHREAD_STRING_GET_DAY(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_DAY)
+#define DUK_HEAP_STRING_GET_UTC_DATE(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_DATE)
+#define DUK_HTHREAD_STRING_GET_UTC_DATE(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_DATE)
+#define DUK_HEAP_STRING_GET_DATE(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_DATE)
+#define DUK_HTHREAD_STRING_GET_DATE(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_DATE)
+#define DUK_HEAP_STRING_GET_UTC_MONTH(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_MONTH)
+#define DUK_HTHREAD_STRING_GET_UTC_MONTH(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_MONTH)
+#define DUK_HEAP_STRING_GET_MONTH(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_MONTH)
+#define DUK_HTHREAD_STRING_GET_MONTH(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_MONTH)
+#define DUK_HEAP_STRING_GET_UTC_FULL_YEAR(heap)                       DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_FULL_YEAR)
+#define DUK_HTHREAD_STRING_GET_UTC_FULL_YEAR(thr)                     DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_FULL_YEAR)
+#define DUK_HEAP_STRING_GET_FULL_YEAR(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_FULL_YEAR)
+#define DUK_HTHREAD_STRING_GET_FULL_YEAR(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_FULL_YEAR)
+#define DUK_HEAP_STRING_GET_TIME(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_TIME)
+#define DUK_HTHREAD_STRING_GET_TIME(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_TIME)
+#define DUK_HEAP_STRING_TO_LOCALE_TIME_STRING(heap)                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_LOCALE_TIME_STRING)
+#define DUK_HTHREAD_STRING_TO_LOCALE_TIME_STRING(thr)                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_LOCALE_TIME_STRING)
+#define DUK_HEAP_STRING_TO_LOCALE_DATE_STRING(heap)                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_LOCALE_DATE_STRING)
+#define DUK_HTHREAD_STRING_TO_LOCALE_DATE_STRING(thr)                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_LOCALE_DATE_STRING)
+#define DUK_HEAP_STRING_TO_TIME_STRING(heap)                          DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_TIME_STRING)
+#define DUK_HTHREAD_STRING_TO_TIME_STRING(thr)                        DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_TIME_STRING)
+#define DUK_HEAP_STRING_TO_DATE_STRING(heap)                          DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_DATE_STRING)
+#define DUK_HTHREAD_STRING_TO_DATE_STRING(thr)                        DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_DATE_STRING)
+#define DUK_HEAP_STRING_NOW(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NOW)
+#define DUK_HTHREAD_STRING_NOW(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NOW)
+#define DUK_HEAP_STRING_UTC(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UTC)
+#define DUK_HTHREAD_STRING_UTC(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UTC)
+#define DUK_HEAP_STRING_PARSE(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PARSE)
+#define DUK_HTHREAD_STRING_PARSE(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PARSE)
+#define DUK_HEAP_STRING_TO_PRECISION(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_PRECISION)
+#define DUK_HTHREAD_STRING_TO_PRECISION(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_PRECISION)
+#define DUK_HEAP_STRING_TO_EXPONENTIAL(heap)                          DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_EXPONENTIAL)
+#define DUK_HTHREAD_STRING_TO_EXPONENTIAL(thr)                        DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_EXPONENTIAL)
+#define DUK_HEAP_STRING_TO_FIXED(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_FIXED)
+#define DUK_HTHREAD_STRING_TO_FIXED(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_FIXED)
+#define DUK_HEAP_STRING_POSITIVE_INFINITY(heap)                       DUK_HEAP_GET_STRING((heap),DUK_STRIDX_POSITIVE_INFINITY)
+#define DUK_HTHREAD_STRING_POSITIVE_INFINITY(thr)                     DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_POSITIVE_INFINITY)
+#define DUK_HEAP_STRING_NEGATIVE_INFINITY(heap)                       DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NEGATIVE_INFINITY)
+#define DUK_HTHREAD_STRING_NEGATIVE_INFINITY(thr)                     DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NEGATIVE_INFINITY)
+#define DUK_HEAP_STRING_NAN(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NAN)
+#define DUK_HTHREAD_STRING_NAN(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NAN)
+#define DUK_HEAP_STRING_MIN_VALUE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MIN_VALUE)
+#define DUK_HTHREAD_STRING_MIN_VALUE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MIN_VALUE)
+#define DUK_HEAP_STRING_MAX_VALUE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MAX_VALUE)
+#define DUK_HTHREAD_STRING_MAX_VALUE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MAX_VALUE)
+#define DUK_HEAP_STRING_SUBSTR(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SUBSTR)
+#define DUK_HTHREAD_STRING_SUBSTR(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SUBSTR)
+#define DUK_HEAP_STRING_TRIM(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TRIM)
+#define DUK_HTHREAD_STRING_TRIM(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TRIM)
+#define DUK_HEAP_STRING_TO_LOCALE_UPPER_CASE(heap)                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_LOCALE_UPPER_CASE)
+#define DUK_HTHREAD_STRING_TO_LOCALE_UPPER_CASE(thr)                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_LOCALE_UPPER_CASE)
+#define DUK_HEAP_STRING_TO_UPPER_CASE(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_UPPER_CASE)
+#define DUK_HTHREAD_STRING_TO_UPPER_CASE(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_UPPER_CASE)
+#define DUK_HEAP_STRING_TO_LOCALE_LOWER_CASE(heap)                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_LOCALE_LOWER_CASE)
+#define DUK_HTHREAD_STRING_TO_LOCALE_LOWER_CASE(thr)                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_LOCALE_LOWER_CASE)
+#define DUK_HEAP_STRING_TO_LOWER_CASE(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_LOWER_CASE)
+#define DUK_HTHREAD_STRING_TO_LOWER_CASE(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_LOWER_CASE)
+#define DUK_HEAP_STRING_SUBSTRING(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SUBSTRING)
+#define DUK_HTHREAD_STRING_SUBSTRING(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SUBSTRING)
+#define DUK_HEAP_STRING_SPLIT(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SPLIT)
+#define DUK_HTHREAD_STRING_SPLIT(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SPLIT)
+#define DUK_HEAP_STRING_SEARCH(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SEARCH)
+#define DUK_HTHREAD_STRING_SEARCH(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SEARCH)
+#define DUK_HEAP_STRING_REPLACE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REPLACE)
+#define DUK_HTHREAD_STRING_REPLACE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REPLACE)
+#define DUK_HEAP_STRING_MATCH(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MATCH)
+#define DUK_HTHREAD_STRING_MATCH(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MATCH)
+#define DUK_HEAP_STRING_LOCALE_COMPARE(heap)                          DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LOCALE_COMPARE)
+#define DUK_HTHREAD_STRING_LOCALE_COMPARE(thr)                        DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LOCALE_COMPARE)
+#define DUK_HEAP_STRING_CHAR_CODE_AT(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CHAR_CODE_AT)
+#define DUK_HTHREAD_STRING_CHAR_CODE_AT(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CHAR_CODE_AT)
+#define DUK_HEAP_STRING_CHAR_AT(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CHAR_AT)
+#define DUK_HTHREAD_STRING_CHAR_AT(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CHAR_AT)
+#define DUK_HEAP_STRING_FROM_CHAR_CODE(heap)                          DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FROM_CHAR_CODE)
+#define DUK_HTHREAD_STRING_FROM_CHAR_CODE(thr)                        DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FROM_CHAR_CODE)
+#define DUK_HEAP_STRING_REDUCE_RIGHT(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REDUCE_RIGHT)
+#define DUK_HTHREAD_STRING_REDUCE_RIGHT(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REDUCE_RIGHT)
+#define DUK_HEAP_STRING_REDUCE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REDUCE)
+#define DUK_HTHREAD_STRING_REDUCE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REDUCE)
+#define DUK_HEAP_STRING_FILTER(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FILTER)
+#define DUK_HTHREAD_STRING_FILTER(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FILTER)
+#define DUK_HEAP_STRING_MAP(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MAP)
+#define DUK_HTHREAD_STRING_MAP(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MAP)
+#define DUK_HEAP_STRING_FOR_EACH(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FOR_EACH)
+#define DUK_HTHREAD_STRING_FOR_EACH(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FOR_EACH)
+#define DUK_HEAP_STRING_SOME(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SOME)
+#define DUK_HTHREAD_STRING_SOME(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SOME)
+#define DUK_HEAP_STRING_EVERY(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EVERY)
+#define DUK_HTHREAD_STRING_EVERY(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EVERY)
+#define DUK_HEAP_STRING_LAST_INDEX_OF(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LAST_INDEX_OF)
+#define DUK_HTHREAD_STRING_LAST_INDEX_OF(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LAST_INDEX_OF)
+#define DUK_HEAP_STRING_INDEX_OF(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INDEX_OF)
+#define DUK_HTHREAD_STRING_INDEX_OF(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INDEX_OF)
+#define DUK_HEAP_STRING_UNSHIFT(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UNSHIFT)
+#define DUK_HTHREAD_STRING_UNSHIFT(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UNSHIFT)
+#define DUK_HEAP_STRING_SPLICE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SPLICE)
+#define DUK_HTHREAD_STRING_SPLICE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SPLICE)
+#define DUK_HEAP_STRING_SORT(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SORT)
+#define DUK_HTHREAD_STRING_SORT(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SORT)
+#define DUK_HEAP_STRING_SLICE(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SLICE)
+#define DUK_HTHREAD_STRING_SLICE(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SLICE)
+#define DUK_HEAP_STRING_SHIFT(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SHIFT)
+#define DUK_HTHREAD_STRING_SHIFT(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SHIFT)
+#define DUK_HEAP_STRING_REVERSE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REVERSE)
+#define DUK_HTHREAD_STRING_REVERSE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REVERSE)
+#define DUK_HEAP_STRING_PUSH(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PUSH)
+#define DUK_HTHREAD_STRING_PUSH(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PUSH)
+#define DUK_HEAP_STRING_POP(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_POP)
+#define DUK_HTHREAD_STRING_POP(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_POP)
+#define DUK_HEAP_STRING_JOIN(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JOIN)
+#define DUK_HTHREAD_STRING_JOIN(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JOIN)
+#define DUK_HEAP_STRING_CONCAT(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CONCAT)
+#define DUK_HTHREAD_STRING_CONCAT(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CONCAT)
+#define DUK_HEAP_STRING_IS_ARRAY(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_ARRAY)
+#define DUK_HTHREAD_STRING_IS_ARRAY(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_ARRAY)
+#define DUK_HEAP_STRING_LC_ARGUMENTS(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_ARGUMENTS)
+#define DUK_HTHREAD_STRING_LC_ARGUMENTS(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_ARGUMENTS)
+#define DUK_HEAP_STRING_CALLER(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CALLER)
+#define DUK_HTHREAD_STRING_CALLER(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CALLER)
+#define DUK_HEAP_STRING_BIND(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_BIND)
+#define DUK_HTHREAD_STRING_BIND(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_BIND)
+#define DUK_HEAP_STRING_CALL(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CALL)
+#define DUK_HTHREAD_STRING_CALL(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CALL)
+#define DUK_HEAP_STRING_APPLY(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_APPLY)
+#define DUK_HTHREAD_STRING_APPLY(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_APPLY)
+#define DUK_HEAP_STRING_PROPERTY_IS_ENUMERABLE(heap)                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PROPERTY_IS_ENUMERABLE)
+#define DUK_HTHREAD_STRING_PROPERTY_IS_ENUMERABLE(thr)                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PROPERTY_IS_ENUMERABLE)
+#define DUK_HEAP_STRING_IS_PROTOTYPE_OF(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_PROTOTYPE_OF)
+#define DUK_HTHREAD_STRING_IS_PROTOTYPE_OF(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_PROTOTYPE_OF)
+#define DUK_HEAP_STRING_HAS_OWN_PROPERTY(heap)                        DUK_HEAP_GET_STRING((heap),DUK_STRIDX_HAS_OWN_PROPERTY)
+#define DUK_HTHREAD_STRING_HAS_OWN_PROPERTY(thr)                      DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_HAS_OWN_PROPERTY)
+#define DUK_HEAP_STRING_VALUE_OF(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_VALUE_OF)
+#define DUK_HTHREAD_STRING_VALUE_OF(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_VALUE_OF)
+#define DUK_HEAP_STRING_TO_LOCALE_STRING(heap)                        DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_LOCALE_STRING)
+#define DUK_HTHREAD_STRING_TO_LOCALE_STRING(thr)                      DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_LOCALE_STRING)
+#define DUK_HEAP_STRING_TO_STRING(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_STRING)
+#define DUK_HTHREAD_STRING_TO_STRING(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_STRING)
+#define DUK_HEAP_STRING_CONSTRUCTOR(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CONSTRUCTOR)
+#define DUK_HTHREAD_STRING_CONSTRUCTOR(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CONSTRUCTOR)
+#define DUK_HEAP_STRING_SET(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET)
+#define DUK_HTHREAD_STRING_SET(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET)
+#define DUK_HEAP_STRING_GET(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET)
+#define DUK_HTHREAD_STRING_GET(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET)
+#define DUK_HEAP_STRING_ENUMERABLE(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENUMERABLE)
+#define DUK_HTHREAD_STRING_ENUMERABLE(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENUMERABLE)
+#define DUK_HEAP_STRING_CONFIGURABLE(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CONFIGURABLE)
+#define DUK_HTHREAD_STRING_CONFIGURABLE(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CONFIGURABLE)
+#define DUK_HEAP_STRING_WRITABLE(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_WRITABLE)
+#define DUK_HTHREAD_STRING_WRITABLE(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_WRITABLE)
+#define DUK_HEAP_STRING_VALUE(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_VALUE)
+#define DUK_HTHREAD_STRING_VALUE(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_VALUE)
+#define DUK_HEAP_STRING_KEYS(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_KEYS)
+#define DUK_HTHREAD_STRING_KEYS(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_KEYS)
+#define DUK_HEAP_STRING_IS_EXTENSIBLE(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_EXTENSIBLE)
+#define DUK_HTHREAD_STRING_IS_EXTENSIBLE(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_EXTENSIBLE)
+#define DUK_HEAP_STRING_IS_FROZEN(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_FROZEN)
+#define DUK_HTHREAD_STRING_IS_FROZEN(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_FROZEN)
+#define DUK_HEAP_STRING_IS_SEALED(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_SEALED)
+#define DUK_HTHREAD_STRING_IS_SEALED(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_SEALED)
+#define DUK_HEAP_STRING_PREVENT_EXTENSIONS(heap)                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PREVENT_EXTENSIONS)
+#define DUK_HTHREAD_STRING_PREVENT_EXTENSIONS(thr)                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PREVENT_EXTENSIONS)
+#define DUK_HEAP_STRING_FREEZE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FREEZE)
+#define DUK_HTHREAD_STRING_FREEZE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FREEZE)
+#define DUK_HEAP_STRING_SEAL(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SEAL)
+#define DUK_HTHREAD_STRING_SEAL(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SEAL)
+#define DUK_HEAP_STRING_DEFINE_PROPERTIES(heap)                       DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DEFINE_PROPERTIES)
+#define DUK_HTHREAD_STRING_DEFINE_PROPERTIES(thr)                     DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DEFINE_PROPERTIES)
+#define DUK_HEAP_STRING_DEFINE_PROPERTY(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DEFINE_PROPERTY)
+#define DUK_HTHREAD_STRING_DEFINE_PROPERTY(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DEFINE_PROPERTY)
+#define DUK_HEAP_STRING_CREATE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CREATE)
+#define DUK_HTHREAD_STRING_CREATE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CREATE)
+#define DUK_HEAP_STRING_GET_OWN_PROPERTY_NAMES(heap)                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_OWN_PROPERTY_NAMES)
+#define DUK_HTHREAD_STRING_GET_OWN_PROPERTY_NAMES(thr)                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_OWN_PROPERTY_NAMES)
+#define DUK_HEAP_STRING_GET_OWN_PROPERTY_DESCRIPTOR(heap)             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_OWN_PROPERTY_DESCRIPTOR)
+#define DUK_HTHREAD_STRING_GET_OWN_PROPERTY_DESCRIPTOR(thr)           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_OWN_PROPERTY_DESCRIPTOR)
+#define DUK_HEAP_STRING_GET_PROTOTYPE_OF(heap)                        DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_PROTOTYPE_OF)
+#define DUK_HTHREAD_STRING_GET_PROTOTYPE_OF(thr)                      DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_PROTOTYPE_OF)
+#define DUK_HEAP_STRING_PROTOTYPE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PROTOTYPE)
+#define DUK_HTHREAD_STRING_PROTOTYPE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PROTOTYPE)
+#define DUK_HEAP_STRING_LENGTH(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LENGTH)
+#define DUK_HTHREAD_STRING_LENGTH(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LENGTH)
+#define DUK_HEAP_STRING_ALERT(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ALERT)
+#define DUK_HTHREAD_STRING_ALERT(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ALERT)
+#define DUK_HEAP_STRING_PRINT(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PRINT)
+#define DUK_HTHREAD_STRING_PRINT(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PRINT)
+#define DUK_HEAP_STRING_UNESCAPE(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UNESCAPE)
+#define DUK_HTHREAD_STRING_UNESCAPE(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UNESCAPE)
+#define DUK_HEAP_STRING_ESCAPE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ESCAPE)
+#define DUK_HTHREAD_STRING_ESCAPE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ESCAPE)
+#define DUK_HEAP_STRING_ENCODE_URI_COMPONENT(heap)                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENCODE_URI_COMPONENT)
+#define DUK_HTHREAD_STRING_ENCODE_URI_COMPONENT(thr)                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENCODE_URI_COMPONENT)
+#define DUK_HEAP_STRING_ENCODE_URI(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENCODE_URI)
+#define DUK_HTHREAD_STRING_ENCODE_URI(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENCODE_URI)
+#define DUK_HEAP_STRING_DECODE_URI_COMPONENT(heap)                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DECODE_URI_COMPONENT)
+#define DUK_HTHREAD_STRING_DECODE_URI_COMPONENT(thr)                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DECODE_URI_COMPONENT)
+#define DUK_HEAP_STRING_DECODE_URI(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DECODE_URI)
+#define DUK_HTHREAD_STRING_DECODE_URI(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DECODE_URI)
+#define DUK_HEAP_STRING_IS_FINITE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_FINITE)
+#define DUK_HTHREAD_STRING_IS_FINITE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_FINITE)
+#define DUK_HEAP_STRING_IS_NAN(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_NAN)
+#define DUK_HTHREAD_STRING_IS_NAN(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_NAN)
+#define DUK_HEAP_STRING_PARSE_FLOAT(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PARSE_FLOAT)
+#define DUK_HTHREAD_STRING_PARSE_FLOAT(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PARSE_FLOAT)
+#define DUK_HEAP_STRING_PARSE_INT(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PARSE_INT)
+#define DUK_HTHREAD_STRING_PARSE_INT(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PARSE_INT)
+#define DUK_HEAP_STRING_EVAL(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EVAL)
+#define DUK_HTHREAD_STRING_EVAL(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EVAL)
+#define DUK_HEAP_STRING_URI_ERROR(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_URI_ERROR)
+#define DUK_HTHREAD_STRING_URI_ERROR(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_URI_ERROR)
+#define DUK_HEAP_STRING_TYPE_ERROR(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TYPE_ERROR)
+#define DUK_HTHREAD_STRING_TYPE_ERROR(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TYPE_ERROR)
+#define DUK_HEAP_STRING_SYNTAX_ERROR(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SYNTAX_ERROR)
+#define DUK_HTHREAD_STRING_SYNTAX_ERROR(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SYNTAX_ERROR)
+#define DUK_HEAP_STRING_REFERENCE_ERROR(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REFERENCE_ERROR)
+#define DUK_HTHREAD_STRING_REFERENCE_ERROR(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REFERENCE_ERROR)
+#define DUK_HEAP_STRING_RANGE_ERROR(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_RANGE_ERROR)
+#define DUK_HTHREAD_STRING_RANGE_ERROR(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_RANGE_ERROR)
+#define DUK_HEAP_STRING_EVAL_ERROR(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EVAL_ERROR)
+#define DUK_HTHREAD_STRING_EVAL_ERROR(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EVAL_ERROR)
+#define DUK_HEAP_STRING_BREAK(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_BREAK)
+#define DUK_HTHREAD_STRING_BREAK(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_BREAK)
+#define DUK_HEAP_STRING_CASE(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CASE)
+#define DUK_HTHREAD_STRING_CASE(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CASE)
+#define DUK_HEAP_STRING_CATCH(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CATCH)
+#define DUK_HTHREAD_STRING_CATCH(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CATCH)
+#define DUK_HEAP_STRING_CONTINUE(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CONTINUE)
+#define DUK_HTHREAD_STRING_CONTINUE(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CONTINUE)
+#define DUK_HEAP_STRING_DEBUGGER(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DEBUGGER)
+#define DUK_HTHREAD_STRING_DEBUGGER(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DEBUGGER)
+#define DUK_HEAP_STRING_DEFAULT(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DEFAULT)
+#define DUK_HTHREAD_STRING_DEFAULT(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DEFAULT)
+#define DUK_HEAP_STRING_DELETE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DELETE)
+#define DUK_HTHREAD_STRING_DELETE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DELETE)
+#define DUK_HEAP_STRING_DO(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DO)
+#define DUK_HTHREAD_STRING_DO(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DO)
+#define DUK_HEAP_STRING_ELSE(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ELSE)
+#define DUK_HTHREAD_STRING_ELSE(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ELSE)
+#define DUK_HEAP_STRING_FINALLY(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FINALLY)
+#define DUK_HTHREAD_STRING_FINALLY(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FINALLY)
+#define DUK_HEAP_STRING_FOR(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FOR)
+#define DUK_HTHREAD_STRING_FOR(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FOR)
+#define DUK_HEAP_STRING_LC_FUNCTION(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_FUNCTION)
+#define DUK_HTHREAD_STRING_LC_FUNCTION(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_FUNCTION)
+#define DUK_HEAP_STRING_IF(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IF)
+#define DUK_HTHREAD_STRING_IF(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IF)
+#define DUK_HEAP_STRING_IN(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IN)
+#define DUK_HTHREAD_STRING_IN(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IN)
+#define DUK_HEAP_STRING_INSTANCEOF(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INSTANCEOF)
+#define DUK_HTHREAD_STRING_INSTANCEOF(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INSTANCEOF)
+#define DUK_HEAP_STRING_NEW(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NEW)
+#define DUK_HTHREAD_STRING_NEW(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NEW)
+#define DUK_HEAP_STRING_RETURN(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_RETURN)
+#define DUK_HTHREAD_STRING_RETURN(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_RETURN)
+#define DUK_HEAP_STRING_SWITCH(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SWITCH)
+#define DUK_HTHREAD_STRING_SWITCH(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SWITCH)
+#define DUK_HEAP_STRING_THIS(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_THIS)
+#define DUK_HTHREAD_STRING_THIS(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_THIS)
+#define DUK_HEAP_STRING_THROW(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_THROW)
+#define DUK_HTHREAD_STRING_THROW(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_THROW)
+#define DUK_HEAP_STRING_TRY(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TRY)
+#define DUK_HTHREAD_STRING_TRY(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TRY)
+#define DUK_HEAP_STRING_TYPEOF(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TYPEOF)
+#define DUK_HTHREAD_STRING_TYPEOF(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TYPEOF)
+#define DUK_HEAP_STRING_VAR(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_VAR)
+#define DUK_HTHREAD_STRING_VAR(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_VAR)
+#define DUK_HEAP_STRING_VOID(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_VOID)
+#define DUK_HTHREAD_STRING_VOID(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_VOID)
+#define DUK_HEAP_STRING_WHILE(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_WHILE)
+#define DUK_HTHREAD_STRING_WHILE(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_WHILE)
+#define DUK_HEAP_STRING_WITH(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_WITH)
+#define DUK_HTHREAD_STRING_WITH(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_WITH)
+#define DUK_HEAP_STRING_CLASS(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CLASS)
+#define DUK_HTHREAD_STRING_CLASS(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CLASS)
+#define DUK_HEAP_STRING_CONST(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CONST)
+#define DUK_HTHREAD_STRING_CONST(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CONST)
+#define DUK_HEAP_STRING_ENUM(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENUM)
+#define DUK_HTHREAD_STRING_ENUM(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENUM)
+#define DUK_HEAP_STRING_EXPORT(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EXPORT)
+#define DUK_HTHREAD_STRING_EXPORT(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EXPORT)
+#define DUK_HEAP_STRING_EXTENDS(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EXTENDS)
+#define DUK_HTHREAD_STRING_EXTENDS(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EXTENDS)
+#define DUK_HEAP_STRING_IMPORT(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IMPORT)
+#define DUK_HTHREAD_STRING_IMPORT(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IMPORT)
+#define DUK_HEAP_STRING_SUPER(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SUPER)
+#define DUK_HTHREAD_STRING_SUPER(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SUPER)
+#define DUK_HEAP_STRING_LC_NULL(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_NULL)
+#define DUK_HTHREAD_STRING_LC_NULL(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_NULL)
+#define DUK_HEAP_STRING_TRUE(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TRUE)
+#define DUK_HTHREAD_STRING_TRUE(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TRUE)
+#define DUK_HEAP_STRING_FALSE(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FALSE)
+#define DUK_HTHREAD_STRING_FALSE(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FALSE)
+#define DUK_HEAP_STRING_IMPLEMENTS(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IMPLEMENTS)
+#define DUK_HTHREAD_STRING_IMPLEMENTS(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IMPLEMENTS)
+#define DUK_HEAP_STRING_INTERFACE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INTERFACE)
+#define DUK_HTHREAD_STRING_INTERFACE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INTERFACE)
+#define DUK_HEAP_STRING_LET(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LET)
+#define DUK_HTHREAD_STRING_LET(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LET)
+#define DUK_HEAP_STRING_PACKAGE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PACKAGE)
+#define DUK_HTHREAD_STRING_PACKAGE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PACKAGE)
+#define DUK_HEAP_STRING_PRIVATE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PRIVATE)
+#define DUK_HTHREAD_STRING_PRIVATE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PRIVATE)
+#define DUK_HEAP_STRING_PROTECTED(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PROTECTED)
+#define DUK_HTHREAD_STRING_PROTECTED(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PROTECTED)
+#define DUK_HEAP_STRING_PUBLIC(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PUBLIC)
+#define DUK_HTHREAD_STRING_PUBLIC(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PUBLIC)
+#define DUK_HEAP_STRING_STATIC(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_STATIC)
+#define DUK_HTHREAD_STRING_STATIC(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_STATIC)
+#define DUK_HEAP_STRING_YIELD(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_YIELD)
+#define DUK_HTHREAD_STRING_YIELD(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_YIELD)
+
+#define DUK_HEAP_NUM_STRINGS                                          336
+
+#define DUK_STRIDX_START_RESERVED                                     291
+#define DUK_STRIDX_START_STRICT_RESERVED                              327
+#define DUK_STRIDX_END_RESERVED                                       336                            /* exclusive endpoint */
+
+extern const duk_c_function duk_bi_native_functions[];
+extern const duk_uint8_t duk_builtins_data[];
+#ifdef DUK_USE_BUILTIN_INITJS
+extern const duk_uint8_t duk_initjs_data[];
+#endif  /* DUK_USE_BUILTIN_INITJS */
+
+#define DUK_BUILTINS_DATA_LENGTH                                      1336
+#ifdef DUK_USE_BUILTIN_INITJS
+#define DUK_BUILTIN_INITJS_DATA_LENGTH                                187
+#endif  /* DUK_USE_BUILTIN_INITJS */
+
+#define DUK_BIDX_GLOBAL                                               0
+#define DUK_BIDX_GLOBAL_ENV                                           1
+#define DUK_BIDX_OBJECT_CONSTRUCTOR                                   2
+#define DUK_BIDX_OBJECT_PROTOTYPE                                     3
+#define DUK_BIDX_FUNCTION_CONSTRUCTOR                                 4
+#define DUK_BIDX_FUNCTION_PROTOTYPE                                   5
+#define DUK_BIDX_ARRAY_CONSTRUCTOR                                    6
+#define DUK_BIDX_ARRAY_PROTOTYPE                                      7
+#define DUK_BIDX_STRING_CONSTRUCTOR                                   8
+#define DUK_BIDX_STRING_PROTOTYPE                                     9
+#define DUK_BIDX_BOOLEAN_CONSTRUCTOR                                  10
+#define DUK_BIDX_BOOLEAN_PROTOTYPE                                    11
+#define DUK_BIDX_NUMBER_CONSTRUCTOR                                   12
+#define DUK_BIDX_NUMBER_PROTOTYPE                                     13
+#define DUK_BIDX_DATE_CONSTRUCTOR                                     14
+#define DUK_BIDX_DATE_PROTOTYPE                                       15
+#define DUK_BIDX_REGEXP_CONSTRUCTOR                                   16
+#define DUK_BIDX_REGEXP_PROTOTYPE                                     17
+#define DUK_BIDX_ERROR_CONSTRUCTOR                                    18
+#define DUK_BIDX_ERROR_PROTOTYPE                                      19
+#define DUK_BIDX_EVAL_ERROR_CONSTRUCTOR                               20
+#define DUK_BIDX_EVAL_ERROR_PROTOTYPE                                 21
+#define DUK_BIDX_RANGE_ERROR_CONSTRUCTOR                              22
+#define DUK_BIDX_RANGE_ERROR_PROTOTYPE                                23
+#define DUK_BIDX_REFERENCE_ERROR_CONSTRUCTOR                          24
+#define DUK_BIDX_REFERENCE_ERROR_PROTOTYPE                            25
+#define DUK_BIDX_SYNTAX_ERROR_CONSTRUCTOR                             26
+#define DUK_BIDX_SYNTAX_ERROR_PROTOTYPE                               27
+#define DUK_BIDX_TYPE_ERROR_CONSTRUCTOR                               28
+#define DUK_BIDX_TYPE_ERROR_PROTOTYPE                                 29
+#define DUK_BIDX_URI_ERROR_CONSTRUCTOR                                30
+#define DUK_BIDX_URI_ERROR_PROTOTYPE                                  31
+#define DUK_BIDX_MATH                                                 32
+#define DUK_BIDX_JSON                                                 33
+#define DUK_BIDX_TYPE_ERROR_THROWER                                   34
+#define DUK_BIDX_PROXY_CONSTRUCTOR                                    35
+#define DUK_BIDX_DUKTAPE                                              36
+#define DUK_BIDX_THREAD_CONSTRUCTOR                                   37
+#define DUK_BIDX_THREAD_PROTOTYPE                                     38
+#define DUK_BIDX_BUFFER_CONSTRUCTOR                                   39
+#define DUK_BIDX_BUFFER_PROTOTYPE                                     40
+#define DUK_BIDX_POINTER_CONSTRUCTOR                                  41
+#define DUK_BIDX_POINTER_PROTOTYPE                                    42
+#define DUK_BIDX_LOGGER_CONSTRUCTOR                                   43
+#define DUK_BIDX_LOGGER_PROTOTYPE                                     44
+#define DUK_BIDX_DOUBLE_ERROR                                         45
+
+#define DUK_NUM_BUILTINS                                              46
+
+#else
+#error invalid endianness defines
+#endif
+#endif  /* DUK_BUILTINS_H_INCLUDED */
+#line 50 "duk_internal.h"
+
+#line 1 "duk_strings.h"
+/*
+ *  Shared error messages: externs and macros
+ *
+ *  Error messages are accessed through macros with fine-grained, explicit
+ *  error message distinctions.  Concrete error messages are selected by the
+ *  macros and multiple macros can map to the same concrete string to save
+ *  on code footprint.  This allows flexible footprint/verbosity tuning with
+ *  minimal code impact.  There are a few limitations to this approach:
+ *  (1) switching between plain messages and format strings doesn't work
+ *  conveniently, and (2) conditional strings are a bit awkward to handle.
+ *
+ *  Because format strings behave differently in the call site (they need to
+ *  be followed by format arguments), they have a special prefix (DUK_STR_FMT_
+ *  and duk_str_fmt_).
+ *
+ *  On some compilers using explicit shared strings is preferable; on others
+ *  it may be better to use straight literals because the compiler will combine
+ *  them anyway, and such strings won't end up unnecessarily in a symbol table.
+ */
+
+#ifndef DUK_ERRMSG_H_INCLUDED
+#define DUK_ERRMSG_H_INCLUDED
+
+#define DUK_STR_INTERNAL_ERROR duk_str_internal_error
+#define DUK_STR_INVALID_COUNT duk_str_invalid_count
+#define DUK_STR_INVALID_CALL_ARGS duk_str_invalid_call_args
+#define DUK_STR_NOT_CONSTRUCTABLE duk_str_not_constructable
+#define DUK_STR_NOT_CALLABLE duk_str_not_callable
+#define DUK_STR_NOT_EXTENSIBLE duk_str_not_extensible
+#define DUK_STR_NOT_WRITABLE duk_str_not_writable
+#define DUK_STR_NOT_CONFIGURABLE duk_str_not_configurable
+
+extern const char *duk_str_internal_error;
+extern const char *duk_str_invalid_count;
+extern const char *duk_str_invalid_call_args;
+extern const char *duk_str_not_constructable;
+extern const char *duk_str_not_callable;
+extern const char *duk_str_not_extensible;
+extern const char *duk_str_not_writable;
+extern const char *duk_str_not_configurable;
+
+#define DUK_STR_INVALID_INDEX duk_str_invalid_index
+#define DUK_STR_PUSH_BEYOND_ALLOC_STACK duk_str_push_beyond_alloc_stack
+#define DUK_STR_SRC_STACK_NOT_ENOUGH duk_str_src_stack_not_enough
+#define DUK_STR_NOT_UNDEFINED duk_str_not_undefined
+#define DUK_STR_NOT_NULL duk_str_not_null
+#define DUK_STR_NOT_BOOLEAN duk_str_not_boolean
+#define DUK_STR_NOT_NUMBER duk_str_not_number
+#define DUK_STR_NOT_STRING duk_str_not_string
+#define DUK_STR_NOT_POINTER duk_str_not_pointer
+#define DUK_STR_NOT_BUFFER duk_str_not_buffer
+#define DUK_STR_NOT_OBJECT duk_str_not_object
+#define DUK_STR_UNEXPECTED_TYPE duk_str_unexpected_type
+#define DUK_STR_NOT_THREAD duk_str_not_thread
+#define DUK_STR_NOT_COMPILEDFUNCTION duk_str_not_compiledfunction
+#define DUK_STR_NOT_NATIVEFUNCTION duk_str_not_nativefunction
+#define DUK_STR_NOT_C_FUNCTION duk_str_not_c_function
+#define DUK_STR_DEFAULTVALUE_COERCE_FAILED duk_str_defaultvalue_coerce_failed
+#define DUK_STR_NUMBER_OUTSIDE_RANGE duk_str_number_outside_range
+#define DUK_STR_NOT_OBJECT_COERCIBLE duk_str_not_object_coercible
+#define DUK_STR_STRING_TOO_LONG duk_str_string_too_long
+#define DUK_STR_BUFFER_TOO_LONG duk_str_buffer_too_long
+#define DUK_STR_SPRINTF_TOO_LONG duk_str_sprintf_too_long
+#define DUK_STR_OBJECT_ALLOC_FAILED duk_str_object_alloc_failed
+#define DUK_STR_THREAD_ALLOC_FAILED duk_str_thread_alloc_failed
+#define DUK_STR_FUNC_ALLOC_FAILED duk_str_func_alloc_failed
+#define DUK_STR_BUFFER_ALLOC_FAILED duk_str_buffer_alloc_failed
+#define DUK_STR_POP_TOO_MANY duk_str_pop_too_many
+
+extern const char *duk_str_invalid_index;
+extern const char *duk_str_push_beyond_alloc_stack;
+extern const char *duk_str_src_stack_not_enough;
+extern const char *duk_str_not_undefined;
+extern const char *duk_str_not_null;
+extern const char *duk_str_not_boolean;
+extern const char *duk_str_not_number;
+extern const char *duk_str_not_string;
+extern const char *duk_str_not_pointer;
+extern const char *duk_str_not_buffer;
+extern const char *duk_str_not_object;
+extern const char *duk_str_unexpected_type;
+extern const char *duk_str_not_thread;
+extern const char *duk_str_not_compiledfunction;
+extern const char *duk_str_not_nativefunction;
+extern const char *duk_str_not_c_function;
+extern const char *duk_str_defaultvalue_coerce_failed;
+extern const char *duk_str_number_outside_range;
+extern const char *duk_str_not_object_coercible;
+extern const char *duk_str_string_too_long;
+extern const char *duk_str_buffer_too_long;
+extern const char *duk_str_sprintf_too_long;
+extern const char *duk_str_object_alloc_failed;
+extern const char *duk_str_thread_alloc_failed;
+extern const char *duk_str_func_alloc_failed;
+extern const char *duk_str_buffer_alloc_failed;
+extern const char *duk_str_pop_too_many;
+
+#define DUK_STR_FMT_PTR duk_str_fmt_ptr
+#define DUK_STR_INVALID_JSON duk_str_invalid_json
+#define DUK_STR_INVALID_NUMBER duk_str_invalid_number
+#define DUK_STR_JSONDEC_RECLIMIT duk_str_jsondec_reclimit
+#define DUK_STR_JSONENC_RECLIMIT duk_str_jsonenc_reclimit
+#define DUK_STR_CYCLIC_INPUT duk_str_cyclic_input
+
+extern const char *duk_str_fmt_ptr;
+extern const char *duk_str_invalid_json;
+extern const char *duk_str_invalid_number;
+extern const char *duk_str_jsondec_reclimit;
+extern const char *duk_str_jsonenc_reclimit;
+extern const char *duk_str_cyclic_input;
+
+#define DUK_STR_PROXY_REVOKED duk_str_proxy_revoked
+#define DUK_STR_OBJECT_RESIZE_FAILED duk_str_object_resize_failed
+#define DUK_STR_INVALID_BASE duk_str_invalid_base
+#define DUK_STR_STRICT_CALLER_READ duk_str_strict_caller_read
+#define DUK_STR_PROXY_REJECTED duk_str_proxy_rejected
+#define DUK_STR_INVALID_ARRAY_LENGTH duk_str_invalid_array_length
+#define DUK_STR_ARRAY_LENGTH_WRITE_FAILED duk_str_array_length_write_failed
+#define DUK_STR_ARRAY_LENGTH_NOT_WRITABLE duk_str_array_length_not_writable
+#define DUK_STR_SETTER_UNDEFINED duk_str_setter_undefined
+#define DUK_STR_REDEFINE_VIRT_PROP duk_str_redefine_virt_prop
+#define DUK_STR_INVALID_DESCRIPTOR duk_str_invalid_descriptor
+#define DUK_STR_PROPERTY_IS_VIRTUAL duk_str_property_is_virtual
+
+extern const char *duk_str_proxy_revoked;
+extern const char *duk_str_object_resize_failed;
+extern const char *duk_str_invalid_base;
+extern const char *duk_str_strict_caller_read;
+extern const char *duk_str_proxy_rejected;
+extern const char *duk_str_invalid_array_length;
+extern const char *duk_str_array_length_write_failed;
+extern const char *duk_str_array_length_not_writable;
+extern const char *duk_str_setter_undefined;
+extern const char *duk_str_redefine_virt_prop;
+extern const char *duk_str_invalid_descriptor;
+extern const char *duk_str_property_is_virtual;
+
+#define DUK_STR_PARSE_ERROR duk_str_parse_error
+#define DUK_STR_DUPLICATE_LABEL duk_str_duplicate_label
+#define DUK_STR_INVALID_LABEL duk_str_invalid_label
+#define DUK_STR_INVALID_ARRAY_LITERAL duk_str_invalid_array_literal
+#define DUK_STR_INVALID_OBJECT_LITERAL duk_str_invalid_object_literal
+#define DUK_STR_INVALID_VAR_DECLARATION duk_str_invalid_var_declaration
+#define DUK_STR_CANNOT_DELETE_IDENTIFIER duk_str_cannot_delete_identifier
+#define DUK_STR_INVALID_EXPRESSION duk_str_invalid_expression
+#define DUK_STR_INVALID_LVALUE duk_str_invalid_lvalue
+#define DUK_STR_EXPECTED_IDENTIFIER duk_str_expected_identifier
+#define DUK_STR_EMPTY_EXPR_NOT_ALLOWED duk_str_empty_expr_not_allowed
+#define DUK_STR_INVALID_FOR duk_str_invalid_for
+#define DUK_STR_INVALID_SWITCH duk_str_invalid_switch
+#define DUK_STR_INVALID_BREAK_CONT_LABEL duk_str_invalid_break_cont_label
+#define DUK_STR_INVALID_RETURN duk_str_invalid_return
+#define DUK_STR_INVALID_TRY duk_str_invalid_try
+#define DUK_STR_WITH_IN_STRICT_MODE duk_str_with_in_strict_mode
+#define DUK_STR_FUNC_STMT_NOT_ALLOWED duk_str_func_stmt_not_allowed
+#define DUK_STR_UNTERMINATED_STMT duk_str_unterminated_stmt
+#define DUK_STR_INVALID_ARG_NAME duk_str_invalid_arg_name
+#define DUK_STR_INVALID_FUNC_NAME duk_str_invalid_func_name
+#define DUK_STR_INVALID_GETSET_NAME duk_str_invalid_getset_name
+#define DUK_STR_FUNC_NAME_REQUIRED duk_str_func_name_required
+
+extern const char *duk_str_parse_error;
+extern const char *duk_str_duplicate_label;
+extern const char *duk_str_invalid_label;
+extern const char *duk_str_invalid_array_literal;
+extern const char *duk_str_invalid_object_literal;
+extern const char *duk_str_invalid_var_declaration;
+extern const char *duk_str_cannot_delete_identifier;
+extern const char *duk_str_invalid_expression;
+extern const char *duk_str_invalid_lvalue;
+extern const char *duk_str_expected_identifier;
+extern const char *duk_str_empty_expr_not_allowed;
+extern const char *duk_str_invalid_for;
+extern const char *duk_str_invalid_switch;
+extern const char *duk_str_invalid_break_cont_label;
+extern const char *duk_str_invalid_return;
+extern const char *duk_str_invalid_try;
+extern const char *duk_str_with_in_strict_mode;
+extern const char *duk_str_func_stmt_not_allowed;
+extern const char *duk_str_unterminated_stmt;
+extern const char *duk_str_invalid_arg_name;
+extern const char *duk_str_invalid_func_name;
+extern const char *duk_str_invalid_getset_name;
+extern const char *duk_str_func_name_required;
+
+#define DUK_STR_INTERNAL_ERROR_EXEC_LONGJMP duk_str_internal_error_exec_longjmp
+
+extern const char *duk_str_internal_error_exec_longjmp;
+
+#define DUK_STR_VALSTACK_LIMIT duk_str_valstack_limit
+#define DUK_STR_OBJECT_PROPERTY_LIMIT duk_str_object_property_limit
+#define DUK_STR_PROTOTYPE_CHAIN_LIMIT duk_str_prototype_chain_limit
+#define DUK_STR_BOUND_CHAIN_LIMIT duk_str_bound_chain_limit
+#define DUK_STR_C_CALLSTACK_LIMIT duk_str_c_callstack_limit
+#define DUK_STR_COMPILER_RECURSION_LIMIT duk_str_compiler_recursion_limit
+#define DUK_STR_BYTECODE_LIMIT duk_str_bytecode_limit
+#define DUK_STR_REG_LIMIT duk_str_reg_limit
+#define DUK_STR_TEMP_LIMIT duk_str_temp_limit
+#define DUK_STR_CONST_LIMIT duk_str_const_limit
+#define DUK_STR_FUNC_LIMIT duk_str_func_limit
+
+extern const char *duk_str_valstack_limit;
+extern const char *duk_str_object_property_limit;
+extern const char *duk_str_prototype_chain_limit;
+extern const char *duk_str_bound_chain_limit;
+extern const char *duk_str_c_callstack_limit;
+extern const char *duk_str_compiler_recursion_limit;
+extern const char *duk_str_bytecode_limit;
+extern const char *duk_str_reg_limit;
+extern const char *duk_str_temp_limit;
+extern const char *duk_str_const_limit;
+extern const char *duk_str_func_limit;
+
+#endif  /* DUK_ERRMSG_H_INCLUDED */
+#line 1 "duk_js_bytecode.h"
+/*
+ *  Ecmascript bytecode
+ */
+
+#ifndef DUK_JS_BYTECODE_H_INCLUDED
+#define DUK_JS_BYTECODE_H_INCLUDED
+
+/*
+ *  Logical instruction layout
+ *  ==========================
+ *
+ *  !3!3!2!2!2!2!2!2!2!2!2!2!1!1!1!1!1!1!1!1!1!1! ! ! ! ! ! ! ! ! ! !
+ *  !1!0!9!8!7!6!5!4!3!2!1!0!9!8!7!6!5!4!3!2!1!0!9!8!7!6!5!4!3!2!1!0!
+ *  +---------------------------------------------------+-----------+
+ *  !       C         !       B         !      A        !    OP     !
+ *  +---------------------------------------------------+-----------+
+ *
+ *  OP (6 bits):  opcode (DUK_OP_*), access should be fastest
+ *  A (8 bits):   typically a target register number
+ *  B (9 bits):   typically first source register/constant number
+ *  C (9 bits):   typically second source register/constant number
+ *
+ *  Some instructions combine BC or ABC together for larger parameter values.
+ *  Signed integers (e.g. jump offsets) are encoded as unsigned, with an opcode
+ *  specific bias.  B and C may denote a register or a constant, see
+ *  DUK_BC_ISREG() and DUK_BC_ISCONST().
+ *
+ *  Note: macro naming is a bit misleading, e.g. "ABC" in macro name but
+ *  the field layout is logically "CBA".
+ */ 
+
+typedef duk_uint32_t duk_instr_t;
+
+#define DUK_DEC_OP(x)               ((x) & 0x3fUL)
+#define DUK_DEC_A(x)                (((x) >> 6) & 0xffUL)
+#define DUK_DEC_B(x)                (((x) >> 14) & 0x1ffUL)
+#define DUK_DEC_C(x)                (((x) >> 23) & 0x1ffUL)
+#define DUK_DEC_BC(x)               (((x) >> 14) & 0x3ffffUL)
+#define DUK_DEC_ABC(x)              (((x) >> 6) & 0x3ffffffUL)
+
+#define DUK_ENC_OP_ABC(op,abc)      ((duk_instr_t) ( \
+                                        (((duk_instr_t) (abc)) << 6) | \
+                                        ((duk_instr_t) (op)) \
+                                    ))
+#define DUK_ENC_OP_A_BC(op,a,bc)    ((duk_instr_t) ( \
+                                        (((duk_instr_t) (bc)) << 14) | \
+                                        (((duk_instr_t) (a)) << 6) | \
+                                        ((duk_instr_t) (op)) \
+                                    ))
+#define DUK_ENC_OP_A_B_C(op,a,b,c)  ((duk_instr_t) ( \
+                                        (((duk_instr_t) (c)) << 23) | \
+                                        (((duk_instr_t) (b)) << 14) | \
+                                        (((duk_instr_t) (a)) << 6) | \
+                                        ((duk_instr_t) (op)) \
+                                    ))
+#define DUK_ENC_OP_A_B(op,a,b)      DUK_ENC_OP_A_B_C(op,a,b,0)
+#define DUK_ENC_OP_A(op,a)          DUK_ENC_OP_A_B_C(op,a,0,0)
+
+/* Constants should be signed so that signed arithmetic involving them
+ * won't cause values to be coerced accidentally to unsigned.
+ */
+#define DUK_BC_OP_MIN               0
+#define DUK_BC_OP_MAX               0x3fL
+#define DUK_BC_A_MIN                0
+#define DUK_BC_A_MAX                0xffL
+#define DUK_BC_B_MIN                0
+#define DUK_BC_B_MAX                0x1ffL
+#define DUK_BC_C_MIN                0
+#define DUK_BC_C_MAX                0x1ffL
+#define DUK_BC_BC_MIN               0
+#define DUK_BC_BC_MAX               0x3ffffL
+#define DUK_BC_ABC_MIN              0
+#define DUK_BC_ABC_MAX              0x3ffffffL
+#define DUK_BC_EXTRAOP_MIN          DUK_BC_A_MIN
+#define DUK_BC_EXTRAOP_MAX          DUK_BC_A_MAX
+
+#define DUK_OP_LDREG                0 
+#define DUK_OP_STREG                1
+#define DUK_OP_LDCONST              2
+#define DUK_OP_LDINT                3
+#define DUK_OP_LDINTX               4
+#define DUK_OP_MPUTOBJ              5
+#define DUK_OP_MPUTOBJI             6
+#define DUK_OP_MPUTARR              7
+#define DUK_OP_MPUTARRI             8
+#define DUK_OP_NEW                  9
+#define DUK_OP_NEWI                 10
+#define DUK_OP_REGEXP               11
+#define DUK_OP_CSREG                12
+#define DUK_OP_CSREGI               13
+#define DUK_OP_GETVAR               14
+#define DUK_OP_PUTVAR               15
+#define DUK_OP_DECLVAR              16
+#define DUK_OP_DELVAR               17
+#define DUK_OP_CSVAR                18
+#define DUK_OP_CSVARI               19
+#define DUK_OP_CLOSURE              20
+#define DUK_OP_GETPROP              21
+#define DUK_OP_PUTPROP              22
+#define DUK_OP_DELPROP              23
+#define DUK_OP_CSPROP               24
+#define DUK_OP_CSPROPI              25
+#define DUK_OP_ADD                  26
+#define DUK_OP_SUB                  27
+#define DUK_OP_MUL                  28
+#define DUK_OP_DIV                  29
+#define DUK_OP_MOD                  30
+#define DUK_OP_BAND                 31
+#define DUK_OP_BOR                  32
+#define DUK_OP_BXOR                 33
+#define DUK_OP_BASL                 34
+#define DUK_OP_BLSR                 35
+#define DUK_OP_BASR                 36
+#define DUK_OP_BNOT                 37
+#define DUK_OP_LNOT                 38
+#define DUK_OP_EQ                   39
+#define DUK_OP_NEQ                  40
+#define DUK_OP_SEQ                  41
+#define DUK_OP_SNEQ                 42
+#define DUK_OP_GT                   43
+#define DUK_OP_GE                   44
+#define DUK_OP_LT                   45
+#define DUK_OP_LE                   46
+#define DUK_OP_IF                   47
+#define DUK_OP_INSTOF               48
+#define DUK_OP_IN                   49
+#define DUK_OP_JUMP                 50
+#define DUK_OP_RETURN               51
+#define DUK_OP_CALL                 52
+#define DUK_OP_CALLI                53
+#define DUK_OP_LABEL                54
+#define DUK_OP_ENDLABEL             55
+#define DUK_OP_BREAK                56
+#define DUK_OP_CONTINUE             57
+#define DUK_OP_TRYCATCH             58
+#define DUK_OP_UNUSED59             59
+#define DUK_OP_UNUSED60             60
+#define DUK_OP_UNUSED61             61
+#define DUK_OP_EXTRA                62
+#define DUK_OP_INVALID              63
+
+/* DUK_OP_EXTRA, sub-operation in A */
+#define DUK_EXTRAOP_NOP             0
+#define DUK_EXTRAOP_LDTHIS          1
+#define DUK_EXTRAOP_LDUNDEF         2
+#define DUK_EXTRAOP_LDNULL          3
+#define DUK_EXTRAOP_LDTRUE          4
+#define DUK_EXTRAOP_LDFALSE         5
+#define DUK_EXTRAOP_NEWOBJ          6
+#define DUK_EXTRAOP_NEWARR          7
+#define DUK_EXTRAOP_SETALEN         8
+#define DUK_EXTRAOP_TYPEOF          9
+#define DUK_EXTRAOP_TYPEOFID        10
+#define DUK_EXTRAOP_TONUM           11
+#define DUK_EXTRAOP_INITENUM        12
+#define DUK_EXTRAOP_NEXTENUM        13
+#define DUK_EXTRAOP_INITSET         14
+#define DUK_EXTRAOP_INITSETI        15
+#define DUK_EXTRAOP_INITGET         16
+#define DUK_EXTRAOP_INITGETI        17
+#define DUK_EXTRAOP_ENDTRY          18
+#define DUK_EXTRAOP_ENDCATCH        19
+#define DUK_EXTRAOP_ENDFIN          20
+#define DUK_EXTRAOP_THROW           21
+#define DUK_EXTRAOP_INVLHS          22
+#define DUK_EXTRAOP_UNM             23
+#define DUK_EXTRAOP_UNP             24
+#define DUK_EXTRAOP_INC             25
+#define DUK_EXTRAOP_DEC             26
+
+/* DUK_OP_EXTRA for debugging */
+#define DUK_EXTRAOP_DUMPREG         128
+#define DUK_EXTRAOP_DUMPREGS        129
+#define DUK_EXTRAOP_DUMPTHREAD      130
+#define DUK_EXTRAOP_LOGMARK         131
+
+/* DUK_OP_CALL flags in A */
+#define DUK_BC_CALL_FLAG_TAILCALL           (1 << 0)
+#define DUK_BC_CALL_FLAG_EVALCALL           (1 << 1)
+
+/* DUK_OP_TRYCATCH flags in A */
+#define DUK_BC_TRYCATCH_FLAG_HAVE_CATCH     (1 << 0)
+#define DUK_BC_TRYCATCH_FLAG_HAVE_FINALLY   (1 << 1)
+#define DUK_BC_TRYCATCH_FLAG_CATCH_BINDING  (1 << 2)
+#define DUK_BC_TRYCATCH_FLAG_WITH_BINDING   (1 << 3)
+
+/* DUK_OP_RETURN flags in A */
+#define DUK_BC_RETURN_FLAG_FAST             (1 << 0)
+#define DUK_BC_RETURN_FLAG_HAVE_RETVAL      (1 << 1)
+
+/* DUK_OP_DECLVAR flags in A; bottom bits are reserved for propdesc flags (DUK_PROPDESC_FLAG_XXX) */
+#define DUK_BC_DECLVAR_FLAG_UNDEF_VALUE     (1 << 4)  /* use 'undefined' for value automatically */
+#define DUK_BC_DECLVAR_FLAG_FUNC_DECL       (1 << 5)  /* function declaration */
+
+/* misc constants and helper macros */
+#define DUK_BC_REGLIMIT             256  /* if B/C is >= this value, refers to a const */
+#define DUK_BC_ISREG(x)             ((x) < DUK_BC_REGLIMIT)
+#define DUK_BC_ISCONST(x)           ((x) >= DUK_BC_REGLIMIT)
+#define DUK_BC_LDINT_BIAS           (1L << 17)
+#define DUK_BC_LDINTX_SHIFT         18
+#define DUK_BC_JUMP_BIAS            (1L << 25)
+
+#endif  /* DUK_JS_BYTECODE_H_INCLUDED */
+
+#line 1 "duk_lexer.h"
+/*
+ *  Lexer defines.
+ */
+
+#ifndef DUK_LEXER_H_INCLUDED
+#define DUK_LEXER_H_INCLUDED
+
+typedef void (*duk_re_range_callback)(void *user, duk_codepoint_t r1, duk_codepoint_t r2, duk_bool_t direct);
+
+/*
+ *  A token is interpreted as any possible production of InputElementDiv
+ *  and InputElementRegExp, see E5 Section 7 in its entirety.  Note that
+ *  the E5 "Token" production does not cover all actual tokens of the
+ *  language (which is explicitly stated in the specification, Section 7.5).
+ *  Null and boolean literals are defined as part of both ReservedWord
+ *  (E5 Section 7.6.1) and Literal (E5 Section 7.8) productions.  Here,
+ *  null and boolean values have literal tokens, and are not reserved
+ *  words.
+ *
+ *  Decimal literal negative/positive sign is -not- part of DUK_TOK_NUMBER.
+ *  The number tokens always have a non-negative value.  The unary minus
+ *  operator in "-1.0" is optimized during compilation to yield a single
+ *  negative constant.
+ *
+ *  Token numbering is free except that reserved words are required to be
+ *  in a continuous range and in a particular order.  See genstrings.py.
+ */
+
+#define DUK_LEXER_INITCTX(ctx)        duk_lexer_initctx((ctx))
+
+#define DUK_LEXER_SETPOINT(ctx,pt)    duk_lexer_setpoint((ctx), (pt))
+
+#define DUK_LEXER_GETPOINT(ctx,pt)    do { (pt)->offset = (ctx)->offsets[0]; \
+                                           (pt)->line = (ctx)->lines[0]; } while (0)
+
+/* currently 6 characters of lookup are actually needed (duk_lexer.c) */
+#define DUK_LEXER_WINDOW_SIZE                     8
+
+#define DUK_TOK_MINVAL                            0
+
+/* returned after EOF (infinite amount) */
+#define DUK_TOK_EOF                               0
+
+/* line terminator or multi-line comment with internal lineterm (E5 Sections 7.3, 7.4) */
+#define DUK_TOK_LINETERM                          1
+
+/* single-line comment or multi-line comment without internal lineterm (E5 Section 7.4) */
+#define DUK_TOK_COMMENT                           2
+
+/* identifier names (E5 Section 7.6) */
+#define DUK_TOK_IDENTIFIER                        3
+
+/* reserved words: keywords */
+#define DUK_TOK_START_RESERVED                    4
+#define DUK_TOK_BREAK                             4
+#define DUK_TOK_CASE                              5
+#define DUK_TOK_CATCH                             6
+#define DUK_TOK_CONTINUE                          7
+#define DUK_TOK_DEBUGGER                          8
+#define DUK_TOK_DEFAULT                           9
+#define DUK_TOK_DELETE                            10
+#define DUK_TOK_DO                                11
+#define DUK_TOK_ELSE                              12
+#define DUK_TOK_FINALLY                           13
+#define DUK_TOK_FOR                               14
+#define DUK_TOK_FUNCTION                          15
+#define DUK_TOK_IF                                16
+#define DUK_TOK_IN                                17
+#define DUK_TOK_INSTANCEOF                        18
+#define DUK_TOK_NEW                               19
+#define DUK_TOK_RETURN                            20
+#define DUK_TOK_SWITCH                            21
+#define DUK_TOK_THIS                              22
+#define DUK_TOK_THROW                             23
+#define DUK_TOK_TRY                               24
+#define DUK_TOK_TYPEOF                            25
+#define DUK_TOK_VAR                               26
+#define DUK_TOK_VOID                              27
+#define DUK_TOK_WHILE                             28
+#define DUK_TOK_WITH                              29
+
+/* reserved words: future reserved words */
+#define DUK_TOK_CLASS                             30
+#define DUK_TOK_CONST                             31
+#define DUK_TOK_ENUM                              32
+#define DUK_TOK_EXPORT                            33
+#define DUK_TOK_EXTENDS                           34
+#define DUK_TOK_IMPORT                            35
+#define DUK_TOK_SUPER                             36
+
+/* "null", "true", and "false" are always reserved words.
+ * Note that "get" and "set" are not!
+ */
+#define DUK_TOK_NULL                              37
+#define DUK_TOK_TRUE                              38
+#define DUK_TOK_FALSE                             39
+
+/* reserved words: additional future reserved words in strict mode */
+#define DUK_TOK_START_STRICT_RESERVED             40  /* inclusive */
+#define DUK_TOK_IMPLEMENTS                        40
+#define DUK_TOK_INTERFACE                         41
+#define DUK_TOK_LET                               42
+#define DUK_TOK_PACKAGE                           43
+#define DUK_TOK_PRIVATE                           44
+#define DUK_TOK_PROTECTED                         45
+#define DUK_TOK_PUBLIC                            46
+#define DUK_TOK_STATIC                            47
+#define DUK_TOK_YIELD                             48
+
+#define DUK_TOK_END_RESERVED                      49  /* exclusive */
+
+/* "get" and "set" are tokens but NOT ReservedWords.  They are currently
+ * parsed and identifiers and these defines are actually now unused.
+ */
+#define DUK_TOK_GET                               49
+#define DUK_TOK_SET                               50
+
+/* punctuators (unlike the spec, also includes "/" and "/=") */
+#define DUK_TOK_LCURLY                            51
+#define DUK_TOK_RCURLY                            52
+#define DUK_TOK_LBRACKET                          53
+#define DUK_TOK_RBRACKET                          54
+#define DUK_TOK_LPAREN                            55
+#define DUK_TOK_RPAREN                            56
+#define DUK_TOK_PERIOD                            57
+#define DUK_TOK_SEMICOLON                         58
+#define DUK_TOK_COMMA                             59
+#define DUK_TOK_LT                                60
+#define DUK_TOK_GT                                61
+#define DUK_TOK_LE                                62
+#define DUK_TOK_GE                                63
+#define DUK_TOK_EQ                                64
+#define DUK_TOK_NEQ                               65
+#define DUK_TOK_SEQ                               66
+#define DUK_TOK_SNEQ                              67
+#define DUK_TOK_ADD                               68
+#define DUK_TOK_SUB                               69
+#define DUK_TOK_MUL                               70
+#define DUK_TOK_DIV                               71
+#define DUK_TOK_MOD                               72
+#define DUK_TOK_INCREMENT                         73
+#define DUK_TOK_DECREMENT                         74
+#define DUK_TOK_ALSHIFT                           75  /* named "arithmetic" because result is signed */
+#define DUK_TOK_ARSHIFT                           76
+#define DUK_TOK_RSHIFT                            77
+#define DUK_TOK_BAND                              78
+#define DUK_TOK_BOR                               79
+#define DUK_TOK_BXOR                              80
+#define DUK_TOK_LNOT                              81
+#define DUK_TOK_BNOT                              82
+#define DUK_TOK_LAND                              83
+#define DUK_TOK_LOR                               84
+#define DUK_TOK_QUESTION                          85
+#define DUK_TOK_COLON                             86
+#define DUK_TOK_EQUALSIGN                         87
+#define DUK_TOK_ADD_EQ                            88
+#define DUK_TOK_SUB_EQ                            89
+#define DUK_TOK_MUL_EQ                            90
+#define DUK_TOK_DIV_EQ                            91
+#define DUK_TOK_MOD_EQ                            92
+#define DUK_TOK_ALSHIFT_EQ                        93
+#define DUK_TOK_ARSHIFT_EQ                        94
+#define DUK_TOK_RSHIFT_EQ                         95
+#define DUK_TOK_BAND_EQ                           96
+#define DUK_TOK_BOR_EQ                            97
+#define DUK_TOK_BXOR_EQ                           98
+
+/* literals (E5 Section 7.8), except null, true, false, which are treated
+ * like reserved words (above).
+ */
+#define DUK_TOK_NUMBER                            99
+#define DUK_TOK_STRING                            100
+#define DUK_TOK_REGEXP                            101
+
+#define DUK_TOK_MAXVAL                            101  /* inclusive */
+
+/* Convert heap string index to a token (reserved words) */
+#define DUK_STRIDX_TO_TOK(x)                        ((x) - DUK_STRIDX_START_RESERVED + DUK_TOK_START_RESERVED)
+
+/* Sanity check */
+#if (DUK_TOK_MAXVAL > 255)
+#error DUK_TOK_MAXVAL too large, code assumes it fits into 8 bits
+#endif
+
+/* Sanity checks for string and token defines */
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_BREAK) != DUK_TOK_BREAK)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_CASE) != DUK_TOK_CASE)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_CATCH) != DUK_TOK_CATCH)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_CONTINUE) != DUK_TOK_CONTINUE)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_DEBUGGER) != DUK_TOK_DEBUGGER)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_DEFAULT) != DUK_TOK_DEFAULT)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_DELETE) != DUK_TOK_DELETE)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_DO) != DUK_TOK_DO)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_ELSE) != DUK_TOK_ELSE)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_FINALLY) != DUK_TOK_FINALLY)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_FOR) != DUK_TOK_FOR)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_LC_FUNCTION) != DUK_TOK_FUNCTION)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_IF) != DUK_TOK_IF)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_IN) != DUK_TOK_IN)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_INSTANCEOF) != DUK_TOK_INSTANCEOF)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_NEW) != DUK_TOK_NEW)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_RETURN) != DUK_TOK_RETURN)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_SWITCH) != DUK_TOK_SWITCH)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_THIS) != DUK_TOK_THIS)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_THROW) != DUK_TOK_THROW)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_TRY) != DUK_TOK_TRY)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_TYPEOF) != DUK_TOK_TYPEOF)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_VAR) != DUK_TOK_VAR)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_VOID) != DUK_TOK_VOID)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_WHILE) != DUK_TOK_WHILE)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_WITH) != DUK_TOK_WITH)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_CLASS) != DUK_TOK_CLASS)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_CONST) != DUK_TOK_CONST)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_ENUM) != DUK_TOK_ENUM)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_EXPORT) != DUK_TOK_EXPORT)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_EXTENDS) != DUK_TOK_EXTENDS)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_IMPORT) != DUK_TOK_IMPORT)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_SUPER) != DUK_TOK_SUPER)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_LC_NULL) != DUK_TOK_NULL)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_TRUE) != DUK_TOK_TRUE)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_FALSE) != DUK_TOK_FALSE)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_IMPLEMENTS) != DUK_TOK_IMPLEMENTS)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_INTERFACE) != DUK_TOK_INTERFACE)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_LET) != DUK_TOK_LET)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_PACKAGE) != DUK_TOK_PACKAGE)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_PRIVATE) != DUK_TOK_PRIVATE)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_PROTECTED) != DUK_TOK_PROTECTED)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_PUBLIC) != DUK_TOK_PUBLIC)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_STATIC) != DUK_TOK_STATIC)
+#error mismatch in token defines
+#endif
+#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_YIELD) != DUK_TOK_YIELD)
+#error mismatch in token defines
+#endif
+
+/* Regexp tokens */
+#define DUK_RETOK_EOF                              0
+#define DUK_RETOK_DISJUNCTION                      1
+#define DUK_RETOK_QUANTIFIER                       2
+#define DUK_RETOK_ASSERT_START                     3
+#define DUK_RETOK_ASSERT_END                       4
+#define DUK_RETOK_ASSERT_WORD_BOUNDARY             5
+#define DUK_RETOK_ASSERT_NOT_WORD_BOUNDARY         6
+#define DUK_RETOK_ASSERT_START_POS_LOOKAHEAD       7
+#define DUK_RETOK_ASSERT_START_NEG_LOOKAHEAD       8
+#define DUK_RETOK_ATOM_PERIOD                      9
+#define DUK_RETOK_ATOM_CHAR                        10
+#define DUK_RETOK_ATOM_DIGIT                       11
+#define DUK_RETOK_ATOM_NOT_DIGIT                   12
+#define DUK_RETOK_ATOM_WHITE                       13
+#define DUK_RETOK_ATOM_NOT_WHITE                   14
+#define DUK_RETOK_ATOM_WORD_CHAR                   15
+#define DUK_RETOK_ATOM_NOT_WORD_CHAR               16
+#define DUK_RETOK_ATOM_BACKREFERENCE               17
+#define DUK_RETOK_ATOM_START_CAPTURE_GROUP         18
+#define DUK_RETOK_ATOM_START_NONCAPTURE_GROUP      19
+#define DUK_RETOK_ATOM_START_CHARCLASS             20
+#define DUK_RETOK_ATOM_START_CHARCLASS_INVERTED    21
+#define DUK_RETOK_ATOM_END_GROUP                   22
+
+/* constants for duk_lexer_ctx.buf */
+#define DUK_LEXER_TEMP_BUF_INITIAL                 64
+#define DUK_LEXER_TEMP_BUF_LIMIT                   256
+
+/* A token value.  Can be memcpy()'d, but note that slot1/slot2 values are on the valstack. */
+struct duk_token {
+	duk_small_int_t t;            /* token type (with reserved word identification) */
+	duk_small_int_t t_nores;      /* token type (with reserved words as DUK_TOK_IDENTIFER) */
+	duk_double_t num;             /* numeric value of token */
+	duk_hstring *str1;            /* string 1 of token (borrowed, stored to ctx->slot1_idx) */
+	duk_hstring *str2;            /* string 2 of token (borrowed, stored to ctx->slot1_idx) */
+	duk_size_t start_offset;      /* start byte offset of token in lexer input */
+	duk_int_t start_line;         /* start line of token (first char) */
+	duk_int_t end_line;           /* end line of token (char after last token char) */
+	duk_int_t num_escapes;        /* number of escapes and line continuations (for directive prologue) */
+	duk_bool_t lineterm;          /* token was preceded by a lineterm */
+	duk_bool_t allow_auto_semi;   /* token allows automatic semicolon insertion (eof or preceded by newline) */
+};
+
+#define DUK_RE_QUANTIFIER_INFINITE         ((duk_uint32_t) 0xffffffffUL)
+
+/* A regexp token value. */
+struct duk_re_token {
+	duk_small_int_t t;           /* token type */
+	duk_small_int_t greedy;
+	duk_uint_fast32_t num;       /* numeric value (character, count) */
+	duk_uint_fast32_t qmin;
+	duk_uint_fast32_t qmax;
+};
+
+/* A structure for 'snapshotting' a point for rewinding */
+struct duk_lexer_point {
+	duk_size_t offset;
+	duk_int_t line;
+};
+
+/* Lexer context.  Same context is used for Ecmascript and Regexp parsing. */
+struct duk_lexer_ctx {
+	duk_hthread *thr;                              /* thread; minimizes argument passing */
+
+	const duk_uint8_t *input;                      /* input string (may be a user pointer) */
+	duk_size_t input_length;                       /* input byte length */
+	duk_size_t input_offset;                       /* input offset for window leading edge (not window[0]) */
+
+	duk_codepoint_t window[DUK_LEXER_WINDOW_SIZE]; /* window of unicode code points */
+	duk_size_t offsets[DUK_LEXER_WINDOW_SIZE];     /* input byte offset for each char */
+	duk_int_t lines[DUK_LEXER_WINDOW_SIZE];        /* input lines for each char */
+	duk_int_t input_line;                          /* input linenumber at input_offset (not window[0]), init to 1 */
+	duk_idx_t slot1_idx;                           /* valstack slot for 1st token value */
+	duk_idx_t slot2_idx;                           /* valstack slot for 2nd token value */
+	duk_idx_t buf_idx;                             /* valstack slot for temp buffer */
+	duk_hbuffer_dynamic *buf;                      /* temp accumulation buffer (on valstack) */
+
+	duk_int_t token_count;                         /* number of tokens parsed */
+	duk_int_t token_limit;                         /* maximum token count before error (sanity backstop) */
+};
+
+/*
+ *  Prototypes
+ */
+
+void duk_lexer_initctx(duk_lexer_ctx *lex_ctx);
+
+void duk_lexer_setpoint(duk_lexer_ctx *lex_ctx, duk_lexer_point *pt);
+
+void duk_lexer_parse_js_input_element(duk_lexer_ctx *lex_ctx,
+                                      duk_token *out_token,
+                                      duk_bool_t strict_mode,
+                                      duk_bool_t regexp_mode);
+#ifdef DUK_USE_REGEXP_SUPPORT
+void duk_lexer_parse_re_token(duk_lexer_ctx *lex_ctx, duk_re_token *out_token);
+void duk_lexer_parse_re_ranges(duk_lexer_ctx *lex_ctx, duk_re_range_callback gen_range, void *userdata);
+#endif  /* DUK_USE_REGEXP_SUPPORT */
+
+#endif  /* DUK_LEXER_H_INCLUDED */
+#line 1 "duk_js_compiler.h"
+/*
+ *  Ecmascript compiler.
+ */
+
+#ifndef DUK_JS_COMPILER_H_INCLUDED
+#define DUK_JS_COMPILER_H_INCLUDED
+
+/* ecmascript compiler limits */
+#if defined(DUK_USE_DEEP_C_STACK)
+#define DUK_COMPILER_RECURSION_LIMIT       2500L
+#else
+#define DUK_COMPILER_RECURSION_LIMIT       50L
+#endif
+#define DUK_COMPILER_TOKEN_LIMIT           100000000L  /* 1e8: protects against deeply nested inner functions */
+
+/* maximum loopcount for peephole optimization */
+#define DUK_COMPILER_PEEPHOLE_MAXITER      3
+
+/* maximum bytecode length in instructions */
+#define DUK_COMPILER_MAX_BYTECODE_LENGTH   (256L * 1024L * 1024L)  /* 1 GB */
+
+/*
+ *  Compiler intermediate values
+ *
+ *  Intermediate values describe either plain values (e.g. strings or
+ *  numbers) or binary operations which have not yet been coerced into
+ *  either a left-hand-side or right-hand-side role (e.g. object property).
+ */
+
+#define DUK_IVAL_NONE          0   /* no value */
+#define DUK_IVAL_PLAIN         1   /* register, constant, or value */
+#define DUK_IVAL_ARITH         2   /* binary arithmetic; DUK_OP_ADD, DUK_OP_EQ, other binary ops */
+#define DUK_IVAL_PROP          3   /* property access */
+#define DUK_IVAL_VAR           4   /* variable access */
+
+#define DUK_ISPEC_NONE         0   /* no value */
+#define DUK_ISPEC_VALUE        1   /* value resides in 'valstack_idx' */
+#define DUK_ISPEC_REGCONST     2   /* value resides in a register or constant */
+
+/* bit mask which indicates that a regconst is a constant instead of a register */
+#define DUK_JS_CONST_MARKER    0x80000000UL
+
+/* type to represent a reg/const reference during compilation */
+typedef duk_uint32_t duk_regconst_t;
+
+/* type to represent a straight register reference, with <0 indicating none */
+typedef duk_int32_t duk_reg_t;
+
+typedef struct {
+	duk_small_uint_t t;          /* DUK_ISPEC_XXX */
+	duk_regconst_t regconst;
+	duk_idx_t valstack_idx;      /* always set; points to a reserved valstack slot */
+} duk_ispec;
+
+typedef struct {
+	/*
+	 *  PLAIN: x1
+	 *  ARITH: x1 <op> x2
+	 *  PROP: x1.x2
+	 *  VAR: x1 (name)
+	 */
+
+	/* XXX: can be optimized for smaller footprint esp. on 32-bit environments */
+	duk_small_uint_t t;          /* DUK_IVAL_XXX */
+	duk_small_uint_t op;         /* bytecode opcode for binary ops */
+	duk_ispec x1;
+	duk_ispec x2;
+} duk_ivalue;
+
+/*
+ *  Bytecode instruction representation during compilation
+ *
+ *  Contains the actual instruction and (optionally) debug info.
+ */
+
+struct duk_compiler_instr {
+	duk_instr_t ins;
+#if defined(DUK_USE_PC2LINE)
+	duk_uint32_t line;
+#endif
+};
+
+/*
+ *  Compiler state
+ */
+
+#define DUK_LABEL_FLAG_ALLOW_BREAK       (1 << 0)
+#define DUK_LABEL_FLAG_ALLOW_CONTINUE    (1 << 1)
+
+#define DUK_DECL_TYPE_VAR                0
+#define DUK_DECL_TYPE_FUNC               1
+
+/* XXX: optimize to 16 bytes */
+typedef struct {
+	duk_small_uint_t flags;
+	duk_int_t label_id;          /* numeric label_id (-1 reserved as marker) */
+	duk_hstring *h_label;        /* borrowed label name */
+	duk_int_t catch_depth;       /* catch depth at point of definition */
+	duk_int_t pc_label;          /* pc of label statement:
+	                              * pc+1: break jump site
+	                              * pc+2: continue jump site
+	                              */
+
+	/* Fast jumps (which avoid longjmp) jump directly to the jump sites
+	 * which are always known even while the iteration/switch statement
+	 * is still being parsed.  A final peephole pass "straightens out"
+	 * the jumps.
+	 */
+} duk_labelinfo;
+
+/* Compiling state of one function, eventually converted to duk_hcompiledfunction */
+struct duk_compiler_func {
+	/* These pointers are at the start of the struct so that they pack
+	 * nicely.  Mixing pointers and integer values is bad on some
+	 * platforms (e.g. if int is 32 bits and pointers are 64 bits).
+	 */
+
+	duk_hstring *h_name;                /* function name (borrowed reference), ends up in _name */
+	duk_hbuffer_dynamic *h_code;        /* C array of duk_compiler_instr */
+	duk_hobject *h_consts;              /* array */
+	duk_hobject *h_funcs;               /* array of function templates: [func1, offset1, line1, func2, offset2, line2]
+	                                     * offset/line points to closing brace to allow skipping on pass 2
+	                                     */
+	duk_hobject *h_decls;               /* array of declarations: [ name1, val1, name2, val2, ... ]
+	                                     * valN = (typeN) | (fnum << 8), where fnum is inner func number (0 for vars)
+	                                     * record function and variable declarations in pass 1
+	                                     */
+	duk_hobject *h_labelnames;          /* array of active label names */
+	duk_hbuffer_dynamic *h_labelinfos;  /* C array of duk_labelinfo */
+	duk_hobject *h_argnames;            /* array of formal argument names (-> _formals) */
+	duk_hobject *h_varmap;              /* variable map for pass 2 (identifier -> register number or null (unmapped)) */
+
+	/* value stack indices for tracking objects */
+	duk_idx_t code_idx;
+	duk_idx_t consts_idx;
+	duk_idx_t funcs_idx;
+	duk_idx_t decls_idx;
+	duk_idx_t labelnames_idx;
+	duk_idx_t labelinfos_idx;
+	duk_idx_t argnames_idx;
+	duk_idx_t varmap_idx;
+
+	/* temp reg handling */
+	duk_reg_t temp_first;               /* first register that is a temporary (below: variables) */
+	duk_reg_t temp_next;                /* next temporary register to allocate */
+	duk_reg_t temp_max;                 /* highest value of temp_reg (temp_max - 1 is highest used reg) */
+
+	/* shuffle registers if large number of regs/consts */
+	duk_reg_t shuffle1;
+	duk_reg_t shuffle2;
+	duk_reg_t shuffle3;
+
+	/* stats for current expression being parsed */
+	duk_int_t nud_count;
+	duk_int_t led_count;
+	duk_int_t paren_level;              /* parenthesis count, 0 = top level */
+	duk_bool_t expr_lhs;                /* expression is left-hand-side compatible */
+	duk_bool_t allow_in;                /* current paren level allows 'in' token */
+
+	/* misc */
+	duk_int_t stmt_next;                /* statement id allocation (running counter) */
+	duk_int_t label_next;               /* label id allocation (running counter) */
+	duk_int_t catch_depth;              /* catch stack depth */
+	duk_int_t with_depth;               /* with stack depth (affects identifier lookups) */
+	duk_int_t fnum_next;                /* inner function numbering */
+	duk_int_t num_formals;              /* number of formal arguments */
+	duk_reg_t reg_stmt_value;           /* register for writing value of 'non-empty' statements (global or eval code), -1 is marker */
+
+	/* status booleans */
+	duk_bool_t is_function;             /* is an actual function (not global/eval code) */
+	duk_bool_t is_eval;                 /* is eval code */
+	duk_bool_t is_global;               /* is global code */
+	duk_bool_t is_setget;               /* is a setter/getter */
+	duk_bool_t is_decl;                 /* is a function declaration (as opposed to function expression) */
+	duk_bool_t is_strict;               /* function is strict */
+	duk_bool_t is_notail;               /* function must not be tailcalled */
+	duk_bool_t in_directive_prologue;   /* parsing in "directive prologue", recognize directives */
+	duk_bool_t in_scanning;             /* parsing in "scanning" phase (first pass) */
+	duk_bool_t may_direct_eval;         /* function may call direct eval */
+	duk_bool_t id_access_arguments;     /* function refers to 'arguments' identifier */
+	duk_bool_t id_access_slow;          /* function makes one or more slow path accesses */
+	duk_bool_t is_arguments_shadowed;   /* argument/function declaration shadows 'arguments' */
+	duk_bool_t needs_shuffle;           /* function needs shuffle registers */
+	duk_bool_t reject_regexp_in_adv;    /* reject RegExp literal on next advance() call; needed for handling IdentifierName productions */
+};
+
+struct duk_compiler_ctx {
+	duk_hthread *thr;
+
+	/* filename being compiled (ends up in functions' '_filename' property) */
+	duk_hstring *h_filename;            /* borrowed reference */
+
+	/* lexing (tokenization) state (contains two valstack slot indices) */
+	duk_lexer_ctx lex;
+
+	/* current and previous token for parsing */
+	duk_token prev_token;
+	duk_token curr_token;
+	duk_idx_t tok11_idx;                /* curr_token slot1 (matches 'lex' slot1_idx) */
+	duk_idx_t tok12_idx;                /* curr_token slot2 (matches 'lex' slot2_idx) */
+	duk_idx_t tok21_idx;                /* prev_token slot1 */
+	duk_idx_t tok22_idx;                /* prev_token slot2 */
+
+	/* recursion limit */
+	duk_int_t recursion_depth;
+	duk_int_t recursion_limit;
+
+	/* current function being compiled (embedded instead of pointer for more compact access) */
+	duk_compiler_func curr_func;
+};
+
+/*
+ *  Prototypes
+ */
+
+#define DUK_JS_COMPILE_FLAG_EVAL      (1 << 0)  /* source is eval code (not program) */
+#define DUK_JS_COMPILE_FLAG_STRICT    (1 << 1)  /* strict outer context */
+#define DUK_JS_COMPILE_FLAG_FUNCEXPR  (1 << 2)  /* source is a function expression (used for Function constructor) */
+
+void duk_js_compile(duk_hthread *thr, const duk_uint8_t *src_buffer, duk_size_t src_length, duk_small_uint_t flags);
+
+#endif  /* DUK_JS_COMPILER_H_INCLUDED */
+#line 1 "duk_regexp.h"
+/*
+ *  Regular expression structs, constants, and bytecode defines.
+ */
+
+#ifndef DUK_REGEXP_H_INCLUDED
+#define DUK_REGEXP_H_INCLUDED
+
+/* maximum bytecode copies for {n,m} quantifiers */
+#define DUK_RE_MAX_ATOM_COPIES             1000
+
+/* regexp compilation limits */
+#if defined(DUK_USE_DEEP_C_STACK)
+#define DUK_RE_COMPILE_RECURSION_LIMIT     1000
+#else
+#define DUK_RE_COMPILE_RECURSION_LIMIT     100
+#endif
+#define DUK_RE_COMPILE_TOKEN_LIMIT         100000000L   /* 1e8 */
+
+/* regexp execution limits */
+#if defined(DUK_USE_DEEP_C_STACK)
+#define DUK_RE_EXECUTE_RECURSION_LIMIT     1000
+#else
+#define DUK_RE_EXECUTE_RECURSION_LIMIT     100
+#endif
+#define DUK_RE_EXECUTE_STEPS_LIMIT         1000000000L  /* 1e9 */
+
+/* regexp opcodes */
+#define DUK_REOP_MATCH                     1
+#define DUK_REOP_CHAR                      2
+#define DUK_REOP_PERIOD                    3
+#define DUK_REOP_RANGES                    4
+#define DUK_REOP_INVRANGES                 5
+#define DUK_REOP_JUMP                      6
+#define DUK_REOP_SPLIT1                    7
+#define DUK_REOP_SPLIT2                    8
+#define DUK_REOP_SQMINIMAL                 9
+#define DUK_REOP_SQGREEDY                  10
+#define DUK_REOP_SAVE                      11
+#define DUK_REOP_WIPERANGE                 12
+#define DUK_REOP_LOOKPOS                   13
+#define DUK_REOP_LOOKNEG                   14
+#define DUK_REOP_BACKREFERENCE             15
+#define DUK_REOP_ASSERT_START              16
+#define DUK_REOP_ASSERT_END                17
+#define DUK_REOP_ASSERT_WORD_BOUNDARY      18
+#define DUK_REOP_ASSERT_NOT_WORD_BOUNDARY  19
+
+/* flags */
+#define DUK_RE_FLAG_GLOBAL                 (1 << 0)
+#define DUK_RE_FLAG_IGNORE_CASE            (1 << 1)
+#define DUK_RE_FLAG_MULTILINE              (1 << 2)
+
+struct duk_re_matcher_ctx {
+	duk_hthread *thr;
+
+	duk_uint32_t re_flags;
+	duk_uint8_t *input;
+	duk_uint8_t *input_end;
+	duk_uint8_t *bytecode;
+	duk_uint8_t *bytecode_end;
+	duk_uint8_t **saved;		/* allocated from valstack (fixed buffer) */
+	duk_uint32_t nsaved;
+	duk_uint32_t recursion_depth;
+	duk_uint32_t recursion_limit;
+	duk_uint32_t steps_count;
+	duk_uint32_t steps_limit;
+};
+
+struct duk_re_compiler_ctx {
+	duk_hthread *thr;
+
+	duk_uint32_t re_flags;
+	duk_lexer_ctx lex;
+	duk_re_token curr_token;
+	duk_hbuffer_dynamic *buf;
+	duk_uint32_t captures;  /* highest capture number emitted so far (used as: ++captures) */
+	duk_uint32_t highest_backref;
+	duk_uint32_t recursion_depth;
+	duk_uint32_t recursion_limit;
+	duk_uint32_t nranges;	/* internal temporary value, used for char classes */
+};
+
+/*
+ *  Prototypes
+ */
+
+void duk_regexp_compile(duk_hthread *thr);
+void duk_regexp_create_instance(duk_hthread *thr);
+void duk_regexp_match(duk_hthread *thr);
+void duk_regexp_match_force_global(duk_hthread *thr);  /* hacky helper for String.prototype.split() */
+
+#endif  /* DUK_REGEXP_H_INCLUDED */
+
+#line 1 "duk_tval.h"
+/*
+ *  Tagged type definition (duk_tval) and accessor macros.
+ *
+ *  Access all fields through the accessor macros, as the representation
+ *  is quite tricky.
+ *
+ *  There are two packed type alternatives: an 8-byte representation
+ *  based on an IEEE double (preferred for compactness), and a 12-byte
+ *  representation (portability).  The latter is needed also in e.g.
+ *  64-bit environments (it usually pads to 16 bytes per value).
+ *
+ *  Selecting the tagged type format involves many trade-offs (memory
+ *  use, size and performance of generated code, portability, etc),
+ *  see doc/types.txt for a detailed discussion (especially of how the
+ *  IEEE double format is used to pack tagged values).
+ *
+ *  NB: because macro arguments are often expressions, macros should
+ *  avoid evaluating their argument more than once.
+ */
+
+#ifndef DUK_TVAL_H_INCLUDED
+#define DUK_TVAL_H_INCLUDED
+
+/* sanity */
+#if !defined(DUK_USE_DOUBLE_LE) && !defined(DUK_USE_DOUBLE_ME) && !defined(DUK_USE_DOUBLE_BE)
+#error unsupported: cannot determine byte order variant
+#endif
+
+#ifdef DUK_USE_PACKED_TVAL
+/* ======================================================================== */
+
+/*
+ *  Packed 8-byte representation
+ */
+
+/* sanity */
+#if !defined(DUK_USE_PACKED_TVAL_POSSIBLE)
+#error packed representation not supported
+#endif
+
+/* use duk_double_union as duk_tval directly */
+typedef union duk_double_union duk_tval;
+
+/* tags */
+#define DUK_TAG_NORMALIZED_NAN    0x7ff8UL   /* the NaN variant we use */
+/* avoid tag 0xfff0, no risk of confusion with negative infinity */
+#define DUK_TAG_UNDEFINED         0xfff1UL   /* embed: 0 or 1 (normal or unused) */
+#define DUK_TAG_NULL              0xfff2UL   /* embed: nothing */
+#define DUK_TAG_BOOLEAN           0xfff3UL   /* embed: 0 or 1 (false or true) */
+/* DUK_TAG_NUMBER would logically go here, but it has multiple 'tags' */
+#define DUK_TAG_POINTER           0xfff4UL   /* embed: void ptr */
+#define DUK_TAG_STRING            0xfff5UL   /* embed: duk_hstring ptr */
+#define DUK_TAG_OBJECT            0xfff6UL   /* embed: duk_hobject ptr */
+#define DUK_TAG_BUFFER            0xfff7UL   /* embed: duk_hbuffer ptr */
+
+/* for convenience */
+#define DUK_XTAG_UNDEFINED_ACTUAL 0xfff10000UL
+#define DUK_XTAG_UNDEFINED_UNUSED 0xfff10001UL
+#define DUK_XTAG_NULL             0xfff20000UL
+#define DUK_XTAG_BOOLEAN_FALSE    0xfff30000UL
+#define DUK_XTAG_BOOLEAN_TRUE     0xfff30001UL
+
+#define DUK__TVAL_SET_UNDEFINED_ACTUAL_FULL(v)      DUK_DBLUNION_SET_HIGH32_ZERO_LOW32((v), DUK_XTAG_UNDEFINED_ACTUAL)
+#define DUK__TVAL_SET_UNDEFINED_ACTUAL_NOTFULL(v)   DUK_DBLUNION_SET_HIGH32((v), DUK_XTAG_UNDEFINED_ACTUAL)
+#define DUK__TVAL_SET_UNDEFINED_UNUSED_FULL(v)      DUK_DBLUNION_SET_HIGH32_ZERO_LOW32((v), DUK_XTAG_UNDEFINED_UNUSED)
+#define DUK__TVAL_SET_UNDEFINED_UNUSED_NOTFULL(v)   DUK_DBLUNION_SET_HIGH32((v), DUK_XTAG_UNDEFINED_UNUSED)
+
+/* Note: 16-bit initializer suffices (unlike for undefined/boolean) */
+#define DUK__TVAL_SET_NULL_FULL(v)     DUK_DBLUNION_SET_HIGH32_ZERO_LOW32((v), DUK_XTAG_NULL)
+#define DUK__TVAL_SET_NULL_NOTFULL(v)  do { \
+		(v)->us[DUK_DBL_IDX_US0] = (duk_uint16_t) DUK_TAG_NULL; \
+	} while (0)
+
+#define DUK__TVAL_SET_BOOLEAN_FULL(v,val)    DUK_DBLUNION_SET_HIGH32_ZERO_LOW32((v), (((duk_uint32_t) DUK_TAG_BOOLEAN) << 16) | ((duk_uint32_t) val))
+#define DUK__TVAL_SET_BOOLEAN_NOTFULL(v,val) DUK_DBLUNION_SET_HIGH32((v), (((duk_uint32_t) DUK_TAG_BOOLEAN) << 16) | ((duk_uint32_t) (val)))
+
+/* assumes that caller has normalized a possible NaN value of 'val', otherwise trouble ahead;
+ * no notfull variant
+ */
+#define DUK__TVAL_SET_NUMBER_FULL(v,val)     DUK_DBLUNION_SET_DOUBLE((v), (val))
+#define DUK__TVAL_SET_NUMBER_NOTFULL(v,val)  DUK_DBLUNION_SET_DOUBLE((v), (val))
+
+/* two casts to avoid gcc warning: "warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]" */
+#ifdef DUK_USE_64BIT_OPS
+#ifdef DUK_USE_DOUBLE_ME
+#define DUK__TVAL_SET_TAGGEDPOINTER(v,h,tag)  do { \
+		(v)->ull[DUK_DBL_IDX_ULL0] = (((duk_uint64_t) (tag)) << 16) | (((duk_uint64_t) (duk_uint32_t) (h)) << 32); \
+	} while (0)
+#else
+#define DUK__TVAL_SET_TAGGEDPOINTER(v,h,tag)  do { \
+		(v)->ull[DUK_DBL_IDX_ULL0] = (((duk_uint64_t) (tag)) << 48) | ((duk_uint64_t) (duk_uint32_t) (h)); \
+	} while (0)
+#endif
+#else  /* DUK_USE_64BIT_OPS */
+#define DUK__TVAL_SET_TAGGEDPOINTER(v,h,tag)  do { \
+		(v)->ui[DUK_DBL_IDX_UI0] = ((duk_uint32_t) (tag)) << 16; \
+		(v)->ui[DUK_DBL_IDX_UI1] = (duk_uint32_t) (h); \
+	} while (0)
+#endif  /* DUK_USE_64BIT_OPS */
+
+/* select actual setters */
+#ifdef DUK_USE_FULL_TVAL
+#define DUK_TVAL_SET_UNDEFINED_ACTUAL(v)    DUK__TVAL_SET_UNDEFINED_ACTUAL_FULL((v))
+#define DUK_TVAL_SET_UNDEFINED_UNUSED(v)    DUK__TVAL_SET_UNDEFINED_UNUSED_FULL((v))
+#define DUK_TVAL_SET_NULL(v)                DUK__TVAL_SET_NULL_FULL((v))
+#define DUK_TVAL_SET_BOOLEAN(v,i)           DUK__TVAL_SET_BOOLEAN_FULL((v),(i))
+#define DUK_TVAL_SET_NUMBER(v,d)            DUK__TVAL_SET_NUMBER_FULL((v),(d))
+#define DUK_TVAL_SET_NAN(v)                 DUK__TVAL_SET_NAN_FULL((v))
+#else
+#define DUK_TVAL_SET_UNDEFINED_ACTUAL(v)    DUK__TVAL_SET_UNDEFINED_ACTUAL_NOTFULL((v))
+#define DUK_TVAL_SET_UNDEFINED_UNUSED(v)    DUK__TVAL_SET_UNDEFINED_UNUSED_NOTFULL((v))
+#define DUK_TVAL_SET_NULL(v)                DUK__TVAL_SET_NULL_NOTFULL((v))
+#define DUK_TVAL_SET_BOOLEAN(v,i)           DUK__TVAL_SET_BOOLEAN_NOTFULL((v),(i))
+#define DUK_TVAL_SET_NUMBER(v,d)            DUK__TVAL_SET_NUMBER_NOTFULL((v),(d))
+#define DUK_TVAL_SET_NAN(v)                 DUK__TVAL_SET_NAN_NOTFULL((v))
+#endif
+
+#define DUK_TVAL_SET_STRING(v,h)            DUK__TVAL_SET_TAGGEDPOINTER((v),(h),DUK_TAG_STRING)
+#define DUK_TVAL_SET_OBJECT(v,h)            DUK__TVAL_SET_TAGGEDPOINTER((v),(h),DUK_TAG_OBJECT)
+#define DUK_TVAL_SET_BUFFER(v,h)            DUK__TVAL_SET_TAGGEDPOINTER((v),(h),DUK_TAG_BUFFER)
+#define DUK_TVAL_SET_POINTER(v,p)           DUK__TVAL_SET_TAGGEDPOINTER((v),(p),DUK_TAG_POINTER)
+
+#define DUK_TVAL_SET_TVAL(v,x)              do { *(v) = *(x); } while (0)
+
+/* getters */
+#define DUK_TVAL_GET_BOOLEAN(v)             ((int) (v)->us[DUK_DBL_IDX_US1])
+#define DUK_TVAL_GET_NUMBER(v)              ((v)->d)
+#define DUK_TVAL_GET_STRING(v)              ((duk_hstring *) (v)->vp[DUK_DBL_IDX_VP1])
+#define DUK_TVAL_GET_OBJECT(v)              ((duk_hobject *) (v)->vp[DUK_DBL_IDX_VP1])
+#define DUK_TVAL_GET_BUFFER(v)              ((duk_hbuffer *) (v)->vp[DUK_DBL_IDX_VP1])
+#define DUK_TVAL_GET_POINTER(v)             ((void *) (v)->vp[DUK_DBL_IDX_VP1])
+#define DUK_TVAL_GET_HEAPHDR(v)             ((duk_heaphdr *) (v)->vp[DUK_DBL_IDX_VP1])
+
+/* decoding */
+#define DUK_TVAL_GET_TAG(v)                 ((duk_small_uint_t) (v)->us[DUK_DBL_IDX_US0])
+
+#define DUK_TVAL_IS_UNDEFINED(v)            (DUK_TVAL_GET_TAG((v)) == DUK_TAG_UNDEFINED)
+#define DUK_TVAL_IS_UNDEFINED_ACTUAL(v)     ((v)->ui[DUK_DBL_IDX_UI0] == DUK_XTAG_UNDEFINED_ACTUAL)
+#define DUK_TVAL_IS_UNDEFINED_UNUSED(v)     ((v)->ui[DUK_DBL_IDX_UI0] == DUK_XTAG_UNDEFINED_UNUSED)
+#define DUK_TVAL_IS_NULL(v)                 (DUK_TVAL_GET_TAG((v)) == DUK_TAG_NULL)
+#define DUK_TVAL_IS_BOOLEAN(v)              (DUK_TVAL_GET_TAG((v)) == DUK_TAG_BOOLEAN)
+#define DUK_TVAL_IS_BOOLEAN_TRUE(v)         ((v)->ui[DUK_DBL_IDX_UI0] == DUK_XTAG_BOOLEAN_TRUE)
+#define DUK_TVAL_IS_BOOLEAN_FALSE(v)        ((v)->ui[DUK_DBL_IDX_UI0] == DUK_XTAG_BOOLEAN_FALSE)
+#define DUK_TVAL_IS_STRING(v)               (DUK_TVAL_GET_TAG((v)) == DUK_TAG_STRING)
+#define DUK_TVAL_IS_OBJECT(v)               (DUK_TVAL_GET_TAG((v)) == DUK_TAG_OBJECT)
+#define DUK_TVAL_IS_BUFFER(v)               (DUK_TVAL_GET_TAG((v)) == DUK_TAG_BUFFER)
+#define DUK_TVAL_IS_POINTER(v)              (DUK_TVAL_GET_TAG((v)) == DUK_TAG_POINTER)
+/* 0xfff0 is -Infinity */
+#define DUK_TVAL_IS_NUMBER(v)               (DUK_TVAL_GET_TAG((v)) <= 0xfff0UL)
+
+#define DUK_TVAL_IS_HEAP_ALLOCATED(v)       (DUK_TVAL_GET_TAG((v)) >= DUK_TAG_STRING)
+
+#else  /* DUK_USE_PACKED_TVAL */
+/* ======================================================================== */
+
+/*
+ *  Portable 12-byte representation
+ */
+
+#ifdef DUK_USE_FULL_TVAL
+#error no 'full' tagged values in 12-byte representation
+#endif
+
+typedef struct duk_tval_struct duk_tval;
+
+struct duk_tval_struct {
+	duk_small_uint_t t;
+	union {
+		duk_double_t d;
+		duk_small_int_t i;
+		void *voidptr;
+		duk_hstring *hstring;
+		duk_hobject *hobject;
+		duk_hcompiledfunction *hcompiledfunction;
+		duk_hnativefunction *hnativefunction;
+		duk_hthread *hthread;
+		duk_hbuffer *hbuffer;
+		duk_heaphdr *heaphdr;
+	} v;
+};
+
+#define DUK__TAG_NUMBER               0  /* not exposed */
+#define DUK_TAG_UNDEFINED             1
+#define DUK_TAG_NULL                  2
+#define DUK_TAG_BOOLEAN               3
+#define DUK_TAG_POINTER               4
+#define DUK_TAG_STRING                5
+#define DUK_TAG_OBJECT                6
+#define DUK_TAG_BUFFER                7
+
+/* DUK__TAG_NUMBER is intentionally first, as it is the default clause in code
+ * to support the 8-byte representation.  Further, it is a non-heap-allocated
+ * type so it should come before DUK_TAG_STRING.  Finally, it should not break
+ * the tag value ranges covered by case-clauses in a switch-case.
+ */
+
+/* setters */
+#define DUK_TVAL_SET_UNDEFINED_ACTUAL(tv)  do { \
+		(tv)->t = DUK_TAG_UNDEFINED; \
+		(tv)->v.i = 0; \
+	} while (0)
+
+#define DUK_TVAL_SET_UNDEFINED_UNUSED(tv)  do { \
+		(tv)->t = DUK_TAG_UNDEFINED; \
+		(tv)->v.i = 1; \
+	} while (0)
+
+#define DUK_TVAL_SET_NULL(tv)  do { \
+		(tv)->t = DUK_TAG_NULL; \
+	} while (0)
+
+#define DUK_TVAL_SET_BOOLEAN(tv,val)  do { \
+		(tv)->t = DUK_TAG_BOOLEAN; \
+		(tv)->v.i = (val); \
+	} while (0)
+
+#define DUK_TVAL_SET_NUMBER(tv,val)  do { \
+		(tv)->t = DUK__TAG_NUMBER; \
+		(tv)->v.d = (val); \
+	} while (0)
+
+#define DUK_TVAL_SET_STRING(tv,hptr)  do { \
+		(tv)->t = DUK_TAG_STRING; \
+		(tv)->v.hstring = (hptr); \
+	} while (0)
+
+#define DUK_TVAL_SET_OBJECT(tv,hptr)  do { \
+		(tv)->t = DUK_TAG_OBJECT; \
+		(tv)->v.hobject = (hptr); \
+	} while (0)
+
+#define DUK_TVAL_SET_BUFFER(tv,hptr)  do { \
+		(tv)->t = DUK_TAG_BUFFER; \
+		(tv)->v.hbuffer = (hptr); \
+	} while (0)
+
+#define DUK_TVAL_SET_POINTER(tv,hptr)  do { \
+		(tv)->t = DUK_TAG_POINTER; \
+		(tv)->v.voidptr = (hptr); \
+	} while (0)
+
+#define DUK_TVAL_SET_NAN(tv)  do { \
+		/* in non-packed representation we don't care about which NaN is used */ \
+		(tv)->t = DUK__TAG_NUMBER; \
+		(tv)->v.d = DUK_DOUBLE_NAN; \
+	} while (0)
+
+#define DUK_TVAL_SET_TVAL(v,x)              do { *(v) = *(x); } while (0)
+
+/* getters */
+#define DUK_TVAL_GET_BOOLEAN(tv)           ((tv)->v.i)
+#define DUK_TVAL_GET_NUMBER(tv)            ((tv)->v.d)
+#define DUK_TVAL_GET_STRING(tv)            ((tv)->v.hstring)
+#define DUK_TVAL_GET_OBJECT(tv)            ((tv)->v.hobject)
+#define DUK_TVAL_GET_BUFFER(tv)            ((tv)->v.hbuffer)
+#define DUK_TVAL_GET_POINTER(tv)           ((tv)->v.voidptr)
+#define DUK_TVAL_GET_HEAPHDR(tv)           ((tv)->v.heaphdr)
+
+/* decoding */
+#define DUK_TVAL_GET_TAG(tv)               ((tv)->t)
+#define DUK_TVAL_IS_NUMBER(tv)             ((tv)->t == DUK__TAG_NUMBER)
+#define DUK_TVAL_IS_UNDEFINED(tv)          ((tv)->t == DUK_TAG_UNDEFINED)
+#define DUK_TVAL_IS_UNDEFINED_ACTUAL(tv)   (((tv)->t == DUK_TAG_UNDEFINED) && ((tv)->v.i == 0))
+#define DUK_TVAL_IS_UNDEFINED_UNUSED(tv)   (((tv)->t == DUK_TAG_UNDEFINED) && ((tv)->v.i != 0))
+#define DUK_TVAL_IS_NULL(tv)               ((tv)->t == DUK_TAG_NULL)
+#define DUK_TVAL_IS_BOOLEAN(tv)            ((tv)->t == DUK_TAG_BOOLEAN)
+#define DUK_TVAL_IS_BOOLEAN_TRUE(tv)       (((tv)->t == DUK_TAG_BOOLEAN) && ((tv)->v.i != 0))
+#define DUK_TVAL_IS_BOOLEAN_FALSE(tv)      (((tv)->t == DUK_TAG_BOOLEAN) && ((tv)->v.i == 0))
+#define DUK_TVAL_IS_STRING(tv)             ((tv)->t == DUK_TAG_STRING)
+#define DUK_TVAL_IS_OBJECT(tv)             ((tv)->t == DUK_TAG_OBJECT)
+#define DUK_TVAL_IS_BUFFER(tv)             ((tv)->t == DUK_TAG_BUFFER)
+#define DUK_TVAL_IS_POINTER(tv)            ((tv)->t == DUK_TAG_POINTER)
+
+#define DUK_TVAL_IS_HEAP_ALLOCATED(tv)     ((tv)->t >= DUK_TAG_STRING)
+
+#endif  /* DUK_USE_PACKED_TVAL */
+
+/*
+ *  Convenience (independent of representation)
+ */
+
+#define DUK_TVAL_SET_BOOLEAN_TRUE(v)        DUK_TVAL_SET_BOOLEAN(v, 1)
+#define DUK_TVAL_SET_BOOLEAN_FALSE(v)       DUK_TVAL_SET_BOOLEAN(v, 0)
+
+#endif  /* DUK_TVAL_H_INCLUDED */
+#line 1 "duk_heaphdr.h"
+/*
+ *  Heap header definition and assorted macros, including ref counting.
+ *  Access all fields through the accessor macros.
+ */
+
+#ifndef DUK_HEAPHDR_H_INCLUDED
+#define DUK_HEAPHDR_H_INCLUDED
+
+/*
+ *  Common heap header
+ *
+ *  All heap objects share the same flags and refcount fields.  Objects other
+ *  than strings also need to have a single or double linked list pointers
+ *  for insertion into the "heap allocated" list.  Strings are held in the
+ *  heap-wide string table so they don't need link pointers.
+ *
+ *  Technically, 'h_refcount' must be wide enough to guarantee that it cannot
+ *  wrap (otherwise objects might be freed incorrectly after wrapping).  This
+ *  means essentially that the refcount field must be as wide as data pointers.
+ *  On 64-bit platforms this means that the refcount needs to be 64 bits even
+ *  if an 'int' is 32 bits.  This is a bit unfortunate, and compromising on
+ *  this might be reasonable in the future.
+ *
+ *  Heap header size on 32-bit platforms: 8 bytes without reference counting,
+ *  16 bytes with reference counting.
+ */
+
+struct duk_heaphdr {
+	duk_uint32_t h_flags;
+#if defined(DUK_USE_REFERENCE_COUNTING)
+	duk_size_t h_refcount;
+#endif
+	duk_heaphdr *h_next;
+#if defined(DUK_USE_DOUBLE_LINKED_HEAP)
+	/* refcounting requires direct heap frees, which in turn requires a dual linked heap */
+	duk_heaphdr *h_prev;
+#endif
+};
+
+struct duk_heaphdr_string {
+	duk_uint32_t h_flags;
+#if defined(DUK_USE_REFERENCE_COUNTING)
+	duk_size_t h_refcount;
+#endif
+};
+
+#define DUK_HEAPHDR_FLAGS_TYPE_MASK      0x00000003UL
+#define DUK_HEAPHDR_FLAGS_FLAG_MASK      (~DUK_HEAPHDR_FLAGS_TYPE_MASK)
+
+                                             /* 2 bits for heap type */
+#define DUK_HEAPHDR_FLAGS_HEAP_START     2   /* 4 heap flags */
+#define DUK_HEAPHDR_FLAGS_USER_START     6   /* 26 user flags */
+
+#define DUK_HEAPHDR_HEAP_FLAG_NUMBER(n)  (DUK_HEAPHDR_FLAGS_HEAP_START + (n))
+#define DUK_HEAPHDR_USER_FLAG_NUMBER(n)  (DUK_HEAPHDR_FLAGS_USER_START + (n))
+#define DUK_HEAPHDR_HEAP_FLAG(n)         (1UL << (DUK_HEAPHDR_FLAGS_HEAP_START + (n)))
+#define DUK_HEAPHDR_USER_FLAG(n)         (1UL << (DUK_HEAPHDR_FLAGS_USER_START + (n)))
+
+#define DUK_HEAPHDR_FLAG_REACHABLE       DUK_HEAPHDR_HEAP_FLAG(0)  /* mark-and-sweep: reachable */
+#define DUK_HEAPHDR_FLAG_TEMPROOT        DUK_HEAPHDR_HEAP_FLAG(1)  /* mark-and-sweep: children not processed */
+#define DUK_HEAPHDR_FLAG_FINALIZABLE     DUK_HEAPHDR_HEAP_FLAG(2)  /* mark-and-sweep: finalizable (on current pass) */
+#define DUK_HEAPHDR_FLAG_FINALIZED       DUK_HEAPHDR_HEAP_FLAG(3)  /* mark-and-sweep: finalized (on previous pass) */
+
+#define DUK_HTYPE_MIN                    1
+#define DUK_HTYPE_STRING                 1
+#define DUK_HTYPE_OBJECT                 2
+#define DUK_HTYPE_BUFFER                 3
+#define DUK_HTYPE_MAX                    3
+
+#define DUK_HEAPHDR_GET_NEXT(h)       ((h)->h_next)
+#define DUK_HEAPHDR_SET_NEXT(h,val)   do { \
+		(h)->h_next = (val); \
+	} while (0)
+
+#if defined(DUK_USE_DOUBLE_LINKED_HEAP)
+#define DUK_HEAPHDR_GET_PREV(h)       ((h)->h_prev)
+#define DUK_HEAPHDR_SET_PREV(h,val)   do { \
+		(h)->h_prev = (val); \
+	} while (0)
+#endif
+
+#if defined(DUK_USE_REFERENCE_COUNTING)
+#define DUK_HEAPHDR_GET_REFCOUNT(h)   ((h)->h_refcount)
+#define DUK_HEAPHDR_SET_REFCOUNT(h,val)  do { \
+		(h)->h_refcount = (val); \
+	} while (0)
+#else
+/* refcount macros not defined without refcounting, caller must #ifdef now */
+#endif  /* DUK_USE_REFERENCE_COUNTING */
+
+/*
+ *  Note: type is treated as a field separate from flags, so some masking is
+ *  involved in the macros below.
+ */
+
+#define DUK_HEAPHDR_GET_FLAGS(h)      ((h)->h_flags & DUK_HEAPHDR_FLAGS_FLAG_MASK)
+#define DUK_HEAPHDR_SET_FLAGS(h,val)  do { \
+		(h)->h_flags = ((h)->h_flags & ~(DUK_HEAPHDR_FLAGS_FLAG_MASK)) | (val); \
+	} while (0)
+
+#define DUK_HEAPHDR_GET_TYPE(h)       ((h)->h_flags & DUK_HEAPHDR_FLAGS_TYPE_MASK)
+#define DUK_HEAPHDR_SET_TYPE(h,val)   do { \
+		(h)->h_flags = ((h)->h_flags & ~(DUK_HEAPHDR_FLAGS_TYPE_MASK)) | (val); \
+	} while (0)
+
+#define DUK_HEAPHDR_HTYPE_VALID(h)    ( \
+	DUK_HEAPHDR_GET_TYPE((h)) >= DUK_HTYPE_MIN && \
+	DUK_HEAPHDR_GET_TYPE((h)) <= DUK_HTYPE_MAX \
+	)
+
+#define DUK_HEAPHDR_SET_TYPE_AND_FLAGS(h,tval,fval)  do { \
+		(h)->h_flags = ((tval) & DUK_HEAPHDR_FLAGS_TYPE_MASK) | \
+		               ((fval) & DUK_HEAPHDR_FLAGS_FLAG_MASK); \
+	} while (0)
+
+#define DUK_HEAPHDR_SET_FLAG_BITS(h,bits)  do { \
+		DUK_ASSERT(((bits) & ~(DUK_HEAPHDR_FLAGS_FLAG_MASK)) == 0); \
+		(h)->h_flags |= (bits); \
+	} while (0)
+
+#define DUK_HEAPHDR_CLEAR_FLAG_BITS(h,bits)  do { \
+		DUK_ASSERT(((bits) & ~(DUK_HEAPHDR_FLAGS_FLAG_MASK)) == 0); \
+		(h)->h_flags &= ~((bits)); \
+	} while (0)
+
+#define DUK_HEAPHDR_CHECK_FLAG_BITS(h,bits)  (((h)->h_flags & (bits)) != 0)
+
+#define DUK_HEAPHDR_SET_REACHABLE(h)      DUK_HEAPHDR_SET_FLAG_BITS((h),DUK_HEAPHDR_FLAG_REACHABLE)
+#define DUK_HEAPHDR_CLEAR_REACHABLE(h)    DUK_HEAPHDR_CLEAR_FLAG_BITS((h),DUK_HEAPHDR_FLAG_REACHABLE)
+#define DUK_HEAPHDR_HAS_REACHABLE(h)      DUK_HEAPHDR_CHECK_FLAG_BITS((h),DUK_HEAPHDR_FLAG_REACHABLE)
+
+#define DUK_HEAPHDR_SET_TEMPROOT(h)       DUK_HEAPHDR_SET_FLAG_BITS((h),DUK_HEAPHDR_FLAG_TEMPROOT)
+#define DUK_HEAPHDR_CLEAR_TEMPROOT(h)     DUK_HEAPHDR_CLEAR_FLAG_BITS((h),DUK_HEAPHDR_FLAG_TEMPROOT)
+#define DUK_HEAPHDR_HAS_TEMPROOT(h)       DUK_HEAPHDR_CHECK_FLAG_BITS((h),DUK_HEAPHDR_FLAG_TEMPROOT)
+
+#define DUK_HEAPHDR_SET_FINALIZABLE(h)    DUK_HEAPHDR_SET_FLAG_BITS((h),DUK_HEAPHDR_FLAG_FINALIZABLE)
+#define DUK_HEAPHDR_CLEAR_FINALIZABLE(h)  DUK_HEAPHDR_CLEAR_FLAG_BITS((h),DUK_HEAPHDR_FLAG_FINALIZABLE)
+#define DUK_HEAPHDR_HAS_FINALIZABLE(h)    DUK_HEAPHDR_CHECK_FLAG_BITS((h),DUK_HEAPHDR_FLAG_FINALIZABLE)
+
+#define DUK_HEAPHDR_SET_FINALIZED(h)      DUK_HEAPHDR_SET_FLAG_BITS((h),DUK_HEAPHDR_FLAG_FINALIZED)
+#define DUK_HEAPHDR_CLEAR_FINALIZED(h)    DUK_HEAPHDR_CLEAR_FLAG_BITS((h),DUK_HEAPHDR_FLAG_FINALIZED)
+#define DUK_HEAPHDR_HAS_FINALIZED(h)      DUK_HEAPHDR_CHECK_FLAG_BITS((h),DUK_HEAPHDR_FLAG_FINALIZED)
+
+/* get or set a range of flags; m=first bit number, n=number of bits */
+#define DUK_HEAPHDR_GET_FLAG_RANGE(h,m,n)  (((h)->h_flags >> (m)) & ((1UL << (n)) - 1UL))
+
+#define DUK_HEAPHDR_SET_FLAG_RANGE(h,m,n,v)  do { \
+		(h)->h_flags = \
+			((h)->h_flags & (~(((1 << (n)) - 1) << (m)))) \
+			| ((v) << (m)); \
+	} while (0)
+
+/* init pointer fields to null */
+#if defined(DUK_USE_DOUBLE_LINKED_HEAP)
+#define DUK_HEAPHDR_INIT_NULLS(h)       do { \
+		(h)->h_next = NULL; \
+	} while (0)
+#else
+#define DUK_HEAPHDR_INIT_NULLS(h)       do { \
+		(h)->h_next = NULL; \
+		(h)->h_prev = NULL; \
+	} while (0)
+#endif
+
+#define DUK_HEAPHDR_STRING_INIT_NULLS(h)  /* currently nop */
+
+/*
+ *  Reference counting helper macros.  The macros take a thread argument
+ *  and must thus always be executed in a specific thread context.  The
+ *  thread argument is needed for features like finalization.  Currently
+ *  it is not required for INCREF, but it is included just in case.
+ *
+ *  Note that 'raw' macros such as DUK_HEAPHDR_GET_REFCOUNT() are not
+ *  defined without DUK_USE_REFERENCE_COUNTING, so caller must #ifdef
+ *  around them.
+ */
+
+#if defined(DUK_USE_REFERENCE_COUNTING)
+
+#define DUK_TVAL_INCREF(thr,tv)                duk_heap_tval_incref((tv))
+#define DUK_TVAL_DECREF(thr,tv)                duk_heap_tval_decref((thr),(tv))
+#define DUK__HEAPHDR_INCREF(thr,h)             duk_heap_heaphdr_incref((h))
+#define DUK__HEAPHDR_DECREF(thr,h)             duk_heap_heaphdr_decref((thr),(h))
+#define DUK_HEAPHDR_INCREF(thr,h)              DUK__HEAPHDR_INCREF((thr),(duk_heaphdr *) (h))
+#define DUK_HEAPHDR_DECREF(thr,h)              DUK__HEAPHDR_DECREF((thr),(duk_heaphdr *) (h))
+#define DUK_HSTRING_INCREF(thr,h)              DUK__HEAPHDR_INCREF((thr),(duk_heaphdr *) (h))
+#define DUK_HSTRING_DECREF(thr,h)              DUK__HEAPHDR_DECREF((thr),(duk_heaphdr *) (h))
+#define DUK_HOBJECT_INCREF(thr,h)              DUK__HEAPHDR_INCREF((thr),(duk_heaphdr *) (h))
+#define DUK_HOBJECT_DECREF(thr,h)              DUK__HEAPHDR_DECREF((thr),(duk_heaphdr *) (h))
+#define DUK_HBUFFER_INCREF(thr,h)              DUK__HEAPHDR_INCREF((thr),(duk_heaphdr *) (h))
+#define DUK_HBUFFER_DECREF(thr,h)              DUK__HEAPHDR_DECREF((thr),(duk_heaphdr *) (h))
+#define DUK_HCOMPILEDFUNCTION_INCREF(thr,h)    DUK__HEAPHDR_INCREF((thr),(duk_heaphdr *) &(h)->obj)
+#define DUK_HCOMPILEDFUNCTION_DECREF(thr,h)    DUK__HEAPHDR_DECREF((thr),(duk_heaphdr *) &(h)->obj)
+#define DUK_HNATIVEFUNCTION_INCREF(thr,h)      DUK__HEAPHDR_INCREF((thr),(duk_heaphdr *) &(h)->obj)
+#define DUK_HNATIVEFUNCTION_DECREF(thr,h)      DUK__HEAPHDR_DECREF((thr),(duk_heaphdr *) &(h)->obj)
+#define DUK_HTHREAD_INCREF(thr,h)              DUK__HEAPHDR_INCREF((thr),(duk_heaphdr *) &(h)->obj)
+#define DUK_HTHREAD_DECREF(thr,h)              DUK__HEAPHDR_DECREF((thr),(duk_heaphdr *) &(h)->obj)
+
+#else  /* DUK_USE_REFERENCE_COUNTING */
+
+#define DUK_TVAL_INCREF(thr,v)                 /* nop */
+#define DUK_TVAL_DECREF(thr,v)                 /* nop */
+#define DUK_HEAPHDR_INCREF(thr,h)              /* nop */
+#define DUK_HEAPHDR_DECREF(thr,h)              /* nop */
+#define DUK_HSTRING_INCREF(thr,h)              /* nop */
+#define DUK_HSTRING_DECREF(thr,h)              /* nop */
+#define DUK_HOBJECT_INCREF(thr,h)              /* nop */
+#define DUK_HOBJECT_DECREF(thr,h)              /* nop */
+#define DUK_HBUFFER_INCREF(thr,h)              /* nop */
+#define DUK_HBUFFER_DECREF(thr,h)              /* nop */
+#define DUK_HCOMPILEDFUNCTION_INCREF(thr,h)    /* nop */
+#define DUK_HCOMPILEDFUNCTION_DECREF(thr,h)    /* nop */
+#define DUK_HNATIVEFUNCTION_INCREF(thr,h)      /* nop */
+#define DUK_HNATIVEFUNCTION_DECREF(thr,h)      /* nop */
+#define DUK_HTHREAD_INCREF(thr,h)              /* nop */
+#define DUK_HTHREAD_DECREF(thr,h)              /* nop */
+
+#endif  /* DUK_USE_REFERENCE_COUNTING */
+
+#endif  /* DUK_HEAPHDR_H_INCLUDED */
+#line 1 "duk_api_internal.h"
+/*
+ *  Internal API calls which have (stack and other) semantics similar
+ *  to the public API.
+ */
+
+#ifndef DUK_API_INTERNAL_H_INCLUDED
+#define DUK_API_INTERNAL_H_INCLUDED
+
+/* duk_push_sprintf constants */
+#define DUK_PUSH_SPRINTF_INITIAL_SIZE  256L
+#define DUK_PUSH_SPRINTF_SANITY_LIMIT  (1L * 1024L * 1024L * 1024L)
+
+/* Flag ORed to err_code to indicate __FILE__ / __LINE__ is not
+ * blamed as source of error for error fileName / lineNumber.
+ */
+#define DUK_ERRCODE_FLAG_NOBLAME_FILELINE  (1L << 24)
+
+/* Current convention is to use duk_size_t for value stack sizes and global indices,
+ * and duk_idx_t for local frame indices.
+ */
+duk_bool_t duk_check_valstack_resize(duk_context *ctx, duk_size_t min_new_size, duk_bool_t allow_shrink);
+void duk_require_valstack_resize(duk_context *ctx, duk_size_t min_new_size, duk_bool_t allow_shrink);
+
+duk_tval *duk_get_tval(duk_context *ctx, duk_idx_t index);
+duk_tval *duk_require_tval(duk_context *ctx, duk_idx_t index);
+void duk_push_tval(duk_context *ctx, duk_tval *tv);
+
+void duk_push_this_check_object_coercible(duk_context *ctx);   /* push the current 'this' binding; throw TypeError
+                                                                * if binding is not object coercible (CheckObjectCoercible).
+                                                                */
+duk_hobject *duk_push_this_coercible_to_object(duk_context *ctx);       /* duk_push_this() + CheckObjectCoercible() + duk_to_object() */
+duk_hstring *duk_push_this_coercible_to_string(duk_context *ctx);       /* duk_push_this() + CheckObjectCoercible() + duk_to_string() */
+
+/* duk_push_uint() is guaranteed to support at least unsigned 32-bit range */
+#define duk_push_u32(ctx,val) \
+	duk_push_uint((ctx), (duk_uint_t) (val))
+
+/* sometimes stack and array indices need to go on the stack */
+#define duk_push_idx(ctx,val) \
+	duk_push_int((ctx), (duk_int_t) (val))
+#define duk_push_uarridx(ctx,val) \
+	duk_push_uint((ctx), (duk_uint_t) (val))
+#define duk_push_size_t(ctx,val) \
+	duk_push_uint((ctx), (duk_uint_t) (val))  /* XXX: assumed to fit for now */
+
+/* internal helper for looking up a tagged type */
+#define  DUK_GETTAGGED_FLAG_ALLOW_NULL  (1L << 24)
+#define  DUK_GETTAGGED_FLAG_CHECK_CLASS (1L << 25)
+#define  DUK_GETTAGGED_CLASS_SHIFT      16
+
+duk_heaphdr *duk_get_tagged_heaphdr_raw(duk_context *ctx, duk_idx_t index, duk_uint_t flags_and_tag);
+
+duk_hstring *duk_get_hstring(duk_context *ctx, duk_idx_t index);
+duk_hobject *duk_get_hobject(duk_context *ctx, duk_idx_t index);
+duk_hbuffer *duk_get_hbuffer(duk_context *ctx, duk_idx_t index);
+duk_hthread *duk_get_hthread(duk_context *ctx, duk_idx_t index);
+duk_hcompiledfunction *duk_get_hcompiledfunction(duk_context *ctx, duk_idx_t index);
+duk_hnativefunction *duk_get_hnativefunction(duk_context *ctx, duk_idx_t index);
+
+#define duk_get_hobject_with_class(ctx,index,classnum) \
+	((duk_hobject *) duk_get_tagged_heaphdr_raw((ctx), (index), \
+		DUK_TAG_OBJECT | DUK_GETTAGGED_FLAG_ALLOW_NULL | \
+		DUK_GETTAGGED_FLAG_CHECK_CLASS | ((classnum) << DUK_GETTAGGED_CLASS_SHIFT)))
+
+void *duk_get_voidptr(duk_context *ctx, duk_idx_t index);
+
+duk_hstring *duk_to_hstring(duk_context *ctx, duk_idx_t index);
+duk_int_t duk_to_int_clamped_raw(duk_context *ctx, duk_idx_t index, duk_int_t minval, duk_int_t maxval, duk_bool_t *out_clamped);  /* out_clamped=NULL, RangeError if outside range */
+duk_int_t duk_to_int_clamped(duk_context *ctx, duk_idx_t index, duk_int_t minval, duk_int_t maxval);
+duk_int_t duk_to_int_check_range(duk_context *ctx, duk_idx_t index, duk_int_t minval, duk_int_t maxval);
+
+duk_hstring *duk_require_hstring(duk_context *ctx, duk_idx_t index);
+duk_hobject *duk_require_hobject(duk_context *ctx, duk_idx_t index);
+duk_hbuffer *duk_require_hbuffer(duk_context *ctx, duk_idx_t index);
+duk_hthread *duk_require_hthread(duk_context *ctx, duk_idx_t index);
+duk_hcompiledfunction *duk_require_hcompiledfunction(duk_context *ctx, duk_idx_t index);
+duk_hnativefunction *duk_require_hnativefunction(duk_context *ctx, duk_idx_t index);
+
+#define duk_require_hobject_with_class(ctx,index,classnum) \
+	((duk_hobject *) duk_get_tagged_heaphdr_raw((ctx), (index), \
+		DUK_TAG_OBJECT | \
+		DUK_GETTAGGED_FLAG_CHECK_CLASS | ((classnum) << DUK_GETTAGGED_CLASS_SHIFT)))
+
+void duk_push_unused(duk_context *ctx);
+void duk_push_hstring(duk_context *ctx, duk_hstring *h);
+void duk_push_hstring_stridx(duk_context *ctx, duk_small_int_t stridx);
+void duk_push_hobject(duk_context *ctx, duk_hobject *h);
+void duk_push_hbuffer(duk_context *ctx, duk_hbuffer *h);
+#define duk_push_hthread(ctx,h) \
+	duk_push_hobject((ctx), (duk_hobject *) (h))
+#define duk_push_hcompiledfunction(ctx,h) \
+	duk_push_hobject((ctx), (duk_hobject *) (h))
+#define duk_push_hnativefunction(ctx,h) \
+	duk_push_hobject((ctx), (duk_hobject *) (h))
+void duk_push_hobject_bidx(duk_context *ctx, duk_small_int_t builtin_idx);
+duk_idx_t duk_push_object_helper(duk_context *ctx, duk_uint_t hobject_flags_and_class, duk_small_int_t prototype_bidx);
+duk_idx_t duk_push_object_helper_proto(duk_context *ctx, duk_uint_t hobject_flags_and_class, duk_hobject *proto);
+duk_idx_t duk_push_object_internal(duk_context *ctx);
+duk_idx_t duk_push_compiledfunction(duk_context *ctx);
+void duk_push_c_function_noexotic(duk_context *ctx, duk_c_function func, duk_int_t nargs);
+void duk_push_c_function_noconstruct_noexotic(duk_context *ctx, duk_c_function func, duk_int_t nargs);
+
+duk_bool_t duk_get_prop_stridx(duk_context *ctx, duk_idx_t obj_index, duk_small_int_t stridx);     /* [] -> [val] */
+duk_bool_t duk_put_prop_stridx(duk_context *ctx, duk_idx_t obj_index, duk_small_int_t stridx);     /* [val] -> [] */
+duk_bool_t duk_del_prop_stridx(duk_context *ctx, duk_idx_t obj_index, duk_small_int_t stridx);     /* [] -> [] */
+duk_bool_t duk_has_prop_stridx(duk_context *ctx, duk_idx_t obj_index, duk_small_int_t stridx);     /* [] -> [] */
+
+duk_bool_t duk_get_prop_stridx_boolean(duk_context *ctx, duk_idx_t obj_index, duk_small_int_t stridx, duk_bool_t *out_has_prop);  /* [] -> [] */
+
+void duk_def_prop(duk_context *ctx, duk_idx_t obj_index, duk_small_uint_t desc_flags);  /* [key val] -> [] */
+void duk_def_prop_index(duk_context *ctx, duk_idx_t obj_index, duk_uarridx_t arr_index, duk_small_uint_t desc_flags);  /* [val] -> [] */
+void duk_def_prop_stridx(duk_context *ctx, duk_idx_t obj_index, duk_small_int_t stridx, duk_small_uint_t desc_flags);  /* [val] -> [] */
+void duk_def_prop_stridx_builtin(duk_context *ctx, duk_idx_t obj_index, duk_small_int_t stridx, duk_small_int_t builtin_idx, duk_small_uint_t desc_flags);  /* [] -> [] */
+void duk_def_prop_stridx_thrower(duk_context *ctx, duk_idx_t obj_index, duk_small_int_t stridx, duk_small_uint_t desc_flags);  /* [] -> [] */
+
+/* These are macros for now, but could be separate functions to reduce code
+ * footprint (check call site count before refactoring).
+ */
+#define duk_def_prop_wec(ctx,obj_index) \
+	duk_def_prop((ctx), (obj_index), DUK_PROPDESC_FLAGS_WEC)
+#define duk_def_prop_index_wec(ctx,obj_index,arr_index) \
+	duk_def_prop_index((ctx), (obj_index), (arr_index), DUK_PROPDESC_FLAGS_WEC)
+#define duk_def_prop_stridx_wec(ctx,obj_index,stridx) \
+	duk_def_prop_stridx((ctx), (obj_index), (stridx), DUK_PROPDESC_FLAGS_WEC)
+
+/* Set object 'length'. */
+void duk_set_length(duk_context *ctx, duk_idx_t index, duk_size_t length);
+
+#endif  /* DUK_API_INTERNAL_H_INCLUDED */
+#line 1 "duk_hstring.h"
+/*
+ *  Heap string representation.
+ *
+ *  Strings are byte sequences ordinarily stored in extended UTF-8 format,
+ *  allowing values larger than the official UTF-8 range (used internally)
+ *  and also allowing UTF-8 encoding of surrogate pairs (CESU-8 format).
+ *  Strings may also be invalid UTF-8 altogether which is the case e.g. with
+ *  strings used as internal property names and raw buffers converted to
+ *  strings.  In such cases the 'clen' field contains an inaccurate value.
+ *
+ *  Ecmascript requires support for 32-bit long strings.  However, since each
+ *  16-bit codepoint can take 3 bytes in CESU-8, this representation can only
+ *  support about 1.4G codepoint long strings in extreme cases.  This is not
+ *  really a practical issue.
+ */
+
+#ifndef DUK_HSTRING_H_INCLUDED
+#define DUK_HSTRING_H_INCLUDED
+
+/* Impose a maximum string length for now.  Restricted artificially to
+ * ensure adding a heap header length won't overflow size_t.  The limit
+ * should be synchronized with DUK_HBUFFER_MAX_BYTELEN.
+ *
+ * E5.1 makes provisions to support strings longer than 4G characters.
+ * This limit should be eliminated on 64-bit platforms (and increased
+ * closer to maximum support on 32-bit platforms).
+ */
+#define DUK_HSTRING_MAX_BYTELEN                     (0x7fffffffUL)
+
+/* XXX: could add flags for "is valid CESU-8" (Ecmascript compatible strings),
+ * "is valid UTF-8", "is valid extended UTF-8" (internal strings are not,
+ * regexp bytecode is), and "contains non-BMP characters".  These are not
+ * needed right now.
+ */
+
+#define DUK_HSTRING_FLAG_ARRIDX                     DUK_HEAPHDR_USER_FLAG(0)  /* string is a valid array index */
+#define DUK_HSTRING_FLAG_INTERNAL                   DUK_HEAPHDR_USER_FLAG(1)  /* string is internal */
+#define DUK_HSTRING_FLAG_RESERVED_WORD              DUK_HEAPHDR_USER_FLAG(2)  /* string is a reserved word (non-strict) */
+#define DUK_HSTRING_FLAG_STRICT_RESERVED_WORD       DUK_HEAPHDR_USER_FLAG(3)  /* string is a reserved word (strict) */
+#define DUK_HSTRING_FLAG_EVAL_OR_ARGUMENTS          DUK_HEAPHDR_USER_FLAG(4)  /* string is 'eval' or 'arguments' */
+
+#define DUK_HSTRING_HAS_ARRIDX(x)                   DUK_HEAPHDR_CHECK_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_ARRIDX)
+#define DUK_HSTRING_HAS_INTERNAL(x)                 DUK_HEAPHDR_CHECK_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_INTERNAL)
+#define DUK_HSTRING_HAS_RESERVED_WORD(x)            DUK_HEAPHDR_CHECK_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_RESERVED_WORD)
+#define DUK_HSTRING_HAS_STRICT_RESERVED_WORD(x)     DUK_HEAPHDR_CHECK_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_STRICT_RESERVED_WORD)
+#define DUK_HSTRING_HAS_EVAL_OR_ARGUMENTS(x)        DUK_HEAPHDR_CHECK_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_EVAL_OR_ARGUMENTS)
+
+#define DUK_HSTRING_SET_ARRIDX(x)                   DUK_HEAPHDR_SET_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_ARRIDX)
+#define DUK_HSTRING_SET_INTERNAL(x)                 DUK_HEAPHDR_SET_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_INTERNAL)
+#define DUK_HSTRING_SET_RESERVED_WORD(x)            DUK_HEAPHDR_SET_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_RESERVED_WORD)
+#define DUK_HSTRING_SET_STRICT_RESERVED_WORD(x)     DUK_HEAPHDR_SET_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_STRICT_RESERVED_WORD)
+#define DUK_HSTRING_SET_EVAL_OR_ARGUMENTS(x)        DUK_HEAPHDR_SET_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_EVAL_OR_ARGUMENTS)
+
+#define DUK_HSTRING_CLEAR_ARRIDX(x)                 DUK_HEAPHDR_CLEAR_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_ARRIDX)
+#define DUK_HSTRING_CLEAR_INTERNAL(x)               DUK_HEAPHDR_CLEAR_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_INTERNAL)
+#define DUK_HSTRING_CLEAR_RESERVED_WORD(x)          DUK_HEAPHDR_CLEAR_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_RESERVED_WORD)
+#define DUK_HSTRING_CLEAR_STRICT_RESERVED_WORD(x)   DUK_HEAPHDR_CLEAR_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_STRICT_RESERVED_WORD)
+#define DUK_HSTRING_CLEAR_EVAL_OR_ARGUMENTS(x)      DUK_HEAPHDR_CLEAR_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_EVAL_OR_ARGUMENTS)
+
+#define DUK_HSTRING_IS_ASCII(x)                     ((x)->blen == (x)->clen)
+#define DUK_HSTRING_IS_EMPTY(x)                     ((x)->blen == 0)
+
+#define DUK_HSTRING_GET_HASH(x)                     ((x)->hash)
+#define DUK_HSTRING_GET_BYTELEN(x)                  ((x)->blen)
+#define DUK_HSTRING_GET_CHARLEN(x)                  ((x)->clen)
+#define DUK_HSTRING_GET_DATA(x)                     ((duk_uint8_t *) ((x) + 1))
+#define DUK_HSTRING_GET_DATA_END(x)                 (((duk_uint8_t *) ((x) + 1)) + ((x)->blen))
+
+/* marker value; in E5 2^32-1 is not a valid array index (2^32-2 is highest valid) */
+#define DUK_HSTRING_NO_ARRAY_INDEX  (0xffffffffUL)
+
+/* get array index related to string (or return DUK_HSTRING_NO_ARRAY_INDEX);
+ * avoids helper call if string has no array index value.
+ */
+#define DUK_HSTRING_GET_ARRIDX_FAST(h)  \
+	(DUK_HSTRING_HAS_ARRIDX((h)) ? duk_js_to_arrayindex_string_helper((h)) : DUK_HSTRING_NO_ARRAY_INDEX)
+
+/* slower but more compact variant */
+#define DUK_HSTRING_GET_ARRIDX_SLOW(h)  \
+	(duk_js_to_arrayindex_string_helper((h)))
+
+/*
+ *  Misc
+ */
+
+struct duk_hstring {
+	/* smaller heaphdr than for other objects, because strings are held
+	 * in string intern table which requires no link pointers.
+	 */
+	duk_heaphdr_string hdr;
+
+	/* Note: we could try to stuff a partial hash (e.g. 16 bits) into the
+	 * shared heap header.  Good hashing needs more hash bits though.
+	 */
+
+	duk_uint32_t hash;         /* string hash */
+	duk_uint32_t blen;         /* length in bytes (not counting NUL term) */
+	duk_uint32_t clen;         /* length in codepoints (must be E5 compatible) */
+
+	/*
+	 *  String value of 'blen+1' bytes follows (+1 for NUL termination
+	 *  convenience for C API).  No alignment needs to be guaranteed
+	 *  for strings, but fields above should guarantee alignment-by-4
+	 *  (but not alignment-by-8).
+	 */
+};
+
+/*
+ *  Prototypes
+ */
+
+duk_ucodepoint_t duk_hstring_char_code_at_raw(duk_hthread *thr, duk_hstring *h, duk_uint_t pos);
+
+#endif  /* DUK_HSTRING_H_INCLUDED */
+
+#line 1 "duk_hobject.h"
+/*
+ *  Heap object representation.
+ *
+ *  Heap objects are used for Ecmascript objects, arrays, and functions,
+ *  but also for internal control like declarative and object environment
+ *  records.  Compiled functions, native functions, and threads are also
+ *  objects but with an extended C struct.
+ *
+ *  Objects provide the required Ecmascript semantics and exotic behaviors
+ *  especially for property access.
+ *
+ *  Properties are stored in three conceptual parts:
+ *
+ *    1. A linear 'entry part' contains ordered key-value-attributes triples
+ *       and is the main method of string properties.
+ *
+ *    2. An optional linear 'array part' is used for array objects to store a
+ *       (dense) range of [0,N[ array indexed entries with default attributes
+ *       (writable, enumerable, configurable).  If the array part would become
+ *       sparse or non-default attributes are required, the array part is
+ *       abandoned and moved to the 'entry part'.
+ *
+ *    3. An optional 'hash part' is used to optimize lookups of the entry
+ *       part; it is used only for objects with sufficiently many properties 
+ *       and can be abandoned without loss of information.
+ *
+ *  These three conceptual parts are stored in a single memory allocated area.
+ *  This minimizes memory allocation overhead but also means that all three
+ *  parts are resized together, and makes property access a bit complicated.
+ */
+
+#ifndef DUK_HOBJECT_H_INCLUDED
+#define DUK_HOBJECT_H_INCLUDED
+
+/* there are currently 26 flag bits available */
+#define DUK_HOBJECT_FLAG_EXTENSIBLE            DUK_HEAPHDR_USER_FLAG(0)   /* object is extensible */
+#define DUK_HOBJECT_FLAG_CONSTRUCTABLE         DUK_HEAPHDR_USER_FLAG(1)   /* object is constructable */
+#define DUK_HOBJECT_FLAG_BOUND                 DUK_HEAPHDR_USER_FLAG(2)   /* object established using Function.prototype.bind() */
+#define DUK_HOBJECT_FLAG_COMPILEDFUNCTION      DUK_HEAPHDR_USER_FLAG(4)   /* object is a compiled function (duk_hcompiledfunction) */
+#define DUK_HOBJECT_FLAG_NATIVEFUNCTION        DUK_HEAPHDR_USER_FLAG(5)   /* object is a native function (duk_hnativefunction) */
+#define DUK_HOBJECT_FLAG_THREAD                DUK_HEAPHDR_USER_FLAG(6)   /* object is a thread (duk_hthread) */
+#define DUK_HOBJECT_FLAG_ARRAY_PART            DUK_HEAPHDR_USER_FLAG(7)   /* object has an array part (a_size may still be 0) */
+#define DUK_HOBJECT_FLAG_STRICT                DUK_HEAPHDR_USER_FLAG(8)   /* function: function object is strict */
+#define DUK_HOBJECT_FLAG_NOTAIL                DUK_HEAPHDR_USER_FLAG(9)   /* function: function must not be tailcalled */
+#define DUK_HOBJECT_FLAG_NEWENV                DUK_HEAPHDR_USER_FLAG(10)  /* function: create new environment when called (see duk_hcompiledfunction) */
+#define DUK_HOBJECT_FLAG_NAMEBINDING           DUK_HEAPHDR_USER_FLAG(11)  /* function: create binding for func name (function templates only, used for named function expressions) */
+#define DUK_HOBJECT_FLAG_CREATEARGS            DUK_HEAPHDR_USER_FLAG(12)  /* function: create an arguments object on function call */
+#define DUK_HOBJECT_FLAG_ENVRECCLOSED          DUK_HEAPHDR_USER_FLAG(13)  /* envrec: (declarative) record is closed */
+#define DUK_HOBJECT_FLAG_EXOTIC_ARRAY          DUK_HEAPHDR_USER_FLAG(14)  /* 'Array' object, array length and index exotic behavior */
+#define DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ      DUK_HEAPHDR_USER_FLAG(15)  /* 'String' object, array index exotic behavior */
+#define DUK_HOBJECT_FLAG_EXOTIC_ARGUMENTS      DUK_HEAPHDR_USER_FLAG(16)  /* 'Arguments' object and has arguments exotic behavior (non-strict callee) */
+#define DUK_HOBJECT_FLAG_EXOTIC_DUKFUNC        DUK_HEAPHDR_USER_FLAG(17)  /* Duktape/C (nativefunction) object, exotic 'length' */
+#define DUK_HOBJECT_FLAG_EXOTIC_BUFFEROBJ      DUK_HEAPHDR_USER_FLAG(18)  /* 'Buffer' object, array index exotic behavior, virtual 'length' */
+#define DUK_HOBJECT_FLAG_EXOTIC_PROXYOBJ       DUK_HEAPHDR_USER_FLAG(19)  /* 'Proxy' object */
+/* bit 20 unused */
+
+#define DUK_HOBJECT_FLAG_CLASS_BASE            DUK_HEAPHDR_USER_FLAG_NUMBER(21)
+#define DUK_HOBJECT_FLAG_CLASS_BITS            5
+
+#define DUK_HOBJECT_GET_CLASS_NUMBER(h)        \
+	DUK_HEAPHDR_GET_FLAG_RANGE(&(h)->hdr, DUK_HOBJECT_FLAG_CLASS_BASE, DUK_HOBJECT_FLAG_CLASS_BITS)
+#define DUK_HOBJECT_SET_CLASS_NUMBER(h,v)      \
+	DUK_HEAPHDR_SET_FLAG_RANGE(&(h)->hdr, DUK_HOBJECT_FLAG_CLASS_BASE, DUK_HOBJECT_FLAG_CLASS_BITS, (v))
+
+/* Macro for creating flag initializer from a class number.
+ * Unsigned type cast is needed to avoid warnings about coercing
+ * a signed integer to an unsigned one; the largest class values
+ * have the highest bit (bit 31) set which causes this.
+ */
+#define DUK_HOBJECT_CLASS_AS_FLAGS(v)          (((duk_uint_t) (v)) << DUK_HOBJECT_FLAG_CLASS_BASE)
+
+/* E5 Section 8.6.2 + custom classes */
+#define DUK_HOBJECT_CLASS_UNUSED               0
+#define DUK_HOBJECT_CLASS_ARGUMENTS            1
+#define DUK_HOBJECT_CLASS_ARRAY                2
+#define DUK_HOBJECT_CLASS_BOOLEAN              3
+#define DUK_HOBJECT_CLASS_DATE                 4
+#define DUK_HOBJECT_CLASS_ERROR                5
+#define DUK_HOBJECT_CLASS_FUNCTION             6
+#define DUK_HOBJECT_CLASS_JSON                 7
+#define DUK_HOBJECT_CLASS_MATH                 8
+#define DUK_HOBJECT_CLASS_NUMBER               9
+#define DUK_HOBJECT_CLASS_OBJECT               10
+#define DUK_HOBJECT_CLASS_REGEXP               11
+#define DUK_HOBJECT_CLASS_STRING               12
+#define DUK_HOBJECT_CLASS_GLOBAL               13
+#define DUK_HOBJECT_CLASS_OBJENV               14  /* custom */
+#define DUK_HOBJECT_CLASS_DECENV               15  /* custom */
+#define DUK_HOBJECT_CLASS_BUFFER               16  /* custom */
+#define DUK_HOBJECT_CLASS_POINTER              17  /* custom */
+#define DUK_HOBJECT_CLASS_THREAD               18  /* custom */
+
+#define DUK_HOBJECT_IS_OBJENV(h)               (DUK_HOBJECT_GET_CLASS_NUMBER((h)) == DUK_HOBJECT_CLASS_OBJENV)
+#define DUK_HOBJECT_IS_DECENV(h)               (DUK_HOBJECT_GET_CLASS_NUMBER((h)) == DUK_HOBJECT_CLASS_DECENV)
+#define DUK_HOBJECT_IS_ENV(h)                  (DUK_HOBJECT_IS_OBJENV((h)) || DUK_HOBJECT_IS_DECENV((h)))
+#define DUK_HOBJECT_IS_ARRAY(h)                (DUK_HOBJECT_GET_CLASS_NUMBER((h)) == DUK_HOBJECT_CLASS_ARRAY)
+#define DUK_HOBJECT_IS_COMPILEDFUNCTION(h)     DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_COMPILEDFUNCTION)
+#define DUK_HOBJECT_IS_NATIVEFUNCTION(h)       DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NATIVEFUNCTION)
+#define DUK_HOBJECT_IS_THREAD(h)               DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_THREAD)
+
+#define DUK_HOBJECT_IS_NONBOUND_FUNCTION(h)    DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, \
+                                                        DUK_HOBJECT_FLAG_COMPILEDFUNCTION | \
+                                                        DUK_HOBJECT_FLAG_NATIVEFUNCTION)
+
+#define DUK_HOBJECT_IS_FUNCTION(h)             DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, \
+                                                        DUK_HOBJECT_FLAG_BOUND | \
+                                                        DUK_HOBJECT_FLAG_COMPILEDFUNCTION | \
+                                                        DUK_HOBJECT_FLAG_NATIVEFUNCTION)
+
+#define DUK_HOBJECT_IS_CALLABLE(h)             DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, \
+                                                        DUK_HOBJECT_FLAG_BOUND | \
+                                                        DUK_HOBJECT_FLAG_COMPILEDFUNCTION | \
+                                                        DUK_HOBJECT_FLAG_NATIVEFUNCTION)
+
+/* object has any exotic behavior(s) */
+#define DUK_HOBJECT_EXOTIC_BEHAVIOR_FLAGS      (DUK_HOBJECT_FLAG_EXOTIC_ARRAY | \
+                                                DUK_HOBJECT_FLAG_EXOTIC_ARGUMENTS | \
+                                                DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ | \
+                                                DUK_HOBJECT_FLAG_EXOTIC_DUKFUNC | \
+                                                DUK_HOBJECT_FLAG_EXOTIC_BUFFEROBJ | \
+                                                DUK_HOBJECT_FLAG_EXOTIC_PROXYOBJ)
+
+#define DUK_HOBJECT_HAS_EXOTIC_BEHAVIOR(h)     DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_EXOTIC_BEHAVIOR_FLAGS)
+
+#define DUK_HOBJECT_HAS_EXTENSIBLE(h)          DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXTENSIBLE)
+#define DUK_HOBJECT_HAS_CONSTRUCTABLE(h)       DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CONSTRUCTABLE)
+#define DUK_HOBJECT_HAS_BOUND(h)               DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_BOUND)
+#define DUK_HOBJECT_HAS_COMPILEDFUNCTION(h)    DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_COMPILEDFUNCTION)
+#define DUK_HOBJECT_HAS_NATIVEFUNCTION(h)      DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NATIVEFUNCTION)
+#define DUK_HOBJECT_HAS_THREAD(h)              DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_THREAD)
+#define DUK_HOBJECT_HAS_ARRAY_PART(h)          DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_ARRAY_PART)
+#define DUK_HOBJECT_HAS_STRICT(h)              DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_STRICT)
+#define DUK_HOBJECT_HAS_NOTAIL(h)              DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NOTAIL)
+#define DUK_HOBJECT_HAS_NEWENV(h)              DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NEWENV)
+#define DUK_HOBJECT_HAS_NAMEBINDING(h)         DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NAMEBINDING)
+#define DUK_HOBJECT_HAS_CREATEARGS(h)          DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CREATEARGS)
+#define DUK_HOBJECT_HAS_ENVRECCLOSED(h)        DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_ENVRECCLOSED)
+#define DUK_HOBJECT_HAS_EXOTIC_ARRAY(h)        DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_ARRAY)
+#define DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(h)    DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ)
+#define DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(h)    DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_ARGUMENTS)
+#define DUK_HOBJECT_HAS_EXOTIC_DUKFUNC(h)      DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_DUKFUNC)
+#define DUK_HOBJECT_HAS_EXOTIC_BUFFEROBJ(h)    DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_BUFFEROBJ)
+#define DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(h)     DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_PROXYOBJ)
+
+#define DUK_HOBJECT_SET_EXTENSIBLE(h)          DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXTENSIBLE)
+#define DUK_HOBJECT_SET_CONSTRUCTABLE(h)       DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CONSTRUCTABLE)
+#define DUK_HOBJECT_SET_BOUND(h)               DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_BOUND)
+#define DUK_HOBJECT_SET_COMPILEDFUNCTION(h)    DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_COMPILEDFUNCTION)
+#define DUK_HOBJECT_SET_NATIVEFUNCTION(h)      DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NATIVEFUNCTION)
+#define DUK_HOBJECT_SET_THREAD(h)              DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_THREAD)
+#define DUK_HOBJECT_SET_ARRAY_PART(h)          DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_ARRAY_PART)
+#define DUK_HOBJECT_SET_STRICT(h)              DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_STRICT)
+#define DUK_HOBJECT_SET_NOTAIL(h)              DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NOTAIL)
+#define DUK_HOBJECT_SET_NEWENV(h)              DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NEWENV)
+#define DUK_HOBJECT_SET_NAMEBINDING(h)         DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NAMEBINDING)
+#define DUK_HOBJECT_SET_CREATEARGS(h)          DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CREATEARGS)
+#define DUK_HOBJECT_SET_ENVRECCLOSED(h)        DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_ENVRECCLOSED)
+#define DUK_HOBJECT_SET_EXOTIC_ARRAY(h)        DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_ARRAY)
+#define DUK_HOBJECT_SET_EXOTIC_STRINGOBJ(h)    DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ)
+#define DUK_HOBJECT_SET_EXOTIC_ARGUMENTS(h)    DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_ARGUMENTS)
+#define DUK_HOBJECT_SET_EXOTIC_DUKFUNC(h)      DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_DUKFUNC)
+#define DUK_HOBJECT_SET_EXOTIC_BUFFEROBJ(h)    DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_BUFFEROBJ)
+#define DUK_HOBJECT_SET_EXOTIC_PROXYOBJ(h)     DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_PROXYOBJ)
+
+#define DUK_HOBJECT_CLEAR_EXTENSIBLE(h)        DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXTENSIBLE)
+#define DUK_HOBJECT_CLEAR_CONSTRUCTABLE(h)     DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CONSTRUCTABLE)
+#define DUK_HOBJECT_CLEAR_BOUND(h)             DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_BOUND)
+#define DUK_HOBJECT_CLEAR_COMPILEDFUNCTION(h)  DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_COMPILEDFUNCTION)
+#define DUK_HOBJECT_CLEAR_NATIVEFUNCTION(h)    DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NATIVEFUNCTION)
+#define DUK_HOBJECT_CLEAR_THREAD(h)            DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_THREAD)
+#define DUK_HOBJECT_CLEAR_ARRAY_PART(h)        DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_ARRAY_PART)
+#define DUK_HOBJECT_CLEAR_STRICT(h)            DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_STRICT)
+#define DUK_HOBJECT_CLEAR_NOTAIL(h)            DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NOTAIL)
+#define DUK_HOBJECT_CLEAR_NEWENV(h)            DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NEWENV)
+#define DUK_HOBJECT_CLEAR_NAMEBINDING(h)       DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NAMEBINDING)
+#define DUK_HOBJECT_CLEAR_CREATEARGS(h)        DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CREATEARGS)
+#define DUK_HOBJECT_CLEAR_ENVRECCLOSED(h)      DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_ENVRECCLOSED)
+#define DUK_HOBJECT_CLEAR_EXOTIC_ARRAY(h)      DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_ARRAY)
+#define DUK_HOBJECT_CLEAR_EXOTIC_STRINGOBJ(h)  DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ)
+#define DUK_HOBJECT_CLEAR_EXOTIC_ARGUMENTS(h)  DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_ARGUMENTS)
+#define DUK_HOBJECT_CLEAR_EXOTIC_DUKFUNC(h)    DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_DUKFUNC)
+#define DUK_HOBJECT_CLEAR_EXOTIC_BUFFEROBJ(h)  DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_BUFFEROBJ)
+#define DUK_HOBJECT_CLEAR_EXOTIC_PROXYOBJ(h)   DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_PROXYOBJ)
+
+/* flags used for property attributes in duk_propdesc and packed flags */
+#define DUK_PROPDESC_FLAG_WRITABLE              (1 << 0)    /* E5 Section 8.6.1 */
+#define DUK_PROPDESC_FLAG_ENUMERABLE            (1 << 1)    /* E5 Section 8.6.1 */
+#define DUK_PROPDESC_FLAG_CONFIGURABLE          (1 << 2)    /* E5 Section 8.6.1 */
+#define DUK_PROPDESC_FLAG_ACCESSOR              (1 << 3)    /* accessor */
+#define DUK_PROPDESC_FLAG_VIRTUAL               (1 << 4)    /* property is virtual: used in duk_propdesc, never stored
+                                                             * (used by e.g. buffer virtual properties)
+                                                             */
+#define DUK_PROPDESC_FLAGS_MASK                 (DUK_PROPDESC_FLAG_WRITABLE | \
+                                                 DUK_PROPDESC_FLAG_ENUMERABLE | \
+                                                 DUK_PROPDESC_FLAG_CONFIGURABLE | \
+                                                 DUK_PROPDESC_FLAG_ACCESSOR)
+
+/* additional flags which are passed in the same flags argument as property
+ * flags but are not stored in object properties.
+ */
+#define DUK_PROPDESC_FLAG_NO_OVERWRITE          (1 << 4)    /* internal define property: skip write silently if exists */
+
+/* convenience */
+#define DUK_PROPDESC_FLAGS_NONE                 0
+#define DUK_PROPDESC_FLAGS_W                    (DUK_PROPDESC_FLAG_WRITABLE)
+#define DUK_PROPDESC_FLAGS_E                    (DUK_PROPDESC_FLAG_ENUMERABLE)
+#define DUK_PROPDESC_FLAGS_C                    (DUK_PROPDESC_FLAG_CONFIGURABLE)
+#define DUK_PROPDESC_FLAGS_WE                   (DUK_PROPDESC_FLAG_WRITABLE | DUK_PROPDESC_FLAG_ENUMERABLE)
+#define DUK_PROPDESC_FLAGS_WC                   (DUK_PROPDESC_FLAG_WRITABLE | DUK_PROPDESC_FLAG_CONFIGURABLE)
+#define DUK_PROPDESC_FLAGS_EC                   (DUK_PROPDESC_FLAG_ENUMERABLE | DUK_PROPDESC_FLAG_CONFIGURABLE)
+#define DUK_PROPDESC_FLAGS_WEC                  (DUK_PROPDESC_FLAG_WRITABLE | \
+                                                 DUK_PROPDESC_FLAG_ENUMERABLE | \
+                                                 DUK_PROPDESC_FLAG_CONFIGURABLE)
+
+/*
+ *  Macros to access the 'p' allocation.
+ */
+
+#if defined(DUK_USE_HOBJECT_LAYOUT_1)
+/* LAYOUT 1 */
+#define DUK_HOBJECT_E_GET_KEY_BASE(h)           \
+	((duk_hstring **) ( \
+		(h)->p \
+	))
+#define DUK_HOBJECT_E_GET_VALUE_BASE(h)         \
+	((duk_propvalue *) ( \
+		(h)->p + \
+			(h)->e_size * sizeof(duk_hstring *) \
+	))
+#define DUK_HOBJECT_E_GET_FLAGS_BASE(h)         \
+	((duk_uint8_t *) ( \
+		(h)->p + (h)->e_size * (sizeof(duk_hstring *) + sizeof(duk_propvalue)) \
+	))
+#define DUK_HOBJECT_A_GET_BASE(h)               \
+	((duk_tval *) ( \
+		(h)->p + \
+			(h)->e_size * (sizeof(duk_hstring *) + sizeof(duk_propvalue) + sizeof(duk_uint8_t)) \
+	))
+#define DUK_HOBJECT_H_GET_BASE(h)               \
+	((duk_uint32_t *) ( \
+		(h)->p + \
+			(h)->e_size * (sizeof(duk_hstring *) + sizeof(duk_propvalue) + sizeof(duk_uint8_t)) + \
+			(h)->a_size * sizeof(duk_tval) \
+	))
+#define DUK_HOBJECT_P_COMPUTE_SIZE(n_ent,n_arr,n_hash) \
+	( \
+		(n_ent) * (sizeof(duk_hstring *) + sizeof(duk_propvalue) + sizeof(duk_uint8_t)) + \
+		(n_arr) * sizeof(duk_tval) + \
+		(n_hash) * sizeof(duk_uint32_t) \
+	)
+#define DUK_HOBJECT_P_SET_REALLOC_PTRS(p_base,set_e_k,set_e_pv,set_e_f,set_a,set_h,n_ent,n_arr,n_hash)  do { \
+		(set_e_k) = (duk_hstring **) (p_base); \
+		(set_e_pv) = (duk_propvalue *) ((set_e_k) + (n_ent)); \
+		(set_e_f) = (duk_uint8_t *) ((set_e_pv) + (n_ent)); \
+		(set_a) = (duk_tval *) ((set_e_f) + (n_ent)); \
+		(set_h) = (duk_uint32_t *) ((set_a) + (n_arr)); \
+	} while (0)
+#elif defined(DUK_USE_HOBJECT_LAYOUT_2)
+/* LAYOUT 2 */
+#if defined(DUK_USE_ALIGN_4)
+#define DUK_HOBJECT_E_FLAG_PADDING(e_sz) ((4 - (e_sz)) & 0x03)
+#elif defined(DUK_USE_ALIGN_8)
+#define DUK_HOBJECT_E_FLAG_PADDING(e_sz) ((8 - (e_sz)) & 0x07)
+#else
+#define DUK_HOBJECT_E_FLAG_PADDING(e_sz) 0
+#endif
+#define DUK_HOBJECT_E_GET_KEY_BASE(h)           \
+	((duk_hstring **) ( \
+		(h)->p + \
+			(h)->e_size * sizeof(duk_propvalue) \
+	))
+#define DUK_HOBJECT_E_GET_VALUE_BASE(h)         \
+	((duk_propvalue *) ( \
+		(h)->p \
+	))
+#define DUK_HOBJECT_E_GET_FLAGS_BASE(h)         \
+	((duk_uint8_t *) ( \
+		(h)->p + (h)->e_size * (sizeof(duk_hstring *) + sizeof(duk_propvalue)) \
+	))
+#define DUK_HOBJECT_A_GET_BASE(h)               \
+	((duk_tval *) ( \
+		(h)->p + \
+			(h)->e_size * (sizeof(duk_hstring *) + sizeof(duk_propvalue) + sizeof(duk_uint8_t)) + \
+			DUK_HOBJECT_E_FLAG_PADDING((h)->e_size) \
+	))
+#define DUK_HOBJECT_H_GET_BASE(h)               \
+	((duk_uint32_t *) ( \
+		(h)->p + \
+			(h)->e_size * (sizeof(duk_hstring *) + sizeof(duk_propvalue) + sizeof(duk_uint8_t)) + \
+			DUK_HOBJECT_E_FLAG_PADDING((h)->e_size) + \
+			(h)->a_size * sizeof(duk_tval) \
+	))
+#define DUK_HOBJECT_P_COMPUTE_SIZE(n_ent,n_arr,n_hash) \
+	( \
+		(n_ent) * (sizeof(duk_hstring *) + sizeof(duk_propvalue) + sizeof(duk_uint8_t)) + \
+		DUK_HOBJECT_E_FLAG_PADDING((n_ent)) + \
+		(n_arr) * sizeof(duk_tval) + \
+		(n_hash) * sizeof(duk_uint32_t) \
+	)
+#define DUK_HOBJECT_P_SET_REALLOC_PTRS(p_base,set_e_k,set_e_pv,set_e_f,set_a,set_h,n_ent,n_arr,n_hash)  do { \
+		(set_e_pv) = (duk_propvalue *) (p_base); \
+		(set_e_k) = (duk_hstring **) ((set_e_pv) + (n_ent)); \
+		(set_e_f) = (duk_uint8_t *) ((set_e_k) + (n_ent)); \
+		(set_a) = (duk_tval *) (((duk_uint8_t *) (set_e_f)) + \
+		                        sizeof(duk_uint8_t) * (n_ent) + \
+		                        DUK_HOBJECT_E_FLAG_PADDING((n_ent))); \
+		(set_h) = (duk_uint32_t *) ((set_a) + (n_arr)); \
+	} while (0)
+#elif defined(DUK_USE_HOBJECT_LAYOUT_3)
+/* LAYOUT 3 */
+#define DUK_HOBJECT_E_GET_KEY_BASE(h)           \
+	((duk_hstring **) ( \
+		(h)->p + \
+			(h)->e_size * sizeof(duk_propvalue) + \
+			(h)->a_size * sizeof(duk_tval) \
+	))
+#define DUK_HOBJECT_E_GET_VALUE_BASE(h)         \
+	((duk_propvalue *) ( \
+		(h)->p \
+	))
+#define DUK_HOBJECT_E_GET_FLAGS_BASE(h)         \
+	((duk_uint8_t *) ( \
+		(h)->p + \
+			(h)->e_size * (sizeof(duk_propvalue) + sizeof(duk_hstring *)) + \
+			(h)->a_size * sizeof(duk_tval) + \
+			(h)->h_size * sizeof(duk_uint32_t) \
+	))
+#define DUK_HOBJECT_A_GET_BASE(h)               \
+	((duk_tval *) ( \
+		(h)->p + \
+			(h)->e_size * sizeof(duk_propvalue) \
+	))
+#define DUK_HOBJECT_H_GET_BASE(h)               \
+	((duk_uint32_t *) ( \
+		(h)->p + \
+			(h)->e_size * (sizeof(duk_propvalue) + sizeof(duk_hstring *)) + \
+			(h)->a_size * sizeof(duk_tval) \
+	))
+#define DUK_HOBJECT_P_COMPUTE_SIZE(n_ent,n_arr,n_hash) \
+	( \
+		(n_ent) * (sizeof(duk_propvalue) + sizeof(duk_hstring *) + sizeof(duk_uint8_t)) + \
+		(n_arr) * sizeof(duk_tval) + \
+		(n_hash) * sizeof(duk_uint32_t) \
+	)
+#define DUK_HOBJECT_P_SET_REALLOC_PTRS(p_base,set_e_k,set_e_pv,set_e_f,set_a,set_h,n_ent,n_arr,n_hash)  do { \
+		(set_e_pv) = (duk_propvalue *) (p_base); \
+		(set_a) = (duk_tval *) ((set_e_pv) + (n_ent)); \
+		(set_e_k) = (duk_hstring **) ((set_a) + (n_arr)); \
+		(set_h) = (duk_uint32_t *) ((set_e_k) + (n_ent)); \
+		(set_e_f) = (duk_uint8_t *) ((set_h) + (n_hash)); \
+	} while (0)
+#else
+#error invalid hobject layout defines
+#endif  /* hobject property layout */
+
+#define DUK_HOBJECT_E_ALLOC_SIZE(h) DUK_HOBJECT_P_COMPUTE_SIZE((h)->e_size, (h)->a_size, (h)->h_size)
+
+#define DUK_HOBJECT_E_GET_KEY(h,i)              (DUK_HOBJECT_E_GET_KEY_BASE((h))[(i)])
+#define DUK_HOBJECT_E_GET_KEY_PTR(h,i)          (&DUK_HOBJECT_E_GET_KEY_BASE((h))[(i)])
+#define DUK_HOBJECT_E_GET_VALUE(h,i)            (DUK_HOBJECT_E_GET_VALUE_BASE((h))[(i)])
+#define DUK_HOBJECT_E_GET_VALUE_PTR(h,i)        (&DUK_HOBJECT_E_GET_VALUE_BASE((h))[(i)])
+#define DUK_HOBJECT_E_GET_VALUE_TVAL(h,i)       (DUK_HOBJECT_E_GET_VALUE((h),(i)).v)
+#define DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(h,i)   (&DUK_HOBJECT_E_GET_VALUE((h),(i)).v)
+#define DUK_HOBJECT_E_GET_VALUE_GETTER(h,i)     (DUK_HOBJECT_E_GET_VALUE((h),(i)).a.get)
+#define DUK_HOBJECT_E_GET_VALUE_GETTER_PTR(h,i) (&DUK_HOBJECT_E_GET_VALUE((h),(i)).a.get)
+#define DUK_HOBJECT_E_GET_VALUE_SETTER(h,i)     (DUK_HOBJECT_E_GET_VALUE((h),(i)).a.set)
+#define DUK_HOBJECT_E_GET_VALUE_SETTER_PTR(h,i) (&DUK_HOBJECT_E_GET_VALUE((h),(i)).a.set)
+#define DUK_HOBJECT_E_GET_FLAGS(h,i)            (DUK_HOBJECT_E_GET_FLAGS_BASE((h))[(i)])
+#define DUK_HOBJECT_E_GET_FLAGS_PTR(h,i)        (&DUK_HOBJECT_E_GET_FLAGS_BASE((h))[(i)])
+#define DUK_HOBJECT_A_GET_VALUE(h,i)            (DUK_HOBJECT_A_GET_BASE((h))[(i)])
+#define DUK_HOBJECT_A_GET_VALUE_PTR(h,i)        (&DUK_HOBJECT_A_GET_BASE((h))[(i)])
+#define DUK_HOBJECT_H_GET_INDEX(h,i)            (DUK_HOBJECT_H_GET_BASE((h))[(i)])
+#define DUK_HOBJECT_H_GET_INDEX_PTR(h,i)        (&DUK_HOBJECT_H_GET_BASE((h))[(i)])
+
+#define DUK_HOBJECT_E_SET_KEY(h,i,k)  do { \
+		DUK_HOBJECT_E_GET_KEY((h),(i)) = (k); \
+	} while (0)
+#define DUK_HOBJECT_E_SET_VALUE(h,i,v)  do { \
+		DUK_HOBJECT_E_GET_VALUE((h),(i)) = (v); \
+	} while (0)
+#define DUK_HOBJECT_E_SET_VALUE_TVAL(h,i,v)  do { \
+		DUK_HOBJECT_E_GET_VALUE((h),(i)).v = (v); \
+	} while (0)
+#define DUK_HOBJECT_E_SET_VALUE_GETTER(h,i,v)  do { \
+		DUK_HOBJECT_E_GET_VALUE((h),(i)).a.get = (v); \
+	} while (0)
+#define DUK_HOBJECT_E_SET_VALUE_SETTER(h,i,v)  do { \
+		DUK_HOBJECT_E_GET_VALUE((h),(i)).a.set = (v); \
+	} while (0)
+#define DUK_HOBJECT_E_SET_FLAGS(h,i,f)  do { \
+		DUK_HOBJECT_E_GET_FLAGS((h),(i)) = (f); \
+	} while (0)
+#define DUK_HOBJECT_A_SET_VALUE(h,i,v)  do { \
+		DUK_HOBJECT_A_GET_VALUE((h),(i)) = (v); \
+	} while (0)
+#define DUK_HOBJECT_A_SET_VALUE_TVAL(h,i,v)  DUK_HOBJECT_A_SET_VALUE((h),(i),(v))  /* alias for above */
+#define DUK_HOBJECT_H_SET_INDEX(h,i,v)  do { \
+		DUK_HOBJECT_H_GET_INDEX((h),(i)) = (v); \
+	} while (0)
+
+#define DUK_HOBJECT_E_SET_FLAG_BITS(h,i,mask)  do { \
+		DUK_HOBJECT_E_GET_FLAGS_BASE((h))[(i)] |= (mask); \
+	} while (0)
+
+#define DUK_HOBJECT_E_CLEAR_FLAG_BITS(h,i,mask)  do { \
+		DUK_HOBJECT_E_GET_FLAGS_BASE((h))[(i)] &= ~(mask); \
+	} while (0)
+
+#define DUK_HOBJECT_E_SLOT_IS_WRITABLE(h,i)     ((DUK_HOBJECT_E_GET_FLAGS((h),(i)) & DUK_PROPDESC_FLAG_WRITABLE) != 0)
+#define DUK_HOBJECT_E_SLOT_IS_ENUMERABLE(h,i)   ((DUK_HOBJECT_E_GET_FLAGS((h),(i)) & DUK_PROPDESC_FLAG_ENUMERABLE) != 0)
+#define DUK_HOBJECT_E_SLOT_IS_CONFIGURABLE(h,i) ((DUK_HOBJECT_E_GET_FLAGS((h),(i)) & DUK_PROPDESC_FLAG_CONFIGURABLE) != 0)
+#define DUK_HOBJECT_E_SLOT_IS_ACCESSOR(h,i)     ((DUK_HOBJECT_E_GET_FLAGS((h),(i)) & DUK_PROPDESC_FLAG_ACCESSOR) != 0)
+
+#define DUK_HOBJECT_E_SLOT_SET_WRITABLE(h,i)        DUK_HOBJECT_E_SET_FLAG_BITS((h),(i),DUK_PROPDESC_FLAG_WRITABLE)
+#define DUK_HOBJECT_E_SLOT_SET_ENUMERABLE(h,i)      DUK_HOBJECT_E_SET_FLAG_BITS((h),(i),DUK_PROPDESC_FLAG_ENUMERABLE)
+#define DUK_HOBJECT_E_SLOT_SET_CONFIGURABLE(h,i)    DUK_HOBJECT_E_SET_FLAG_BITS((h),(i),DUK_PROPDESC_FLAG_CONFIGURABLE)
+#define DUK_HOBJECT_E_SLOT_SET_ACCESSOR(h,i)        DUK_HOBJECT_E_SET_FLAG_BITS((h),(i),DUK_PROPDESC_FLAG_ACCESSOR)
+
+#define DUK_HOBJECT_E_SLOT_CLEAR_WRITABLE(h,i)      DUK_HOBJECT_E_CLEAR_FLAG_BITS((h),(i),DUK_PROPDESC_FLAG_WRITABLE)
+#define DUK_HOBJECT_E_SLOT_CLEAR_ENUMERABLE(h,i)    DUK_HOBJECT_E_CLEAR_FLAG_BITS((h),(i),DUK_PROPDESC_FLAG_ENUMERABLE)
+#define DUK_HOBJECT_E_SLOT_CLEAR_CONFIGURABLE(h,i)  DUK_HOBJECT_E_CLEAR_FLAG_BITS((h),(i),DUK_PROPDESC_FLAG_CONFIGURABLE)
+#define DUK_HOBJECT_E_SLOT_CLEAR_ACCESSOR(h,i)      DUK_HOBJECT_E_CLEAR_FLAG_BITS((h),(i),DUK_PROPDESC_FLAG_ACCESSOR)
+
+#define DUK_PROPDESC_IS_WRITABLE(p)             (((p)->flags & DUK_PROPDESC_FLAG_WRITABLE) != 0)
+#define DUK_PROPDESC_IS_ENUMERABLE(p)           (((p)->flags & DUK_PROPDESC_FLAG_ENUMERABLE) != 0)
+#define DUK_PROPDESC_IS_CONFIGURABLE(p)         (((p)->flags & DUK_PROPDESC_FLAG_CONFIGURABLE) != 0)
+#define DUK_PROPDESC_IS_ACCESSOR(p)             (((p)->flags & DUK_PROPDESC_FLAG_ACCESSOR) != 0)
+
+#define DUK_HOBJECT_HASHIDX_UNUSED              0xffffffffUL
+#define DUK_HOBJECT_HASHIDX_DELETED             0xfffffffeUL
+
+/*
+ *  Misc
+ */
+
+/* Maximum prototype traversal depth.  Sanity limit which handles e.g.
+ * prototype loops (even complex ones like 1->2->3->4->2->3->4->2->3->4).
+ */
+#define DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY      10000L
+
+/* Maximum traversal depth for "bound function" chains. */
+#define DUK_HOBJECT_BOUND_CHAIN_SANITY          10000L
+
+/*
+ *  Ecmascript [[Class]]
+ */
+
+/* range check not necessary because all 4-bit values are mapped */
+#define DUK_HOBJECT_CLASS_NUMBER_TO_STRIDX(n)  duk_class_number_to_stridx[(n)]
+
+#define DUK_HOBJECT_GET_CLASS_STRING(heap,h)          \
+	DUK_HEAP_GET_STRING( \
+		(heap), \
+		DUK_HOBJECT_CLASS_NUMBER_TO_STRIDX(DUK_HOBJECT_GET_CLASS_NUMBER((h))) \
+	)
+
+/*
+ *  Macros for property handling
+ */		
+
+/* note: this updates refcounts */
+#define DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr,h,p)       duk_hobject_set_prototype((thr),(h),(p))
+
+/*
+ *  Macros for Ecmascript built-in semantics
+ */
+
+#define DUK_HOBJECT_OBJECT_SEAL(thr,obj)                duk_hobject_object_seal_freeze_helper((thr),(obj),0)
+#define DUK_HOBJECT_OBJECT_FREEZE(htr,obj)              duk_hobject_object_seal_freeze_helper((thr),(obj),1)
+#define DUK_HOBJECT_OBJECT_IS_SEALED(obj)               duk_hobject_object_is_sealed_frozen_helper((obj),0)
+#define DUK_HOBJECT_OBJECT_IS_FROZEN(obj)               duk_hobject_object_is_sealed_frozen_helper((obj),1)
+#define DUK_HOBJECT_OBJECT_PREVENT_EXTENSIONS(vm,obj)   DUK_HOBJECT_CLEAR_EXTENSIBLE((obj))
+#define DUK_HOBJECT_OBJECT_IS_EXTENSIBLE(vm,obj)        DUK_HOBJECT_HAS_EXTENSIBLE((obj))
+
+/*
+ *  Resizing and hash behavior
+ */
+
+/* Sanity limit on max number of properties (allocated, not necessarily used).
+ * This is somewhat arbitrary, but if we're close to 2**32 properties some
+ * algorithms will fail (e.g. hash size selection, next prime selection).
+ * Also, we use negative array/entry table indices to indicate 'not found',
+ * so anything above 0x80000000 will cause trouble now.
+ */
+#define DUK_HOBJECT_MAX_PROPERTIES       0x7fffffffUL   /* 2**31-1 ~= 2G properties */
+
+/* higher value conserves memory; also note that linear scan is cache friendly */
+#define DUK_HOBJECT_E_USE_HASH_LIMIT     32
+
+/* hash size relative to entries size: for value X, approx. hash_prime(e_size + e_size / X) */
+#define DUK_HOBJECT_H_SIZE_DIVISOR       4  /* hash size approx. 1.25 times entries size */
+
+/* if new_size < L * old_size, resize without abandon check; L = 3-bit fixed point, e.g. 9 -> 9/8 = 112.5% */
+#define DUK_HOBJECT_A_FAST_RESIZE_LIMIT  9  /* 112.5%, i.e. new size less than 12.5% higher -> fast resize */
+
+/* if density < L, abandon array part, L = 3-bit fixed point, e.g. 2 -> 2/8 = 25% */
+/* limit is quite low: one array entry is 8 bytes, one normal entry is 4+1+8+4 = 17 bytes (with hash entry) */
+#define DUK_HOBJECT_A_ABANDON_LIMIT      2  /* 25%, i.e. less than 25% used -> abandon */
+
+/* internal align target for props allocation, must be 2*n for some n */
+#if defined(DUK_USE_ALIGN_4)
+#define DUK_HOBJECT_ALIGN_TARGET         4
+#elif defined(DUK_USE_ALIGN_8)
+#define DUK_HOBJECT_ALIGN_TARGET         8
+#else
+#define DUK_HOBJECT_ALIGN_TARGET         1
+#endif
+
+/* controls for minimum entry part growth */
+#define DUK_HOBJECT_E_MIN_GROW_ADD       16
+#define DUK_HOBJECT_E_MIN_GROW_DIVISOR   8  /* 2^3 -> 1/8 = 12.5% min growth */
+
+/* controls for minimum array part growth */
+#define DUK_HOBJECT_A_MIN_GROW_ADD       16
+#define DUK_HOBJECT_A_MIN_GROW_DIVISOR   8  /* 2^3 -> 1/8 = 12.5% min growth */
+
+/* probe sequence */
+#define DUK_HOBJECT_HASH_INITIAL(hash,h_size)  ((hash) % (h_size))
+#define DUK_HOBJECT_HASH_PROBE_STEP(hash)      DUK_UTIL_GET_HASH_PROBE_STEP((hash))
+
+/*
+ *  PC-to-line constants
+ */
+
+#define DUK_PC2LINE_SKIP    64
+
+/* maximum length for a SKIP-1 diffstream: 35 bits per entry, rounded up to bytes */
+#define DUK_PC2LINE_MAX_DIFF_LENGTH    (((DUK_PC2LINE_SKIP - 1) * 35 + 7) / 8)
+
+/*
+ *  Struct defs
+ */
+
+struct duk_propaccessor {
+	duk_hobject *get;
+	duk_hobject *set;
+};
+
+union duk_propvalue {
+	duk_tval v;
+	duk_propaccessor a;
+};
+
+struct duk_propdesc {
+	/* read-only values 'lifted' for ease of use */
+	duk_small_int_t flags;
+	duk_hobject *get;
+	duk_hobject *set;
+
+	/* for updating (all are set to < 0 for virtual properties) */
+	duk_int_t e_idx;	/* prop index in 'entry part', < 0 if not there */
+	duk_int_t h_idx;	/* prop index in 'hash part', < 0 if not there */
+	duk_int_t a_idx;	/* prop index in 'array part', < 0 if not there */
+};
+
+struct duk_hobject {
+	duk_heaphdr hdr;
+
+	/*
+	 *  'p' contains {key,value,flags} entries, optional array entries, and an
+	 *  optional hash lookup table for non-array entries in a single 'sliced'
+	 *  allocation.  There are several layout options, which differ slightly in
+	 *  generated code size/speed and alignment/padding; duk_features.h selects
+	 *  the layout used.
+	 *
+	 *  Layout 1 (DUK_USE_HOBJECT_LAYOUT_1):
+	 *
+	 *    e_size * sizeof(duk_hstring *)         bytes of   entry keys (e_used gc reachable)
+	 *    e_size * sizeof(duk_propvalue)         bytes of   entry values (e_used gc reachable)
+	 *    e_size * sizeof(duk_uint8_t)           bytes of   entry flags (e_used gc reachable)
+	 *    a_size * sizeof(duk_tval)              bytes of   (opt) array values (plain only) (all gc reachable)
+	 *    h_size * sizeof(duk_uint32_t)          bytes of   (opt) hash indexes to entries (e_size),
+	 *                                                      0xffffffffUL = unused, 0xfffffffeUL = deleted
+	 *
+	 *  Layout 2 (DUK_USE_HOBJECT_LAYOUT_2):
+	 *
+	 *    e_size * sizeof(duk_propvalue)         bytes of   entry values (e_used gc reachable)
+	 *    e_size * sizeof(duk_hstring *)         bytes of   entry keys (e_used gc reachable)
+	 *    e_size * sizeof(duk_uint8_t) + pad     bytes of   entry flags (e_used gc reachable)
+	 *    a_size * sizeof(duk_tval)              bytes of   (opt) array values (plain only) (all gc reachable)
+	 *    h_size * sizeof(duk_uint32_t)          bytes of   (opt) hash indexes to entries (e_size),
+	 *                                                      0xffffffffUL = unused, 0xfffffffeUL = deleted
+	 *
+	 *  Layout 3 (DUK_USE_HOBJECT_LAYOUT_3):
+	 *
+	 *    e_size * sizeof(duk_propvalue)         bytes of   entry values (e_used gc reachable)
+	 *    a_size * sizeof(duk_tval)              bytes of   (opt) array values (plain only) (all gc reachable)
+	 *    e_size * sizeof(duk_hstring *)         bytes of   entry keys (e_used gc reachable)
+	 *    h_size * sizeof(duk_uint32_t)          bytes of   (opt) hash indexes to entries (e_size),
+	 *                                                      0xffffffffUL = unused, 0xfffffffeUL = deleted
+	 *    e_size * sizeof(duk_uint8_t)           bytes of   entry flags (e_used gc reachable)
+	 *
+	 *  In layout 1, the 'e_used' count is rounded to 4 or 8 on platforms
+	 *  requiring 4 or 8 byte alignment.  This ensures proper alignment
+	 *  for the entries, at the cost of memory footprint.  However, it's
+	 *  probably preferable to use another layout on such platforms instead.
+	 *
+	 *  In layout 2, the key and value parts are swapped to avoid padding
+	 *  the key array on platforms requiring alignment by 8.  The flags part
+	 *  is padded to get alignment for array entries.  The 'e_used' count does
+	 *  not need to be rounded as in layout 1.
+	 *
+	 *  In layout 3, entry values and array values are always aligned properly,
+	 *  and assuming pointers are at most 8 bytes, so are the entry keys.  Hash
+	 *  indices will be properly aligned (assuming pointers are at least 4 bytes).
+	 *  Finally, flags don't need additional alignment.  This layout provides
+	 *  compact allocations without padding (even on platforms with alignment
+	 *  requirements) at the cost of a bit slower lookups.
+	 *
+	 *  Objects with few keys don't have a hash index; keys are looked up linearly,
+	 *  which is cache efficient because the keys are consecutive.  Larger objects
+	 *  have a hash index part which contains integer indexes to the entries part.
+	 *
+	 *  A single allocation reduces memory allocation overhead but requires more
+	 *  work when any part needs to be resized.  A sliced allocation for entries
+	 *  makes linear key matching faster on most platforms (more locality) and
+	 *  skimps on flags size (which would be followed by 3 bytes of padding in
+	 *  most architectures if entries were placed in a struct).
+	 *
+	 *  'p' also contains internal properties distinguished with a non-BMP
+	 *  prefix.  Often used properties should be placed early in 'p' whenever
+	 *  possible to make accessing them as fast a possible.
+	 */
+
+	duk_uint8_t *p;
+	duk_uint32_t e_size;
+	duk_uint32_t e_used;
+	duk_uint32_t a_size;
+	duk_uint32_t h_size;
+
+	/* prototype: the only internal property lifted outside 'e' as it is so central */
+	duk_hobject *prototype;
+};
+
+/*
+ *  Exposed data
+ */
+
+extern duk_uint8_t duk_class_number_to_stridx[32];
+
+/*
+ *  Prototypes
+ */
+
+/* alloc and init */
+duk_hobject *duk_hobject_alloc(duk_heap *heap, duk_uint_t hobject_flags);
+duk_hobject *duk_hobject_alloc_checked(duk_hthread *thr, duk_uint_t hobject_flags);
+duk_hcompiledfunction *duk_hcompiledfunction_alloc(duk_heap *heap, duk_uint_t hobject_flags);
+duk_hnativefunction *duk_hnativefunction_alloc(duk_heap *heap, duk_uint_t hobject_flags);
+duk_hthread *duk_hthread_alloc(duk_heap *heap, duk_uint_t hobject_flags);
+
+/* low-level property functions */
+void duk_hobject_find_existing_entry(duk_hobject *obj, duk_hstring *key, duk_int_t *e_idx, duk_int_t *h_idx);
+duk_tval *duk_hobject_find_existing_entry_tval_ptr(duk_hobject *obj, duk_hstring *key);
+duk_tval *duk_hobject_find_existing_entry_tval_ptr_and_attrs(duk_hobject *obj, duk_hstring *key, duk_int_t *out_attrs);
+duk_tval *duk_hobject_find_existing_array_entry_tval_ptr(duk_hobject *obj, duk_uarridx_t i);
+
+/* core property functions */
+duk_bool_t duk_hobject_getprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key);
+duk_bool_t duk_hobject_putprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key, duk_tval *tv_val, duk_bool_t throw_flag);
+duk_bool_t duk_hobject_delprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key, duk_bool_t throw_flag);
+duk_bool_t duk_hobject_hasprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key);
+
+/* internal property functions */
+duk_bool_t duk_hobject_delprop_raw(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_bool_t throw_flag);
+duk_bool_t duk_hobject_hasprop_raw(duk_hthread *thr, duk_hobject *obj, duk_hstring *key);
+void duk_hobject_define_property_internal(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_small_uint_t flags);
+void duk_hobject_define_property_internal_arridx(duk_hthread *thr, duk_hobject *obj, duk_uarridx_t arr_idx, duk_small_uint_t flags);
+void duk_hobject_define_accessor_internal(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_hobject *getter, duk_hobject *setter, duk_small_uint_t propflags);
+void duk_hobject_set_length(duk_hthread *thr, duk_hobject *obj, duk_uint32_t length);  /* XXX: duk_uarridx_t? */
+void duk_hobject_set_length_zero(duk_hthread *thr, duk_hobject *obj);
+duk_uint32_t duk_hobject_get_length(duk_hthread *thr, duk_hobject *obj);  /* XXX: duk_uarridx_t? */
+
+/* Object built-in methods */
+duk_ret_t duk_hobject_object_define_property(duk_context *ctx);
+duk_ret_t duk_hobject_object_define_properties(duk_context *ctx);
+duk_ret_t duk_hobject_object_get_own_property_descriptor(duk_context *ctx);
+void duk_hobject_object_seal_freeze_helper(duk_hthread *thr, duk_hobject *obj, duk_bool_t is_freeze);
+duk_bool_t duk_hobject_object_is_sealed_frozen_helper(duk_hobject *obj, duk_bool_t is_frozen);
+duk_bool_t duk_hobject_object_ownprop_helper(duk_context *ctx, duk_small_uint_t required_desc_flags);
+
+/* internal properties */
+duk_bool_t duk_hobject_get_internal_value(duk_heap *heap, duk_hobject *obj, duk_tval *tv);
+duk_hstring *duk_hobject_get_internal_value_string(duk_heap *heap, duk_hobject *obj);
+duk_hbuffer *duk_hobject_get_internal_value_buffer(duk_heap *heap, duk_hobject *obj);
+	
+/* hobject management functions */
+void duk_hobject_compact_props(duk_hthread *thr, duk_hobject *obj);
+
+/* ES6 proxy */
+#if defined(DUK_USE_ES6_PROXY)
+duk_bool_t duk_hobject_proxy_check(duk_hthread *thr, duk_hobject *obj, duk_hobject **out_target, duk_hobject **out_handler);
+#endif
+
+/* enumeration */
+void duk_hobject_enumerator_create(duk_context *ctx, duk_small_uint_t enum_flags);
+duk_ret_t duk_hobject_get_enumerated_keys(duk_context *ctx, duk_small_uint_t enum_flags);
+duk_bool_t duk_hobject_enumerator_next(duk_context *ctx, duk_bool_t get_value);
+
+/* macros */
+void duk_hobject_set_prototype(duk_hthread *thr, duk_hobject *h, duk_hobject *p);
+
+/* finalization */
+void duk_hobject_run_finalizer(duk_hthread *thr, duk_hobject *obj);
+
+/* pc2line */
+#if defined(DUK_USE_PC2LINE)
+void duk_hobject_pc2line_pack(duk_hthread *thr, duk_compiler_instr *instrs, duk_uint_fast32_t length);
+duk_uint_fast32_t duk_hobject_pc2line_query(duk_context *ctx, duk_idx_t idx_func, duk_uint_fast32_t pc);
+#endif
+
+/* misc */	
+duk_bool_t duk_hobject_prototype_chain_contains(duk_hthread *thr, duk_hobject *h, duk_hobject *p);
+
+#endif  /* DUK_HOBJECT_H_INCLUDED */
+#line 1 "duk_hcompiledfunction.h"
+/*
+ *  Heap compiled function (Ecmascript function) representation.
+ *
+ *  There is a single data buffer containing the Ecmascript function's
+ *  bytecode, constants, and inner functions.
+ */
+
+#ifndef DUK_HCOMPILEDFUNCTION_H_INCLUDED
+#define DUK_HCOMPILEDFUNCTION_H_INCLUDED
+
+/*
+ *  Accessor macros for function specific data areas
+ */
+
+/* Note: assumes 'data' is always a fixed buffer */
+#define DUK_HCOMPILEDFUNCTION_GET_BUFFER_BASE(h)  \
+	DUK_HBUFFER_FIXED_GET_DATA_PTR((duk_hbuffer_fixed *) (h)->data)
+
+#define DUK_HCOMPILEDFUNCTION_GET_CONSTS_BASE(h)  \
+	((duk_tval *) DUK_HCOMPILEDFUNCTION_GET_BUFFER_BASE((h)))
+
+#define DUK_HCOMPILEDFUNCTION_GET_FUNCS_BASE(h)  \
+	((h)->funcs)
+
+#define DUK_HCOMPILEDFUNCTION_GET_CODE_BASE(h)  \
+	((h)->bytecode)
+
+#define DUK_HCOMPILEDFUNCTION_GET_CONSTS_END(h)  \
+	((duk_tval *) DUK_HCOMPILEDFUNCTION_GET_FUNCS_BASE((h)))
+
+#define DUK_HCOMPILEDFUNCTION_GET_FUNCS_END(h)  \
+	((duk_hobject **) DUK_HCOMPILEDFUNCTION_GET_CODE_BASE((h)))
+
+#define DUK_HCOMPILEDFUNCTION_GET_CODE_END(h)  \
+	((duk_instr_t *) (DUK_HBUFFER_FIXED_GET_DATA_PTR((duk_hbuffer_fixed *) (h)->data) + \
+	                DUK_HBUFFER_GET_SIZE((h)->data)))
+
+#define DUK_HCOMPILEDFUNCTION_GET_CONSTS_SIZE(h)  \
+	( \
+	 (duk_size_t) \
+	 ( \
+	   ((duk_uint8_t *) DUK_HCOMPILEDFUNCTION_GET_CONSTS_END((h))) - \
+	   ((duk_uint8_t *) DUK_HCOMPILEDFUNCTION_GET_CONSTS_BASE((h))) \
+	 ) \
+	)
+
+#define DUK_HCOMPILEDFUNCTION_GET_FUNCS_SIZE(h)  \
+	( \
+	 (duk_size_t) \
+	 ( \
+	   ((duk_uint8_t *) DUK_HCOMPILEDFUNCTION_GET_FUNCS_END((h))) - \
+	   ((duk_uint8_t *) DUK_HCOMPILEDFUNCTION_GET_FUNCS_BASE((h))) \
+	 ) \
+	)
+
+#define DUK_HCOMPILEDFUNCTION_GET_CODE_SIZE(h)  \
+	( \
+	 (duk_size_t) \
+	 ( \
+	   ((duk_uint8_t *) DUK_HCOMPILEDFUNCTION_GET_CODE_END((h))) - \
+	   ((duk_uint8_t *) DUK_HCOMPILEDFUNCTION_GET_CODE_BASE((h))) \
+	 ) \
+	)
+
+#define DUK_HCOMPILEDFUNCTION_GET_CONSTS_COUNT(h)  \
+	((duk_size_t) (DUK_HCOMPILEDFUNCTION_GET_CONSTS_SIZE((h)) / sizeof(duk_tval)))
+
+#define DUK_HCOMPILEDFUNCTION_GET_FUNCS_COUNT(h)  \
+	((duk_size_t) (DUK_HCOMPILEDFUNCTION_GET_FUNCS_SIZE((h)) / sizeof(duk_hobject *)))
+
+#define DUK_HCOMPILEDFUNCTION_GET_CODE_COUNT(h)  \
+	((duk_size_t) (DUK_HCOMPILEDFUNCTION_GET_CODE_SIZE((h)) / sizeof(duk_instr_t)))
+
+
+/*
+ *  Main struct
+ */
+
+struct duk_hcompiledfunction {
+	/* shared object part */
+	duk_hobject obj;
+
+	/*
+	 *  Pointers to function data area for faster access.  Function
+	 *  data is a buffer shared between all closures of the same
+	 *  "template" function.  The data buffer is always fixed (non-
+	 *  dynamic, hence stable), with a layout as follows:
+	 *
+	 *    constants (duk_tval)
+	 *    inner functions (duk_hobject *)
+	 *    bytecode (duk_instr_t)
+	 *
+	 *  Note: bytecode end address can be computed from 'data' buffer
+	 *  size.  It is not strictly necessary functionally, assuming
+	 *  bytecode never jumps outside its allocated area.  However,
+	 *  it's a safety/robustness feature for avoiding the chance of
+	 *  executing random data as bytecode due to a compiler error.
+	 *
+	 *  Note: values in the data buffer must be incref'd (they will
+	 *  be decref'd on release) for every compiledfunction referring
+	 *  to the 'data' element.
+	 */
+
+	duk_hbuffer *data;    /* data area, fixed allocation, stable data ptrs */
+
+	/* no need for constants pointer */
+	duk_hobject **funcs;
+	duk_instr_t *bytecode;
+
+	/*
+	 *  'nregs' registers are allocated on function entry, at most 'nargs'
+	 *  are initialized to arguments, and the rest to undefined.  Arguments
+	 *  above 'nregs' are not mapped to registers.  All registers in the
+	 *  active stack range must be initialized because they are GC reachable.
+	 *  'nargs' is needed so that if the function is given more than 'nargs'
+	 *  arguments, the additional arguments do not 'clobber' registers
+	 *  beyond 'nregs' which must be consistently initialized to undefined.
+	 *
+	 *  Usually there is no need to know which registers are mapped to
+	 *  local variables.  Registers may be allocated to variable in any
+	 *  way (even including gaps).  However, a register-variable mapping
+	 *  must be the same for the duration of the function execution and
+	 *  the register cannot be used for anything else.
+	 *
+	 *  When looking up variables by name, the '_varmap' map is used.
+	 *  When an activation closes, registers mapped to arguments are
+	 *  copied into the environment record based on the same map.  The
+	 *  reverse map (from register to variable) is not currently needed
+	 *  at run time, except for debugging, so it is not maintained.
+	 */
+
+	duk_uint16_t nregs;                /* regs to allocate */
+	duk_uint16_t nargs;                /* number of arguments allocated to regs */
+
+	/*
+	 *  Additional control information is placed into the object itself
+	 *  as internal properties to avoid unnecessary fields for the
+	 *  majority of functions.  The compiler tries to omit internal
+	 *  control fields when possible.
+	 *
+	 *  Function templates:
+	 *
+	 *    {
+	 *      _varmap: { "arg1": 0, "arg2": 1, "varname": 2 },
+	 *      _formals: [ "arg1", "arg2" ],
+	 *      _name: "func",    // declaration, named function expressions
+	 *      _source: "function func(arg1, arg2) { ... }",
+	 *      _pc2line: <debug info for pc-to-line mapping>,
+	 *      _filename: <debug info for creating nice errors>
+	 *    }
+	 *
+	 *  Function instances:
+	 *
+	 *    {
+	 *      length: 2,
+	 *      prototype: { constructor: <func> },
+	 *      caller: <thrower>,
+	 *      arguments: <thrower>,
+	 *      _varmap: { "arg1": 0, "arg2": 1, "varname": 2 },
+	 *      _formals: [ "arg1", "arg2" ],
+	 *      _name: "func",    // declaration, named function expressions
+	 *      _source: "function func(arg1, arg2) { ... }",
+	 *      _pc2line: <debug info for pc-to-line mapping>,
+	 *      _filename: <debug info for creating nice errors>
+	 *      _varenv: <variable environment of closure>,
+	 *      _lexenv: <lexical environment of closure (if differs from _varenv)>
+	 *    }
+	 *
+	 *  More detailed description of these properties can be found
+	 *  in the documentation.
+	 */
+};
+
+#endif  /* DUK_HCOMPILEDFUNCTION_H_INCLUDED */
+#line 1 "duk_hnativefunction.h"
+/*
+ *  Heap native function representation.
+ */
+
+#ifndef DUK_HNATIVEFUNCTION_H_INCLUDED
+#define DUK_HNATIVEFUNCTION_H_INCLUDED
+
+#define DUK_HNATIVEFUNCTION_NARGS_VARARGS  ((duk_int16_t) -1)
+#define DUK_HNATIVEFUNCTION_NARGS_MAX      ((duk_int16_t) 0x7fff)
+
+struct duk_hnativefunction {
+	/* shared object part */
+	duk_hobject obj;
+
+	duk_c_function func;
+	duk_int16_t nargs;
+	duk_int16_t magic;
+
+	/* The 'magic' field allows an opaque 16-bit field to be accessed by the
+	 * Duktape/C function.  This allows, for instance, the same native function
+	 * to be used for a set of very similar functions, with the 'magic' field
+	 * providing the necessary non-argument flags / values to guide the behavior
+	 * of the native function.  The value is signed on purpose: it is easier to
+	 * convert a signed value to unsigned (simply AND with 0xffff) than vice
+	 * versa.
+	 *
+	 * Note: cannot place nargs/magic into the heaphdr flags, because
+	 * duk_hobject takes almost all flags already (and needs the spare).
+	 */
+};
+
+#endif  /* DUK_HNATIVEFUNCTION_H_INCLUDED */
+#line 1 "duk_hthread.h"
+/*
+ *  Heap thread object representation.
+ *
+ *  duk_hthread is also the 'context' (duk_context) for exposed APIs
+ *  which mostly operate on the topmost frame of the value stack.
+ */
+
+#ifndef DUK_HTHREAD_H_INCLUDED
+#define DUK_HTHREAD_H_INCLUDED
+
+/*
+ *  Stack constants
+ */
+
+#define DUK_VALSTACK_GROW_STEP          128     /* roughly 1 kiB */
+#define DUK_VALSTACK_SHRINK_THRESHOLD   256     /* roughly 2 kiB */
+#define DUK_VALSTACK_SHRINK_SPARE       64      /* roughly 0.5 kiB */
+#define DUK_VALSTACK_INITIAL_SIZE       128     /* roughly 1.0 kiB -> but rounds up to DUK_VALSTACK_GROW_STEP in practice */
+#define DUK_VALSTACK_INTERNAL_EXTRA     64      /* internal extra elements assumed on function entry,
+                                                 * always added to user-defined 'extra' for e.g. the
+                                                 * duk_check_stack() call.
+                                                 */
+#define DUK_VALSTACK_API_ENTRY_MINIMUM  DUK_API_ENTRY_STACK
+                                                /* number of elements guaranteed to be user accessible
+                                                 * (in addition to call arguments) on Duktape/C function entry.
+                                                 */
+
+/* Note: DUK_VALSTACK_INITIAL_SIZE must be >= DUK_VALSTACK_API_ENTRY_MINIMUM
+ * + DUK_VALSTACK_INTERNAL_EXTRA so that the initial stack conforms to spare
+ * requirements.
+ */
+
+#define DUK_VALSTACK_DEFAULT_MAX        1000000L
+
+#define DUK_CALLSTACK_GROW_STEP         8       /* roughly 256 bytes */
+#define DUK_CALLSTACK_SHRINK_THRESHOLD  16      /* roughly 512 bytes */
+#define DUK_CALLSTACK_SHRINK_SPARE      8       /* roughly 256 bytes */
+#define DUK_CALLSTACK_INITIAL_SIZE      8
+#define DUK_CALLSTACK_DEFAULT_MAX       10000L
+
+#define DUK_CATCHSTACK_GROW_STEP         4      /* roughly 64 bytes */
+#define DUK_CATCHSTACK_SHRINK_THRESHOLD  8      /* roughly 128 bytes */
+#define DUK_CATCHSTACK_SHRINK_SPARE      4      /* roughly 64 bytes */
+#define DUK_CATCHSTACK_INITIAL_SIZE      4
+#define DUK_CATCHSTACK_DEFAULT_MAX       10000L
+
+/*
+ *  Activation defines
+ */
+
+#define DUK_ACT_FLAG_STRICT          (1 << 0)  /* function executes in strict mode */
+#define DUK_ACT_FLAG_TAILCALLED      (1 << 1)  /* activation has tailcalled one or more times */
+#define DUK_ACT_FLAG_CONSTRUCT       (1 << 2)  /* function executes as a constructor (called via "new") */
+#define DUK_ACT_FLAG_PREVENT_YIELD   (1 << 3)  /* activation prevents yield (native call or "new") */
+#define DUK_ACT_FLAG_DIRECT_EVAL     (1 << 4)  /* activation is a direct eval call */
+
+/*
+ *  Flags for __FILE__ / __LINE__ registered into tracedata
+ */
+
+#define DUK_TB_FLAG_NOBLAME_FILELINE   (1 << 0)  /* don't report __FILE__ / __LINE__ as fileName/lineNumber */
+
+/*
+ *  Catcher defines
+ */
+
+/* flags field: LLLLLLFT, L = label (24 bits), F = flags (4 bits), T = type (4 bits) */
+#define DUK_CAT_TYPE_MASK            0x0000000fUL
+#define DUK_CAT_TYPE_BITS            4
+#define DUK_CAT_LABEL_MASK           0xffffff00UL
+#define DUK_CAT_LABEL_BITS           24
+#define DUK_CAT_LABEL_SHIFT          8
+
+#define DUK_CAT_FLAG_CATCH_ENABLED          (1 << 4)   /* catch part will catch */
+#define DUK_CAT_FLAG_FINALLY_ENABLED        (1 << 5)   /* finally part will catch */
+#define DUK_CAT_FLAG_CATCH_BINDING_ENABLED  (1 << 6)   /* request to create catch binding */
+#define DUK_CAT_FLAG_LEXENV_ACTIVE          (1 << 7)   /* catch or with binding is currently active */
+
+#define DUK_CAT_TYPE_UNKNOWN         0
+#define DUK_CAT_TYPE_TCF             1
+#define DUK_CAT_TYPE_LABEL           2
+
+#define DUK_CAT_GET_TYPE(c)          ((c)->flags & DUK_CAT_TYPE_MASK)
+#define DUK_CAT_GET_LABEL(c)         (((c)->flags & DUK_CAT_LABEL_MASK) >> DUK_CAT_LABEL_SHIFT)
+
+#define DUK_CAT_HAS_CATCH_ENABLED(c)           ((c)->flags & DUK_CAT_FLAG_CATCH_ENABLED)
+#define DUK_CAT_HAS_FINALLY_ENABLED(c)         ((c)->flags & DUK_CAT_FLAG_FINALLY_ENABLED)
+#define DUK_CAT_HAS_CATCH_BINDING_ENABLED(c)   ((c)->flags & DUK_CAT_FLAG_CATCH_BINDING_ENABLED)
+#define DUK_CAT_HAS_LEXENV_ACTIVE(c)           ((c)->flags & DUK_CAT_FLAG_LEXENV_ACTIVE)
+
+#define DUK_CAT_SET_CATCH_ENABLED(c)    do { \
+		(c)->flags |= DUK_CAT_FLAG_CATCH_ENABLED; \
+	} while (0)
+#define DUK_CAT_SET_FINALLY_ENABLED(c)  do { \
+		(c)->flags |= DUK_CAT_FLAG_FINALLY_ENABLED; \
+	} while (0)
+#define DUK_CAT_SET_CATCH_BINDING_ENABLED(c)    do { \
+		(c)->flags |= DUK_CAT_FLAG_CATCH_BINDING_ENABLED; \
+	} while (0)
+#define DUK_CAT_SET_LEXENV_ACTIVE(c)    do { \
+		(c)->flags |= DUK_CAT_FLAG_LEXENV_ACTIVE; \
+	} while (0)
+
+#define DUK_CAT_CLEAR_CATCH_ENABLED(c)    do { \
+		(c)->flags &= ~DUK_CAT_FLAG_CATCH_ENABLED; \
+	} while (0)
+#define DUK_CAT_CLEAR_FINALLY_ENABLED(c)  do { \
+		(c)->flags &= ~DUK_CAT_FLAG_FINALLY_ENABLED; \
+	} while (0)
+#define DUK_CAT_CLEAR_CATCH_BINDING_ENABLED(c)    do { \
+		(c)->flags &= ~DUK_CAT_FLAG_CATCH_BINDING_ENABLED; \
+	} while (0)
+#define DUK_CAT_CLEAR_LEXENV_ACTIVE(c)    do { \
+		(c)->flags &= ~DUK_CAT_FLAG_LEXENV_ACTIVE; \
+	} while (0)
+
+/*
+ *  Thread defines
+ */
+
+#define DUK_HTHREAD_GET_STRING(thr,idx)          ((thr)->strs[(idx)])
+
+#define DUK_HTHREAD_GET_CURRENT_ACTIVATION(thr)  (&(thr)->callstack[(thr)->callstack_top - 1])
+
+/* values for the state field */
+#define DUK_HTHREAD_STATE_INACTIVE     1   /* thread not currently running */
+#define DUK_HTHREAD_STATE_RUNNING      2   /* thread currently running (only one at a time) */
+#define DUK_HTHREAD_STATE_RESUMED      3   /* thread resumed another thread (active but not running) */
+#define DUK_HTHREAD_STATE_YIELDED      4   /* thread has yielded */
+#define DUK_HTHREAD_STATE_TERMINATED   5   /* thread has terminated */
+
+/*
+ *  Struct defines
+ */
+
+/* Note: it's nice if size is 2^N (now 32 bytes on 32 bit without 'caller' property) */
+struct duk_activation {
+	duk_hobject *func;      /* function being executed; for bound function calls, this is the final, real function */
+	duk_hobject *var_env;   /* current variable environment (may be NULL if delayed) */
+	duk_hobject *lex_env;   /* current lexical environment (may be NULL if delayed) */
+#ifdef DUK_USE_NONSTD_FUNC_CALLER_PROPERTY
+	/* Previous value of 'func' caller, restored when unwound.  Only in use
+	 * when 'func' is non-strict.
+	 */
+	duk_hobject *prev_caller;
+#endif
+
+	duk_small_uint_t flags;
+	duk_uint32_t pc;        /* next instruction to execute */
+
+	/* idx_bottom and idx_retval are only used for book-keeping of
+	 * Ecmascript-initiated calls, to allow returning to an Ecmascript
+	 * function properly.  They are duk_size_t to match the convention
+	 * that value stack sizes are duk_size_t and local frame indices
+	 * are duk_idx_t.
+	 */
+
+	/* Bottom of valstack for this activation, used to reset
+	 * valstack_bottom on return; index is absolute.  Note:
+	 * idx_top not needed because top is set to 'nregs' always
+	 * when returning to an Ecmascript activation.
+	 */
+	duk_size_t idx_bottom;
+
+	/* Return value when returning to this activation (points to caller
+	 * reg, not callee reg); index is absolute (only set if activation is
+	 * not topmost).
+	 *
+	 * Note: idx_bottom is always set, while idx_retval is only applicable
+	 * for activations below the topmost one.  Currently idx_retval for
+	 * the topmost activation is considered garbage (and it not initialized
+	 * on entry or cleared on return; may contain previous or garbage
+	 * values).
+	 */
+	duk_size_t idx_retval;
+
+	/* Current 'this' binding is the value just below idx_bottom.
+	 * Previously, 'this' binding was handled with an index to the
+	 * (calling) valstack.  This works for everything except tail
+	 * calls, which must not "cumulate" valstack temps.
+	 */
+
+#if defined(DUK_USE_32BIT_PTRS) && !defined(DUK_USE_NONSTD_FUNC_CALLER_PROPERTY)
+	/* Minor optimization: pad structure to 2^N size on 32-bit platforms. */
+	duk_int_t unused1;  /* pad to 2^N */
+#endif
+};
+
+/* Note: it's nice if size is 2^N (not 4x4 = 16 bytes on 32 bit) */
+struct duk_catcher {
+	duk_hstring *h_varname;         /* borrowed reference to catch variable name (or NULL if none) */
+	                                /* (reference is valid as long activation exists) */
+	duk_size_t callstack_index;     /* callstack index of related activation */
+	duk_size_t idx_base;            /* idx_base and idx_base+1 get completion value and type */
+	duk_uint32_t pc_base;           /* resume execution from pc_base or pc_base+1 */
+	duk_uint32_t flags;             /* type and control flags, label number */
+};
+
+struct duk_hthread {
+	/* shared object part */
+	duk_hobject obj;
+
+	/* backpointers */
+	duk_heap *heap;
+
+	/* current strictness flag: affects API calls */
+	duk_uint8_t strict;
+	duk_uint8_t state;
+	duk_uint8_t unused1;
+	duk_uint8_t unused2;
+
+	/* sanity limits */
+	duk_size_t valstack_max;
+	duk_size_t callstack_max;
+	duk_size_t catchstack_max;
+
+	/* XXX: valstack, callstack, and catchstack are currently assumed
+	 * to have non-NULL pointers.  Relaxing this would not lead to big
+	 * benefits (except perhaps for terminated threads).
+	 */
+
+	/* value stack: these are expressed as pointers for faster stack manipulation */
+	duk_tval *valstack;			/* start of valstack allocation */
+	duk_tval *valstack_end;			/* end of valstack allocation (exclusive) */
+	duk_tval *valstack_bottom;		/* bottom of current frame */
+	duk_tval *valstack_top;			/* top of current frame (exclusive) */
+
+	/* call stack */
+	duk_activation *callstack;
+	duk_size_t callstack_size;		/* allocation size */
+	duk_size_t callstack_top;		/* next to use, highest used is top - 1 */
+	duk_size_t callstack_preventcount;	/* number of activation records in callstack preventing a yield */
+
+	/* catch stack */
+	duk_catcher *catchstack;
+	duk_size_t catchstack_size;		/* allocation size */
+	duk_size_t catchstack_top;		/* next to use, highest used is top - 1 */
+
+	/* yield/resume book-keeping */
+	duk_hthread *resumer;			/* who resumed us (if any) */
+
+#ifdef DUK_USE_INTERRUPT_COUNTER
+	/* Interrupt counter for triggering a slow path check for execution
+	 * timeout, debugger interaction such as breakpoints, etc.  This is
+	 * actually a value copied from the heap structure into the current
+	 * thread to be more convenient for the bytecode executor inner loop.
+	 * The final value is copied back to the heap structure on a thread
+	 * switch by DUK_HEAP_SWITCH_THREAD().
+	 */
+	duk_int_t interrupt_counter;
+#endif
+
+	/* Builtin-objects; may or may not be shared with other threads,
+	 * threads existing in different "compartments" will have different
+	 * built-ins.  Must be stored on a per-thread basis because there
+	 * is no intermediate structure for a thread group / compartment.
+	 * This takes quite a lot of space, currently 43x4 = 172 bytes on
+	 * 32-bit platforms.
+	 */
+	duk_hobject *builtins[DUK_NUM_BUILTINS];
+
+	/* convenience copies from heap/vm for faster access */
+	duk_hstring **strs;			/* (from duk_heap) */
+};
+
+/*
+ *  Prototypes
+ */
+
+void duk_hthread_copy_builtin_objects(duk_hthread *thr_from, duk_hthread *thr_to);
+void duk_hthread_create_builtin_objects(duk_hthread *thr);
+duk_bool_t duk_hthread_init_stacks(duk_heap *heap, duk_hthread *thr);
+void duk_hthread_terminate(duk_hthread *thr);
+
+void duk_hthread_callstack_grow(duk_hthread *thr);
+void duk_hthread_callstack_shrink_check(duk_hthread *thr);
+void duk_hthread_callstack_unwind(duk_hthread *thr, duk_size_t new_top);
+void duk_hthread_catchstack_grow(duk_hthread *thr);
+void duk_hthread_catchstack_shrink_check(duk_hthread *thr);
+void duk_hthread_catchstack_unwind(duk_hthread *thr, duk_size_t new_top);
+
+duk_activation *duk_hthread_get_current_activation(duk_hthread *thr);
+void *duk_hthread_get_valstack_ptr(void *ud);  /* indirect allocs */
+void *duk_hthread_get_callstack_ptr(void *ud);  /* indirect allocs */
+void *duk_hthread_get_catchstack_ptr(void *ud);  /* indirect allocs */
+
+#endif  /* DUK_HTHREAD_H_INCLUDED */
+#line 1 "duk_hbuffer.h"
+/*
+ *  Heap buffer representation.
+ *
+ *  Heap allocated user data buffer which is either:
+ *
+ *    1. A fixed size buffer (data follows header statically)
+ *    2. A dynamic size buffer (data pointer follows header)
+ *
+ *  The data pointer for a variable size buffer of zero size may be NULL.
+ */
+
+#ifndef DUK_HBUFFER_H_INCLUDED
+#define DUK_HBUFFER_H_INCLUDED
+
+/* Impose a maximum buffer length for now.  Restricted artificially to
+ * ensure resize computations or adding a heap header length won't
+ * overflow size_t.  The limit should be synchronized with
+ * DUK_HSTRING_MAX_BYTELEN.
+ */
+#define DUK_HBUFFER_MAX_BYTELEN                   (0x7fffffffUL)
+
+#define DUK_HBUFFER_FLAG_DYNAMIC                  DUK_HEAPHDR_USER_FLAG(0)  /* buffer is resizable */
+
+#define DUK_HBUFFER_HAS_DYNAMIC(x)                DUK_HEAPHDR_CHECK_FLAG_BITS(&(x)->hdr, DUK_HBUFFER_FLAG_DYNAMIC)
+
+#define DUK_HBUFFER_SET_DYNAMIC(x)                DUK_HEAPHDR_SET_FLAG_BITS(&(x)->hdr, DUK_HBUFFER_FLAG_DYNAMIC)
+
+#define DUK_HBUFFER_CLEAR_DYNAMIC(x)              DUK_HEAPHDR_CLEAR_FLAG_BITS(&(x)->hdr, DUK_HBUFFER_FLAG_DYNAMIC)
+
+#define DUK_HBUFFER_FIXED_GET_DATA_PTR(x)         ((duk_uint8_t *) (((duk_hbuffer_fixed *) (x)) + 1))
+#define DUK_HBUFFER_FIXED_GET_SIZE(x)             ((x)->u.s.size)
+
+#define DUK_HBUFFER_DYNAMIC_GET_ALLOC_SIZE(x)     ((x)->usable_size)
+#define DUK_HBUFFER_DYNAMIC_GET_USABLE_SIZE(x)    ((x)->usable_size)
+#define DUK_HBUFFER_DYNAMIC_GET_SPARE_SIZE(x)     ((x)->usable_size - (x)->size)
+#define DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(x)  ((x)->curr_alloc)
+
+/* gets the actual buffer contents which matches the current allocation size
+ * (may be NULL for zero size dynamic buffer)
+ */
+#define DUK_HBUFFER_GET_DATA_PTR(x)  ( \
+	DUK_HBUFFER_HAS_DYNAMIC((x)) ? \
+		DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR((duk_hbuffer_dynamic *) (x)) : \
+		DUK_HBUFFER_FIXED_GET_DATA_PTR((duk_hbuffer_fixed *) (x)) \
+	)
+
+/* gets the current user visible size, without accounting for a dynamic
+ * buffer's "spare" (= usable size).
+ */
+#define DUK_HBUFFER_GET_SIZE(x)         ((x)->size)
+
+#define DUK_HBUFFER_SET_SIZE(x,val)  do { \
+		(x)->size = (val); \
+	} while (0)
+
+/* growth parameters */
+#define DUK_HBUFFER_SPARE_ADD      16
+#define DUK_HBUFFER_SPARE_DIVISOR  16   /* 2^4 -> 1/16 = 6.25% spare */
+
+struct duk_hbuffer {
+	duk_heaphdr hdr;
+
+	/* it's not strictly necessary to track the current size, but
+	 * it is useful for writing robust native code.
+	 */
+
+	duk_size_t size;  /* current size (not counting a dynamic buffer's "spare") */
+
+	/*
+	 *  Data following the header depends on the DUK_HBUFFER_FLAG_DYNAMIC
+	 *  flag.
+	 *
+	 *  If the flag is clear (the buffer is a fixed size one), the buffer
+	 *  data follows the header directly, consisting of 'size' bytes.
+	 *
+	 *  If the flag is set, the actual buffer is allocated separately, and
+	 *  a few control fields follow the header.  Specifically:
+	 *
+	 *    - a "void *" pointing to the current allocation
+	 *    - a duk_size_t indicating the full allocated size (always >= 'size')
+	 *
+	 *  Unlike strings, no terminator byte (NUL) is guaranteed after the
+	 *  data.  This would be convenient, but would pad aligned user buffers
+	 *  unnecessarily upwards in size.  For instance, if user code requested
+	 *  a 64-byte dynamic buffer, 65 bytes would actually be allocated which
+	 *  would then potentially round upwards to perhaps 68 or 72 bytes.
+	 */
+};
+
+#if defined(DUK_USE_ALIGN_8) && defined(DUK_USE_PACK_MSVC_PRAGMA)
+#pragma pack(push, 8)
+#endif
+struct duk_hbuffer_fixed {
+	/* A union is used here as a portable struct size / alignment trick:
+	 * by adding a 32-bit or a 64-bit (unused) union member, the size of
+	 * the struct is effectively forced to be a multiple of 4 or 8 bytes
+	 * (respectively) without increasing the size of the struct unless
+	 * necessary.
+	 */
+	union {
+		struct {
+			duk_heaphdr hdr;
+			duk_size_t size;
+		} s;
+#if defined(DUK_USE_ALIGN_4)
+		duk_uint32_t dummy_for_align4;
+#elif defined(DUK_USE_ALIGN_8)
+		duk_uint64_t dummy_for_align8;
+#else
+		/* no extra padding */
+#endif
+	} u;
+
+	/*
+	 *  Data follows the struct header.  The struct size is padded by the
+	 *  compiler based on the struct members.  This guarantees that the
+	 *  buffer data will be aligned-by-4 but not necessarily aligned-by-8.
+	 *
+	 *  On platforms where alignment does not matter, the struct padding
+	 *  could be removed (if there is any).  On platforms where alignment
+	 *  by 8 is required, the struct size must be forced to be a multiple
+	 *  of 8 by some means.  Without it, some user code may break, and also
+	 *  Duktape itself breaks (e.g. the compiler stores duk_tvals in a
+	 *  dynamic buffer).
+	 */
+}
+#if defined(DUK_USE_ALIGN_8) && defined(DUK_USE_PACK_GCC_ATTR)
+__attribute__ ((aligned (8)))
+#elif defined(DUK_USE_ALIGN_8) && defined(DUK_USE_PACK_CLANG_ATTR)
+__attribute__ ((aligned (8)))
+#endif
+;
+#if defined(DUK_USE_ALIGN_8) && defined(DUK_USE_PACK_MSVC_PRAGMA)
+#pragma pack(pop)
+#endif
+
+struct duk_hbuffer_dynamic {
+	duk_heaphdr hdr;
+	duk_size_t size;
+
+	void *curr_alloc;  /* may be NULL if usable_size == 0 */
+	duk_size_t usable_size;
+
+	/*
+	 *  Allocation size for 'curr_alloc' is usable_size directly.
+	 *  There is no automatic NUL terminator for buffers (see above
+	 *  for rationale).
+	 *
+	 *  'curr_alloc' is explicitly allocated with heap allocation
+	 *  primitives and will thus always have alignment suitable for
+	 *  e.g. duk_tval and an IEEE double.
+	 */
+};
+
+/*
+ *  Prototypes
+ */
+
+duk_hbuffer *duk_hbuffer_alloc(duk_heap *heap, duk_size_t size, duk_bool_t dynamic);
+void *duk_hbuffer_get_dynalloc_ptr(void *ud);  /* indirect allocs */
+
+/* dynamic buffer ops */
+void duk_hbuffer_resize(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_size_t new_size, duk_size_t new_usable_size);
+void duk_hbuffer_reset(duk_hthread *thr, duk_hbuffer_dynamic *buf);
+void duk_hbuffer_compact(duk_hthread *thr, duk_hbuffer_dynamic *buf);
+void duk_hbuffer_append_bytes(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_uint8_t *data, duk_size_t length);
+void duk_hbuffer_append_byte(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_uint8_t byte);
+duk_size_t duk_hbuffer_append_cstring(duk_hthread *thr, duk_hbuffer_dynamic *buf, const char *str);
+duk_size_t duk_hbuffer_append_hstring(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_hstring *str);
+duk_size_t duk_hbuffer_append_xutf8(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_ucodepoint_t codepoint);
+duk_size_t duk_hbuffer_append_cesu8(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_ucodepoint_t codepoint);
+void duk_hbuffer_append_native_u32(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_uint32_t val);
+void duk_hbuffer_insert_bytes(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_size_t offset, duk_uint8_t *data, duk_size_t length);
+void duk_hbuffer_insert_byte(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_size_t offset, duk_uint8_t byte);
+duk_size_t duk_hbuffer_insert_cstring(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_size_t offset, const char *str);
+duk_size_t duk_hbuffer_insert_hstring(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_size_t offset, duk_hstring *str);
+duk_size_t duk_hbuffer_insert_xutf8(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_size_t offset, duk_ucodepoint_t codepoint);
+duk_size_t duk_hbuffer_insert_cesu8(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_size_t offset, duk_ucodepoint_t codepoint);
+void duk_hbuffer_remove_slice(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_size_t offset, duk_size_t length);
+void duk_hbuffer_insert_slice(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_size_t dst_offset, duk_size_t src_offset, duk_size_t length);
+void duk_hbuffer_append_slice(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_size_t src_offset, duk_size_t length);
+
+#endif  /* DUK_HBUFFER_H_INCLUDED */
+#line 1 "duk_heap.h"
+/*
+ *  Heap structure.
+ *
+ *  Heap contains allocated heap objects, interned strings, and built-in
+ *  strings for one or more threads.
+ */
+
+#ifndef DUK_HEAP_H_INCLUDED
+#define DUK_HEAP_H_INCLUDED
+
+/* alloc function typedefs in duktape.h */
+
+/*
+ *  Heap flags
+ */
+
+#define DUK_HEAP_FLAG_MARKANDSWEEP_RUNNING                     (1 << 0)  /* mark-and-sweep is currently running */
+#define DUK_HEAP_FLAG_MARKANDSWEEP_RECLIMIT_REACHED            (1 << 1)  /* mark-and-sweep marking reached a recursion limit and must use multi-pass marking */
+#define DUK_HEAP_FLAG_REFZERO_FREE_RUNNING                     (1 << 2)  /* refcount code is processing refzero list */
+#define DUK_HEAP_FLAG_ERRHANDLER_RUNNING                       (1 << 3)  /* an error handler (user callback to augment/replace error) is running */
+
+#define DUK__HEAP_HAS_FLAGS(heap,bits)               ((heap)->flags & (bits))
+#define DUK__HEAP_SET_FLAGS(heap,bits)  do { \
+		(heap)->flags |= (bits); \
+	} while (0)
+#define DUK__HEAP_CLEAR_FLAGS(heap,bits)  do { \
+		(heap)->flags &= ~(bits); \
+	} while (0)
+
+#define DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)            DUK__HEAP_HAS_FLAGS((heap), DUK_HEAP_FLAG_MARKANDSWEEP_RUNNING)
+#define DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap)   DUK__HEAP_HAS_FLAGS((heap), DUK_HEAP_FLAG_MARKANDSWEEP_RECLIMIT_REACHED)
+#define DUK_HEAP_HAS_REFZERO_FREE_RUNNING(heap)            DUK__HEAP_HAS_FLAGS((heap), DUK_HEAP_FLAG_REFZERO_FREE_RUNNING)
+#define DUK_HEAP_HAS_ERRHANDLER_RUNNING(heap)              DUK__HEAP_HAS_FLAGS((heap), DUK_HEAP_FLAG_ERRHANDLER_RUNNING)
+
+#define DUK_HEAP_SET_MARKANDSWEEP_RUNNING(heap)            DUK__HEAP_SET_FLAGS((heap), DUK_HEAP_FLAG_MARKANDSWEEP_RUNNING)
+#define DUK_HEAP_SET_MARKANDSWEEP_RECLIMIT_REACHED(heap)   DUK__HEAP_SET_FLAGS((heap), DUK_HEAP_FLAG_MARKANDSWEEP_RECLIMIT_REACHED)
+#define DUK_HEAP_SET_REFZERO_FREE_RUNNING(heap)            DUK__HEAP_SET_FLAGS((heap), DUK_HEAP_FLAG_REFZERO_FREE_RUNNING)
+#define DUK_HEAP_SET_ERRHANDLER_RUNNING(heap)              DUK__HEAP_SET_FLAGS((heap), DUK_HEAP_FLAG_ERRHANDLER_RUNNING)
+
+#define DUK_HEAP_CLEAR_MARKANDSWEEP_RUNNING(heap)          DUK__HEAP_CLEAR_FLAGS((heap), DUK_HEAP_FLAG_MARKANDSWEEP_RUNNING)
+#define DUK_HEAP_CLEAR_MARKANDSWEEP_RECLIMIT_REACHED(heap) DUK__HEAP_CLEAR_FLAGS((heap), DUK_HEAP_FLAG_MARKANDSWEEP_RECLIMIT_REACHED)
+#define DUK_HEAP_CLEAR_REFZERO_FREE_RUNNING(heap)          DUK__HEAP_CLEAR_FLAGS((heap), DUK_HEAP_FLAG_REFZERO_FREE_RUNNING)
+#define DUK_HEAP_CLEAR_ERRHANDLER_RUNNING(heap)            DUK__HEAP_CLEAR_FLAGS((heap), DUK_HEAP_FLAG_ERRHANDLER_RUNNING)
+
+/*
+ *  Longjmp types, also double as identifying continuation type for a rethrow (in 'finally')
+ */
+
+#define DUK_LJ_TYPE_UNKNOWN      0    /* unused */
+#define DUK_LJ_TYPE_RETURN       1    /* value1 -> return value */
+#define DUK_LJ_TYPE_THROW        2    /* value1 -> error object */
+#define DUK_LJ_TYPE_BREAK        3    /* value1 -> label number */
+#define DUK_LJ_TYPE_CONTINUE     4    /* value1 -> label number */
+#define DUK_LJ_TYPE_YIELD        5    /* value1 -> yield value, iserror -> error / normal */
+#define DUK_LJ_TYPE_RESUME       6    /* value1 -> resume value, value2 -> resumee thread, iserror -> error/normal */
+#define DUK_LJ_TYPE_NORMAL       7    /* pseudo-type to indicate a normal continuation (for 'finally' rethrowing) */
+
+/* dummy non-zero value to be used as an argument for longjmp(), see man longjmp */
+#define DUK_LONGJMP_DUMMY_VALUE  1
+
+/*
+ *  Mark-and-sweep flags
+ *
+ *  These are separate from heap level flags now but could be merged.
+ *  The heap structure only contains a 'base mark-and-sweep flags'
+ *  field and the GC caller can impose further flags.
+ */
+
+#define DUK_MS_FLAG_EMERGENCY                (1 << 0)   /* emergency mode: try extra hard */
+#define DUK_MS_FLAG_NO_STRINGTABLE_RESIZE    (1 << 1)   /* don't resize stringtable (but may sweep it); needed during stringtable resize */
+#define DUK_MS_FLAG_NO_FINALIZERS            (1 << 2)   /* don't run finalizers (which may have arbitrary side effects) */
+#define DUK_MS_FLAG_NO_OBJECT_COMPACTION     (1 << 3)   /* don't compact objects; needed during object property allocation resize */
+
+/*
+ *  Thread switching
+ *
+ *  To switch heap->curr_thread, use the macro below so that interrupt counters
+ *  get updated correctly.  The macro allows a NULL target thread because that
+ *  happens e.g. in call handling.
+ */
+
+#ifdef DUK_USE_INTERRUPT_COUNTER
+#define DUK_HEAP_SWITCH_THREAD(heap,newthr)  duk_heap_switch_thread((heap), (newthr))
+#else
+#define DUK_HEAP_SWITCH_THREAD(heap,newthr)  do { \
+		(heap)->curr_thread = (newthr); \
+	} while (0)
+#endif
+
+/*
+ *  Other heap related defines
+ */
+
+/* Maximum duk_handle_call / duk_handle_safe_call depth.  Note that this
+ * does not limit bytecode executor internal call depth at all (e.g.
+ * for Ecmascript-to-Ecmascript calls, thread yields/resumes, etc).
+ * There is a separate callstack depth limit for threads.
+ */
+
+#if defined(DUK_USE_DEEP_C_STACK)
+#define DUK_HEAP_DEFAULT_CALL_RECURSION_LIMIT             1000  /* assuming 0.5 kB between calls, about 500kB of stack */ 
+#else
+#define DUK_HEAP_DEFAULT_CALL_RECURSION_LIMIT             60    /* assuming 0.5 kB between calls, about 30kB of stack */ 
+#endif
+
+/* Mark-and-sweep C recursion depth for marking phase; if reached,
+ * mark object as a TEMPROOT and use multi-pass marking.
+ */
+#if defined(DUK_USE_MARK_AND_SWEEP)
+#if defined(DUK_USE_GC_TORTURE)
+#define DUK_HEAP_MARK_AND_SWEEP_RECURSION_LIMIT   3
+#elif defined(DUK_USE_DEEP_C_STACK)
+#define DUK_HEAP_MARK_AND_SWEEP_RECURSION_LIMIT   256
+#else
+#define DUK_HEAP_MARK_AND_SWEEP_RECURSION_LIMIT   32
+#endif
+#endif
+
+/* Mark-and-sweep interval is relative to combined count of objects and
+ * strings kept in the heap during the latest mark-and-sweep pass.
+ * Fixed point .8 multiplier and .0 adder.  Trigger count (interval) is
+ * decreased by each (re)allocation attempt (regardless of size), and each
+ * refzero processed object.
+ *
+ * 'SKIP' indicates how many (re)allocations to wait until a retry if
+ * GC is skipped because there is no thread do it with yet (happens
+ * only during init phases).
+ */
+#if defined(DUK_USE_MARK_AND_SWEEP)
+#if defined(DUK_USE_REFERENCE_COUNTING)
+#define DUK_HEAP_MARK_AND_SWEEP_TRIGGER_MULT              12800L  /* 50x heap size */
+#define DUK_HEAP_MARK_AND_SWEEP_TRIGGER_ADD               1024L
+#define DUK_HEAP_MARK_AND_SWEEP_TRIGGER_SKIP              256L
+#else
+#define DUK_HEAP_MARK_AND_SWEEP_TRIGGER_MULT              256L    /* 1x heap size */
+#define DUK_HEAP_MARK_AND_SWEEP_TRIGGER_ADD               1024L
+#define DUK_HEAP_MARK_AND_SWEEP_TRIGGER_SKIP              256L
+#endif
+#endif
+
+/* Stringcache is used for speeding up char-offset-to-byte-offset
+ * translations for non-ASCII strings.
+ */
+#define DUK_HEAP_STRCACHE_SIZE                            4
+#define DUK_HEAP_STRINGCACHE_NOCACHE_LIMIT                16  /* strings up to the this length are not cached */
+
+/* helper to insert a (non-string) heap object into heap allocated list */
+#define DUK_HEAP_INSERT_INTO_HEAP_ALLOCATED(heap,hdr)     duk_heap_insert_into_heap_allocated((heap),(hdr))
+
+/* Executor interrupt default interval when nothing else requires a
+ * smaller value.  The default interval must be small enough to allow
+ * for reasonable execution timeout checking.
+ */
+#ifdef DUK_USE_INTERRUPT_COUNTER
+#define DUK_HEAP_INTCTR_DEFAULT                           (256L * 1024L)
+#endif
+
+/*
+ *  Stringtable
+ */
+
+/* initial stringtable size, must be prime and higher than DUK_UTIL_MIN_HASH_PRIME */
+#define DUK_STRTAB_INITIAL_SIZE            17
+
+/* indicates a deleted string; any fixed non-NULL, non-hstring pointer works */
+#define DUK_STRTAB_DELETED_MARKER(heap)    ((duk_hstring *) heap)
+
+/* resizing parameters */
+#define DUK_STRTAB_MIN_FREE_DIVISOR        4                /* load factor max 75% */
+#define DUK_STRTAB_MIN_USED_DIVISOR        4                /* load factor min 25% */
+#define DUK_STRTAB_GROW_ST_SIZE(n)         ((n) + (n))      /* used entries + approx 100% -> reset load to 50% */
+
+#define DUK_STRTAB_U32_MAX_STRLEN          10               /* 4'294'967'295 */
+#define DUK_STRTAB_HIGHEST_32BIT_PRIME     0xfffffffbUL
+
+/* probe sequence */
+#define DUK_STRTAB_HASH_INITIAL(hash,h_size)    ((hash) % (h_size))
+#define DUK_STRTAB_HASH_PROBE_STEP(hash)        DUK_UTIL_GET_HASH_PROBE_STEP((hash))
+
+/*
+ *  Built-in strings
+ */
+
+/* heap string indices are autogenerated in duk_strings.h */
+#define DUK_HEAP_GET_STRING(heap,idx)  ((heap)->strs[(idx)])
+
+/*
+ *  Raw memory calls: relative to heap, but no GC interaction
+ */
+
+#define DUK_ALLOC_RAW(heap,size) \
+	((heap)->alloc_func((heap)->alloc_udata, (size)))
+
+#define DUK_REALLOC_RAW(heap,ptr,newsize) \
+	((heap)->realloc_func((heap)->alloc_udata, (ptr), (newsize)))
+
+#define DUK_FREE_RAW(heap,ptr) \
+	((heap)->free_func((heap)->alloc_udata, (ptr)))
+
+/*
+ *  Memory calls: relative to heap, GC interaction, but no error throwing.
+ *
+ *  XXX: Currently a mark-and-sweep triggered by memory allocation will run
+ *  using the heap->heap_thread.  This thread is also used for running
+ *  mark-and-sweep finalization; this is not ideal because it breaks the
+ *  isolation between multiple global environments.
+ *
+ *  Notes:
+ *
+ *    - DUK_FREE() is required to ignore NULL and any other possible return
+ *      value of a zero-sized alloc/realloc (same as ANSI C free()).
+ * 
+ *    - There is no DUK_REALLOC_ZEROED (and checked variant) because we don't
+ *      assume to know the old size.  Caller must zero the reallocated memory.
+ *
+ *    - DUK_REALLOC_INDIRECT() must be used when a mark-and-sweep triggered
+ *      by an allocation failure might invalidate the original 'ptr', thus
+ *      causing a realloc retry to use an invalid pointer.  Example: we're
+ *      reallocating the value stack and a finalizer resizes the same value
+ *      stack during mark-and-sweep.  The indirect variant requests for the
+ *      current location of the pointer being reallocated using a callback
+ *      right before every realloc attempt; this circuitous approach is used
+ *      to avoid strict aliasing issues in a more straightforward indirect
+ *      pointer (void **) approach.  Note: the pointer in the storage
+ *      location is read but is NOT updated; the caller must do that.
+ */
+
+/* callback for indirect reallocs, request for current pointer */
+typedef void *(*duk_mem_getptr)(void *ud);
+
+#define DUK_ALLOC(heap,size)                            duk_heap_mem_alloc((heap), (size))
+#define DUK_ALLOC_ZEROED(heap,size)                     duk_heap_mem_alloc_zeroed((heap), (size))
+#define DUK_REALLOC(heap,ptr,newsize)                   duk_heap_mem_realloc((heap), (ptr), (newsize))
+#define DUK_REALLOC_INDIRECT(heap,cb,ud,newsize)        duk_heap_mem_realloc_indirect((heap), (cb), (ud), (newsize))
+#define DUK_FREE(heap,ptr)                              duk_heap_mem_free((heap), (ptr))
+
+/*
+ *  Memory calls: relative to a thread, GC interaction, throw error on alloc failure
+ */
+
+/* XXX: add __func__; use DUK_FUNC_MACRO because __func__ is not always available */
+
+#ifdef DUK_USE_VERBOSE_ERRORS
+#define DUK_ALLOC_CHECKED(thr,size)                     duk_heap_mem_alloc_checked((thr), (size), DUK_FILE_MACRO, DUK_LINE_MACRO)
+#define DUK_ALLOC_CHECKED_ZEROED(thr,size)              duk_heap_mem_alloc_checked_zeroed((thr), (size), DUK_FILE_MACRO, DUK_LINE_MACRO)
+#define DUK_REALLOC_CHECKED(thr,ptr,newsize)            duk_heap_mem_realloc_checked((thr), (ptr), (newsize), DUK_FILE_MACRO, DUK_LINE_MACRO)
+#define DUK_REALLOC_INDIRECT_CHECKED(thr,cb,ud,newsize) duk_heap_mem_realloc_indirect_checked((thr), (cb), (ud), (newsize), DUK_FILE_MACRO, DUK_LINE_MACRO)
+#define DUK_FREE_CHECKED(thr,ptr)                       duk_heap_mem_free((thr)->heap, (ptr))  /* must not fail */
+#else
+#define DUK_ALLOC_CHECKED(thr,size)                     duk_heap_mem_alloc_checked((thr), (size))
+#define DUK_ALLOC_CHECKED_ZEROED(thr,size)              duk_heap_mem_alloc_checked_zeroed((thr), (size))
+#define DUK_REALLOC_CHECKED(thr,ptr,newsize)            duk_heap_mem_realloc_checked((thr), (ptr), (newsize))
+#define DUK_REALLOC_INDIRECT_CHECKED(thr,cb,ud,newsize) duk_heap_mem_realloc_indirect_checked((thr), (cb), (ud), (newsize))
+#define DUK_FREE_CHECKED(thr,ptr)                       duk_heap_mem_free((thr)->heap, (ptr))  /* must not fail */
+#endif
+
+/*
+ *  Memory constants
+ */
+
+#define DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_LIMIT           5   /* Retry allocation after mark-and-sweep for this
+                                                              * many times.  A single mark-and-sweep round is
+                                                              * not guaranteed to free all unreferenced memory
+                                                              * because of finalization (in fact, ANY number of
+                                                              * rounds is strictly not enough).
+                                                              */
+
+#define DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_EMERGENCY_LIMIT  3  /* Starting from this round, use emergency mode
+                                                              * for mark-and-sweep.
+                                                              */
+
+/*
+ *  String cache should ideally be at duk_hthread level, but that would
+ *  cause string finalization to slow down relative to the number of
+ *  threads; string finalization must check the string cache for "weak"
+ *  references to the string being finalized to avoid dead pointers.
+ *
+ *  Thus, string caches are now at the heap level now.
+ */
+
+struct duk_strcache {
+	duk_hstring *h;
+	duk_uint32_t bidx;
+	duk_uint32_t cidx;
+};
+
+/*
+ *  Longjmp state, contains the information needed to perform a longjmp.
+ *  Longjmp related values are written to value1, value2, and iserror.
+ */
+
+struct duk_ljstate {
+	duk_jmpbuf *jmpbuf_ptr;   /* current setjmp() catchpoint */
+	duk_small_uint_t type;    /* longjmp type */
+	duk_bool_t iserror;       /* isError flag for yield */
+	duk_tval value1;          /* 1st related value (type specific) */
+	duk_tval value2;          /* 2nd related value (type specific) */
+};
+
+/*
+ *  Main heap structure
+ */
+
+struct duk_heap {
+	duk_small_uint_t flags;
+
+	/* allocator functions */
+	duk_alloc_function alloc_func;
+	duk_realloc_function realloc_func;
+	duk_free_function free_func;
+	void *alloc_udata;
+
+	/* Fatal error handling, called e.g. when a longjmp() is needed but
+	 * lj.jmpbuf_ptr is NULL.  fatal_func must never return; it's not
+	 * declared as "noreturn" because doing that for typedefs is a bit
+	 * challenging portability-wise.
+	 */
+	duk_fatal_function fatal_func;
+
+	/* allocated heap objects */
+	duk_heaphdr *heap_allocated;
+
+	/* work list for objects whose refcounts are zero but which have not been
+	 * "finalized"; avoids recursive C calls when refcounts go to zero in a
+	 * chain of objects.
+	 */
+#ifdef DUK_USE_REFERENCE_COUNTING
+	duk_heaphdr *refzero_list;
+	duk_heaphdr *refzero_list_tail;
+#endif
+
+#ifdef DUK_USE_MARK_AND_SWEEP
+	/* mark-and-sweep control */
+#ifdef DUK_USE_VOLUNTARY_GC
+	duk_int_t mark_and_sweep_trigger_counter;
+#endif
+	duk_int_t mark_and_sweep_recursion_depth;
+
+	/* mark-and-sweep flags automatically active (used for critical sections) */
+	duk_small_uint_t mark_and_sweep_base_flags;
+
+	/* work list for objects to be finalized (by mark-and-sweep) */
+	duk_heaphdr *finalize_list;
+#endif
+
+	/* longjmp state */
+	duk_ljstate lj;
+
+	/* marker for detecting internal "double faults", see duk_error_throw.c */
+	duk_bool_t handling_error;
+
+	/* heap thread, used internally and for finalization */
+	duk_hthread *heap_thread;
+
+	/* current thread */
+	duk_hthread *curr_thread;	/* currently running thread */
+
+	/* heap level "stash" object (e.g., various reachability roots) */
+	duk_hobject *heap_object;
+
+	/* heap level temporary log formatting buffer */
+	duk_hbuffer_dynamic *log_buffer;
+
+	/* duk_handle_call / duk_handle_safe_call recursion depth limiting */
+	duk_int_t call_recursion_depth;
+	duk_int_t call_recursion_limit;
+
+	/* mix-in value for computing string hashes; should be reasonably unpredictable */
+	duk_uint32_t hash_seed;
+
+	/* rnd_state for duk_util_tinyrandom.c */
+	duk_uint32_t rnd_state;
+
+	/* interrupt counter */
+#ifdef DUK_USE_INTERRUPT_COUNTER
+	duk_int_t interrupt_init;     /* start value for current countdown */
+	duk_int_t interrupt_counter;  /* countdown state (mirrored in current thread state) */
+#endif
+
+	/* string intern table (weak refs) */
+	duk_hstring **st;
+	duk_uint32_t st_size;     /* alloc size in elements */
+	duk_uint32_t st_used;     /* used elements (includes DELETED) */
+
+	/* string access cache (codepoint offset -> byte offset) for fast string
+	 * character looping; 'weak' reference which needs special handling in GC.
+	 */
+	duk_strcache strcache[DUK_HEAP_STRCACHE_SIZE];
+
+	/* built-in strings */
+	duk_hstring *strs[DUK_HEAP_NUM_STRINGS];
+};
+
+/*
+ *  Prototypes
+ */
+
+duk_heap *duk_heap_alloc(duk_alloc_function alloc_func,
+                         duk_realloc_function realloc_func,
+                         duk_free_function free_func,
+                         void *alloc_udata,
+                         duk_fatal_function fatal_func);
+void duk_heap_free(duk_heap *heap);
+void duk_heap_free_heaphdr_raw(duk_heap *heap, duk_heaphdr *hdr);
+
+void duk_heap_insert_into_heap_allocated(duk_heap *heap, duk_heaphdr *hdr);
+#if defined(DUK_USE_DOUBLE_LINKED_HEAP) && defined(DUK_USE_REFERENCE_COUNTING)
+void duk_heap_remove_any_from_heap_allocated(duk_heap *heap, duk_heaphdr *hdr);
+#endif
+#ifdef DUK_USE_INTERRUPT_COUNTER
+void duk_heap_switch_thread(duk_heap *heap, duk_hthread *new_thr);
+#endif
+
+duk_hstring *duk_heap_string_lookup(duk_heap *heap, duk_uint8_t *str, duk_uint32_t blen);
+duk_hstring *duk_heap_string_intern(duk_heap *heap, duk_uint8_t *str, duk_uint32_t blen);
+duk_hstring *duk_heap_string_intern_checked(duk_hthread *thr, duk_uint8_t *str, duk_uint32_t len);
+duk_hstring *duk_heap_string_lookup_u32(duk_heap *heap, duk_uint32_t val);
+duk_hstring *duk_heap_string_intern_u32(duk_heap *heap, duk_uint32_t val);
+duk_hstring *duk_heap_string_intern_u32_checked(duk_hthread *thr, duk_uint32_t val);
+void duk_heap_string_remove(duk_heap *heap, duk_hstring *h);
+#if defined(DUK_USE_MARK_AND_SWEEP) && defined(DUK_USE_MS_STRINGTABLE_RESIZE)
+void duk_heap_force_stringtable_resize(duk_heap *heap);
+#endif
+
+void duk_heap_strcache_string_remove(duk_heap *heap, duk_hstring *h);
+duk_uint_fast32_t duk_heap_strcache_offset_char2byte(duk_hthread *thr, duk_hstring *h, duk_uint_fast32_t char_offset);
+
+#ifdef DUK_USE_PROVIDE_DEFAULT_ALLOC_FUNCTIONS
+void *duk_default_alloc_function(void *udata, duk_size_t size);
+void *duk_default_realloc_function(void *udata, void *ptr, duk_size_t newsize);
+void duk_default_free_function(void *udata, void *ptr);
+#endif
+
+void *duk_heap_mem_alloc(duk_heap *heap, duk_size_t size);
+void *duk_heap_mem_alloc_zeroed(duk_heap *heap, duk_size_t size);
+void *duk_heap_mem_realloc(duk_heap *heap, void *ptr, duk_size_t newsize);
+void *duk_heap_mem_realloc_indirect(duk_heap *heap, duk_mem_getptr cb, void *ud, duk_size_t newsize);
+void duk_heap_mem_free(duk_heap *heap, void *ptr);
+
+#ifdef DUK_USE_VERBOSE_ERRORS
+void *duk_heap_mem_alloc_checked(duk_hthread *thr, duk_size_t size, const char *filename, duk_int_t line);
+void *duk_heap_mem_alloc_checked_zeroed(duk_hthread *thr, duk_size_t size, const char *filename, duk_int_t line);
+void *duk_heap_mem_realloc_checked(duk_hthread *thr, void *ptr, duk_size_t newsize, const char *filename, duk_int_t line);
+void *duk_heap_mem_realloc_indirect_checked(duk_hthread *thr, duk_mem_getptr cb, void *ud, duk_size_t newsize, const char *filename, duk_int_t line);
+#else
+void *duk_heap_mem_alloc_checked(duk_hthread *thr, duk_size_t size);
+void *duk_heap_mem_alloc_checked_zeroed(duk_hthread *thr, duk_size_t size);
+void *duk_heap_mem_realloc_checked(duk_hthread *thr, void *ptr, duk_size_t newsize);
+void *duk_heap_mem_realloc_indirect_checked(duk_hthread *thr, duk_mem_getptr cb, void *ud, duk_size_t newsize);
+#endif
+
+#ifdef DUK_USE_REFERENCE_COUNTING
+void duk_heap_tval_incref(duk_tval *tv);
+void duk_heap_tval_decref(duk_hthread *thr, duk_tval *tv);
+void duk_heap_heaphdr_incref(duk_heaphdr *h);
+void duk_heap_heaphdr_decref(duk_hthread *thr, duk_heaphdr *h);
+void duk_heap_refcount_finalize_heaphdr(duk_hthread *thr, duk_heaphdr *hdr);
+#else
+/* no refcounting */
+#endif
+
+#ifdef DUK_USE_MARK_AND_SWEEP
+duk_bool_t duk_heap_mark_and_sweep(duk_heap *heap, duk_small_uint_t flags);
+#endif
+
+duk_uint32_t duk_heap_hashstring(duk_heap *heap, duk_uint8_t *str, duk_size_t len);
+
+#endif  /* DUK_HEAP_H_INCLUDED */
+#line 1 "duk_debug.h"
+/*
+ *  Debugging macros, DUK_DPRINT() and its variants in particular.
+ *
+ *  DUK_DPRINT() allows formatted debug prints, and supports standard
+ *  and Duktape specific formatters.  See duk_debug_vsnprintf.c for details.
+ *
+ *  DUK_D(x), DUK_DD(x), and DUK_DDD(x) are used together with log macros
+ *  for technical reasons.  They are concretely used to hide 'x' from the
+ *  compiler when the corresponding log level is disabled.  This allows
+ *  clean builds on non-C99 compilers, at the cost of more verbose code.
+ *  Examples:
+ *
+ *    DUK_D(DUK_DPRINT("foo"));
+ *    DUK_DD(DUK_DDPRINT("foo"));
+ *    DUK_DDD(DUK_DDDPRINT("foo"));
+ *
+ *  This approach is preferable to the old "double parentheses" hack because
+ *  double parentheses make the C99 solution worse: __FILE__ and __LINE__ can
+ *  no longer be added transparently without going through globals, which
+ *  works poorly with threading.
+ */
+
+#ifndef DUK_DEBUG_H_INCLUDED
+#define DUK_DEBUG_H_INCLUDED
+
+#ifdef DUK_USE_DEBUG
+
+#if defined(DUK_USE_DPRINT)
+#define DUK_D(x) x
+#else
+#define DUK_D(x) do { } while (0) /* omit */
+#endif
+
+#if defined(DUK_USE_DDPRINT)
+#define DUK_DD(x) x
+#else
+#define DUK_DD(x) do { } while (0) /* omit */
+#endif
+
+#if defined(DUK_USE_DDDPRINT)
+#define DUK_DDD(x) x
+#else
+#define DUK_DDD(x) do { } while (0) /* omit */
+#endif
+
+/*
+ *  Exposed debug macros: debugging enabled
+ */
+
+#define DUK_LEVEL_DEBUG    1
+#define DUK_LEVEL_DDEBUG   2
+#define DUK_LEVEL_DDDEBUG  3
+
+#ifdef DUK_USE_VARIADIC_MACROS
+
+/* Note: combining __FILE__, __LINE__, and __func__ into fmt would be
+ * possible compile time, but waste some space with shared function names.
+ */
+#define DUK__DEBUG_LOG(lev,...)  duk_debug_log((duk_small_int_t) (lev), DUK_FILE_MACRO, (duk_int_t) DUK_LINE_MACRO, DUK_FUNC_MACRO, __VA_ARGS__);
+
+#define DUK_DPRINT(...)          DUK__DEBUG_LOG(DUK_LEVEL_DEBUG, __VA_ARGS__)
+
+#ifdef DUK_USE_DDPRINT
+#define DUK_DDPRINT(...)         DUK__DEBUG_LOG(DUK_LEVEL_DDEBUG, __VA_ARGS__)
+#else
+#define DUK_DDPRINT(...)
+#endif
+
+#ifdef DUK_USE_DDDPRINT
+#define DUK_DDDPRINT(...)        DUK__DEBUG_LOG(DUK_LEVEL_DDDEBUG, __VA_ARGS__)
+#else
+#define DUK_DDDPRINT(...)
+#endif
+
+#else  /* DUK_USE_VARIADIC_MACROS */
+
+#define DUK__DEBUG_STASH(lev)    \
+	(void) DUK_SNPRINTF(duk_debug_file_stash, DUK_DEBUG_STASH_SIZE, "%s", (const char *) DUK_FILE_MACRO), \
+	duk_debug_file_stash[DUK_DEBUG_STASH_SIZE - 1] = (char) 0; \
+	(void) DUK_SNPRINTF(duk_debug_line_stash, DUK_DEBUG_STASH_SIZE, "%ld", (long) DUK_LINE_MACRO), \
+	duk_debug_line_stash[DUK_DEBUG_STASH_SIZE - 1] = (char) 0; \
+	(void) DUK_SNPRINTF(duk_debug_func_stash, DUK_DEBUG_STASH_SIZE, "%s", (const char *) DUK_FUNC_MACRO), \
+	duk_debug_func_stash[DUK_DEBUG_STASH_SIZE - 1] = (char) 0; \
+	(void) (duk_debug_level_stash = (lev))
+
+/* Without variadic macros resort to comma expression trickery to handle debug
+ * prints.  This generates a lot of harmless warnings.  These hacks are not
+ * needed normally because DUK_D() and friends will hide the entire debug log
+ * statement from the compiler.
+ */
+
+#ifdef DUK_USE_DPRINT
+#define DUK_DPRINT  DUK__DEBUG_STASH(DUK_LEVEL_DEBUG), (void) duk_debug_log  /* args go here in parens */
+#else
+#define DUK_DPRINT  0 && /* args go here as a comma expression in parens */
+#endif
+
+#ifdef DUK_USE_DDPRINT
+#define DUK_DDPRINT  DUK__DEBUG_STASH(DUK_LEVEL_DDEBUG), (void) duk_debug_log  /* args go here in parens */
+#else
+#define DUK_DDPRINT  0 && 
+#endif
+
+#ifdef DUK_USE_DDDPRINT
+#define DUK_DDDPRINT  DUK__DEBUG_STASH(DUK_LEVEL_DDDEBUG), (void) duk_debug_log  /* args go here in parens */
+#else
+#define DUK_DDDPRINT  0 && 
+#endif
+
+#endif  /* DUK_USE_VARIADIC_MACROS */
+
+/* object dumpers */
+
+#define DUK_DEBUG_DUMP_HEAP(x)               duk_debug_dump_heap((x))
+#define DUK_DEBUG_DUMP_HSTRING(x)            /* XXX: unimplemented */
+#define DUK_DEBUG_DUMP_HOBJECT(x)            duk_debug_dump_hobject((x))
+#define DUK_DEBUG_DUMP_HCOMPILEDFUNCTION(x)  /* XXX: unimplemented */
+#define DUK_DEBUG_DUMP_HNATIVEFUNCTION(x)    /* XXX: unimplemented */
+#define DUK_DEBUG_DUMP_HTHREAD(thr)          duk_debug_dump_hobject((duk_hobject *) (thr))
+#define DUK_DEBUG_DUMP_CALLSTACK(thr)        duk_debug_dump_callstack((thr))
+#define DUK_DEBUG_DUMP_ACTIVATION(thr,act)   duk_debug_dump_activation((thr),(act))
+
+/* summary macros */
+
+#define DUK_DEBUG_SUMMARY_INIT()  do { \
+		DUK_MEMZERO(duk_debug_summary_buf, sizeof(duk_debug_summary_buf)); \
+		duk_debug_summary_idx = 0; \
+	} while (0)
+
+#define DUK_DEBUG_SUMMARY_CHAR(ch)  do { \
+		duk_debug_summary_buf[duk_debug_summary_idx++] = (ch); \
+		if ((duk_size_t) duk_debug_summary_idx >= (duk_size_t) (sizeof(duk_debug_summary_buf) - 1)) { \
+			duk_debug_summary_buf[duk_debug_summary_idx++] = (char) 0; \
+			DUK_DPRINT("    %s", (const char *) duk_debug_summary_buf); \
+			DUK_DEBUG_SUMMARY_INIT(); \
+		} \
+	} while (0)
+
+#define DUK_DEBUG_SUMMARY_FINISH()  do { \
+		if (duk_debug_summary_idx > 0) { \
+			duk_debug_summary_buf[duk_debug_summary_idx++] = (char) 0; \
+			DUK_DPRINT("    %s", (const char *) duk_debug_summary_buf); \
+			DUK_DEBUG_SUMMARY_INIT(); \
+		} \
+	} while (0)
+
+#else  /* DUK_USE_DEBUG */
+
+/*
+ *  Exposed debug macros: debugging disabled
+ */
+
+#define DUK_D(x) do { } while (0) /* omit */
+#define DUK_DD(x) do { } while (0) /* omit */
+#define DUK_DDD(x) do { } while (0) /* omit */
+
+#ifdef DUK_USE_VARIADIC_MACROS
+
+#define DUK_DPRINT(...)
+#define DUK_DDPRINT(...)
+#define DUK_DDDPRINT(...)
+
+#else  /* DUK_USE_VARIADIC_MACROS */
+
+#define DUK_DPRINT    0 && /* args go here as a comma expression in parens */
+#define DUK_DDPRINT   0 && 
+#define DUK_DDDPRINT  0 && 
+
+#endif  /* DUK_USE_VARIADIC_MACROS */
+
+#define DUK_DEBUG_DUMP_HEAP(x)
+#define DUK_DEBUG_DUMP_HSTRING(x)
+#define DUK_DEBUG_DUMP_HOBJECT(x)
+#define DUK_DEBUG_DUMP_HCOMPILEDFUNCTION(x)
+#define DUK_DEBUG_DUMP_HNATIVEFUNCTION(x)
+#define DUK_DEBUG_DUMP_HTHREAD(x)
+
+#define DUK_DEBUG_SUMMARY_INIT()
+#define DUK_DEBUG_SUMMARY_CHAR(ch)
+#define DUK_DEBUG_SUMMARY_FINISH()
+
+#endif  /* DUK_USE_DEBUG */
+
+/*
+ *  Structs
+ */
+
+#ifdef DUK_USE_DEBUG
+struct duk_fixedbuffer {
+	duk_uint8_t *buffer;
+	duk_size_t length;
+	duk_size_t offset;
+	duk_bool_t truncated;
+};
+#endif
+
+/*
+ *  Prototypes
+ */
+
+#ifdef DUK_USE_DEBUG
+duk_int_t duk_debug_vsnprintf(char *str, duk_size_t size, const char *format, va_list ap);
+duk_int_t duk_debug_snprintf(char *str, duk_size_t size, const char *format, ...);
+void duk_debug_format_funcptr(char *buf, duk_size_t buf_size, duk_uint8_t *fptr, duk_size_t fptr_size);
+
+#ifdef DUK_USE_VARIADIC_MACROS
+void duk_debug_log(duk_small_int_t level, const char *file, duk_int_t line, const char *func, char *fmt, ...);
+#else  /* DUK_USE_VARIADIC_MACROS */
+/* parameter passing, not thread safe */
+#define DUK_DEBUG_STASH_SIZE  128
+extern char duk_debug_file_stash[DUK_DEBUG_STASH_SIZE];
+extern char duk_debug_line_stash[DUK_DEBUG_STASH_SIZE];
+extern char duk_debug_func_stash[DUK_DEBUG_STASH_SIZE];
+extern duk_small_int_t duk_debug_level_stash;
+extern void duk_debug_log(char *fmt, ...);
+#endif  /* DUK_USE_VARIADIC_MACROS */
+
+void duk_fb_put_bytes(duk_fixedbuffer *fb, duk_uint8_t *buffer, duk_size_t length);
+void duk_fb_put_byte(duk_fixedbuffer *fb, duk_uint8_t x);
+void duk_fb_put_cstring(duk_fixedbuffer *fb, const char *x);
+void duk_fb_sprintf(duk_fixedbuffer *fb, const char *fmt, ...);
+duk_bool_t duk_fb_is_full(duk_fixedbuffer *fb);
+
+void duk_debug_dump_heap(duk_heap *heap);
+void duk_debug_heap_graphviz(duk_heap *heap);
+void duk_debug_dump_hobject(duk_hobject *obj);
+void duk_debug_dump_hthread(duk_hthread *thr);
+void duk_debug_dump_callstack(duk_hthread *thr);
+void duk_debug_dump_activation(duk_hthread *thr, duk_activation *act);
+
+#define DUK_DEBUG_SUMMARY_BUF_SIZE  76
+extern char duk_debug_summary_buf[DUK_DEBUG_SUMMARY_BUF_SIZE];
+extern duk_int_t duk_debug_summary_idx;
+
+#endif  /* DUK_USE_DEBUG */
+
+#endif  /* DUK_DEBUG_H_INCLUDED */
+#line 1 "duk_error.h"
+/*
+ *  Error handling macros, assertion macro, error codes.
+ *
+ *  There are three level of 'errors':
+ *
+ *    1. Ordinary errors, relative to a thread, cause a longjmp, catchable.
+ *    2. Fatal errors, relative to a heap, cause fatal handler to be called.
+ *    3. Panic errors, unrelated to a heap and cause a process exit.
+ *
+ *  Panics are used by the default fatal error handler and by debug code
+ *  such as assertions.  By providing a proper fatal error handler, user
+ *  code can avoid panics in non-debug builds.
+ */
+
+#ifndef DUK_ERROR_H_INCLUDED
+#define DUK_ERROR_H_INCLUDED
+
+/*
+ *  Error codes: defined in duktape.h
+ *
+ *  Error codes are used as a shorthand to throw exceptions from inside
+ *  the implementation.  The appropriate Ecmascript object is constructed
+ *  based on the code.  Ecmascript code throws objects directly.  The error
+ *  codes are defined in the public API header because they are also used
+ *  by calling code.
+ */
+
+/*
+ *  Normal error
+ *
+ *  Normal error is thrown with a longjmp() through the current setjmp()
+ *  catchpoint record in the duk_heap.  The 'curr_thread' of the duk_heap
+ *  identifies the throwing thread.
+ * 
+ *  Error formatting is not always necessary but there are no separate calls
+ *  (to minimize code size).  Error object creation will consume a considerable
+ *  amount of time, compared to which formatting is probably trivial.  Note
+ *  that special formatting (provided by DUK_DEBUG macros) is NOT available.
+ *
+ *  The _RAW variants allow the caller to specify file and line.  This makes
+ *  it easier to write checked calls which want to use the call site of the
+ *  checked function, not the error macro call inside the checked function.
+ *
+ *  We prefer the standard variadic macros; if they are not available, we
+ *  fall back to awkward hacks.
+ */
+
+#ifdef DUK_USE_VERBOSE_ERRORS
+
+#ifdef DUK_USE_VARIADIC_MACROS
+
+/* __VA_ARGS__ has comma issues for empty lists, so we mandate at least 1 argument for '...' (format string) */
+#define DUK_ERROR(thr,err,...)                    duk_err_handle_error(DUK_FILE_MACRO, (duk_int_t) DUK_LINE_MACRO, (thr), (err), __VA_ARGS__)
+#define DUK_ERROR_RAW(file,line,thr,err,...)      duk_err_handle_error((file), (line), (thr), (err), __VA_ARGS__)
+
+#else  /* DUK_USE_VARIADIC_MACROS */
+
+/* Parameter passing here is not thread safe.  We rely on the __FILE__
+ * pointer being a constant which can be passed through a global.
+ */
+
+#define DUK_ERROR  \
+	duk_err_file_stash = (const char *) DUK_FILE_MACRO, \
+	duk_err_line_stash = (duk_int_t) DUK_LINE_MACRO, \
+	(void) duk_err_handle_error_stash  /* arguments follow */
+#define DUK_ERROR_RAW                             duk_err_handle_error
+
+#endif  /* DUK_USE_VARIADIC_MACROS */
+
+#else  /* DUK_USE_VERBOSE_ERRORS */
+
+#ifdef DUK_USE_VARIADIC_MACROS
+
+#define DUK_ERROR(thr,err,...)                    duk_err_handle_error((thr), (err))
+#define DUK_ERROR_RAW(file,line,thr,err,...)      duk_err_handle_error((thr), (err))
+
+#else  /* DUK_USE_VARIADIC_MACROS */
+
+/* This is sub-optimal because arguments will be passed but ignored, and the strings
+ * will go into the object file.  Can't think of how to do this portably and still
+ * relatively conveniently.
+ */
+#define DUK_ERROR                                 duk_err_handle_error_nonverbose1
+#define DUK_ERROR_RAW                             duk_err_handle_error_nonverbose2
+
+#endif  /* DUK_USE_VARIADIC_MACROS */
+
+#endif  /* DUK_USE_VERBOSE_ERRORS */
+
+/*
+ *  Fatal error
+ *
+ *  There are no fatal error macros at the moment.  There are so few call
+ *  sites that the fatal error handler is called directly.
+ */
+
+/*
+ *  Panic error
+ *
+ *  Panic errors are not relative to either a heap or a thread, and cause
+ *  DUK_PANIC() macro to be invoked.  Unlesa a user provides DUK_OPT_PANIC_HANDLER,
+ *  DUK_PANIC() calls a helper which prints out the error and causes a process
+ *  exit.
+ *
+ *  The user can override the macro to provide custom handling.  A macro is
+ *  used to allow the user to have inline panic handling if desired (without
+ *  causing a potentially risky function call).
+ *
+ *  Panics are only used in debug code such as assertions, and by the default
+ *  fatal error handler.
+ */
+
+#if defined(DUK_USE_PANIC_HANDLER)
+/* already defined, good */
+#define DUK_PANIC(code,msg)  DUK_USE_PANIC_HANDLER((code),(msg))
+#else
+#define DUK_PANIC(code,msg)  duk_default_panic_handler((code),(msg))
+#endif  /* DUK_USE_PANIC_HANDLER */
+
+/*
+ *  Assert macro: failure causes panic.
+ */
+
+#ifdef DUK_USE_ASSERTIONS
+
+/* the message should be a compile time constant without formatting (less risk);
+ * we don't care about assertion text size because they're not used in production
+ * builds.
+ */
+#define DUK_ASSERT(x)  do { \
+	if (!(x)) { \
+		DUK_PANIC(DUK_ERR_ASSERTION_ERROR, \
+			"assertion failed: " #x \
+			" (" DUK_FILE_MACRO ":" DUK_MACRO_STRINGIFY(DUK_LINE_MACRO) ")"); \
+	} \
+	} while (0)
+
+#else  /* DUK_USE_ASSERTIONS */
+
+#define DUK_ASSERT(x)  do { /* assertion omitted */ } while(0)
+
+#endif  /* DUK_USE_ASSERTIONS */
+
+/* this variant is used when an assert would generate a compile warning by
+ * being always true (e.g. >= 0 comparison for an unsigned value
+ */
+#define DUK_ASSERT_DISABLE(x)  do { /* assertion disabled */ } while(0)
+
+/*
+ *  Assertion helpers
+ */
+
+#if defined(DUK_USE_ASSERTIONS) && defined(DUK_USE_REFERENCE_COUNTING)
+#define DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR(h)  do { \
+		DUK_ASSERT((h) == NULL || DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) (h)) > 0); \
+	} while (0)
+#define DUK_ASSERT_REFCOUNT_NONZERO_TVAL(tv)  do { \
+		if ((tv) != NULL && DUK_TVAL_IS_HEAP_ALLOCATED((tv))) { \
+			DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(DUK_TVAL_GET_HEAPHDR((tv))) > 0); \
+		} \
+	} while (0)
+#else
+#define DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR(h)  /* no refcount check */
+#define DUK_ASSERT_REFCOUNT_NONZERO_TVAL(tv)    /* no refcount check */
+#endif
+
+#define DUK_ASSERT_TOP(ctx,n)  DUK_ASSERT((duk_idx_t) duk_get_top((ctx)) == (duk_idx_t) (n))
+
+#if defined(DUK_USE_ASSERTIONS) && defined(DUK_USE_PACKED_TVAL)
+#define DUK_ASSERT_DOUBLE_IS_NORMALIZED(dval)  do { \
+		duk_double_union assert_tmp_du; \
+		assert_tmp_du.d = (dval); \
+		DUK_ASSERT(DUK_DBLUNION_IS_NORMALIZED(&assert_tmp_du)); \
+	} while (0)
+#else
+#define DUK_ASSERT_DOUBLE_IS_NORMALIZED(dval)  /* nop */
+#endif
+
+/*
+ *  Helper for valstack space
+ *
+ *  Caller of DUK_ASSERT_VALSTACK_SPACE() estimates the number of free stack entries
+ *  required for its own use, and any child calls which are not (a) Duktape API calls
+ *  or (b) Duktape calls which involve extending the valstack (e.g. getter call).
+ */
+
+#define DUK_VALSTACK_ASSERT_EXTRA  5  /* this is added to checks to allow for Duktape
+                                        * API calls in addition to function's own use
+                                        */
+#if defined(DUK_USE_ASSERTIONS)
+#define DUK_ASSERT_VALSTACK_SPACE(thr,n)   do { \
+		DUK_ASSERT((thr) != NULL); \
+		DUK_ASSERT((thr)->valstack_end - (thr)->valstack_top >= (n) + DUK_VALSTACK_ASSERT_EXTRA); \
+	} while (0)
+#else
+#define DUK_ASSERT_VALSTACK_SPACE(thr,n)   /* no valstack space check */
+#endif
+
+/*
+ *  Prototypes
+ */
+
+#ifdef DUK_USE_VERBOSE_ERRORS
+#ifdef DUK_USE_VARIADIC_MACROS
+DUK_NORETURN(void duk_err_handle_error(const char *filename, duk_int_t line, duk_hthread *thr, duk_errcode_t code, const char *fmt, ...));
+#else  /* DUK_USE_VARIADIC_MACROS */
+extern const char *duk_err_file_stash;
+extern duk_int_t duk_err_line_stash;
+DUK_NORETURN(void duk_err_handle_error(const char *filename, duk_int_t line, duk_hthread *thr, duk_errcode_t code, const char *fmt, ...));
+DUK_NORETURN(void duk_err_handle_error_stash(duk_hthread *thr, duk_errcode_t code, const char *fmt, ...));
+#endif  /* DUK_USE_VARIADIC_MACROS */
+#else  /* DUK_USE_VERBOSE_ERRORS */
+#ifdef DUK_USE_VARIADIC_MACROS
+DUK_NORETURN(void duk_err_handle_error(duk_hthread *thr, duk_errcode_t code));
+#else  /* DUK_USE_VARIADIC_MACROS */
+DUK_NORETURN(void duk_err_handle_error_nonverbose1(duk_hthread *thr, duk_errcode_t code, const char *fmt, ...));
+DUK_NORETURN(void duk_err_handle_error_nonverbose2(const char *filename, duk_int_t line, duk_hthread *thr, duk_errcode_t code, const char *fmt, ...));
+#endif  /* DUK_USE_VARIADIC_MACROS */
+#endif  /* DUK_USE_VERBOSE_ERRORS */
+
+#ifdef DUK_USE_VERBOSE_ERRORS
+DUK_NORETURN(void duk_err_create_and_throw(duk_hthread *thr, duk_errcode_t code, const char *msg, const char *filename, duk_int_t line));
+#else
+DUK_NORETURN(void duk_err_create_and_throw(duk_hthread *thr, duk_errcode_t code));
+#endif
+
+DUK_NORETURN(void duk_error_throw_from_negative_rc(duk_hthread *thr, duk_ret_t rc));
+
+#if defined(DUK_USE_AUGMENT_ERROR_CREATE)
+void duk_err_augment_error_create(duk_hthread *thr, duk_hthread *thr_callstack, const char *filename, duk_int_t line, duk_bool_t noblame_fileline);
+#endif
+#if defined(DUK_USE_AUGMENT_ERROR_THROW)
+void duk_err_augment_error_throw(duk_hthread *thr);
+#endif
+
+DUK_NORETURN(void duk_err_longjmp(duk_hthread *thr));
+
+DUK_NORETURN(void duk_default_fatal_handler(duk_context *ctx, duk_errcode_t code, const char *msg));
+
+#if !defined(DUK_USE_PANIC_HANDLER)
+DUK_NORETURN(void duk_default_panic_handler(duk_errcode_t code, const char *msg));
+#endif
+
+void duk_err_setup_heap_ljstate(duk_hthread *thr, duk_small_int_t lj_type);
+
+duk_hobject *duk_error_prototype_from_code(duk_hthread *thr, duk_errcode_t err_code);
+
+#endif  /* DUK_ERROR_H_INCLUDED */
+#line 1 "duk_util.h"
+/*
+ *  Utilities
+ */
+
+#ifndef DUK_UTIL_H_INCLUDED
+#define DUK_UTIL_H_INCLUDED
+
+#define DUK_UTIL_MIN_HASH_PRIME  17  /* must match genhashsizes.py */
+
+#define DUK_UTIL_GET_HASH_PROBE_STEP(hash)  (duk_util_probe_steps[(hash) & 0x1f])
+
+/*
+ *  Bitstream decoder
+ */
+
+struct duk_bitdecoder_ctx {
+	const duk_uint8_t *data;
+	duk_size_t offset;
+	duk_size_t length;
+	duk_uint32_t currval;
+	duk_small_int_t currbits;
+};
+
+/*
+ *  Bitstream encoder
+ */
+
+struct duk_bitencoder_ctx {
+	duk_uint8_t *data;
+	duk_size_t offset;
+	duk_size_t length;
+	duk_uint32_t currval;
+	duk_small_int_t currbits;
+	duk_small_int_t truncated;
+};
+
+/*
+ *  Externs and prototypes
+ */
+
+extern duk_uint8_t duk_lc_digits[36];
+extern duk_uint8_t duk_uc_nybbles[16];
+extern duk_int8_t duk_hex_dectab[256];
+
+/* Note: assumes that duk_util_probe_steps size is 32 */
+extern duk_uint8_t duk_util_probe_steps[32];
+
+duk_uint32_t duk_util_hashbytes(duk_uint8_t *data, duk_size_t len, duk_uint32_t seed);
+
+duk_uint32_t duk_util_get_hash_prime(duk_uint32_t size);
+
+duk_int32_t duk_bd_decode(duk_bitdecoder_ctx *ctx, duk_small_int_t bits);
+duk_small_int_t duk_bd_decode_flag(duk_bitdecoder_ctx *ctx);
+duk_int32_t duk_bd_decode_flagged(duk_bitdecoder_ctx *ctx, duk_small_int_t bits, duk_int32_t def_value);
+
+void duk_be_encode(duk_bitencoder_ctx *ctx, duk_uint32_t data, duk_small_int_t bits);
+void duk_be_finish(duk_bitencoder_ctx *ctx);
+
+duk_uint32_t duk_util_tinyrandom_get_bits(duk_hthread *thr, duk_small_int_t n);
+duk_double_t duk_util_tinyrandom_get_double(duk_hthread *thr);
+
+#endif  /* DUK_UTIL_H_INCLUDED */
+
+#line 1 "duk_unicode.h"
+/*
+ *  Unicode helpers
+ */
+
+#ifndef DUK_UNICODE_H_INCLUDED
+#define DUK_UNICODE_H_INCLUDED
+
+/*
+ *  UTF-8 / XUTF-8 / CESU-8 constants
+ */
+
+#define DUK_UNICODE_MAX_XUTF8_LENGTH   7   /* up to 36 bit codepoints */
+#define DUK_UNICODE_MAX_CESU8_LENGTH   6   /* all codepoints up to U+10FFFF */
+
+/*
+ *  Useful Unicode codepoints
+ *
+ *  Integer constants must be signed to avoid unexpected coercions
+ *  in comparisons.
+ */
+
+#define DUK_UNICODE_CP_ZWNJ                   0x200cL  /* zero-width non-joiner */
+#define DUK_UNICODE_CP_ZWJ                    0x200dL  /* zero-width joiner */
+#define DUK_UNICODE_CP_REPLACEMENT_CHARACTER  0xfffdL  /* http://en.wikipedia.org/wiki/Replacement_character#Replacement_character */
+
+/*
+ *  ASCII character constants
+ *
+ *  C character literals like 'x' have a platform specific value and do
+ *  not match ASCII (UTF-8) values on e.g. EBCDIC platforms.  So, use
+ *  these (admittedly awkward) constants instead.  These constants must
+ *  also have signed values to avoid unexpected coercions in comparisons.
+ *
+ *  http://en.wikipedia.org/wiki/ASCII
+ */
+
+#define DUK_ASC_NUL              0x00
+#define DUK_ASC_SOH              0x01
+#define DUK_ASC_STX              0x02
+#define DUK_ASC_ETX              0x03
+#define DUK_ASC_EOT              0x04
+#define DUK_ASC_ENQ              0x05
+#define DUK_ASC_ACK              0x06
+#define DUK_ASC_BEL              0x07
+#define DUK_ASC_BS               0x08
+#define DUK_ASC_HT               0x09
+#define DUK_ASC_LF               0x0a
+#define DUK_ASC_VT               0x0b
+#define DUK_ASC_FF               0x0c
+#define DUK_ASC_CR               0x0d
+#define DUK_ASC_SO               0x0e
+#define DUK_ASC_SI               0x0f
+#define DUK_ASC_DLE              0x10
+#define DUK_ASC_DC1              0x11
+#define DUK_ASC_DC2              0x12
+#define DUK_ASC_DC3              0x13
+#define DUK_ASC_DC4              0x14
+#define DUK_ASC_NAK              0x15
+#define DUK_ASC_SYN              0x16
+#define DUK_ASC_ETB              0x17
+#define DUK_ASC_CAN              0x18
+#define DUK_ASC_EM               0x19
+#define DUK_ASC_SUB              0x1a
+#define DUK_ASC_ESC              0x1b
+#define DUK_ASC_FS               0x1c
+#define DUK_ASC_GS               0x1d
+#define DUK_ASC_RS               0x1e
+#define DUK_ASC_US               0x1f
+#define DUK_ASC_SPACE            0x20
+#define DUK_ASC_EXCLAMATION      0x21
+#define DUK_ASC_DOUBLEQUOTE      0x22
+#define DUK_ASC_HASH             0x23
+#define DUK_ASC_DOLLAR           0x24
+#define DUK_ASC_PERCENT          0x25
+#define DUK_ASC_AMP              0x26
+#define DUK_ASC_SINGLEQUOTE      0x27
+#define DUK_ASC_LPAREN           0x28
+#define DUK_ASC_RPAREN           0x29
+#define DUK_ASC_STAR             0x2a
+#define DUK_ASC_PLUS             0x2b
+#define DUK_ASC_COMMA            0x2c
+#define DUK_ASC_MINUS            0x2d
+#define DUK_ASC_PERIOD           0x2e
+#define DUK_ASC_SLASH            0x2f
+#define DUK_ASC_0                0x30
+#define DUK_ASC_1                0x31
+#define DUK_ASC_2                0x32
+#define DUK_ASC_3                0x33
+#define DUK_ASC_4                0x34
+#define DUK_ASC_5                0x35
+#define DUK_ASC_6                0x36
+#define DUK_ASC_7                0x37
+#define DUK_ASC_8                0x38
+#define DUK_ASC_9                0x39
+#define DUK_ASC_COLON            0x3a
+#define DUK_ASC_SEMICOLON        0x3b
+#define DUK_ASC_LANGLE           0x3c
+#define DUK_ASC_EQUALS           0x3d
+#define DUK_ASC_RANGLE           0x3e
+#define DUK_ASC_QUESTION         0x3f
+#define DUK_ASC_ATSIGN           0x40
+#define DUK_ASC_UC_A             0x41
+#define DUK_ASC_UC_B             0x42
+#define DUK_ASC_UC_C             0x43
+#define DUK_ASC_UC_D             0x44
+#define DUK_ASC_UC_E             0x45
+#define DUK_ASC_UC_F             0x46
+#define DUK_ASC_UC_G             0x47
+#define DUK_ASC_UC_H             0x48
+#define DUK_ASC_UC_I             0x49
+#define DUK_ASC_UC_J             0x4a
+#define DUK_ASC_UC_K             0x4b
+#define DUK_ASC_UC_L             0x4c
+#define DUK_ASC_UC_M             0x4d
+#define DUK_ASC_UC_N             0x4e
+#define DUK_ASC_UC_O             0x4f
+#define DUK_ASC_UC_P             0x50
+#define DUK_ASC_UC_Q             0x51
+#define DUK_ASC_UC_R             0x52
+#define DUK_ASC_UC_S             0x53
+#define DUK_ASC_UC_T             0x54
+#define DUK_ASC_UC_U             0x55
+#define DUK_ASC_UC_V             0x56
+#define DUK_ASC_UC_W             0x57
+#define DUK_ASC_UC_X             0x58
+#define DUK_ASC_UC_Y             0x59
+#define DUK_ASC_UC_Z             0x5a
+#define DUK_ASC_LBRACKET         0x5b
+#define DUK_ASC_BACKSLASH        0x5c
+#define DUK_ASC_RBRACKET         0x5d
+#define DUK_ASC_CARET            0x5e
+#define DUK_ASC_UNDERSCORE       0x5f
+#define DUK_ASC_GRAVE            0x60
+#define DUK_ASC_LC_A             0x61
+#define DUK_ASC_LC_B             0x62
+#define DUK_ASC_LC_C             0x63
+#define DUK_ASC_LC_D             0x64
+#define DUK_ASC_LC_E             0x65
+#define DUK_ASC_LC_F             0x66
+#define DUK_ASC_LC_G             0x67
+#define DUK_ASC_LC_H             0x68
+#define DUK_ASC_LC_I             0x69
+#define DUK_ASC_LC_J             0x6a
+#define DUK_ASC_LC_K             0x6b
+#define DUK_ASC_LC_L             0x6c
+#define DUK_ASC_LC_M             0x6d
+#define DUK_ASC_LC_N             0x6e
+#define DUK_ASC_LC_O             0x6f
+#define DUK_ASC_LC_P             0x70
+#define DUK_ASC_LC_Q             0x71
+#define DUK_ASC_LC_R             0x72
+#define DUK_ASC_LC_S             0x73
+#define DUK_ASC_LC_T             0x74
+#define DUK_ASC_LC_U             0x75
+#define DUK_ASC_LC_V             0x76
+#define DUK_ASC_LC_W             0x77
+#define DUK_ASC_LC_X             0x78
+#define DUK_ASC_LC_Y             0x79
+#define DUK_ASC_LC_Z             0x7a
+#define DUK_ASC_LCURLY           0x7b
+#define DUK_ASC_PIPE             0x7c
+#define DUK_ASC_RCURLY           0x7d
+#define DUK_ASC_TILDE            0x7e
+#define DUK_ASC_DEL              0x7f
+
+/*
+ *  Unicode tables
+ */
+
+#ifdef DUK_USE_SOURCE_NONBMP
+/*
+ *  Automatically generated by extract_chars.py, do not edit!
+ */
+
+extern const duk_uint8_t duk_unicode_ids_noa[797];
+#else
+/*
+ *  Automatically generated by extract_chars.py, do not edit!
+ */
+
+extern const duk_uint8_t duk_unicode_ids_noabmp[614];
+#endif
+
+#ifdef DUK_USE_SOURCE_NONBMP
+/*
+ *  Automatically generated by extract_chars.py, do not edit!
+ */
+
+extern const duk_uint8_t duk_unicode_ids_m_let_noa[42];
+#else
+/*
+ *  Automatically generated by extract_chars.py, do not edit!
+ */
+
+extern const duk_uint8_t duk_unicode_ids_m_let_noabmp[24];
+#endif
+
+#ifdef DUK_USE_SOURCE_NONBMP
+/*
+ *  Automatically generated by extract_chars.py, do not edit!
+ */
+
+extern const duk_uint8_t duk_unicode_idp_m_ids_noa[397];
+#else
+/*
+ *  Automatically generated by extract_chars.py, do not edit!
+ */
+
+extern const duk_uint8_t duk_unicode_idp_m_ids_noabmp[348];
+#endif
+
+/*
+ *  Automatically generated by extract_caseconv.py, do not edit!
+ */
+
+extern const duk_uint8_t duk_unicode_caseconv_uc[1288];
+extern const duk_uint8_t duk_unicode_caseconv_lc[616];
+
+/*
+ *  Extern
+ */
+
+/* duk_unicode_support.c */
+extern duk_uint8_t duk_unicode_xutf8_markers[7];
+extern duk_uint16_t duk_unicode_re_ranges_digit[2];
+extern duk_uint16_t duk_unicode_re_ranges_white[22];
+extern duk_uint16_t duk_unicode_re_ranges_wordchar[8];
+extern duk_uint16_t duk_unicode_re_ranges_not_digit[4];
+extern duk_uint16_t duk_unicode_re_ranges_not_white[24];
+extern duk_uint16_t duk_unicode_re_ranges_not_wordchar[10];
+
+/*
+ *  Prototypes
+ */
+
+duk_small_int_t duk_unicode_get_xutf8_length(duk_ucodepoint_t cp);
+duk_small_int_t duk_unicode_encode_xutf8(duk_ucodepoint_t cp, duk_uint8_t *out);
+duk_small_int_t duk_unicode_encode_cesu8(duk_ucodepoint_t cp, duk_uint8_t *out);
+duk_small_int_t duk_unicode_decode_xutf8(duk_hthread *thr, duk_uint8_t **ptr, duk_uint8_t *ptr_start, duk_uint8_t *ptr_end, duk_ucodepoint_t *out_cp);
+duk_ucodepoint_t duk_unicode_decode_xutf8_checked(duk_hthread *thr, duk_uint8_t **ptr, duk_uint8_t *ptr_start, duk_uint8_t *ptr_end);
+duk_size_t duk_unicode_unvalidated_utf8_length(duk_uint8_t *data, duk_size_t blen);
+duk_small_int_t duk_unicode_is_whitespace(duk_codepoint_t cp);
+duk_small_int_t duk_unicode_is_line_terminator(duk_codepoint_t cp);
+duk_small_int_t duk_unicode_is_identifier_start(duk_codepoint_t cp);
+duk_small_int_t duk_unicode_is_identifier_part(duk_codepoint_t cp);
+duk_small_int_t duk_unicode_is_letter(duk_codepoint_t cp);
+void duk_unicode_case_convert_string(duk_hthread *thr, duk_bool_t uppercase);
+duk_codepoint_t duk_unicode_re_canonicalize_char(duk_hthread *thr, duk_codepoint_t cp);
+duk_small_int_t duk_unicode_re_is_wordchar(duk_codepoint_t cp);
+
+#endif  /* DUK_UNICODE_H_INCLUDED */
+
+#line 1 "duk_json.h"
+/*
+ *  Defines for JSON, especially duk_bi_json.c.
+ */
+
+#ifndef DUK_JSON_H_INCLUDED
+#define DUK_JSON_H_INCLUDED
+
+/* Object/array recursion limit (to protect C stack) */
+#if defined(DUK_USE_DEEP_C_STACK)
+#define DUK_JSON_ENC_RECURSION_LIMIT          1000
+#define DUK_JSON_DEC_RECURSION_LIMIT          1000
+#else
+#define DUK_JSON_ENC_RECURSION_LIMIT          100
+#define DUK_JSON_DEC_RECURSION_LIMIT          100
+#endif
+
+/* Encoding/decoding flags */
+#define DUK_JSON_FLAG_ASCII_ONLY          (1 << 0)  /* escape any non-ASCII characters */
+#define DUK_JSON_FLAG_AVOID_KEY_QUOTES    (1 << 1)  /* avoid key quotes when key is an ASCII Identifier */
+#define DUK_JSON_FLAG_EXT_CUSTOM          (1 << 2)  /* extended types: custom encoding */
+#define DUK_JSON_FLAG_EXT_COMPATIBLE      (1 << 3)  /* extended types: compatible encoding */
+
+/* How much stack to require on entry to object/array encode */
+#define DUK_JSON_ENC_REQSTACK                 32
+
+/* How much stack to require on entry to object/array decode */
+#define DUK_JSON_DEC_REQSTACK                 32
+
+/* Encoding state.  Heap object references are all borrowed. */
+typedef struct {
+	duk_hthread *thr;
+	duk_hbuffer_dynamic *h_buf;
+	duk_hobject *h_replacer;     /* replacer function */
+	duk_hstring *h_gap;          /* gap (if empty string, NULL) */
+	duk_hstring *h_indent;       /* current indent (if gap is NULL, this is NULL) */
+	duk_idx_t idx_proplist;      /* explicit PropertyList */
+	duk_idx_t idx_loop;          /* valstack index of loop detection object */
+	duk_small_uint_t flags;
+	duk_small_uint_t flag_ascii_only;
+	duk_small_uint_t flag_avoid_key_quotes;
+#if defined(DUK_USE_JX) || defined(DUK_USE_JC)
+	duk_small_uint_t flag_ext_custom;
+	duk_small_uint_t flag_ext_compatible;
+#endif
+	duk_int_t recursion_depth;
+	duk_int_t recursion_limit;
+	duk_uint_t mask_for_undefined;      /* type bit mask: types which certainly produce 'undefined' */
+#if defined(DUK_USE_JX) || defined(DUK_USE_JC)
+	duk_small_uint_t stridx_custom_undefined;
+	duk_small_uint_t stridx_custom_nan;
+	duk_small_uint_t stridx_custom_neginf;
+	duk_small_uint_t stridx_custom_posinf;
+	duk_small_uint_t stridx_custom_function;
+#endif
+} duk_json_enc_ctx;
+
+typedef struct {
+	duk_hthread *thr;
+	duk_uint8_t *p;
+	duk_uint8_t *p_end;
+	duk_idx_t idx_reviver;
+	duk_small_uint_t flags;
+#if defined(DUK_USE_JX) || defined(DUK_USE_JC)
+	duk_small_uint_t flag_ext_custom;
+	duk_small_uint_t flag_ext_compatible;
+#endif
+	duk_int_t recursion_depth;
+	duk_int_t recursion_limit;
+} duk_json_dec_ctx;
+
+#endif  /* DUK_JSON_H_INCLUDED */
+#line 1 "duk_js.h"
+/*
+ *  Ecmascript execution, support primitives.
+ */
+
+#ifndef DUK_JS_H_INCLUDED
+#define DUK_JS_H_INCLUDED
+
+/* Flags for call handling. */
+#define DUK_CALL_FLAG_PROTECTED              (1 << 0)  /* duk_handle_call: call is protected */
+#define DUK_CALL_FLAG_IGNORE_RECLIMIT        (1 << 1)  /* duk_handle_call: call ignores C recursion limit (for errhandler calls) */
+#define DUK_CALL_FLAG_CONSTRUCTOR_CALL       (1 << 2)  /* duk_handle_call: constructor call (i.e. called as 'new Foo()') */
+#define DUK_CALL_FLAG_IS_RESUME              (1 << 3)  /* duk_handle_ecma_call_setup: setup for a resume() */
+#define DUK_CALL_FLAG_IS_TAILCALL            (1 << 4)  /* duk_handle_ecma_call_setup: setup for a tailcall */
+#define DUK_CALL_FLAG_DIRECT_EVAL            (1 << 5)  /* call is a direct eval call */
+
+/* Flags for duk_js_equals_helper(). */
+#define DUK_EQUALS_FLAG_SAMEVALUE            (1 << 0)  /* use SameValue instead of non-strict equality */
+#define DUK_EQUALS_FLAG_STRICT               (1 << 1)  /* use strict equality instead of non-strict equality */
+
+/* Flags for duk_js_compare_helper(). */
+#define DUK_COMPARE_FLAG_EVAL_LEFT_FIRST     (1 << 0)  /* eval left argument first */
+#define DUK_COMPARE_FLAG_NEGATE              (1 << 1)  /* negate result */
+
+/* conversions, coercions, comparison, etc */
+duk_bool_t duk_js_toboolean(duk_tval *tv);
+duk_double_t duk_js_tonumber(duk_hthread *thr, duk_tval *tv);
+duk_double_t duk_js_tointeger_number(duk_double_t x);
+duk_double_t duk_js_tointeger(duk_hthread *thr, duk_tval *tv);
+duk_uint32_t duk_js_touint32_number(duk_double_t x);
+duk_uint32_t duk_js_touint32(duk_hthread *thr, duk_tval *tv);
+duk_int32_t duk_js_toint32_number(duk_double_t x);
+duk_int32_t duk_js_toint32(duk_hthread *thr, duk_tval *tv);
+duk_uint16_t duk_js_touint16_number(duk_double_t x);
+duk_uint16_t duk_js_touint16(duk_hthread *thr, duk_tval *tv);
+duk_small_int_t duk_js_to_arrayindex_raw_string(duk_uint8_t *str, duk_uint32_t blen, duk_uarridx_t *out_idx);
+duk_uarridx_t duk_js_to_arrayindex_string_helper(duk_hstring *h);
+duk_bool_t duk_js_equals_helper(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y, duk_small_int_t flags);
+duk_small_int_t duk_js_string_compare(duk_hstring *h1, duk_hstring *h2);
+duk_bool_t duk_js_compare_helper(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y, duk_small_int_t flags);
+duk_bool_t duk_js_instanceof(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y);
+duk_bool_t duk_js_in(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y);
+duk_hstring *duk_js_typeof(duk_hthread *thr, duk_tval *tv_x);
+
+#define duk_js_equals(thr,tv_x,tv_y) \
+	duk_js_equals_helper((thr), (tv_x), (tv_y), 0)
+#define duk_js_strict_equals(tv_x,tv_y) \
+	duk_js_equals_helper(NULL, (tv_x), (tv_y), DUK_EQUALS_FLAG_STRICT)
+#define duk_js_samevalue(tv_x,tv_y) \
+	duk_js_equals_helper(NULL, (tv_x), (tv_y), DUK_EQUALS_FLAG_SAMEVALUE)
+
+/* E5 Sections 11.8.1, 11.8.5; x < y */
+#define duk_js_lessthan(thr,tv_x,tv_y) \
+	duk_js_compare_helper((thr), (tv_x), (tv_Y), DUK_COMPARE_FLAG_EVAL_LEFT_FIRST)
+
+/* E5 Sections 11.8.2, 11.8.5; x > y  -->  y < x */
+#define duk_js_greaterthan(thr,tv_x,tv_y) \
+	duk_js_compare_helper((thr), (tv_y), (tv_x), 0)
+
+/* E5 Sections 11.8.3, 11.8.5; x <= y  -->  not (x > y)  -->  not (y < x) */
+#define duk_js_lessthanorequal(thr,tv_x,tv_y) \
+	duk_js_compare_helper((thr), (tv_y), (tv_x), DUK_COMPARE_FLAG_NEGATE)
+
+/* E5 Sections 11.8.4, 11.8.5; x >= y  -->  not (x < y) */
+#define duk_js_greaterthanorequal(thr,tv_x,tv_y) \
+	duk_js_compare_helper((thr), (tv_x), (tv_y), DUK_COMPARE_FLAG_EVAL_LEFT_FIRST | DUK_COMPARE_FLAG_NEGATE)
+
+/* identifiers and environment handling */
+duk_bool_t duk_js_getvar_envrec(duk_hthread *thr, duk_hobject *env, duk_hstring *name, duk_bool_t throw_flag);
+duk_bool_t duk_js_getvar_activation(duk_hthread *thr, duk_activation *act, duk_hstring *name, duk_bool_t throw_flag);
+void duk_js_putvar_envrec(duk_hthread *thr, duk_hobject *env, duk_hstring *name, duk_tval *val, duk_bool_t strict);
+void duk_js_putvar_activation(duk_hthread *thr, duk_activation *act, duk_hstring *name, duk_tval *val, duk_bool_t strict);
+duk_bool_t duk_js_delvar_envrec(duk_hthread *thr, duk_hobject *env, duk_hstring *name);
+duk_bool_t duk_js_delvar_activation(duk_hthread *thr, duk_activation *act, duk_hstring *name);
+duk_bool_t duk_js_declvar_activation(duk_hthread *thr, duk_activation *act, duk_hstring *name, duk_tval *val, duk_small_int_t prop_flags, duk_bool_t is_func_decl);
+void duk_js_init_activation_environment_records_delayed(duk_hthread *thr, duk_activation *act);
+void duk_js_close_environment_record(duk_hthread *thr, duk_hobject *env, duk_hobject *func, duk_size_t regbase);
+duk_hobject *duk_create_activation_environment_record(duk_hthread *thr, duk_hobject *func, duk_size_t idx_bottom);
+void duk_js_push_closure(duk_hthread *thr,
+                         duk_hcompiledfunction *fun_temp,
+                         duk_hobject *outer_var_env,
+                         duk_hobject *outer_lex_env);
+
+/* call handling */
+duk_int_t duk_handle_call(duk_hthread *thr, duk_idx_t num_stack_args, duk_small_uint_t call_flags);
+duk_int_t duk_handle_safe_call(duk_hthread *thr, duk_safe_call_function func, duk_idx_t num_stack_args, duk_idx_t num_stack_res);
+void duk_handle_ecma_call_setup(duk_hthread *thr, duk_idx_t num_stack_args, duk_small_uint_t call_flags);
+
+/* bytecode execution */
+void duk_js_execute_bytecode(duk_hthread *entry_thread);
+
+#endif  /* DUK_JS_H_INCLUDED */
+#line 1 "duk_numconv.h"
+#ifndef DUK_NUMCONV_H_INCLUDED
+#define DUK_NUMCONV_H_INCLUDED
+
+/*
+ *  Number-to-string conversion.  The semantics of these is very tightly
+ *  bound with the Ecmascript semantics required for call sites.
+ */
+
+/* Output a specified number of digits instead of using the shortest
+ * form.  Used for toPrecision() and toFixed().
+ */
+#define DUK_N2S_FLAG_FIXED_FORMAT         (1 << 0)
+
+/* Force exponential format.  Used for toExponential(). */
+#define DUK_N2S_FLAG_FORCE_EXP            (1 << 1)
+
+/* If number would need zero padding (for whole number part), use
+ * exponential format instead.  E.g. if input number is 12300, 3
+ * digits are generated ("123"), output "1.23e+4" instead of "12300".
+ * Used for toPrecision().
+ */
+#define DUK_N2S_FLAG_NO_ZERO_PAD          (1 << 2)
+
+/* Digit count indicates number of fractions (i.e. an absolute
+ * digit index instead of a relative one).  Used together with
+ * DUK_N2S_FLAG_FIXED_FORMAT for toFixed().
+ */
+#define DUK_N2S_FLAG_FRACTION_DIGITS      (1 << 3)
+
+/*
+ *  String-to-number conversion
+ */
+
+/* Maximum exponent value when parsing numbers.  This is not strictly
+ * compliant as there should be no upper limit, but as we parse the
+ * exponent without a bigint, impose some limit.
+ */
+#define DUK_S2N_MAX_EXPONENT              1000000000
+
+/* Trim white space (= allow leading and trailing whitespace) */
+#define DUK_S2N_FLAG_TRIM_WHITE           (1 << 0)
+
+/* Allow exponent */
+#define DUK_S2N_FLAG_ALLOW_EXP            (1 << 1)
+
+/* Allow trailing garbage (e.g. treat "123foo" as "123) */
+#define DUK_S2N_FLAG_ALLOW_GARBAGE        (1 << 2)
+
+/* Allow leading plus sign */
+#define DUK_S2N_FLAG_ALLOW_PLUS           (1 << 3)
+
+/* Allow leading minus sign */
+#define DUK_S2N_FLAG_ALLOW_MINUS          (1 << 4)
+
+/* Allow 'Infinity' */
+#define DUK_S2N_FLAG_ALLOW_INF            (1 << 5)
+
+/* Allow fraction part */
+#define DUK_S2N_FLAG_ALLOW_FRAC           (1 << 6)
+
+/* Allow naked fraction (e.g. ".123") */
+#define DUK_S2N_FLAG_ALLOW_NAKED_FRAC     (1 << 7)
+
+/* Allow empty fraction (e.g. "123.") */
+#define DUK_S2N_FLAG_ALLOW_EMPTY_FRAC     (1 << 8)
+
+/* Allow empty string to be interpreted as 0 */
+#define DUK_S2N_FLAG_ALLOW_EMPTY_AS_ZERO  (1 << 9)
+
+/* Allow leading zeroes (e.g. "0123" -> "123") */
+#define DUK_S2N_FLAG_ALLOW_LEADING_ZERO   (1 << 10)
+
+/* Allow automatic detection of hex base ("0x" or "0X" prefix),
+ * overrides radix argument and forces integer mode.
+ */
+#define DUK_S2N_FLAG_ALLOW_AUTO_HEX_INT   (1 << 11)
+
+/* Allow automatic detection of octal base, overrides radix
+ * argument and forces integer mode.
+ */
+#define DUK_S2N_FLAG_ALLOW_AUTO_OCT_INT   (1 << 12)
+
+/*
+ *  Prototypes
+ */
+
+void duk_numconv_stringify(duk_context *ctx, duk_small_int_t radix, duk_small_int_t digits, duk_small_uint_t flags);
+void duk_numconv_parse(duk_context *ctx, duk_small_int_t radix, duk_small_uint_t flags);
+
+#endif  /* DUK_NUMCONV_H_INCLUDED */
+
+#line 1 "duk_bi_protos.h"
+/*
+ *  Prototypes for all built-in functions.
+ */
+
+#ifndef DUK_BUILTIN_PROTOS_H_INCLUDED
+#define DUK_BUILTIN_PROTOS_H_INCLUDED
+
+/* Buffer size needed for duk_bi_date_format_timeval().
+ * Accurate value is 32 + 1 for NUL termination:
+ *   >>> len('+123456-01-23T12:34:56.123+12:34')
+ *   32
+ * Include additional space to be safe.
+ */
+#define  DUK_BI_DATE_ISO8601_BUFSIZE  48
+
+/* Buffer size for "short log message" which use a heap-level pre-allocated
+ * dynamic buffer to reduce memory churn.
+ */
+#define  DUK_BI_LOGGER_SHORT_MSG_LIMIT  256
+
+/* Maximum length of CommonJS module identifier to resolve.  Length includes
+ * both current module ID, requested (possibly relative) module ID, and a
+ * slash in between.
+ */
+#define  DUK_BI_COMMONJS_MODULE_ID_LIMIT  256
+
+duk_ret_t duk_bi_array_constructor(duk_context *ctx);
+duk_ret_t duk_bi_array_constructor_is_array(duk_context *ctx);
+duk_ret_t duk_bi_array_prototype_to_string(duk_context *ctx);
+duk_ret_t duk_bi_array_prototype_concat(duk_context *ctx);
+duk_ret_t duk_bi_array_prototype_join_shared(duk_context *ctx);
+duk_ret_t duk_bi_array_prototype_pop(duk_context *ctx);
+duk_ret_t duk_bi_array_prototype_push(duk_context *ctx);
+duk_ret_t duk_bi_array_prototype_reverse(duk_context *ctx);
+duk_ret_t duk_bi_array_prototype_shift(duk_context *ctx);
+duk_ret_t duk_bi_array_prototype_slice(duk_context *ctx);
+duk_ret_t duk_bi_array_prototype_sort(duk_context *ctx);
+duk_ret_t duk_bi_array_prototype_splice(duk_context *ctx);
+duk_ret_t duk_bi_array_prototype_unshift(duk_context *ctx);
+duk_ret_t duk_bi_array_prototype_indexof_shared(duk_context *ctx);
+duk_ret_t duk_bi_array_prototype_iter_shared(duk_context *ctx);
+duk_ret_t duk_bi_array_prototype_reduce_shared(duk_context *ctx);
+
+duk_ret_t duk_bi_boolean_constructor(duk_context *ctx);
+duk_ret_t duk_bi_boolean_prototype_tostring_shared(duk_context *ctx);
+
+duk_ret_t duk_bi_buffer_constructor(duk_context *ctx);
+duk_ret_t duk_bi_buffer_prototype_tostring_shared(duk_context *ctx);
+
+duk_ret_t duk_bi_date_constructor(duk_context *ctx);
+duk_ret_t duk_bi_date_constructor_parse(duk_context *ctx);
+duk_ret_t duk_bi_date_constructor_utc(duk_context *ctx);
+duk_ret_t duk_bi_date_constructor_now(duk_context *ctx);
+duk_ret_t duk_bi_date_prototype_tostring_shared(duk_context *ctx);
+duk_ret_t duk_bi_date_prototype_value_of(duk_context *ctx);
+duk_ret_t duk_bi_date_prototype_to_json(duk_context *ctx);
+duk_ret_t duk_bi_date_prototype_get_shared(duk_context *ctx);
+duk_ret_t duk_bi_date_prototype_get_time(duk_context *ctx);
+duk_ret_t duk_bi_date_prototype_get_timezone_offset(duk_context *ctx);
+duk_ret_t duk_bi_date_prototype_set_shared(duk_context *ctx);
+duk_ret_t duk_bi_date_prototype_set_time(duk_context *ctx);
+/* Helpers exposed for internal use */
+duk_double_t duk_bi_date_get_now(duk_context *ctx);
+void duk_bi_date_format_timeval(duk_double_t timeval, duk_uint8_t *out_buf);
+
+duk_ret_t duk_bi_duktape_object_info(duk_context *ctx);
+duk_ret_t duk_bi_duktape_object_act(duk_context *ctx);
+duk_ret_t duk_bi_duktape_object_gc(duk_context *ctx);
+duk_ret_t duk_bi_duktape_object_fin(duk_context *ctx);
+duk_ret_t duk_bi_duktape_object_enc(duk_context *ctx);
+duk_ret_t duk_bi_duktape_object_dec(duk_context *ctx);
+duk_ret_t duk_bi_duktape_object_compact(duk_context *ctx);
+
+duk_ret_t duk_bi_error_constructor_shared(duk_context *ctx);
+duk_ret_t duk_bi_error_prototype_to_string(duk_context *ctx);
+duk_ret_t duk_bi_error_prototype_stack_getter(duk_context *ctx);
+duk_ret_t duk_bi_error_prototype_filename_getter(duk_context *ctx);
+duk_ret_t duk_bi_error_prototype_linenumber_getter(duk_context *ctx);
+duk_ret_t duk_bi_error_prototype_stack_getter(duk_context *ctx);
+duk_ret_t duk_bi_error_prototype_nop_setter(duk_context *ctx);
+
+duk_ret_t duk_bi_function_constructor(duk_context *ctx);
+duk_ret_t duk_bi_function_prototype(duk_context *ctx);
+duk_ret_t duk_bi_function_prototype_to_string(duk_context *ctx);
+duk_ret_t duk_bi_function_prototype_apply(duk_context *ctx);
+duk_ret_t duk_bi_function_prototype_call(duk_context *ctx);
+duk_ret_t duk_bi_function_prototype_bind(duk_context *ctx);
+
+duk_ret_t duk_bi_global_object_eval(duk_context *ctx);
+duk_ret_t duk_bi_global_object_parse_int(duk_context *ctx);
+duk_ret_t duk_bi_global_object_parse_float(duk_context *ctx);
+duk_ret_t duk_bi_global_object_is_nan(duk_context *ctx);
+duk_ret_t duk_bi_global_object_is_finite(duk_context *ctx);
+duk_ret_t duk_bi_global_object_decode_uri(duk_context *ctx);
+duk_ret_t duk_bi_global_object_decode_uri_component(duk_context *ctx);
+duk_ret_t duk_bi_global_object_encode_uri(duk_context *ctx);
+duk_ret_t duk_bi_global_object_encode_uri_component(duk_context *ctx);
+duk_ret_t duk_bi_global_object_escape(duk_context *ctx);
+duk_ret_t duk_bi_global_object_unescape(duk_context *ctx);
+duk_ret_t duk_bi_global_object_print(duk_context *ctx);
+duk_ret_t duk_bi_global_object_alert(duk_context *ctx);
+duk_ret_t duk_bi_global_object_require(duk_context *ctx);
+
+void duk_bi_json_parse_helper(duk_context *ctx,
+                              duk_idx_t idx_value,
+                              duk_idx_t idx_reviver,
+                              duk_small_uint_t flags);
+void duk_bi_json_stringify_helper(duk_context *ctx,
+                                  duk_idx_t idx_value,
+                                  duk_idx_t idx_replacer,
+                                  duk_idx_t idx_space,
+                                  duk_small_uint_t flags);
+duk_ret_t duk_bi_json_object_parse(duk_context *ctx);
+duk_ret_t duk_bi_json_object_stringify(duk_context *ctx);
+
+duk_ret_t duk_bi_math_object_onearg_shared(duk_context *ctx);
+duk_ret_t duk_bi_math_object_twoarg_shared(duk_context *ctx);
+duk_ret_t duk_bi_math_object_max(duk_context *ctx);
+duk_ret_t duk_bi_math_object_min(duk_context *ctx);
+duk_ret_t duk_bi_math_object_random(duk_context *ctx);
+
+duk_ret_t duk_bi_number_constructor(duk_context *ctx);
+duk_ret_t duk_bi_number_prototype_to_string(duk_context *ctx);
+duk_ret_t duk_bi_number_prototype_to_locale_string(duk_context *ctx);
+duk_ret_t duk_bi_number_prototype_value_of(duk_context *ctx);
+duk_ret_t duk_bi_number_prototype_to_fixed(duk_context *ctx);
+duk_ret_t duk_bi_number_prototype_to_exponential(duk_context *ctx);
+duk_ret_t duk_bi_number_prototype_to_precision(duk_context *ctx);
+
+duk_ret_t duk_bi_object_getprototype_shared(duk_context *ctx);
+duk_ret_t duk_bi_object_setprototype_shared(duk_context *ctx);
+duk_ret_t duk_bi_object_constructor(duk_context *ctx);
+duk_ret_t duk_bi_object_constructor_get_own_property_descriptor(duk_context *ctx);
+duk_ret_t duk_bi_object_constructor_create(duk_context *ctx);
+duk_ret_t duk_bi_object_constructor_define_property(duk_context *ctx);
+duk_ret_t duk_bi_object_constructor_define_properties(duk_context *ctx);
+duk_ret_t duk_bi_object_constructor_seal_freeze_shared(duk_context *ctx);
+duk_ret_t duk_bi_object_constructor_prevent_extensions(duk_context *ctx);
+duk_ret_t duk_bi_object_constructor_is_sealed_frozen_shared(duk_context *ctx);
+duk_ret_t duk_bi_object_constructor_is_extensible(duk_context *ctx);
+duk_ret_t duk_bi_object_constructor_keys_shared(duk_context *ctx);
+duk_ret_t duk_bi_object_prototype_to_string(duk_context *ctx);
+duk_ret_t duk_bi_object_prototype_to_locale_string(duk_context *ctx);
+duk_ret_t duk_bi_object_prototype_value_of(duk_context *ctx);
+duk_ret_t duk_bi_object_prototype_has_own_property(duk_context *ctx);
+duk_ret_t duk_bi_object_prototype_is_prototype_of(duk_context *ctx);
+duk_ret_t duk_bi_object_prototype_property_is_enumerable(duk_context *ctx);
+
+duk_ret_t duk_bi_pointer_constructor(duk_context *ctx);
+duk_ret_t duk_bi_pointer_prototype_tostring_shared(duk_context *ctx);
+
+duk_ret_t duk_bi_regexp_constructor(duk_context *ctx);
+duk_ret_t duk_bi_regexp_prototype_exec(duk_context *ctx);
+duk_ret_t duk_bi_regexp_prototype_test(duk_context *ctx);
+duk_ret_t duk_bi_regexp_prototype_to_string(duk_context *ctx);
+
+duk_ret_t duk_bi_string_constructor(duk_context *ctx);
+duk_ret_t duk_bi_string_constructor_from_char_code(duk_context *ctx);
+duk_ret_t duk_bi_string_prototype_to_string(duk_context *ctx);
+duk_ret_t duk_bi_string_prototype_value_of(duk_context *ctx);
+duk_ret_t duk_bi_string_prototype_char_at(duk_context *ctx);
+duk_ret_t duk_bi_string_prototype_char_code_at(duk_context *ctx);
+duk_ret_t duk_bi_string_prototype_concat(duk_context *ctx);
+duk_ret_t duk_bi_string_prototype_indexof_shared(duk_context *ctx);
+duk_ret_t duk_bi_string_prototype_locale_compare(duk_context *ctx);
+duk_ret_t duk_bi_string_prototype_match(duk_context *ctx);
+duk_ret_t duk_bi_string_prototype_replace(duk_context *ctx);
+duk_ret_t duk_bi_string_prototype_search(duk_context *ctx);
+duk_ret_t duk_bi_string_prototype_slice(duk_context *ctx);
+duk_ret_t duk_bi_string_prototype_split(duk_context *ctx);
+duk_ret_t duk_bi_string_prototype_substring(duk_context *ctx);
+duk_ret_t duk_bi_string_prototype_caseconv_shared(duk_context *ctx);
+duk_ret_t duk_bi_string_prototype_trim(duk_context *ctx);
+#ifdef DUK_USE_SECTION_B
+duk_ret_t duk_bi_string_prototype_substr(duk_context *ctx);
+#endif
+
+duk_ret_t duk_bi_proxy_constructor(duk_context *ctx);
+#if 0  /* unimplemented now */
+duk_ret_t duk_bi_proxy_constructor_revocable(duk_context *ctx);
+#endif
+
+duk_ret_t duk_bi_thread_constructor(duk_context *ctx);
+duk_ret_t duk_bi_thread_resume(duk_context *ctx);
+duk_ret_t duk_bi_thread_yield(duk_context *ctx);
+duk_ret_t duk_bi_thread_current(duk_context *ctx);
+
+duk_ret_t duk_bi_logger_constructor(duk_context *ctx);
+duk_ret_t duk_bi_logger_prototype_fmt(duk_context *ctx);
+duk_ret_t duk_bi_logger_prototype_raw(duk_context *ctx);
+duk_ret_t duk_bi_logger_prototype_log_shared(duk_context *ctx);
+
+duk_ret_t duk_bi_type_error_thrower(duk_context *ctx);
+
+#endif  /* DUK_BUILTIN_PROTOS_H_INCLUDED */
+#line 1 "duk_selftest.h"
+/*
+ *  Selftest code
+ */
+
+#ifndef DUK_SELFTEST_H_INCLUDED
+#define DUK_SELFTEST_H_INCLUDED
+
+void duk_selftest_run_tests(void);
+
+#endif  /* DUK_SELFTEST_H_INCLUDED */
+#line 75 "duk_internal.h"
+
+#endif  /* DUK_INTERNAL_H_INCLUDED */
+#line 1 "duk_alloc_default.c"
+/*
+ *  Default allocation functions.
+ *
+ *  Assumes behavior such as malloc allowing zero size, yielding
+ *  a NULL or a unique pointer which is a no-op for free.
+ */
+
+/* include removed: duk_internal.h */
+
+void *duk_default_alloc_function(void *udata, duk_size_t size) {
+	void *res;
+	DUK_UNREF(udata);
+	res = DUK_ANSI_MALLOC(size);
+	DUK_DDD(DUK_DDDPRINT("default alloc function: %lu -> %p",
+	                     (unsigned long) size, (void *) res));
+	return res;
+}
+
+void *duk_default_realloc_function(void *udata, void *ptr, duk_size_t newsize) {
+	void *res;
+	DUK_UNREF(udata);
+	res = DUK_ANSI_REALLOC(ptr, newsize);
+	DUK_DDD(DUK_DDDPRINT("default realloc function: %p %lu -> %p",
+	                     (void *) ptr, (unsigned long) newsize, (void *) res));
+	return res;
+}
+
+void duk_default_free_function(void *udata, void *ptr) {
+	DUK_DDD(DUK_DDDPRINT("default free function: %p", (void *) ptr));
+	DUK_UNREF(udata);
+	DUK_ANSI_FREE(ptr);
+}
+#line 1 "duk_alloc_torture.c"
+/*
+ *  Torture allocation functions.
+ *
+ *  Provides various debugging features:
+ *
+ *    - Wraps allocations with "buffer zones" which are checked on free
+ *    - Overwrites freed memory with garbage (not zero)
+ *    - Debug prints memory usage info after every alloc/realloc/free
+ *
+ *  Can be left out of a standard compilation.
+ */
+
+/* include removed: duk_internal.h */
+
+/* FIXME: unimplemented */
+
+void *duk_torture_alloc_function(void *udata, duk_size_t size) {
+	void *res;
+	DUK_UNREF(udata);
+	res = DUK_ANSI_MALLOC(size);
+	DUK_DDD(DUK_DDDPRINT("torture alloc function: %lu -> %p",
+	                     (unsigned long) size, (void *) res));
+	return res;
+}
+
+void *duk_torture_realloc_function(void *udata, void *ptr, duk_size_t newsize) {
+	void *res;
+	DUK_UNREF(udata);
+	res = DUK_ANSI_REALLOC(ptr, newsize);
+	DUK_DDD(DUK_DDDPRINT("torture realloc function: %p %lu -> %p",
+	                     (void *) ptr, (unsigned long) newsize, (void *) res));
+	return res;
+}
+
+void duk_torture_free_function(void *udata, void *ptr) {
+	DUK_DDD(DUK_DDDPRINT("torture free function: %p", (void *) ptr));
+	DUK_UNREF(udata);
+	DUK_ANSI_FREE(ptr);
+}
+#line 1 "duk_api.c"
+/*
+ *  API calls not falling into other categories.
+ *
+ *  Also contains internal functions (such as duk_get_tval()), defined
+ *  in duk_api_internal.h, with semantics similar to the public API.
+ */
+
+/* XXX: repetition of stack pre-checks -> helper or macro or inline */
+/* XXX: shared api error strings, and perhaps even throw code for rare cases? */
+
+/* include removed: duk_internal.h */
+
+/*
+ *  Global state for working around missing variadic macros
+ */
+
+#ifndef DUK_USE_VARIADIC_MACROS
+const char *duk_api_global_filename = NULL;
+duk_int_t duk_api_global_line = 0;
+#endif
+
+/*
+ *  Helpers
+ */
+
+static duk_int_t duk__api_coerce_d2i(duk_double_t d) {
+	duk_small_int_t c;
+
+	/*
+	 *  Special cases like NaN and +/- Infinity are handled explicitly
+	 *  because a plain C coercion from double to int handles these cases
+	 *  in undesirable ways.  For instance, NaN may coerce to INT_MIN
+	 *  (not zero), and INT_MAX + 1 may coerce to INT_MIN (not INT_MAX).
+	 *
+	 *  This double-to-int coercion differs from ToInteger() because it
+	 *  has a finite range (ToInteger() allows e.g. +/- Infinity).  It
+	 *  also differs from ToInt32() because the INT_MIN/INT_MAX clamping
+	 *  depends on the size of the int type on the platform.  In particular,
+	 *  on platforms with a 64-bit int type, the full range is allowed.
+	 */
+
+	c = (duk_small_int_t) DUK_FPCLASSIFY(d);
+	if (c == DUK_FP_NAN) {
+		return 0;
+	} else if (d < (duk_double_t) DUK_INT_MIN) {
+		/* covers -Infinity */
+		return DUK_INT_MIN;
+	} else if (d > (duk_double_t) DUK_INT_MAX) {
+		/* covers +Infinity */
+		return DUK_INT_MAX;
+	} else {
+		/* coerce towards zero */
+		return (duk_int_t) d;
+	}
+}
+
+static duk_uint_t duk__api_coerce_d2ui(duk_double_t d) {
+	duk_small_int_t c;
+
+	/* Same as above but for unsigned int range. */
+
+	c = (duk_small_int_t) DUK_FPCLASSIFY(d);
+	if (c == DUK_FP_NAN) {
+		return 0;
+	} else if (d < 0.0) {
+		/* covers -Infinity */
+		return (duk_uint_t) 0;
+	} else if (d > (duk_double_t) DUK_UINT_MAX) {
+		/* covers +Infinity */
+		return (duk_uint_t) DUK_UINT_MAX;
+	} else {
+		/* coerce towards zero */
+		return (duk_uint_t) d;
+	}
+}
+
+/*
+ *  Stack index validation/normalization and getting a stack duk_tval ptr.
+ *
+ *  These are called by many API entrypoints so the implementations must be
+ *  fast and "inlined".
+ *
+ *  There's some repetition because of this; keep the functions in sync.
+ */
+
+duk_idx_t duk_normalize_index(duk_context *ctx, duk_idx_t index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_idx_t vs_size;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(DUK_INVALID_INDEX < 0);
+
+	/* Care must be taken to avoid pointer wrapping in the index
+	 * validation.  For instance, on a 32-bit platform with 8-byte
+	 * duk_tval the index 0x20000000UL would wrap the memory space
+	 * once.
+	 */
+
+	/* Assume value stack sizes (in elements) fits into duk_idx_t. */
+	vs_size = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom);
+	DUK_ASSERT(vs_size >= 0);
+
+	if (index < 0) {
+		index = vs_size + index;
+		if (DUK_UNLIKELY(index < 0)) {
+			/* Also catches index == DUK_INVALID_INDEX: vs_size >= 0
+			 * so that vs_size + DUK_INVALID_INDEX cannot underflow
+			 * and will always be negative.
+			 */
+			return DUK_INVALID_INDEX;
+		}
+	} else {
+		/* since index non-negative */
+		DUK_ASSERT(index != DUK_INVALID_INDEX);
+
+		if (DUK_UNLIKELY(index >= vs_size)) {
+			return DUK_INVALID_INDEX;
+		}
+	}
+
+	DUK_ASSERT(index >= 0 && index < vs_size);
+	return index;
+}
+
+duk_idx_t duk_require_normalize_index(duk_context *ctx, duk_idx_t index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_idx_t vs_size;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(DUK_INVALID_INDEX < 0);
+
+	vs_size = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom);
+	DUK_ASSERT(vs_size >= 0);
+
+	if (index < 0) {
+		index = vs_size + index;
+		if (DUK_UNLIKELY(index < 0)) {
+			goto invalid_index;
+		}
+	} else {
+		DUK_ASSERT(index != DUK_INVALID_INDEX);
+		if (DUK_UNLIKELY(index >= vs_size)) {
+			goto invalid_index;
+		}
+	}
+
+	DUK_ASSERT(index >= 0 && index < vs_size);
+	return index;
+
+ invalid_index:
+	DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_INDEX);
+	return 0;  /* unreachable */
+}
+
+duk_tval *duk_get_tval(duk_context *ctx, duk_idx_t index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_idx_t vs_size;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(DUK_INVALID_INDEX < 0);
+
+	vs_size = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom);
+	DUK_ASSERT(vs_size >= 0);
+
+	if (index < 0) {
+		index = vs_size + index;
+		if (DUK_UNLIKELY(index < 0)) {
+			return NULL;
+		}
+	} else {
+		DUK_ASSERT(index != DUK_INVALID_INDEX);
+		if (DUK_UNLIKELY(index >= vs_size)) {
+			return NULL;
+		}
+	}
+
+	DUK_ASSERT(index >= 0 && index < vs_size);
+	return thr->valstack_bottom + index;
+}
+
+duk_tval *duk_require_tval(duk_context *ctx, duk_idx_t index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_idx_t vs_size;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(DUK_INVALID_INDEX < 0);
+
+	vs_size = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom);
+	DUK_ASSERT(vs_size >= 0);
+
+	if (index < 0) {
+		index = vs_size + index;
+		if (DUK_UNLIKELY(index < 0)) {
+			goto invalid_index;
+		}
+	} else {
+		DUK_ASSERT(index != DUK_INVALID_INDEX);
+		if (DUK_UNLIKELY(index >= vs_size)) {
+			goto invalid_index;
+		}
+	}
+
+	DUK_ASSERT(index >= 0 && index < vs_size);
+	return thr->valstack_bottom + index;
+
+ invalid_index:
+	DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_INDEX);
+	return NULL;
+}
+
+/* Non-critical. */
+duk_bool_t duk_is_valid_index(duk_context *ctx, duk_idx_t index) {
+	DUK_ASSERT(DUK_INVALID_INDEX < 0);
+	return (duk_normalize_index(ctx, index) >= 0);
+}
+
+/* Non-critical. */
+void duk_require_valid_index(duk_context *ctx, duk_idx_t index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(DUK_INVALID_INDEX < 0);
+
+	if (duk_normalize_index(ctx, index) < 0) {
+		DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_INDEX);
+	}
+}
+
+/*
+ *  Value stack top handling
+ */
+
+duk_idx_t duk_get_top(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+
+	DUK_ASSERT(ctx != NULL);
+
+	return (duk_idx_t) (thr->valstack_top - thr->valstack_bottom);
+}
+
+/* set stack top within currently allocated range, but don't reallocate */
+void duk_set_top(duk_context *ctx, duk_idx_t index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_idx_t vs_size;
+	duk_idx_t vs_limit;
+	duk_idx_t count;
+	duk_tval tv_tmp;
+	duk_tval *tv;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(DUK_INVALID_INDEX < 0);
+
+	vs_size = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom);
+	vs_limit = (duk_idx_t) (thr->valstack_end - thr->valstack_bottom);
+
+	if (index < 0) {
+		/* Negative indices are always within allocated stack but
+		 * must not go below zero index.
+		 */
+		index = vs_size + index;
+		if (index < 0) {
+			/* Also catches index == DUK_INVALID_INDEX. */
+			goto invalid_index;
+		}
+	} else {
+		/* Positive index can be higher than valstack top but must
+		 * not go above allocated stack (equality is OK).
+		 */
+		if (index > vs_limit) {
+			goto invalid_index;
+		}
+	}
+	DUK_ASSERT(index >= 0 && index <= vs_limit);
+
+	if (index >= vs_size) {
+		/* Stack size increases or stays the same.  Fill the new
+		 * entries (if any) with undefined.  No pointer stability
+		 * issues here so we can use a running pointer.
+		 */
+
+		tv = thr->valstack_top;
+		count = index - vs_size;
+		DUK_ASSERT(count >= 0);
+		while (count > 0) {
+			/* no need to decref previous or new value */
+			count--;
+			DUK_ASSERT(DUK_TVAL_IS_UNDEFINED_UNUSED(tv));
+			DUK_TVAL_SET_UNDEFINED_ACTUAL(tv);
+			tv++;
+		}
+		thr->valstack_top = tv;
+	} else {
+		/* Stack size decreases, DECREF entries which are above the
+		 * new top.  Each DECREF potentially invalidates valstack
+		 * pointers, so don't hold on to pointers.  The valstack top
+		 * must also be updated on every loop in case a GC is triggered.
+		 */
+
+		/* XXX: Here it would be useful to have a DECREF macro which
+		 * doesn't need a NULL check, and does refzero queueing without
+		 * running the refzero algorithm.  There would be no pointer
+		 * instability in this case, and code could be inlined.  After
+		 * the loop, one call to refzero would be needed.
+		 */
+
+		count = vs_size - index;
+		DUK_ASSERT(count > 0);
+
+		while (count > 0) {
+			count--;
+			tv = --thr->valstack_top;  /* tv -> value just before prev top value */
+			DUK_ASSERT(tv >= thr->valstack_bottom);
+			DUK_TVAL_SET_TVAL(&tv_tmp, tv);
+			DUK_TVAL_SET_UNDEFINED_UNUSED(tv);
+			DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+		}
+	}
+	return;
+
+ invalid_index:
+	DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_INDEX);
+}
+
+duk_idx_t duk_get_top_index(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_idx_t ret;
+
+	DUK_ASSERT(ctx != NULL);
+
+	ret = ((duk_idx_t) (thr->valstack_top - thr->valstack_bottom)) - 1;
+	if (DUK_UNLIKELY(ret < 0)) {
+		/* Return invalid index; if caller uses this without checking
+		 * in another API call, the index won't map to a valid stack
+		 * entry.
+		 */
+		return DUK_INVALID_INDEX;
+	}
+	return ret;
+}
+
+duk_idx_t duk_require_top_index(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_idx_t ret;
+
+	DUK_ASSERT(ctx != NULL);
+
+	ret = ((duk_idx_t) (thr->valstack_top - thr->valstack_bottom)) - 1;
+	if (DUK_UNLIKELY(ret < 0)) {
+		DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_INDEX);
+	}
+	return ret;
+}
+
+/*
+ *  Value stack resizing.
+ *
+ *  This resizing happens above the current "top": the value stack can be
+ *  grown or shrunk, but the "top" is not affected.  The value stack cannot
+ *  be resized to a size below the current "top".
+ *
+ *  The low level reallocation primitive must carefully recompute all value
+ *  stack pointers, and must also work if ALL pointers are NULL.  The resize
+ *  is quite tricky because the valstack realloc may cause a mark-and-sweep,
+ *  which may run finalizers.  Running finalizers may resize the valstack
+ *  recursively (the same value stack we're working on).  So, after realloc
+ *  returns, we know that the valstack "top" should still be the same (there
+ *  should not be live values above the "top"), but its underlying size and
+ *  pointer may have changed.
+ */
+
+/* XXX: perhaps refactor this to allow caller to specify some parameters, or
+ * at least a 'compact' flag which skips any spare or round-up .. useful for
+ * emergency gc.
+ */
+
+static duk_bool_t duk__resize_valstack(duk_context *ctx, duk_size_t new_size) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_ptrdiff_t old_bottom_offset;
+	duk_ptrdiff_t old_top_offset;
+	duk_ptrdiff_t old_end_offset_post;
+#ifdef DUK_USE_DEBUG
+	duk_ptrdiff_t old_end_offset_pre;
+	duk_tval *old_valstack_pre;
+	duk_tval *old_valstack_post;
+#endif
+	duk_tval *new_valstack;
+	duk_tval *p;
+	duk_size_t new_alloc_size;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->valstack_bottom >= thr->valstack);
+	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
+	DUK_ASSERT(thr->valstack_end >= thr->valstack_top);
+	DUK_ASSERT((duk_size_t) (thr->valstack_top - thr->valstack) <= new_size);  /* can't resize below 'top' */
+
+	/* get pointer offsets for tweaking below */
+	old_bottom_offset = (((duk_uint8_t *) thr->valstack_bottom) - ((duk_uint8_t *) thr->valstack));
+	old_top_offset = (((duk_uint8_t *) thr->valstack_top) - ((duk_uint8_t *) thr->valstack));
+#ifdef DUK_USE_DEBUG
+	old_end_offset_pre = (((duk_uint8_t *) thr->valstack_end) - ((duk_uint8_t *) thr->valstack));  /* not very useful, used for debugging */
+	old_valstack_pre = thr->valstack;
+#endif
+
+	/* Allocate a new valstack.
+	 *
+	 * Note: cannot use a plain DUK_REALLOC() because a mark-and-sweep may
+	 * invalidate the original thr->valstack base pointer inside the realloc
+	 * process.  See doc/memory-management.txt.
+	 */
+
+	new_alloc_size = sizeof(duk_tval) * new_size;  /* FIXME: wrap check */
+	new_valstack = (duk_tval *) DUK_REALLOC_INDIRECT(thr->heap, duk_hthread_get_valstack_ptr, (void *) thr, new_alloc_size);
+	if (!new_valstack) {
+		DUK_D(DUK_DPRINT("failed to resize valstack to %lu entries (%lu bytes)",
+		                 (unsigned long) new_size, (unsigned long) new_alloc_size));
+		return 0;
+	}
+
+	/* Note: the realloc may have triggered a mark-and-sweep which may
+	 * have resized our valstack internally.  However, the mark-and-sweep
+	 * MUST NOT leave the stack bottom/top in a different state.  Particular
+	 * assumptions and facts:
+	 *
+	 *   - The thr->valstack pointer may be different after realloc,
+	 *     and the offset between thr->valstack_end <-> thr->valstack
+	 *     may have changed.
+	 *   - The offset between thr->valstack_bottom <-> thr->valstack
+	 *     and thr->valstack_top <-> thr->valstack MUST NOT have changed,
+	 *     because mark-and-sweep must adhere to a strict stack policy.
+	 *     In other words, logical bottom and top MUST NOT have changed.
+	 *   - All values above the top are unreachable but are initialized
+	 *     to UNDEFINED_UNUSED, up to the post-realloc valstack_end.
+	 *   - 'old_end_offset' must be computed after realloc to be correct.
+	 */
+
+	DUK_ASSERT((((duk_uint8_t *) thr->valstack_bottom) - ((duk_uint8_t *) thr->valstack)) == old_bottom_offset);
+	DUK_ASSERT((((duk_uint8_t *) thr->valstack_top) - ((duk_uint8_t *) thr->valstack)) == old_top_offset);
+
+	/* success, fixup pointers */
+	old_end_offset_post = (((duk_uint8_t *) thr->valstack_end) - ((duk_uint8_t *) thr->valstack));  /* must be computed after realloc */
+#ifdef DUK_USE_DEBUG
+	old_valstack_post = thr->valstack;
+#endif
+	thr->valstack = new_valstack;
+	thr->valstack_end = new_valstack + new_size;
+	thr->valstack_bottom = (duk_tval *) ((duk_uint8_t *) new_valstack + old_bottom_offset);
+	thr->valstack_top = (duk_tval *) ((duk_uint8_t *) new_valstack + old_top_offset);
+
+	DUK_ASSERT(thr->valstack_bottom >= thr->valstack);
+	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
+	DUK_ASSERT(thr->valstack_end >= thr->valstack_top);
+
+	/* useful for debugging */
+#ifdef DUK_USE_DEBUG
+	if (old_end_offset_pre != old_end_offset_post) {
+		DUK_D(DUK_DPRINT("valstack was resized during valstack_resize(), probably by mark-and-sweep; "
+		                 "end offset changed: %lu -> %lu",
+		                 (unsigned long) old_end_offset_pre,
+		                 (unsigned long) old_end_offset_post));
+	}
+	if (old_valstack_pre != old_valstack_post) {
+		DUK_D(DUK_DPRINT("valstack pointer changed during valstack_resize(), probably by mark-and-sweep: %p -> %p",
+		                 (void *) old_valstack_pre,
+		                 (void *) old_valstack_post));
+	}
+#endif
+
+	DUK_DD(DUK_DDPRINT("resized valstack to %lu elements (%lu bytes), bottom=%ld, top=%ld, "
+	                   "new pointers: start=%p end=%p bottom=%p top=%p",
+	                   (unsigned long) new_size, (unsigned long) new_alloc_size,
+	                   (long) (thr->valstack_bottom - thr->valstack),
+	                   (long) (thr->valstack_top - thr->valstack),
+	                   (void *) thr->valstack, (void *) thr->valstack_end,
+	                   (void *) thr->valstack_bottom, (void *) thr->valstack_top));
+
+	/* init newly allocated slots (only) */
+	p = (duk_tval *) ((duk_uint8_t *) thr->valstack + old_end_offset_post);
+	while (p < thr->valstack_end) {
+		/* never executed if new size is smaller */
+		DUK_TVAL_SET_UNDEFINED_UNUSED(p);
+		p++;
+	}
+
+	/* assertion check: we maintain elements above top in known state */
+#ifdef DUK_USE_ASSERTIONS
+	p = thr->valstack_top;
+	while (p < thr->valstack_end) {
+		/* everything above old valstack top should be preinitialized now */
+		DUK_ASSERT(DUK_TVAL_IS_UNDEFINED_UNUSED(p));
+		p++;
+	}
+#endif
+	return 1;
+}
+
+static duk_bool_t duk__check_valstack_resize_helper(duk_context *ctx,
+                                                    duk_size_t min_new_size,
+                                                    duk_bool_t shrink_flag,
+                                                    duk_bool_t compact_flag,
+                                                    duk_bool_t throw_flag) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_size_t old_size;
+	duk_size_t new_size;
+	duk_bool_t is_shrink = 0;
+
+	DUK_DDD(DUK_DDDPRINT("check valstack resize: min_new_size=%lu, curr_size=%ld, curr_top=%ld, "
+	                     "curr_bottom=%ld, shrink=%ld, compact=%ld, throw=%ld",
+	                     (unsigned long) min_new_size,
+	                     (long) (thr->valstack_end - thr->valstack),
+	                     (long) (thr->valstack_top - thr->valstack),
+	                     (long) (thr->valstack_bottom - thr->valstack),
+	                     (long) shrink_flag, (long) compact_flag, (long) throw_flag));
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->valstack_bottom >= thr->valstack);
+	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
+	DUK_ASSERT(thr->valstack_end >= thr->valstack_top);
+
+	old_size = (duk_size_t) (thr->valstack_end - thr->valstack);
+
+	if (min_new_size <= old_size) {
+		is_shrink = 1;
+		if (!shrink_flag ||
+		    old_size - min_new_size < DUK_VALSTACK_SHRINK_THRESHOLD) {
+			DUK_DDD(DUK_DDDPRINT("no need to grow or shrink valstack"));
+			return 1;
+		}
+	}
+
+	new_size = min_new_size;
+	if (!compact_flag) {
+		if (is_shrink) {
+			/* shrink case; leave some spare */
+			new_size += DUK_VALSTACK_SHRINK_SPARE;
+		}
+
+		/* round up roughly to next 'grow step' */
+		new_size = (new_size / DUK_VALSTACK_GROW_STEP + 1) * DUK_VALSTACK_GROW_STEP;
+	}
+
+	DUK_DD(DUK_DDPRINT("want to %s valstack: %lu -> %lu elements (min_new_size %lu)",
+	                   (const char *) (new_size > old_size ? "grow" : "shrink"),
+	                   (unsigned long) old_size, (unsigned long) new_size,
+	                   (unsigned long) min_new_size));
+
+	if (new_size >= thr->valstack_max) {
+		/* Note: may be triggered even if minimal new_size would not reach the limit,
+		 * plan limit accordingly (taking DUK_VALSTACK_GROW_STEP into account.
+		 */
+		if (throw_flag) {
+			DUK_ERROR(thr, DUK_ERR_RANGE_ERROR, DUK_STR_VALSTACK_LIMIT);
+		} else {
+			return 0;
+		}
+	}
+
+	/*
+	 *  When resizing the valstack, a mark-and-sweep may be triggered for
+	 *  the allocation of the new valstack.  If the mark-and-sweep needs
+	 *  to use our thread for something, it may cause *the same valstack*
+	 *  to be resized recursively.  This happens e.g. when mark-and-sweep
+	 *  finalizers are called.
+	 *
+	 *  This is taken into account carefully in duk__resize_valstack().
+	 */
+
+	if (!duk__resize_valstack(ctx, new_size)) {
+		if (is_shrink) {
+			DUK_DD(DUK_DDPRINT("valstack resize failed, but is a shrink, ignore"));
+			return 1;
+		}
+
+		DUK_DD(DUK_DDPRINT("valstack resize failed"));
+
+		if (throw_flag) {
+			DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "failed to extend valstack");
+		} else {
+			return 0;
+		}
+	}
+
+	DUK_DDD(DUK_DDDPRINT("valstack resize successful"));
+	return 1;
+}
+
+#if 0  /* XXX: unused */
+duk_bool_t duk_check_valstack_resize(duk_context *ctx, duk_size_t min_new_size, duk_bool_t allow_shrink) {
+	return duk__check_valstack_resize_helper(ctx,
+	                                         min_new_size,  /* min_new_size */
+	                                         allow_shrink,  /* shrink_flag */
+	                                         0,             /* compact flag */
+	                                         0);            /* throw flag */
+}
+#endif
+
+void duk_require_valstack_resize(duk_context *ctx, duk_size_t min_new_size, duk_bool_t allow_shrink) {
+	(void) duk__check_valstack_resize_helper(ctx,
+	                                         min_new_size,  /* min_new_size */
+	                                         allow_shrink,  /* shrink_flag */
+	                                         0,             /* compact flag */
+	                                         1);            /* throw flag */
+}
+
+duk_bool_t duk_check_stack(duk_context *ctx, duk_idx_t extra) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_size_t min_new_size;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(thr != NULL);
+
+	if (DUK_UNLIKELY(extra < 0)) {
+		/* Clamping to zero makes the API more robust to calling code
+		 * calculation errors.
+		 */
+		extra = 0;
+	}
+
+	min_new_size = (thr->valstack_top - thr->valstack) + extra + DUK_VALSTACK_INTERNAL_EXTRA;
+	return duk__check_valstack_resize_helper(ctx,
+	                                         min_new_size,  /* min_new_size */
+	                                         0,             /* shrink_flag */
+	                                         0,             /* compact flag */
+	                                         0);            /* throw flag */
+}
+
+void duk_require_stack(duk_context *ctx, duk_idx_t extra) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_size_t min_new_size;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(thr != NULL);
+
+	if (DUK_UNLIKELY(extra < 0)) {
+		/* Clamping to zero makes the API more robust to calling code
+		 * calculation errors.
+		 */
+		extra = 0;
+	}
+
+	min_new_size = (thr->valstack_top - thr->valstack) + extra + DUK_VALSTACK_INTERNAL_EXTRA;
+	(void) duk__check_valstack_resize_helper(ctx,
+	                                         min_new_size,  /* min_new_size */
+	                                         0,             /* shrink_flag */
+	                                         0,             /* compact flag */
+	                                         1);            /* throw flag */
+}
+
+duk_bool_t duk_check_stack_top(duk_context *ctx, duk_idx_t top) {
+	duk_size_t min_new_size;
+
+	DUK_ASSERT(ctx != NULL);
+
+	if (DUK_UNLIKELY(top < 0)) {
+		/* Clamping to zero makes the API more robust to calling code
+		 * calculation errors.
+		 */
+		top = 0;
+	}
+
+	min_new_size = top + DUK_VALSTACK_INTERNAL_EXTRA;
+	return duk__check_valstack_resize_helper(ctx,
+	                                         min_new_size,  /* min_new_size */
+	                                         0,             /* shrink_flag */
+	                                         0,             /* compact flag */
+	                                         0);            /* throw flag */
+}
+
+void duk_require_stack_top(duk_context *ctx, duk_idx_t top) {
+	duk_size_t min_new_size;
+
+	DUK_ASSERT(ctx != NULL);
+
+	if (DUK_UNLIKELY(top < 0)) {
+		/* Clamping to zero makes the API more robust to calling code
+		 * calculation errors.
+		 */
+		top = 0;
+	}
+
+	min_new_size = top + DUK_VALSTACK_INTERNAL_EXTRA;
+	(void) duk__check_valstack_resize_helper(ctx,
+	                                         min_new_size,  /* min_new_size */
+	                                         0,             /* shrink_flag */
+	                                         0,             /* compact flag */
+	                                         1);            /* throw flag */
+}
+
+/*
+ *  Basic stack manipulation: swap, dup, insert, replace, etc
+ */
+
+void duk_swap(duk_context *ctx, duk_idx_t index1, duk_idx_t index2) {
+	duk_tval *tv1;
+	duk_tval *tv2;
+	duk_tval tv_tmp;
+
+	DUK_ASSERT(ctx != NULL);
+
+	tv1 = duk_require_tval(ctx, index1);
+	DUK_ASSERT(tv1 != NULL);
+	tv2 = duk_require_tval(ctx, index2);
+	DUK_ASSERT(tv2 != NULL);
+
+	/* If tv1==tv2 this is a NOP, no check is needed */
+	DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
+	DUK_TVAL_SET_TVAL(tv1, tv2);
+	DUK_TVAL_SET_TVAL(tv2, &tv_tmp);
+}
+
+void duk_swap_top(duk_context *ctx, duk_idx_t index) {
+	DUK_ASSERT(ctx != NULL);
+
+	duk_swap(ctx, index, -1);
+}
+
+void duk_dup(duk_context *ctx, duk_idx_t from_index) {
+	duk_tval *tv;
+
+	DUK_ASSERT(ctx != NULL);
+
+	tv = duk_require_tval(ctx, from_index);
+	DUK_ASSERT(tv != NULL);
+
+	duk_push_tval(ctx, tv);
+}
+
+void duk_dup_top(duk_context *ctx) {
+	DUK_ASSERT(ctx != NULL);
+
+	duk_dup(ctx, -1);
+}
+
+void duk_insert(duk_context *ctx, duk_idx_t to_index) {
+	duk_tval *p;
+	duk_tval *q;
+	duk_tval tv_tmp;
+	duk_size_t nbytes;
+
+	DUK_ASSERT(ctx != NULL);
+
+	p = duk_require_tval(ctx, to_index);
+	DUK_ASSERT(p != NULL);
+	q = duk_require_tval(ctx, -1);
+	DUK_ASSERT(q != NULL);
+
+	DUK_ASSERT(q >= p);
+
+	/*              nbytes
+	 *           <--------->
+	 *    [ ... | p | x | x | q ]
+	 * => [ ... | q | p | x | x ]
+	 */
+
+	nbytes = (duk_size_t) (((duk_uint8_t *) q) - ((duk_uint8_t *) p));  /* Note: 'q' is top-1 */
+
+	DUK_DDD(DUK_DDDPRINT("duk_insert: to_index=%ld, p=%p, q=%p, nbytes=%lu",
+	                     (long) to_index, (void *) p, (void *) q, (unsigned long) nbytes));
+
+	/* No net refcount changes. */
+
+	if (nbytes > 0) {
+		DUK_TVAL_SET_TVAL(&tv_tmp, q);
+		DUK_ASSERT(nbytes > 0);
+		DUK_MEMMOVE((void *) (p + 1), (void *) p, nbytes);
+		DUK_TVAL_SET_TVAL(p, &tv_tmp);
+	} else {
+		/* nop: insert top to top */
+		DUK_ASSERT(nbytes == 0);
+		DUK_ASSERT(p == q);
+	}
+}
+
+void duk_replace(duk_context *ctx, duk_idx_t to_index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv1;
+	duk_tval *tv2;
+	duk_tval tv_tmp;
+
+	DUK_ASSERT(ctx != NULL);
+
+	tv1 = duk_require_tval(ctx, -1);
+	DUK_ASSERT(tv1 != NULL);
+	tv2 = duk_require_tval(ctx, to_index);
+	DUK_ASSERT(tv2 != NULL);
+
+	/* For tv1 == tv2, both pointing to stack top, the end result
+	 * is same as duk_pop(ctx).
+	 */
+
+	DUK_TVAL_SET_TVAL(&tv_tmp, tv2);
+	DUK_TVAL_SET_TVAL(tv2, tv1);
+	DUK_TVAL_SET_UNDEFINED_UNUSED(tv1);
+	thr->valstack_top--;
+	DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+}
+
+void duk_copy(duk_context *ctx, duk_idx_t from_index, duk_idx_t to_index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv1;
+	duk_tval *tv2;
+	duk_tval tv_tmp;
+
+	DUK_ASSERT(ctx != NULL);
+
+	tv1 = duk_require_tval(ctx, from_index);
+	DUK_ASSERT(tv1 != NULL);
+	tv2 = duk_require_tval(ctx, to_index);
+	DUK_ASSERT(tv2 != NULL);
+
+	/* For tv1 == tv2, this is a no-op (no explicit check needed). */
+
+	DUK_TVAL_SET_TVAL(&tv_tmp, tv2);
+	DUK_TVAL_SET_TVAL(tv2, tv1);
+	DUK_TVAL_INCREF(thr, tv2);  /* no side effects */
+	DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+}
+
+void duk_remove(duk_context *ctx, duk_idx_t index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *p;
+	duk_tval *q;
+#ifdef DUK_USE_REFERENCE_COUNTING
+	duk_tval tv_tmp;
+#endif
+	duk_size_t nbytes;
+
+	DUK_ASSERT(ctx != NULL);
+
+	p = duk_require_tval(ctx, index);
+	DUK_ASSERT(p != NULL);
+	q = duk_require_tval(ctx, -1);
+	DUK_ASSERT(q != NULL);
+
+	DUK_ASSERT(q >= p);
+
+	/*              nbytes            zero size case
+	 *           <--------->
+	 *    [ ... | p | x | x | q ]     [ ... | p==q ]
+	 * => [ ... | x | x | q ]         [ ... ]
+	 */
+
+#ifdef DUK_USE_REFERENCE_COUNTING
+	/* use a temp: decref only when valstack reachable values are correct */
+	DUK_TVAL_SET_TVAL(&tv_tmp, p);
+#endif
+
+	nbytes = (duk_size_t) (((duk_uint8_t *) q) - ((duk_uint8_t *) p));  /* Note: 'q' is top-1 */
+	DUK_MEMMOVE(p, p + 1, nbytes);  /* zero size not an issue: pointers are valid */
+
+	DUK_TVAL_SET_UNDEFINED_UNUSED(q);
+	thr->valstack_top--;
+
+#ifdef DUK_USE_REFERENCE_COUNTING
+	DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+#endif
+}
+
+/*
+ *  Stack slice primitives
+ */
+
+void duk_xmove(duk_context *ctx, duk_context *from_ctx, duk_idx_t count) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hthread *from_thr = (duk_hthread *) from_ctx;
+	void *src;
+	duk_size_t nbytes;
+	duk_tval *p;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(from_ctx != NULL);
+
+	if (count < 0) {
+		DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_COUNT);
+		return;
+	}
+
+	nbytes = sizeof(duk_tval) * count;  /* FIXME: wrap check */
+	if (nbytes == 0) {
+		return;
+	}
+	DUK_ASSERT(thr->valstack_top <= thr->valstack_end);
+	if ((duk_size_t) ((duk_uint8_t *) thr->valstack_end - (duk_uint8_t *) thr->valstack_top) < nbytes) {
+		DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_PUSH_BEYOND_ALLOC_STACK);
+	}
+	src = (void *) ((duk_uint8_t *) from_thr->valstack_top - nbytes);
+	if (src < (void *) from_thr->valstack_bottom) {
+		DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_SRC_STACK_NOT_ENOUGH);
+	}
+
+	/* copy values (no overlap even if ctx == from_ctx) */
+	DUK_ASSERT(nbytes > 0);
+	DUK_MEMCPY((void *) thr->valstack_top, src, nbytes);
+
+	/* incref them */
+	p = thr->valstack_top;
+	thr->valstack_top = (duk_tval *) (((duk_uint8_t *) thr->valstack_top) + nbytes);
+	while (p < thr->valstack_top) {
+		DUK_TVAL_INCREF(thr, p);  /* no side effects */
+		p++;
+	}
+}
+
+/*
+ *  Get/require
+ */
+
+void duk_require_undefined(duk_context *ctx, duk_idx_t index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv;
+
+	DUK_ASSERT(ctx != NULL);
+
+	tv = duk_get_tval(ctx, index);
+	if (tv && DUK_TVAL_IS_UNDEFINED(tv)) {
+		/* Note: accept both 'actual' and 'unused' undefined */
+		return;
+	}
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_UNDEFINED);
+}
+
+void duk_require_null(duk_context *ctx, duk_idx_t index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv;
+
+	DUK_ASSERT(ctx != NULL);
+
+	tv = duk_get_tval(ctx, index);
+	if (tv && DUK_TVAL_IS_NULL(tv)) {
+		return;
+	}
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_NULL);
+	return;  /* not reachable */
+}
+
+duk_bool_t duk_get_boolean(duk_context *ctx, duk_idx_t index) {
+	duk_bool_t ret = 0;  /* default: false */
+	duk_tval *tv;
+
+	DUK_ASSERT(ctx != NULL);
+
+	tv = duk_get_tval(ctx, index);
+	if (tv && DUK_TVAL_IS_BOOLEAN(tv)) {
+		ret = DUK_TVAL_GET_BOOLEAN(tv);
+	}
+
+	DUK_ASSERT(ret == 0 || ret == 1);
+	return ret;
+}
+
+duk_bool_t duk_require_boolean(duk_context *ctx, duk_idx_t index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv;
+
+	DUK_ASSERT(ctx != NULL);
+
+	tv = duk_get_tval(ctx, index);
+	if (tv && DUK_TVAL_IS_BOOLEAN(tv)) {
+		duk_bool_t ret = DUK_TVAL_GET_BOOLEAN(tv);
+		DUK_ASSERT(ret == 0 || ret == 1);
+		return ret;
+	}
+
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_BOOLEAN);
+	return 0;  /* not reachable */
+}
+
+duk_double_t duk_get_number(duk_context *ctx, duk_idx_t index) {
+	duk_double_union ret;
+	duk_tval *tv;
+
+	DUK_ASSERT(ctx != NULL);
+
+	ret.d = DUK_DOUBLE_NAN;  /* default: NaN */
+	tv = duk_get_tval(ctx, index);
+	if (tv && DUK_TVAL_IS_NUMBER(tv)) {
+		ret.d = DUK_TVAL_GET_NUMBER(tv);
+	}
+
+	/*
+	 *  Number should already be in NaN-normalized form, but let's
+	 *  normalize anyway.
+	 */
+
+	DUK_DBLUNION_NORMALIZE_NAN_CHECK(&ret);
+	return ret.d;
+}
+
+duk_double_t duk_require_number(duk_context *ctx, duk_idx_t index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv;
+
+	DUK_ASSERT(ctx != NULL);
+
+	tv = duk_get_tval(ctx, index);
+	if (tv && DUK_TVAL_IS_NUMBER(tv)) {
+		duk_double_union ret;
+		ret.d = DUK_TVAL_GET_NUMBER(tv);
+
+		/*
+		 *  Number should already be in NaN-normalized form,
+		 *  but let's normalize anyway.
+		 */
+
+		DUK_DBLUNION_NORMALIZE_NAN_CHECK(&ret);
+		return ret.d;
+	}
+
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_NUMBER);
+	return DUK_DOUBLE_NAN;  /* not reachable */
+}
+
+duk_int_t duk_get_int(duk_context *ctx, duk_idx_t index) {
+	/* Custom coercion for API */
+	return (duk_int_t) duk__api_coerce_d2i(duk_get_number(ctx, index));
+}
+
+duk_uint_t duk_get_uint(duk_context *ctx, duk_idx_t index) {
+	/* Custom coercion for API */
+	return (duk_uint_t) duk__api_coerce_d2ui(duk_get_number(ctx, index));
+}
+
+duk_int_t duk_require_int(duk_context *ctx, duk_idx_t index) {
+	/* Custom coercion for API */
+	return (duk_int_t) duk__api_coerce_d2i(duk_require_number(ctx, index));
+}
+
+duk_uint_t duk_require_uint(duk_context *ctx, duk_idx_t index) {
+	/* Custom coercion for API */
+	return (duk_uint_t) duk__api_coerce_d2ui(duk_require_number(ctx, index));
+}
+
+const char *duk_get_lstring(duk_context *ctx, duk_idx_t index, duk_size_t *out_len) {
+	const char *ret;
+	duk_tval *tv;
+
+	DUK_ASSERT(ctx != NULL);
+
+	/* default: NULL, length 0 */
+	ret = NULL;
+	if (out_len) {
+		*out_len = 0;
+	}
+
+	tv = duk_get_tval(ctx, index);
+	if (tv && DUK_TVAL_IS_STRING(tv)) {
+		/* Here we rely on duk_hstring instances always being zero
+		 * terminated even if the actual string is not.
+		 */
+		duk_hstring *h = DUK_TVAL_GET_STRING(tv);
+		DUK_ASSERT(h != NULL);
+		ret = (const char *) DUK_HSTRING_GET_DATA(h);
+		if (out_len) {
+			*out_len = DUK_HSTRING_GET_BYTELEN(h);
+		}
+	}
+
+	return ret;
+}
+
+const char *duk_require_lstring(duk_context *ctx, duk_idx_t index, duk_size_t *out_len) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	const char *ret;
+
+	DUK_ASSERT(ctx != NULL);
+
+	/* Note: this check relies on the fact that even a zero-size string
+	 * has a non-NULL pointer.
+	 */
+	ret = duk_get_lstring(ctx, index, out_len);
+	if (ret) {
+		return ret;
+	}
+
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_STRING);
+	return NULL;  /* not reachable */
+}
+
+const char *duk_get_string(duk_context *ctx, duk_idx_t index) {
+	DUK_ASSERT(ctx != NULL);
+
+	return duk_get_lstring(ctx, index, NULL);
+}
+
+const char *duk_require_string(duk_context *ctx, duk_idx_t index) {
+	DUK_ASSERT(ctx != NULL);
+
+	return duk_require_lstring(ctx, index, NULL);
+}
+
+void *duk_get_pointer(duk_context *ctx, duk_idx_t index) {
+	duk_tval *tv;
+
+	DUK_ASSERT(ctx != NULL);
+
+	tv = duk_get_tval(ctx, index);
+	if (tv && DUK_TVAL_IS_POINTER(tv)) {
+		void *p = DUK_TVAL_GET_POINTER(tv);  /* may be NULL */
+		return (void *) p;
+	}
+
+	return NULL;
+}
+
+void *duk_require_pointer(duk_context *ctx, duk_idx_t index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv;
+
+	DUK_ASSERT(ctx != NULL);
+
+	/* Note: here we must be wary of the fact that a pointer may be
+	 * valid and be a NULL.
+	 */
+	tv = duk_get_tval(ctx, index);
+	if (tv && DUK_TVAL_IS_POINTER(tv)) {
+		void *p = DUK_TVAL_GET_POINTER(tv);  /* may be NULL */
+		return (void *) p;
+	}
+
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_POINTER);
+	return NULL;  /* not reachable */
+}
+
+/* XXX: unused */
+void *duk_get_voidptr(duk_context *ctx, duk_idx_t index) {
+	duk_tval *tv;
+
+	DUK_ASSERT(ctx != NULL);
+
+	tv = duk_get_tval(ctx, index);
+	if (tv && DUK_TVAL_IS_HEAP_ALLOCATED(tv)) {
+		duk_heaphdr *h = DUK_TVAL_GET_HEAPHDR(tv);
+		DUK_ASSERT(h != NULL);
+		return (void *) h;
+	}
+
+	return NULL;
+}
+
+void *duk_get_buffer(duk_context *ctx, duk_idx_t index, duk_size_t *out_size) {
+	duk_tval *tv;
+
+	DUK_ASSERT(ctx != NULL);
+
+	if (out_size != NULL) {
+		*out_size = 0;
+	}
+
+	tv = duk_get_tval(ctx, index);
+	if (tv && DUK_TVAL_IS_BUFFER(tv)) {
+		duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv);
+		DUK_ASSERT(h != NULL);
+		if (out_size) {
+			*out_size = DUK_HBUFFER_GET_SIZE(h);
+		}
+		return (void *) DUK_HBUFFER_GET_DATA_PTR(h);  /* may be NULL (but only if size is 0) */
+	}
+
+	return NULL;
+}
+
+void *duk_require_buffer(duk_context *ctx, duk_idx_t index, duk_size_t *out_size) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv;
+
+	DUK_ASSERT(ctx != NULL);
+
+	if (out_size != NULL) {
+		*out_size = 0;
+	}
+
+	/* Note: here we must be wary of the fact that a data pointer may
+	 * be a NULL for a zero-size buffer.
+	 */
+	
+	tv = duk_get_tval(ctx, index);
+	if (tv && DUK_TVAL_IS_BUFFER(tv)) {
+		duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv);
+		DUK_ASSERT(h != NULL);
+		if (out_size) {
+			*out_size = DUK_HBUFFER_GET_SIZE(h);
+		}
+		return (void *) DUK_HBUFFER_GET_DATA_PTR(h);  /* may be NULL (but only if size is 0) */
+	}
+
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_BUFFER);
+	return NULL;  /* not reachable */
+}
+
+/* Raw helper for getting a value from the stack, checking its tag, and possible its object class.
+ * The tag cannot be a number because numbers don't have an internal tag in the packed representation.
+ */
+duk_heaphdr *duk_get_tagged_heaphdr_raw(duk_context *ctx, duk_idx_t index, duk_uint_t flags_and_tag) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv;
+	duk_small_uint_t tag = flags_and_tag & 0xffffU;  /* tags can be up to 16 bits */
+
+	DUK_ASSERT(ctx != NULL);
+
+	tv = duk_get_tval(ctx, index);
+	if (tv && (DUK_TVAL_GET_TAG(tv) == tag)) {
+		duk_heaphdr *ret;
+
+		/* Note: tag comparison in general doesn't work for numbers,
+		 * but it does work for everything else (heap objects here).
+		 */
+		ret = DUK_TVAL_GET_HEAPHDR(tv);
+		DUK_ASSERT(ret != NULL);  /* tagged null pointers should never occur */
+
+		/* If class check has been requested, tag must also be DUK_TAG_OBJECT.
+		 * This allows us to just check the class check flag without checking
+		 * the tag also.
+		 */
+		DUK_ASSERT((flags_and_tag & DUK_GETTAGGED_FLAG_CHECK_CLASS) == 0 ||
+		           tag == DUK_TAG_OBJECT);
+
+		if ((flags_and_tag & DUK_GETTAGGED_FLAG_CHECK_CLASS) == 0 ||  /* no class check */
+		    (duk_int_t) DUK_HOBJECT_GET_CLASS_NUMBER((duk_hobject *) ret) ==  /* or class check matches */
+		        (duk_int_t) ((flags_and_tag >> DUK_GETTAGGED_CLASS_SHIFT) & 0xff)) {
+			return ret;
+		}
+	}
+
+	if (flags_and_tag & DUK_GETTAGGED_FLAG_ALLOW_NULL) {
+		return (duk_heaphdr *) NULL;
+	}
+
+	/* Formatting the tag number here is not very useful: the tag value
+	 * is Duktape internal (not the same as DUK_TYPE_xxx) and even depends
+	 * on the duk_tval layout.  If anything, add a human readable type here.
+	 */
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_UNEXPECTED_TYPE);
+	return NULL;  /* not reachable */
+}
+
+duk_hstring *duk_get_hstring(duk_context *ctx, duk_idx_t index) {
+	return (duk_hstring *) duk_get_tagged_heaphdr_raw(ctx, index, DUK_TAG_STRING | DUK_GETTAGGED_FLAG_ALLOW_NULL);
+}
+
+duk_hstring *duk_require_hstring(duk_context *ctx, duk_idx_t index) {
+	return (duk_hstring *) duk_get_tagged_heaphdr_raw(ctx, index, DUK_TAG_STRING);
+}
+
+duk_hobject *duk_get_hobject(duk_context *ctx, duk_idx_t index) {
+	return (duk_hobject *) duk_get_tagged_heaphdr_raw(ctx, index, DUK_TAG_OBJECT | DUK_GETTAGGED_FLAG_ALLOW_NULL);
+}
+
+duk_hobject *duk_require_hobject(duk_context *ctx, duk_idx_t index) {
+	return (duk_hobject *) duk_get_tagged_heaphdr_raw(ctx, index, DUK_TAG_OBJECT);
+}
+
+duk_hbuffer *duk_get_hbuffer(duk_context *ctx, duk_idx_t index) {
+	return (duk_hbuffer *) duk_get_tagged_heaphdr_raw(ctx, index, DUK_TAG_BUFFER | DUK_GETTAGGED_FLAG_ALLOW_NULL);
+}
+
+duk_hbuffer *duk_require_hbuffer(duk_context *ctx, duk_idx_t index) {
+	return (duk_hbuffer *) duk_get_tagged_heaphdr_raw(ctx, index, DUK_TAG_BUFFER);
+}
+
+duk_hthread *duk_get_hthread(duk_context *ctx, duk_idx_t index) {
+	duk_hobject *h = (duk_hobject *) duk_get_tagged_heaphdr_raw(ctx, index, DUK_TAG_OBJECT | DUK_GETTAGGED_FLAG_ALLOW_NULL);
+	if (h != NULL && !DUK_HOBJECT_IS_THREAD(h)) {
+		h = NULL;
+	}
+	return (duk_hthread *) h;
+}
+
+duk_hthread *duk_require_hthread(duk_context *ctx, duk_idx_t index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hobject *h = (duk_hobject *) duk_get_tagged_heaphdr_raw(ctx, index, DUK_TAG_OBJECT);
+	DUK_ASSERT(h != NULL);
+	if (!DUK_HOBJECT_IS_THREAD(h)) {
+		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_THREAD);
+	}
+	return (duk_hthread *) h;
+}
+
+duk_hcompiledfunction *duk_get_hcompiledfunction(duk_context *ctx, duk_idx_t index) {
+	duk_hobject *h = (duk_hobject *) duk_get_tagged_heaphdr_raw(ctx, index, DUK_TAG_OBJECT | DUK_GETTAGGED_FLAG_ALLOW_NULL);
+	if (h != NULL && !DUK_HOBJECT_IS_COMPILEDFUNCTION(h)) {
+		h = NULL;
+	}
+	return (duk_hcompiledfunction *) h;
+}
+
+duk_hcompiledfunction *duk_require_hcompiledfunction(duk_context *ctx, duk_idx_t index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hobject *h = (duk_hobject *) duk_get_tagged_heaphdr_raw(ctx, index, DUK_TAG_OBJECT);
+	DUK_ASSERT(h != NULL);
+	if (!DUK_HOBJECT_IS_COMPILEDFUNCTION(h)) {
+		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_COMPILEDFUNCTION);
+	}
+	return (duk_hcompiledfunction *) h;
+}
+
+duk_hnativefunction *duk_get_hnativefunction(duk_context *ctx, duk_idx_t index) {
+	duk_hobject *h = (duk_hobject *) duk_get_tagged_heaphdr_raw(ctx, index, DUK_TAG_OBJECT | DUK_GETTAGGED_FLAG_ALLOW_NULL);
+	if (h != NULL && !DUK_HOBJECT_IS_NATIVEFUNCTION(h)) {
+		h = NULL;
+	}
+	return (duk_hnativefunction *) h;
+}
+
+duk_hnativefunction *duk_require_hnativefunction(duk_context *ctx, duk_idx_t index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hobject *h = (duk_hobject *) duk_get_tagged_heaphdr_raw(ctx, index, DUK_TAG_OBJECT);
+	DUK_ASSERT(h != NULL);
+	if (!DUK_HOBJECT_IS_NATIVEFUNCTION(h)) {
+		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_NATIVEFUNCTION);
+	}
+	return (duk_hnativefunction *) h;
+}
+
+duk_c_function duk_get_c_function(duk_context *ctx, duk_idx_t index) {
+	duk_tval *tv;
+	duk_hobject *h;
+	duk_hnativefunction *f;
+
+	DUK_ASSERT(ctx != NULL);
+
+	tv = duk_get_tval(ctx, index);
+	if (!tv) {
+		return NULL;
+	}
+	if (!DUK_TVAL_IS_OBJECT(tv)) {
+		return NULL;
+	}
+	h = DUK_TVAL_GET_OBJECT(tv);
+	DUK_ASSERT(h != NULL);
+	
+	if (!DUK_HOBJECT_IS_NATIVEFUNCTION(h)) {
+		return NULL;
+	}
+	DUK_ASSERT(DUK_HOBJECT_HAS_NATIVEFUNCTION(h));
+	f = (duk_hnativefunction *) h;
+
+	return f->func;
+}
+
+duk_c_function duk_require_c_function(duk_context *ctx, duk_idx_t index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_c_function ret;
+
+	ret = duk_get_c_function(ctx, index);
+	if (!ret) {
+		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_C_FUNCTION);
+	}
+	return ret;
+}
+
+duk_context *duk_get_context(duk_context *ctx, duk_idx_t index) {
+	return (duk_context *) duk_get_hthread(ctx, index);
+}
+
+duk_context *duk_require_context(duk_context *ctx, duk_idx_t index) {
+	return (duk_context *) duk_require_hthread(ctx, index);
+}
+
+duk_size_t duk_get_length(duk_context *ctx, duk_idx_t index) {
+	duk_tval *tv;
+
+	DUK_ASSERT(ctx != NULL);
+
+	tv = duk_get_tval(ctx, index);
+	if (!tv) {
+		return 0;
+	}
+
+	switch (DUK_TVAL_GET_TAG(tv)) {
+	case DUK_TAG_UNDEFINED:
+	case DUK_TAG_NULL:
+	case DUK_TAG_BOOLEAN:
+	case DUK_TAG_POINTER:
+		return 0;
+	case DUK_TAG_STRING: {
+		duk_hstring *h = DUK_TVAL_GET_STRING(tv);
+		DUK_ASSERT(h != NULL);
+		return (duk_size_t) DUK_HSTRING_GET_CHARLEN(h);
+	}
+	case DUK_TAG_OBJECT: {
+		duk_hobject *h = DUK_TVAL_GET_OBJECT(tv);
+		DUK_ASSERT(h != NULL);
+		return (duk_size_t) duk_hobject_get_length((duk_hthread *) ctx, h);
+	}
+	case DUK_TAG_BUFFER: {
+		duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv);
+		DUK_ASSERT(h != NULL);
+		return (duk_size_t) DUK_HBUFFER_GET_SIZE(h);
+	}
+	default:
+		/* number */
+		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
+		return 0;
+	}
+
+	DUK_UNREACHABLE();
+}
+
+void duk_set_length(duk_context *ctx, duk_idx_t index, duk_size_t length) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hobject *h;
+
+	DUK_ASSERT(ctx != NULL);
+
+	h = duk_get_hobject(ctx, index);
+	if (!h) {
+		return;
+	}
+
+	duk_hobject_set_length(thr, h, (duk_uint32_t) length);  /* XXX: typing */
+}
+
+/*
+ *  Conversions and coercions
+ *
+ *  The conversion/coercions are in-place operations on the value stack.
+ *  Some operations are implemented here directly, while others call a
+ *  helper in duk_js_ops.c after validating arguments.
+ */
+
+/* E5 Section 8.12.8 */
+
+static duk_bool_t duk__defaultvalue_coerce_attempt(duk_context *ctx, duk_idx_t index, duk_small_int_t func_stridx) {
+	if (duk_get_prop_stridx(ctx, index, func_stridx)) {
+		/* [ ... func ] */
+		if (duk_is_callable(ctx, -1)) {
+			duk_dup(ctx, index);         /* -> [ ... func this ] */
+			duk_call_method(ctx, 0);     /* -> [ ... retval ] */
+			if (duk_is_primitive(ctx, -1)) {
+				duk_replace(ctx, index);
+				return 1;
+			}
+			/* [ ... retval ]; popped below */
+		}
+	}
+	duk_pop(ctx);  /* [ ... func/retval ] -> [ ... ] */
+	return 0;
+}
+
+void duk_to_defaultvalue(duk_context *ctx, duk_idx_t index, duk_int_t hint) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hobject *obj;
+	/* inline initializer for coercers[] is not allowed by old compilers like BCC */
+	duk_small_int_t coercers[2];
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(thr != NULL);
+
+	coercers[0] = DUK_STRIDX_VALUE_OF;
+	coercers[1] = DUK_STRIDX_TO_STRING;
+
+	index = duk_require_normalize_index(ctx, index);
+
+	if (!duk_is_object(ctx, index)) {
+		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_OBJECT);
+	}
+	obj = duk_get_hobject(ctx, index);
+	DUK_ASSERT(obj != NULL);
+
+	if (hint == DUK_HINT_NONE) {
+		if (DUK_HOBJECT_GET_CLASS_NUMBER(obj) == DUK_HOBJECT_CLASS_DATE) {
+			hint = DUK_HINT_STRING;
+		} else {
+			hint = DUK_HINT_NUMBER;
+		}
+	}
+
+	if (hint == DUK_HINT_STRING) {
+		coercers[0] = DUK_STRIDX_TO_STRING;
+		coercers[1] = DUK_STRIDX_VALUE_OF;
+	}
+
+	if (duk__defaultvalue_coerce_attempt(ctx, index, coercers[0])) {
+		return;
+	}
+
+	if (duk__defaultvalue_coerce_attempt(ctx, index, coercers[1])) {
+		return;
+	}
+
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_DEFAULTVALUE_COERCE_FAILED);
+}
+
+void duk_to_undefined(duk_context *ctx, duk_idx_t index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv;
+	duk_tval tv_tmp;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_UNREF(thr);
+
+	tv = duk_require_tval(ctx, index);
+	DUK_ASSERT(tv != NULL);
+	DUK_TVAL_SET_TVAL(&tv_tmp, tv);
+	DUK_TVAL_SET_UNDEFINED_ACTUAL(tv);  /* no need to incref */
+	DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+}
+
+void duk_to_null(duk_context *ctx, duk_idx_t index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv;
+	duk_tval tv_tmp;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_UNREF(thr);
+
+	tv = duk_require_tval(ctx, index);
+	DUK_ASSERT(tv != NULL);
+	DUK_TVAL_SET_TVAL(&tv_tmp, tv);
+	DUK_TVAL_SET_NULL(tv);  /* no need to incref */
+	DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+}
+
+/* E5 Section 9.1 */
+void duk_to_primitive(duk_context *ctx, duk_idx_t index, duk_int_t hint) {
+	duk_tval *tv;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(hint == DUK_HINT_NONE || hint == DUK_HINT_NUMBER || hint == DUK_HINT_STRING);
+
+	index = duk_require_normalize_index(ctx, index);
+
+	tv = duk_require_tval(ctx, index);
+	DUK_ASSERT(tv != NULL);
+
+	if (DUK_TVAL_GET_TAG(tv) != DUK_TAG_OBJECT) {
+		/* everything except object stay as is */
+		return;
+	}
+	DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
+
+	duk_to_defaultvalue(ctx, index, hint);
+}
+
+/* E5 Section 9.2 */
+duk_bool_t duk_to_boolean(duk_context *ctx, duk_idx_t index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv;
+	duk_tval tv_tmp;
+	duk_bool_t val;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_UNREF(thr);
+
+	index = duk_require_normalize_index(ctx, index);
+
+	tv = duk_require_tval(ctx, index);
+	DUK_ASSERT(tv != NULL);
+
+	val = duk_js_toboolean(tv);
+	DUK_ASSERT(val == 0 || val == 1);
+
+	/* Note: no need to re-lookup tv, conversion is side effect free */
+	DUK_ASSERT(tv != NULL);
+	DUK_TVAL_SET_TVAL(&tv_tmp, tv);
+	DUK_TVAL_SET_BOOLEAN(tv, val);  /* no need to incref */
+	DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+	return val;
+}
+
+duk_double_t duk_to_number(duk_context *ctx, duk_idx_t index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv;
+	duk_tval tv_tmp;
+	duk_double_t d;
+
+	DUK_ASSERT(ctx != NULL);
+
+	tv = duk_require_tval(ctx, index);
+	DUK_ASSERT(tv != NULL);
+	d = duk_js_tonumber(thr, tv);
+
+	/* Note: need to re-lookup because ToNumber() may have side effects */
+	tv = duk_require_tval(ctx, index);
+	DUK_TVAL_SET_TVAL(&tv_tmp, tv);
+	DUK_TVAL_SET_NUMBER(tv, d);  /* no need to incref */
+	DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+	return d;
+}
+
+/* XXX: combine all the integer conversions: they share everything
+ * but the helper function for coercion.
+ */
+
+typedef duk_double_t (*duk__toint_coercer)(duk_hthread *thr, duk_tval *tv);
+
+duk_double_t duk__to_int_uint_helper(duk_context *ctx, duk_idx_t index, duk__toint_coercer coerce_func) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv;
+	duk_tval tv_tmp;
+	duk_double_t d;
+
+	DUK_ASSERT(ctx != NULL);
+
+	tv = duk_require_tval(ctx, index);
+	DUK_ASSERT(tv != NULL);
+	d = coerce_func(thr, tv);
+
+	/* Relookup in case coerce_func() has side effects, e.g. ends up coercing an object */
+	tv = duk_require_tval(ctx, index);
+	DUK_TVAL_SET_TVAL(&tv_tmp, tv);
+	DUK_TVAL_SET_NUMBER(tv, d);  /* no need to incref */
+	DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+	return d;
+}
+
+duk_int_t duk_to_int(duk_context *ctx, duk_idx_t index) {
+	/* Value coercion (in stack): ToInteger(), E5 Section 9.4
+	 * API return value coercion: custom
+	 */
+	return (duk_int_t) duk__api_coerce_d2i(duk__to_int_uint_helper(ctx, index, duk_js_tointeger));
+}
+
+duk_uint_t duk_to_uint(duk_context *ctx, duk_idx_t index) {
+	/* Value coercion (in stack): ToInteger(), E5 Section 9.4
+	 * API return value coercion: custom
+	 */
+	return (duk_uint_t) duk__api_coerce_d2ui(duk__to_int_uint_helper(ctx, index, duk_js_tointeger));
+}
+
+duk_int32_t duk_to_int32(duk_context *ctx, duk_idx_t index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv;
+	duk_tval tv_tmp;
+	duk_int32_t ret;
+
+	DUK_ASSERT(ctx != NULL);
+
+	tv = duk_require_tval(ctx, index);
+	DUK_ASSERT(tv != NULL);
+	ret = duk_js_toint32(thr, tv);
+
+	/* XXX: avoid double coercion with fastints */
+	/* Relookup in case coerce_func() has side effects, e.g. ends up coercing an object */
+	tv = duk_require_tval(ctx, index);
+	DUK_TVAL_SET_TVAL(&tv_tmp, tv);
+	DUK_TVAL_SET_NUMBER(tv, (duk_double_t) ret);  /* no need to incref */
+	DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+	return ret;
+}
+
+duk_uint32_t duk_to_uint32(duk_context *ctx, duk_idx_t index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv;
+	duk_tval tv_tmp;
+	duk_uint32_t ret;
+
+	DUK_ASSERT(ctx != NULL);
+
+	tv = duk_require_tval(ctx, index);
+	DUK_ASSERT(tv != NULL);
+	ret = duk_js_touint32(thr, tv);
+
+	/* XXX: avoid double coercion with fastints */
+	/* Relookup in case coerce_func() has side effects, e.g. ends up coercing an object */
+	tv = duk_require_tval(ctx, index);
+	DUK_TVAL_SET_TVAL(&tv_tmp, tv);
+	DUK_TVAL_SET_NUMBER(tv, (duk_double_t) ret);  /* no need to incref */
+	DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+	return ret;
+}
+
+duk_uint16_t duk_to_uint16(duk_context *ctx, duk_idx_t index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv;
+	duk_tval tv_tmp;
+	duk_uint16_t ret;
+
+	DUK_ASSERT(ctx != NULL);
+
+	tv = duk_require_tval(ctx, index);
+	DUK_ASSERT(tv != NULL);
+	ret = duk_js_touint16(thr, tv);
+
+	/* XXX: avoid double coercion with fastints */
+	/* Relookup in case coerce_func() has side effects, e.g. ends up coercing an object */
+	tv = duk_require_tval(ctx, index);
+	DUK_TVAL_SET_TVAL(&tv_tmp, tv);
+	DUK_TVAL_SET_NUMBER(tv, (duk_double_t) ret);  /* no need to incref */
+	DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+	return ret;
+}
+
+const char *duk_to_lstring(duk_context *ctx, duk_idx_t index, duk_size_t *out_len) {
+	(void) duk_to_string(ctx, index);
+	return duk_require_lstring(ctx, index, out_len);
+}
+
+static duk_ret_t duk__safe_to_string_raw(duk_context *ctx) {
+	duk_to_string(ctx, -1);
+	return 1;
+}
+
+const char *duk_safe_to_lstring(duk_context *ctx, duk_idx_t index, duk_size_t *out_len) {
+	index = duk_require_normalize_index(ctx, index);
+
+	/* We intentionally ignore the duk_safe_call() return value and only
+	 * check the output type.  This way we don't also need to check that
+	 * the returned value is indeed a string in the success case.
+	 */
+
+	duk_dup(ctx, index);
+	(void) duk_safe_call(ctx, duk__safe_to_string_raw, 1 /*nargs*/, 1 /*nrets*/);
+	if (!duk_is_string(ctx, -1)) {
+		/* Error: try coercing error to string once. */
+		(void) duk_safe_call(ctx, duk__safe_to_string_raw, 1 /*nargs*/, 1 /*nrets*/);
+		if (!duk_is_string(ctx, -1)) {
+			/* Double error */
+			duk_pop(ctx);
+			duk_push_hstring_stridx(ctx, DUK_STRIDX_UC_ERROR);
+		} else {
+			;
+		}
+	} else {
+		;
+	}
+	DUK_ASSERT(duk_is_string(ctx, -1));
+
+	duk_replace(ctx, index);
+	return duk_require_lstring(ctx, index, out_len);
+}
+
+/* XXX: other variants like uint, u32 etc */
+duk_int_t duk_to_int_clamped_raw(duk_context *ctx, duk_idx_t index, duk_int_t minval, duk_int_t maxval, duk_bool_t *out_clamped) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv;
+	duk_tval tv_tmp;
+	duk_double_t d;
+	duk_bool_t clamped = 0;
+
+	DUK_ASSERT(ctx != NULL);
+
+	tv = duk_require_tval(ctx, index);
+	DUK_ASSERT(tv != NULL);
+	d = duk_js_tointeger(thr, tv);  /* E5 Section 9.4, ToInteger() */
+
+	if (d < (duk_double_t) minval) {
+		clamped = 1;
+		d = (duk_double_t) minval;
+	} else if (d > (duk_double_t) maxval) {
+		clamped = 1;
+		d = (duk_double_t) maxval;
+	}
+
+	/* relookup in case duk_js_tointeger() ends up e.g. coercing an object */
+	tv = duk_require_tval(ctx, index);
+	DUK_TVAL_SET_TVAL(&tv_tmp, tv);
+	DUK_TVAL_SET_NUMBER(tv, d);  /* no need to incref */
+	DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+
+	if (out_clamped) {
+		*out_clamped = clamped;
+	} else {
+		/* coerced value is updated to value stack even when RangeError thrown */
+		if (clamped) {
+			DUK_ERROR(thr, DUK_ERR_RANGE_ERROR, DUK_STR_NUMBER_OUTSIDE_RANGE);
+		}
+	}
+
+	return (duk_int_t) d;
+}
+
+duk_int_t duk_to_int_clamped(duk_context *ctx, duk_idx_t index, duk_idx_t minval, duk_idx_t maxval) {
+	duk_bool_t dummy;
+	return duk_to_int_clamped_raw(ctx, index, minval, maxval, &dummy);
+}
+
+duk_int_t duk_to_int_check_range(duk_context *ctx, duk_idx_t index, duk_int_t minval, duk_int_t maxval) {
+	return duk_to_int_clamped_raw(ctx, index, minval, maxval, NULL);  /* out_clamped==NULL -> RangeError if outside range */
+}
+
+const char *duk_to_string(duk_context *ctx, duk_idx_t index) {
+	duk_tval *tv;
+
+	DUK_ASSERT(ctx != NULL);
+
+	index = duk_require_normalize_index(ctx, index);
+
+	tv = duk_require_tval(ctx, index);
+	DUK_ASSERT(tv != NULL);
+
+	switch (DUK_TVAL_GET_TAG(tv)) {
+	case DUK_TAG_UNDEFINED: {
+		duk_push_hstring_stridx(ctx, DUK_STRIDX_LC_UNDEFINED);
+		break;
+	}
+	case DUK_TAG_NULL: {
+		duk_push_hstring_stridx(ctx, DUK_STRIDX_LC_NULL);
+		break;
+	}
+	case DUK_TAG_BOOLEAN: {
+		if (DUK_TVAL_GET_BOOLEAN(tv)) {
+			duk_push_hstring_stridx(ctx, DUK_STRIDX_TRUE);
+		} else {
+			duk_push_hstring_stridx(ctx, DUK_STRIDX_FALSE);
+		}
+		break;
+	}
+	case DUK_TAG_STRING: {
+		/* nop */
+		goto skip_replace;
+	}
+	case DUK_TAG_OBJECT: {
+		duk_to_primitive(ctx, index, DUK_HINT_STRING);
+		return duk_to_string(ctx, index);  /* Note: recursive call */
+	}
+	case DUK_TAG_BUFFER: {
+		duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv);
+
+		/* Note: this allows creation of internal strings. */
+
+		DUK_ASSERT(h != NULL);
+		duk_push_lstring(ctx,
+		                 (const char *) DUK_HBUFFER_GET_DATA_PTR(h),
+		                 (duk_size_t) DUK_HBUFFER_GET_SIZE(h));
+		break;
+	}
+	case DUK_TAG_POINTER: {
+		void *ptr = DUK_TVAL_GET_POINTER(tv);
+		if (ptr != NULL) {
+			duk_push_sprintf(ctx, DUK_STR_FMT_PTR, (void *) ptr);
+		} else {
+			/* Represent a null pointer as 'null' to be consistent with
+			 * the JX format variant.  Native '%p' format for a NULL
+			 * pointer may be e.g. '(nil)'.
+			 */
+			duk_push_hstring_stridx(ctx, DUK_STRIDX_LC_NULL);
+		}
+		break;
+	}
+	default: {
+		/* number */
+		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
+		duk_push_tval(ctx, tv);
+		duk_numconv_stringify(ctx,
+		                      10 /*radix*/,
+		                      0 /*precision:shortest*/,
+		                      0 /*force_exponential*/);
+		break;
+	}
+	}
+
+	duk_replace(ctx, index);
+
+ skip_replace:
+	return duk_require_string(ctx, index);
+}
+
+duk_hstring *duk_to_hstring(duk_context *ctx, duk_idx_t index) {
+	duk_hstring *ret;
+	DUK_ASSERT(ctx != NULL);
+	duk_to_string(ctx, index);
+	ret = duk_get_hstring(ctx, index);
+	DUK_ASSERT(ret != NULL);
+	return ret;
+}
+
+static void *duk__to_buffer_raw(duk_context *ctx, duk_idx_t index, duk_size_t *out_size, duk_small_int_t buf_dynamic, duk_small_int_t buf_dontcare) {
+	duk_hbuffer *h_buf;
+	const duk_uint8_t *src_data;
+	duk_size_t src_size;
+	duk_uint8_t *dst_data;
+
+	index = duk_require_normalize_index(ctx, index);
+
+	h_buf = duk_get_hbuffer(ctx, index);
+	if (h_buf != NULL) {
+		/* Buffer is kept as is: note that fixed/dynamic nature of
+		 * the buffer is not changed.
+		 */
+		duk_small_int_t tmp;
+
+		src_data = (const duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(h_buf);
+		src_size = DUK_HBUFFER_GET_SIZE(h_buf);
+
+		tmp = (DUK_HBUFFER_HAS_DYNAMIC(h_buf) ? 1 : 0);
+		if (((tmp ^ buf_dynamic) == 0) || buf_dontcare) {
+			/* Note: src_data may be NULL if input is a zero-size
+			 * dynamic buffer.
+			 */
+			dst_data = (duk_uint8_t *) src_data;
+			goto skip_copy;
+		}
+	} else {
+		/* Non-buffer value is first ToString() coerced, then converted
+		 * to a fixed size buffer.
+		 */
+
+		src_data = (const duk_uint8_t *) duk_to_lstring(ctx, index, &src_size);
+	}
+
+	dst_data = duk_push_buffer(ctx, src_size, buf_dynamic);
+	if (DUK_LIKELY(src_size > 0)) {
+		/* When src_size == 0, src_data may be NULL (if source
+		 * buffer is dynamic), and dst_data may be NULL (if
+		 * target buffer is dynamic).  Avoid zero-size memcpy()
+		 * with an invalid pointer.
+		 */
+		DUK_MEMCPY(dst_data, src_data, src_size);
+	}
+	duk_replace(ctx, index);
+ skip_copy:
+
+	if (out_size) {
+		*out_size = src_size;
+	}
+	return dst_data;
+}
+
+void *duk_to_buffer(duk_context *ctx, duk_idx_t index, duk_size_t *out_size) {
+	return duk__to_buffer_raw(ctx, index, out_size, 0 /*buf_dynamic*/, 1 /*buf_dontcare*/);
+}
+
+void *duk_to_fixed_buffer(duk_context *ctx, duk_idx_t index, duk_size_t *out_size) {
+	return duk__to_buffer_raw(ctx, index, out_size, 0 /*buf_dynamic*/, 0 /*buf_dontcare*/);
+}
+
+void *duk_to_dynamic_buffer(duk_context *ctx, duk_idx_t index, duk_size_t *out_size) {
+	return duk__to_buffer_raw(ctx, index, out_size, 1 /*buf_dynamic*/, 0 /*buf_dontcare*/);
+}
+
+void *duk_to_pointer(duk_context *ctx, duk_idx_t index) {
+	duk_tval *tv;
+	void *res;
+
+	DUK_ASSERT(ctx != NULL);
+
+	index = duk_require_normalize_index(ctx, index);
+
+	tv = duk_require_tval(ctx, index);
+	DUK_ASSERT(tv != NULL);
+
+	switch (DUK_TVAL_GET_TAG(tv)) {
+	case DUK_TAG_UNDEFINED:
+	case DUK_TAG_NULL:
+	case DUK_TAG_BOOLEAN:
+		res = NULL;
+		break;
+	case DUK_TAG_POINTER:
+		res = DUK_TVAL_GET_POINTER(tv);
+		break;
+	case DUK_TAG_STRING:
+	case DUK_TAG_OBJECT:
+	case DUK_TAG_BUFFER:
+		/* Heap allocated: return heap pointer which is NOT useful
+		 * for the caller, except for debugging.
+		 */
+		res = (void *) DUK_TVAL_GET_HEAPHDR(tv);
+		break;
+	default:
+		/* number */
+		res = NULL;
+		break;
+	}
+
+	duk_push_pointer(ctx, res);
+	duk_replace(ctx, index);
+	return res;
+}
+
+void duk_to_object(duk_context *ctx, duk_idx_t index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv;
+	duk_uint_t shared_flags = 0;   /* shared flags for a subset of types */
+	duk_small_int_t shared_proto = 0;
+
+	DUK_ASSERT(ctx != NULL);
+
+	index = duk_require_normalize_index(ctx, index);
+
+	tv = duk_require_tval(ctx, index);
+	DUK_ASSERT(tv != NULL);
+
+	switch (DUK_TVAL_GET_TAG(tv)) {
+	case DUK_TAG_UNDEFINED:
+	case DUK_TAG_NULL: {
+		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_OBJECT_COERCIBLE);
+		break;
+	}
+	case DUK_TAG_BOOLEAN: {
+		shared_flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
+		               DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_BOOLEAN);
+		shared_proto = DUK_BIDX_BOOLEAN_PROTOTYPE;
+		goto create_object;
+	}
+	case DUK_TAG_STRING: {
+		shared_flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
+		               DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ |
+		               DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_STRING);
+		shared_proto = DUK_BIDX_STRING_PROTOTYPE;
+		goto create_object;
+	}
+	case DUK_TAG_OBJECT: {
+		/* nop */
+		break;
+	}
+	case DUK_TAG_BUFFER: {
+		shared_flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
+		               DUK_HOBJECT_FLAG_EXOTIC_BUFFEROBJ |
+		               DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_BUFFER);
+		shared_proto = DUK_BIDX_BUFFER_PROTOTYPE;
+		goto create_object;
+	}
+	case DUK_TAG_POINTER: {
+		shared_flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
+		               DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_POINTER);
+		shared_proto = DUK_BIDX_POINTER_PROTOTYPE;
+		goto create_object;
+	}
+	default: {
+		shared_flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
+		               DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_NUMBER);
+		shared_proto = DUK_BIDX_NUMBER_PROTOTYPE;
+		goto create_object;
+	}
+	}
+	return;
+
+ create_object:
+	(void) duk_push_object_helper(ctx, shared_flags, shared_proto);
+
+	/* Note: Boolean prototype's internal value property is not writable,
+	 * but duk_def_prop_stridx() disregards the write protection.  Boolean
+	 * instances are immutable.
+	 *
+	 * String and buffer special behaviors are already enabled which is not
+	 * ideal, but a write to the internal value is not affected by them.
+	 */
+	duk_dup(ctx, index);
+	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_NONE);
+
+	duk_replace(ctx, index);
+}
+
+/*
+ *  Type checking
+ */
+
+static duk_bool_t duk__tag_check(duk_context *ctx, duk_idx_t index, duk_small_uint_t tag) {
+	duk_tval *tv;
+
+	tv = duk_get_tval(ctx, index);
+	if (!tv) {
+		return 0;
+	}
+	return (DUK_TVAL_GET_TAG(tv) == tag);
+}
+
+static duk_bool_t duk__obj_flag_any_default_false(duk_context *ctx, duk_idx_t index, duk_uint_t flag_mask) {
+	duk_hobject *obj;
+
+	DUK_ASSERT(ctx != NULL);
+
+	obj = duk_get_hobject(ctx, index);
+	if (obj) {
+		return (DUK_HEAPHDR_CHECK_FLAG_BITS((duk_heaphdr *) obj, flag_mask) ? 1 : 0);
+	}
+	return 0;
+}
+
+duk_int_t duk_get_type(duk_context *ctx, duk_idx_t index) {
+	duk_tval *tv;
+
+	tv = duk_get_tval(ctx, index);
+	if (!tv) {
+		return DUK_TYPE_NONE;
+	}
+	switch (DUK_TVAL_GET_TAG(tv)) {
+	case DUK_TAG_UNDEFINED:
+		return DUK_TYPE_UNDEFINED;
+	case DUK_TAG_NULL:
+		return DUK_TYPE_NULL;
+	case DUK_TAG_BOOLEAN:
+		return DUK_TYPE_BOOLEAN;
+	case DUK_TAG_STRING:
+		return DUK_TYPE_STRING;
+	case DUK_TAG_OBJECT:
+		return DUK_TYPE_OBJECT;
+	case DUK_TAG_BUFFER:
+		return DUK_TYPE_BUFFER;
+	case DUK_TAG_POINTER:
+		return DUK_TYPE_POINTER;
+	default:
+		/* Note: number has no explicit tag (in 8-byte representation) */
+		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
+		return DUK_TYPE_NUMBER;
+	}
+	DUK_UNREACHABLE();
+}
+
+duk_bool_t duk_check_type(duk_context *ctx, duk_idx_t index, duk_int_t type) {
+	return (duk_get_type(ctx, index) == type) ? 1 : 0;
+}
+
+duk_uint_t duk_get_type_mask(duk_context *ctx, duk_idx_t index) {
+	duk_tval *tv;
+
+	tv = duk_get_tval(ctx, index);
+	if (!tv) {
+		return DUK_TYPE_MASK_NONE;
+	}
+	switch (DUK_TVAL_GET_TAG(tv)) {
+	case DUK_TAG_UNDEFINED:
+		return DUK_TYPE_MASK_UNDEFINED;
+	case DUK_TAG_NULL:
+		return DUK_TYPE_MASK_NULL;
+	case DUK_TAG_BOOLEAN:
+		return DUK_TYPE_MASK_BOOLEAN;
+	case DUK_TAG_STRING:
+		return DUK_TYPE_MASK_STRING;
+	case DUK_TAG_OBJECT:
+		return DUK_TYPE_MASK_OBJECT;
+	case DUK_TAG_BUFFER:
+		return DUK_TYPE_MASK_BUFFER;
+	case DUK_TAG_POINTER:
+		return DUK_TYPE_MASK_POINTER;
+	default:
+		/* Note: number has no explicit tag (in 8-byte representation) */
+		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
+		return DUK_TYPE_MASK_NUMBER;
+	}
+	DUK_UNREACHABLE();
+}
+
+duk_bool_t duk_check_type_mask(duk_context *ctx, duk_idx_t index, duk_uint_t mask) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	DUK_ASSERT(ctx != NULL);
+	if (duk_get_type_mask(ctx, index) & mask) {
+		return 1;
+	}
+	if (mask & DUK_TYPE_MASK_THROW) {
+		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_UNEXPECTED_TYPE);
+		DUK_UNREACHABLE();
+	}
+	return 0;
+}
+
+duk_bool_t duk_is_undefined(duk_context *ctx, duk_idx_t index) {
+	DUK_ASSERT(ctx != NULL);
+	return duk__tag_check(ctx, index, DUK_TAG_UNDEFINED);
+}
+
+duk_bool_t duk_is_null(duk_context *ctx, duk_idx_t index) {
+	DUK_ASSERT(ctx != NULL);
+	return duk__tag_check(ctx, index, DUK_TAG_NULL);
+}
+
+duk_bool_t duk_is_null_or_undefined(duk_context *ctx, duk_idx_t index) {
+	duk_tval *tv;
+	duk_small_uint_t tag;
+
+	tv = duk_get_tval(ctx, index);
+	if (!tv) {
+		return 0;
+	}
+	tag = DUK_TVAL_GET_TAG(tv);
+	return (tag == DUK_TAG_UNDEFINED) || (tag == DUK_TAG_NULL);
+}
+
+duk_bool_t duk_is_boolean(duk_context *ctx, duk_idx_t index) {
+	DUK_ASSERT(ctx != NULL);
+	return duk__tag_check(ctx, index, DUK_TAG_BOOLEAN);
+}
+
+duk_bool_t duk_is_number(duk_context *ctx, duk_idx_t index) {
+	duk_tval *tv;
+
+	DUK_ASSERT(ctx != NULL);
+
+	/*
+	 *  Number is special because it doesn't have a specific
+	 *  tag in the 8-byte representation.
+	 */
+
+	/* XXX: shorter version for 12-byte representation? */
+
+	tv = duk_get_tval(ctx, index);
+	if (!tv) {
+		return 0;
+	}
+	return DUK_TVAL_IS_NUMBER(tv);
+}
+
+duk_bool_t duk_is_nan(duk_context *ctx, duk_idx_t index) {
+	/* XXX: This will now return false for non-numbers, even though they would
+	 * coerce to NaN (as a general rule).  In particular, duk_get_number()
+	 * returns a NaN for non-numbers, so should this function also return
+	 * true for non-numbers?
+	 */
+
+	duk_tval *tv;
+
+	tv = duk_get_tval(ctx, index);
+	if (!tv || !DUK_TVAL_IS_NUMBER(tv)) {
+		return 0;
+	}
+	return DUK_ISNAN(DUK_TVAL_GET_NUMBER(tv));
+}
+
+duk_bool_t duk_is_string(duk_context *ctx, duk_idx_t index) {
+	DUK_ASSERT(ctx != NULL);
+	return duk__tag_check(ctx, index, DUK_TAG_STRING);
+}
+
+duk_bool_t duk_is_object(duk_context *ctx, duk_idx_t index) {
+	DUK_ASSERT(ctx != NULL);
+	return duk__tag_check(ctx, index, DUK_TAG_OBJECT);
+}
+
+duk_bool_t duk_is_buffer(duk_context *ctx, duk_idx_t index) {
+	DUK_ASSERT(ctx != NULL);
+	return duk__tag_check(ctx, index, DUK_TAG_BUFFER);
+}
+
+duk_bool_t duk_is_pointer(duk_context *ctx, duk_idx_t index) {
+	DUK_ASSERT(ctx != NULL);
+	return duk__tag_check(ctx, index, DUK_TAG_POINTER);
+}
+
+duk_bool_t duk_is_array(duk_context *ctx, duk_idx_t index) {
+	duk_hobject *obj;
+
+	DUK_ASSERT(ctx != NULL);
+
+	obj = duk_get_hobject(ctx, index);
+	if (obj) {
+		return (DUK_HOBJECT_GET_CLASS_NUMBER(obj) == DUK_HOBJECT_CLASS_ARRAY ? 1 : 0);
+	}
+	return 0;
+}
+
+duk_bool_t duk_is_function(duk_context *ctx, duk_idx_t index) {
+	return duk__obj_flag_any_default_false(ctx,
+	                                       index,
+	                                       DUK_HOBJECT_FLAG_COMPILEDFUNCTION |
+	                                       DUK_HOBJECT_FLAG_NATIVEFUNCTION |
+	                                       DUK_HOBJECT_FLAG_BOUND);
+}
+
+duk_bool_t duk_is_c_function(duk_context *ctx, duk_idx_t index) {
+	return duk__obj_flag_any_default_false(ctx,
+	                                       index,
+	                                       DUK_HOBJECT_FLAG_NATIVEFUNCTION);
+}
+
+duk_bool_t duk_is_ecmascript_function(duk_context *ctx, duk_idx_t index) {
+	return duk__obj_flag_any_default_false(ctx,
+	                                       index,
+	                                       DUK_HOBJECT_FLAG_COMPILEDFUNCTION);
+}
+
+duk_bool_t duk_is_bound_function(duk_context *ctx, duk_idx_t index) {
+	return duk__obj_flag_any_default_false(ctx,
+	                                       index,
+	                                       DUK_HOBJECT_FLAG_BOUND);
+}
+
+duk_bool_t duk_is_thread(duk_context *ctx, duk_idx_t index) {
+	return duk__obj_flag_any_default_false(ctx,
+	                                       index,
+	                                       DUK_HOBJECT_FLAG_THREAD);
+}
+
+duk_bool_t duk_is_callable(duk_context *ctx, duk_idx_t index) {
+	/* XXX: currently same as duk_is_function() */
+	return duk__obj_flag_any_default_false(ctx,
+	                                       index,
+	                                       DUK_HOBJECT_FLAG_COMPILEDFUNCTION |
+	                                       DUK_HOBJECT_FLAG_NATIVEFUNCTION |
+	                                       DUK_HOBJECT_FLAG_BOUND);
+}
+
+duk_bool_t duk_is_dynamic(duk_context *ctx, duk_idx_t index) {
+	duk_tval *tv;
+
+	DUK_ASSERT(ctx != NULL);
+
+	tv = duk_get_tval(ctx, index);
+	if (DUK_TVAL_IS_BUFFER(tv)) {
+		duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv);
+		DUK_ASSERT(h != NULL);
+		return (DUK_HBUFFER_HAS_DYNAMIC(h) ? 1 : 0);
+	}
+	return 0;
+}
+
+duk_bool_t duk_is_fixed(duk_context *ctx, duk_idx_t index) {
+	duk_tval *tv;
+
+	DUK_ASSERT(ctx != NULL);
+
+	tv = duk_get_tval(ctx, index);
+	if (DUK_TVAL_IS_BUFFER(tv)) {
+		duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv);
+		DUK_ASSERT(h != NULL);
+		return (DUK_HBUFFER_HAS_DYNAMIC(h) ? 0 : 1);
+	}
+	return 0;
+}
+
+/* XXX: make macro in API */
+duk_bool_t duk_is_primitive(duk_context *ctx, duk_idx_t index) {
+	return !duk_is_object(ctx, index);
+}
+
+/*
+ *  Pushers
+ */
+
+void duk_push_tval(duk_context *ctx, duk_tval *tv) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv_slot;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(tv != NULL);
+
+	if (thr->valstack_top >= thr->valstack_end) {
+		DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_PUSH_BEYOND_ALLOC_STACK);
+	}
+
+	tv_slot = thr->valstack_top;
+	DUK_TVAL_SET_TVAL(tv_slot, tv);
+	DUK_TVAL_INCREF(thr, tv);
+	thr->valstack_top++;
+}
+
+void duk_push_unused(duk_context *ctx) {
+	duk_tval tv;
+	DUK_ASSERT(ctx != NULL);
+	DUK_TVAL_SET_UNDEFINED_ACTUAL(&tv);
+	duk_push_tval(ctx, &tv);
+}
+
+void duk_push_undefined(duk_context *ctx) {
+	duk_tval tv;
+	DUK_ASSERT(ctx != NULL);
+	DUK_TVAL_SET_UNDEFINED_ACTUAL(&tv);  /* XXX: heap constant would be nice */
+	duk_push_tval(ctx, &tv);
+}
+
+void duk_push_null(duk_context *ctx) {
+	duk_tval tv;
+	DUK_ASSERT(ctx != NULL);
+	DUK_TVAL_SET_NULL(&tv);  /* XXX: heap constant would be nice */
+	duk_push_tval(ctx, &tv);
+}
+
+void duk_push_boolean(duk_context *ctx, duk_bool_t val) {
+	duk_tval tv;
+	duk_small_int_t b = (val ? 1 : 0);  /* ensure value is 1 or 0 (not other non-zero) */
+	DUK_ASSERT(ctx != NULL);
+	DUK_TVAL_SET_BOOLEAN(&tv, b);
+	duk_push_tval(ctx, &tv);
+}
+
+void duk_push_true(duk_context *ctx) {
+	duk_push_boolean(ctx, 1);
+}
+
+void duk_push_false(duk_context *ctx) {
+	duk_push_boolean(ctx, 0);
+}
+
+void duk_push_number(duk_context *ctx, duk_double_t val) {
+	duk_tval tv;
+	duk_double_union du;
+	DUK_ASSERT(ctx != NULL);
+
+	/* normalize NaN which may not match our canonical internal NaN */
+	du.d = val;
+	DUK_DBLUNION_NORMALIZE_NAN_CHECK(&du);
+
+	DUK_TVAL_SET_NUMBER(&tv, du.d);
+	duk_push_tval(ctx, &tv);
+}
+
+void duk_push_int(duk_context *ctx, duk_int_t val) {
+	duk_push_number(ctx, (duk_double_t) val);
+}
+
+void duk_push_uint(duk_context *ctx, duk_uint_t val) {
+	duk_push_number(ctx, (duk_double_t) val);
+}
+
+void duk_push_nan(duk_context *ctx) {
+	duk_push_number(ctx, DUK_DOUBLE_NAN);
+}
+
+const char *duk_push_lstring(duk_context *ctx, const char *str, duk_size_t len) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hstring *h;
+	duk_tval *tv_slot;
+
+	DUK_ASSERT(ctx != NULL);
+
+	/* check stack before interning (avoid hanging temp) */
+	if (thr->valstack_top >= thr->valstack_end) {
+		DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_PUSH_BEYOND_ALLOC_STACK);
+	}
+
+	/* NULL with zero length represents an empty string; NULL with higher
+	 * length is also now trated like an empty string although it is
+	 * a bit dubious.  This is unlike duk_push_string() which pushes a
+	 * 'null' if the input string is a NULL.
+	 */
+	if (!str) {
+		len = 0;
+	}
+
+	/* Check for maximum string length */
+	if (len > DUK_HSTRING_MAX_BYTELEN) {
+		DUK_ERROR(thr, DUK_ERR_RANGE_ERROR, DUK_STR_STRING_TOO_LONG);
+	}
+
+	h = duk_heap_string_intern_checked(thr, (duk_uint8_t *) str, (duk_uint32_t) len);
+	DUK_ASSERT(h != NULL);
+
+	tv_slot = thr->valstack_top;
+	DUK_TVAL_SET_STRING(tv_slot, h);
+	DUK_HSTRING_INCREF(thr, h);
+	thr->valstack_top++;
+
+	return (const char *) DUK_HSTRING_GET_DATA(h);
+}
+
+const char *duk_push_string(duk_context *ctx, const char *str) {
+	DUK_ASSERT(ctx != NULL);
+
+	if (str) {
+		return duk_push_lstring(ctx, str, DUK_STRLEN(str));
+	} else {
+		duk_push_null(ctx);
+		return NULL;
+	}
+}
+
+#ifdef DUK_USE_FILE_IO
+/* This is a bit clunky because it is ANSI C portable.  Should perhaps
+ * relocate to another file because this is potentially platform
+ * dependent.
+ */
+const char *duk_push_string_file(duk_context *ctx, const char *path) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_file *f = NULL;
+	char *buf;
+	long sz;  /* ANSI C typing */
+
+	DUK_ASSERT(ctx != NULL);
+	if (!path) {
+		goto fail;
+	}
+	f = DUK_FOPEN(path, "rb");
+	if (!f) {
+		goto fail;
+	}
+	if (DUK_FSEEK(f, 0, SEEK_END) < 0) {
+		goto fail;
+	}
+	sz = DUK_FTELL(f);
+	if (sz < 0) {
+		goto fail;
+	}
+	if (DUK_FSEEK(f, 0, SEEK_SET) < 0) {
+		goto fail;
+	}
+	buf = (char *) duk_push_fixed_buffer(ctx, (duk_size_t) sz);
+	DUK_ASSERT(buf != NULL);
+	if ((duk_size_t) DUK_FREAD(buf, 1, (size_t) sz, f) != (duk_size_t) sz) {
+		goto fail;
+	}
+	(void) DUK_FCLOSE(f);  /* ignore fclose() error */
+	f = NULL;
+	return duk_to_string(ctx, -1);
+
+ fail:
+	if (f) {
+		DUK_FCLOSE(f);
+	}
+	/* XXX: string not shared because it is conditional */
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "read file error");
+	return NULL;
+}
+#else
+const char *duk_push_string_file(duk_context *ctx, const char *path) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	DUK_ASSERT(ctx != NULL);
+	DUK_UNREF(path);
+	/* XXX: string not shared because it is conditional */
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "file I/O disabled");
+	return NULL;
+}
+#endif  /* DUK_USE_FILE_IO */
+
+void duk_push_pointer(duk_context *ctx, void *val) {
+	duk_tval tv;
+	DUK_ASSERT(ctx != NULL);
+
+	DUK_TVAL_SET_POINTER(&tv, val);
+	duk_push_tval(ctx, &tv);
+}
+
+#define DUK__PUSH_THIS_FLAG_CHECK_COERC  (1 << 0)
+#define DUK__PUSH_THIS_FLAG_TO_OBJECT    (1 << 1)
+#define DUK__PUSH_THIS_FLAG_TO_STRING    (1 << 2)
+
+static void duk__push_this_helper(duk_context *ctx, duk_small_uint_t flags) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT_DISABLE(thr->callstack_top >= 0);  /* avoid warning (unsigned) */
+	DUK_ASSERT(thr->callstack_top <= thr->callstack_size);
+
+	if (thr->callstack_top == 0) {
+		if (flags & DUK__PUSH_THIS_FLAG_CHECK_COERC) {
+			goto type_error;
+		}
+		duk_push_undefined(ctx);
+	} else {
+		duk_tval tv_tmp;
+		duk_tval *tv;
+
+		/* 'this' binding is just before current activation's bottom */
+		DUK_ASSERT(thr->valstack_bottom > thr->valstack);
+		tv = thr->valstack_bottom - 1;
+		if (flags & DUK__PUSH_THIS_FLAG_CHECK_COERC) {
+			if (DUK_TVAL_IS_UNDEFINED(tv) || DUK_TVAL_IS_NULL(tv)) {
+				goto type_error;
+			}
+		}
+
+		DUK_TVAL_SET_TVAL(&tv_tmp, tv);
+		duk_push_tval(ctx, &tv_tmp);
+	}
+
+	if (flags & DUK__PUSH_THIS_FLAG_TO_OBJECT) {
+		duk_to_object(ctx, -1);
+	} else if (flags & DUK__PUSH_THIS_FLAG_TO_STRING) {
+		duk_to_string(ctx, -1);
+	}
+
+	return;
+
+ type_error:
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_OBJECT_COERCIBLE);
+}
+
+void duk_push_this(duk_context *ctx) {
+	duk__push_this_helper(ctx, 0 /*flags*/);
+}
+
+void duk_push_this_check_object_coercible(duk_context *ctx) {
+	duk__push_this_helper(ctx, DUK__PUSH_THIS_FLAG_CHECK_COERC /*flags*/);
+}
+
+duk_hobject *duk_push_this_coercible_to_object(duk_context *ctx) {
+	duk_hobject *h;
+	duk__push_this_helper(ctx, DUK__PUSH_THIS_FLAG_CHECK_COERC |
+	                           DUK__PUSH_THIS_FLAG_TO_OBJECT /*flags*/);
+	h = duk_get_hobject(ctx, -1);
+	DUK_ASSERT(h != NULL);
+	return h;
+}
+
+duk_hstring *duk_push_this_coercible_to_string(duk_context *ctx) {
+	duk_hstring *h;
+	duk__push_this_helper(ctx, DUK__PUSH_THIS_FLAG_CHECK_COERC |
+	                           DUK__PUSH_THIS_FLAG_TO_STRING /*flags*/);
+	h = duk_get_hstring(ctx, -1);
+	DUK_ASSERT(h != NULL);
+	return h;
+}
+
+void duk_push_current_function(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_activation *act;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT_DISABLE(thr->callstack_top >= 0);
+	DUK_ASSERT(thr->callstack_top <= thr->callstack_size);
+
+	act = duk_hthread_get_current_activation(thr);
+	if (act) {
+		DUK_ASSERT(act->func != NULL);
+		duk_push_hobject(ctx, act->func);
+	} else {
+		duk_push_undefined(ctx);
+	}
+}
+
+void duk_push_current_thread(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(ctx != NULL);
+
+	if (thr->heap->curr_thread) {
+		duk_push_hobject(ctx, (duk_hobject *) thr->heap->curr_thread);
+	} else {
+		duk_push_undefined(ctx);
+	}
+}
+
+void duk_push_global_object(duk_context *ctx) {
+	DUK_ASSERT(ctx != NULL);
+
+	duk_push_hobject_bidx(ctx, DUK_BIDX_GLOBAL);
+}
+
+/* XXX: size optimize */
+static void duk__push_stash(duk_context *ctx) {
+	DUK_ASSERT(ctx != NULL);
+	if (!duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_VALUE)) {
+		DUK_DDD(DUK_DDDPRINT("creating heap/global/thread stash on first use"));
+		duk_pop(ctx);
+		duk_push_object_internal(ctx);
+		duk_dup_top(ctx);
+		duk_def_prop_stridx(ctx, -3, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_C);  /* [ ... parent stash stash ] -> [ ... parent stash ] */
+	}
+	duk_remove(ctx, -2);
+}
+
+void duk_push_heap_stash(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_heap *heap;
+	DUK_ASSERT(ctx != NULL);
+	heap = thr->heap;
+	DUK_ASSERT(heap->heap_object != NULL);
+	duk_push_hobject(ctx, heap->heap_object);
+	duk__push_stash(ctx);
+}
+
+void duk_push_global_stash(duk_context *ctx) {
+	DUK_ASSERT(ctx != NULL);
+	duk_push_global_object(ctx);
+	duk__push_stash(ctx);
+}
+
+void duk_push_thread_stash(duk_context *ctx, duk_context *target_ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	DUK_ASSERT(ctx != NULL);
+	if (!target_ctx) {
+		DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_CALL_ARGS);
+		return;  /* not reached */
+	}
+	duk_push_hobject(ctx, (duk_hobject *) target_ctx);
+	duk__push_stash(ctx);
+}
+
+/* XXX: duk_ssize_t would be useful here */
+static duk_int_t duk__try_push_vsprintf(duk_context *ctx, void *buf, duk_size_t sz, const char *fmt, va_list ap) {
+	duk_int_t len;
+
+	DUK_UNREF(ctx);
+
+	/* NUL terminator handling doesn't matter here */
+	len = DUK_VSNPRINTF((char *) buf, sz, fmt, ap);
+	if (len < (duk_int_t) sz) {
+		/* Return value of 'sz' or more indicates output was (potentially)
+		 * truncated.
+		 */
+		return (duk_int_t) len;
+	}
+	return -1;
+}
+
+const char *duk_push_vsprintf(duk_context *ctx, const char *fmt, va_list ap) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_uint8_t stack_buf[DUK_PUSH_SPRINTF_INITIAL_SIZE];
+	duk_size_t sz = DUK_PUSH_SPRINTF_INITIAL_SIZE;
+	duk_bool_t pushed_buf = 0;
+	void *buf;
+	duk_int_t len;  /* XXX: duk_ssize_t */
+	const char *res;
+
+	DUK_ASSERT(ctx != NULL);
+
+	/* special handling of fmt==NULL */
+	if (!fmt) {
+		duk_hstring *h_str;
+		duk_push_hstring_stridx(ctx, DUK_STRIDX_EMPTY_STRING);
+		h_str = DUK_HTHREAD_STRING_EMPTY_STRING(thr);  /* rely on interning, must be this string */
+		return (const char *) DUK_HSTRING_GET_DATA(h_str);
+	}
+
+	/* initial estimate based on format string */
+	sz = DUK_STRLEN(fmt) + 16;  /* format plus something to avoid just missing */
+	if (sz < DUK_PUSH_SPRINTF_INITIAL_SIZE) {
+		sz = DUK_PUSH_SPRINTF_INITIAL_SIZE;
+	}
+	DUK_ASSERT(sz > 0);
+
+	/* Try to make do with a stack buffer to avoid allocating a temporary buffer.
+	 * This works 99% of the time which is quite nice.
+	 */
+	for (;;) {
+		va_list ap_copy;  /* copied so that 'ap' can be reused */
+
+		if (sz <= sizeof(stack_buf)) {
+			buf = stack_buf;
+		} else if (!pushed_buf) {
+			pushed_buf = 1;
+			buf = duk_push_dynamic_buffer(ctx, sz);
+		} else {
+			buf = duk_resize_buffer(ctx, -1, sz);
+		}
+		DUK_ASSERT(buf != NULL);
+
+		DUK_VA_COPY(ap_copy, ap);
+		len = duk__try_push_vsprintf(ctx, buf, sz, fmt, ap_copy);
+		va_end(ap_copy);
+		if (len >= 0) {
+			break;
+		}
+
+		/* failed, resize and try again */
+		sz = sz * 2;
+		if (sz >= DUK_PUSH_SPRINTF_SANITY_LIMIT) {
+			DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_SPRINTF_TOO_LONG);
+		}
+	}
+
+	/* Cannot use duk_to_string() on the buffer because it is usually
+	 * larger than 'len'.  Also, 'buf' is usually a stack buffer.
+	 */
+	res = duk_push_lstring(ctx, (const char *) buf, (duk_size_t) len);  /* [ buf? res ] */
+	if (pushed_buf) {
+		duk_remove(ctx, -2);
+	}
+	return res;
+}
+
+const char *duk_push_sprintf(duk_context *ctx, const char *fmt, ...) {
+	va_list ap;
+	const char *ret;
+
+	/* allow fmt==NULL */
+	va_start(ap, fmt);
+	ret = duk_push_vsprintf(ctx, fmt, ap);
+	va_end(ap);
+
+	return ret;
+}
+
+duk_idx_t duk_push_object_helper(duk_context *ctx, duk_uint_t hobject_flags_and_class, duk_small_int_t prototype_bidx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv_slot;
+	duk_hobject *h;
+	duk_idx_t ret;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(prototype_bidx == -1 ||
+	           (prototype_bidx >= 0 && prototype_bidx < DUK_NUM_BUILTINS));
+
+	/* check stack before interning (avoid hanging temp) */
+	if (thr->valstack_top >= thr->valstack_end) {
+		DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_PUSH_BEYOND_ALLOC_STACK);
+	}
+
+	h = duk_hobject_alloc(thr->heap, hobject_flags_and_class);
+	if (!h) {
+		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, DUK_STR_OBJECT_ALLOC_FAILED);
+	}
+
+	DUK_DDD(DUK_DDDPRINT("created object with flags: 0x%08lx", (unsigned long) h->hdr.h_flags));
+
+	tv_slot = thr->valstack_top;
+	DUK_TVAL_SET_OBJECT(tv_slot, h);
+	DUK_HOBJECT_INCREF(thr, h);
+	ret = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom);
+	thr->valstack_top++;
+
+	/* object is now reachable */
+
+	if (prototype_bidx >= 0) {
+		DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, h, thr->builtins[prototype_bidx]);
+	} else {
+		DUK_ASSERT(prototype_bidx == -1);
+		DUK_ASSERT(h->prototype == NULL);
+	}
+
+	return ret;
+}
+
+duk_idx_t duk_push_object_helper_proto(duk_context *ctx, duk_uint_t hobject_flags_and_class, duk_hobject *proto) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_idx_t ret;
+	duk_hobject *h;
+
+	ret = duk_push_object_helper(ctx, hobject_flags_and_class, -1);
+	h = duk_get_hobject(ctx, -1);
+	DUK_ASSERT(h != NULL);
+	DUK_ASSERT(h->prototype == NULL);
+	DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, h, proto);
+	return ret;
+}
+
+duk_idx_t duk_push_object(duk_context *ctx) {
+	return duk_push_object_helper(ctx,
+	                              DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                              DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT),
+	                              DUK_BIDX_OBJECT_PROTOTYPE);
+}
+
+duk_idx_t duk_push_array(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hobject *obj;
+	duk_idx_t ret;
+
+	ret = duk_push_object_helper(ctx,
+	                             DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                             DUK_HOBJECT_FLAG_ARRAY_PART |
+	                             DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ARRAY),
+	                             DUK_BIDX_ARRAY_PROTOTYPE);
+
+	obj = duk_require_hobject(ctx, ret);
+
+	/*
+	 *  An array must have a 'length' property (E5 Section 15.4.5.2).
+	 *  The special array behavior flag must only be enabled once the
+	 *  length property has been added.
+	 */
+
+	duk_push_number(ctx, 0.0);
+	duk_hobject_define_property_internal(thr,
+	                                     obj,
+	                                     DUK_HTHREAD_STRING_LENGTH(thr),
+	                                     DUK_PROPDESC_FLAGS_W);
+	DUK_HOBJECT_SET_EXOTIC_ARRAY(obj);
+
+	return ret;
+}
+
+duk_idx_t duk_push_thread_raw(duk_context *ctx, duk_uint_t flags) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hthread *obj;
+	duk_idx_t ret;
+	duk_tval *tv_slot;
+
+	DUK_ASSERT(ctx != NULL);
+
+	/* check stack before interning (avoid hanging temp) */
+	if (thr->valstack_top >= thr->valstack_end) {
+		DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_PUSH_BEYOND_ALLOC_STACK);
+	}
+
+	obj = duk_hthread_alloc(thr->heap,
+	                        DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                        DUK_HOBJECT_FLAG_THREAD |
+	                        DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_THREAD));
+	if (!obj) {
+		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, DUK_STR_THREAD_ALLOC_FAILED);
+	}
+	obj->state = DUK_HTHREAD_STATE_INACTIVE;
+	obj->strs = thr->strs;
+	DUK_DDD(DUK_DDDPRINT("created thread object with flags: 0x%08lx", (unsigned long) obj->obj.hdr.h_flags));
+
+	/* make the new thread reachable */
+	tv_slot = thr->valstack_top;
+	DUK_TVAL_SET_OBJECT(tv_slot, (duk_hobject *) obj);
+	DUK_HTHREAD_INCREF(thr, obj);
+	ret = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom);
+	thr->valstack_top++;
+
+	/* important to do this *after* pushing, to make the thread reachable for gc */
+	if (!duk_hthread_init_stacks(thr->heap, obj)) {
+		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, DUK_STR_THREAD_ALLOC_FAILED);
+	}
+
+	/* initialize built-ins - either by copying or creating new ones */
+	if (flags & DUK_THREAD_NEW_GLOBAL_ENV) {
+		duk_hthread_create_builtin_objects(obj);
+	} else {
+		duk_hthread_copy_builtin_objects(thr, obj);
+	}
+
+	/* default prototype (Note: 'obj' must be reachable) */
+	DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, (duk_hobject *) obj, obj->builtins[DUK_BIDX_THREAD_PROTOTYPE]);
+
+	/* Initial stack size satisfies the stack spare constraints so there
+	 * is no need to require stack here.
+	 */
+	DUK_ASSERT(DUK_VALSTACK_INITIAL_SIZE >=
+	           DUK_VALSTACK_API_ENTRY_MINIMUM + DUK_VALSTACK_INTERNAL_EXTRA);
+
+	return ret;
+}
+
+duk_idx_t duk_push_compiledfunction(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hcompiledfunction *obj;
+	duk_idx_t ret;
+	duk_tval *tv_slot;
+
+	DUK_ASSERT(ctx != NULL);
+
+	/* check stack before interning (avoid hanging temp) */
+	if (thr->valstack_top >= thr->valstack_end) {
+		DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_PUSH_BEYOND_ALLOC_STACK);
+	}
+
+	/* Template functions are not strictly constructable (they don't
+	 * have a "prototype" property for instance), so leave the
+	 * DUK_HOBJECT_FLAG_CONSRUCTABLE flag cleared here.
+	 */
+
+	obj = duk_hcompiledfunction_alloc(thr->heap,
+	                                  DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                                  DUK_HOBJECT_FLAG_COMPILEDFUNCTION |
+	                                  DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION));
+	if (!obj) {
+		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, DUK_STR_FUNC_ALLOC_FAILED);
+	}
+
+	DUK_DDD(DUK_DDDPRINT("created compiled function object with flags: 0x%08lx", (unsigned long) obj->obj.hdr.h_flags));
+
+	tv_slot = thr->valstack_top;
+	DUK_TVAL_SET_OBJECT(tv_slot, (duk_hobject *) obj);
+	DUK_HOBJECT_INCREF(thr, obj);
+	ret = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom);
+	thr->valstack_top++;
+
+	/* default prototype (Note: 'obj' must be reachable) */
+	DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, (duk_hobject *) obj, thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]);
+
+	return ret;
+}
+
+static duk_idx_t duk__push_c_function_raw(duk_context *ctx, duk_c_function func, duk_idx_t nargs, duk_uint_t flags) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hnativefunction *obj;
+	duk_idx_t ret;
+	duk_tval *tv_slot;
+	duk_uint16_t func_nargs;
+
+	DUK_ASSERT(ctx != NULL);
+
+	/* check stack before interning (avoid hanging temp) */
+	if (thr->valstack_top >= thr->valstack_end) {
+		DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_PUSH_BEYOND_ALLOC_STACK);
+	}
+	if (func == NULL) {
+		goto api_error;
+	}
+	if (nargs >= 0 && nargs < DUK_HNATIVEFUNCTION_NARGS_MAX) {
+		func_nargs = (duk_uint16_t) nargs;
+	} else if (nargs == DUK_VARARGS) {
+		func_nargs = DUK_HNATIVEFUNCTION_NARGS_VARARGS;
+	} else {
+		goto api_error;
+	}
+
+	obj = duk_hnativefunction_alloc(thr->heap, flags);
+	if (!obj) {
+		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, DUK_STR_FUNC_ALLOC_FAILED);
+	}
+
+	obj->func = func;
+	obj->nargs = func_nargs;
+
+	DUK_DDD(DUK_DDDPRINT("created native function object with flags: 0x%08lx, nargs=%ld",
+	                     (unsigned long) obj->obj.hdr.h_flags, (long) obj->nargs));
+
+	tv_slot = thr->valstack_top;
+	DUK_TVAL_SET_OBJECT(tv_slot, (duk_hobject *) obj);
+	DUK_HOBJECT_INCREF(thr, obj);
+	ret = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom);
+	thr->valstack_top++;
+
+	/* default prototype (Note: 'obj' must be reachable) */
+	DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, (duk_hobject *) obj, thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]);
+
+	return ret;
+
+ api_error:
+	DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_CALL_ARGS);
+	return 0;  /* not reached */
+}
+
+duk_idx_t duk_push_c_function(duk_context *ctx, duk_c_function func, duk_int_t nargs) {
+	duk_uint_t flags;
+
+	flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
+	        DUK_HOBJECT_FLAG_CONSTRUCTABLE |
+	        DUK_HOBJECT_FLAG_NATIVEFUNCTION |
+	        DUK_HOBJECT_FLAG_NEWENV |
+	        DUK_HOBJECT_FLAG_STRICT |
+	        DUK_HOBJECT_FLAG_NOTAIL |
+	        DUK_HOBJECT_FLAG_EXOTIC_DUKFUNC |
+	        DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION);
+	
+	return duk__push_c_function_raw(ctx, func, nargs, flags);
+}
+
+void duk_push_c_function_noexotic(duk_context *ctx, duk_c_function func, duk_int_t nargs) {
+	duk_uint_t flags;
+
+	flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
+	        DUK_HOBJECT_FLAG_CONSTRUCTABLE |
+	        DUK_HOBJECT_FLAG_NATIVEFUNCTION |
+	        DUK_HOBJECT_FLAG_NEWENV |
+	        DUK_HOBJECT_FLAG_STRICT |
+	        DUK_HOBJECT_FLAG_NOTAIL |
+	        DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION);
+	
+	(void) duk__push_c_function_raw(ctx, func, nargs, flags);
+}
+
+void duk_push_c_function_noconstruct_noexotic(duk_context *ctx, duk_c_function func, duk_int_t nargs) {
+	duk_uint_t flags;
+
+	flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
+	        DUK_HOBJECT_FLAG_NATIVEFUNCTION |
+	        DUK_HOBJECT_FLAG_NEWENV |
+	        DUK_HOBJECT_FLAG_STRICT |
+	        DUK_HOBJECT_FLAG_NOTAIL |
+	        DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION);
+	
+	(void) duk__push_c_function_raw(ctx, func, nargs, flags);
+}
+
+static duk_idx_t duk__push_error_object_vsprintf(duk_context *ctx, duk_errcode_t err_code, const char *filename, duk_int_t line, const char *fmt, va_list ap) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_idx_t ret;
+	duk_hobject *proto;
+#ifdef DUK_USE_AUGMENT_ERROR_CREATE
+	duk_bool_t noblame_fileline;
+#endif
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(thr != NULL);
+
+	/* Error code also packs a tracedata related flag. */
+#ifdef DUK_USE_AUGMENT_ERROR_CREATE
+	noblame_fileline = err_code & DUK_ERRCODE_FLAG_NOBLAME_FILELINE;
+#endif
+	err_code = err_code & (~DUK_ERRCODE_FLAG_NOBLAME_FILELINE);
+
+	/* error gets its 'name' from the prototype */
+	proto = duk_error_prototype_from_code(thr, err_code);
+	ret = duk_push_object_helper_proto(ctx,
+	                                   DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                                   DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ERROR),
+	                                   proto);
+
+	/* ... and its 'message' from an instance property */
+	if (fmt) {
+		duk_push_vsprintf(ctx, fmt, ap);
+		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_MESSAGE, DUK_PROPDESC_FLAGS_WC);
+	} else {
+		/* If no explicit message given, put error code into message field
+		 * (as a number).  This is not fully in keeping with the Ecmascript
+		 * error model because messages are supposed to be strings (Error
+		 * constructors use ToString() on their argument).  However, it's
+		 * probably more useful than having a separate 'code' property.
+		 */
+		duk_push_int(ctx, err_code);
+		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_MESSAGE, DUK_PROPDESC_FLAGS_WC);
+	}
+
+#if 0
+	/* Disabled for now, not sure this is a useful property */
+	duk_push_int(ctx, err_code);
+	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_CODE, DUK_PROPDESC_FLAGS_WC);
+#endif
+
+	/* Creation time error augmentation */
+#ifdef DUK_USE_AUGMENT_ERROR_CREATE
+	/* filename may be NULL in which case file/line is not recorded */
+	duk_err_augment_error_create(thr, thr, filename, line, noblame_fileline);  /* may throw an error */
+#endif
+
+	return ret;
+}
+
+duk_idx_t duk_push_error_object_raw(duk_context *ctx, duk_errcode_t err_code, const char *filename, duk_int_t line, const char *fmt, ...) {
+	va_list ap;
+	duk_idx_t ret;
+
+	va_start(ap, fmt);
+	ret = duk__push_error_object_vsprintf(ctx, err_code, filename, line, fmt, ap);
+	va_end(ap);
+	return ret;
+}
+
+#ifndef DUK_USE_VARIADIC_MACROS
+duk_idx_t duk_push_error_object_stash(duk_context *ctx, duk_errcode_t err_code, const char *fmt, ...) {
+	const char *filename = duk_api_global_filename;
+	duk_int_t line = duk_api_global_line;
+	va_list ap;
+	duk_idx_t ret;
+
+	duk_api_global_filename = NULL;
+	duk_api_global_line = 0;
+	va_start(ap, fmt);
+	ret = duk__push_error_object_vsprintf(ctx, err_code, filename, line, fmt, ap);
+	va_end(ap);
+	return ret;
+}
+#endif
+
+/* XXX: repetition, see duk_push_object */
+void *duk_push_buffer(duk_context *ctx, duk_size_t size, duk_bool_t dynamic) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv_slot;
+	duk_hbuffer *h;
+
+	DUK_ASSERT(ctx != NULL);
+
+	/* check stack before interning (avoid hanging temp) */
+	if (thr->valstack_top >= thr->valstack_end) {
+		DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_PUSH_BEYOND_ALLOC_STACK);
+	}
+
+	/* Check for maximum buffer length. */
+	if (size > DUK_HBUFFER_MAX_BYTELEN) {
+		DUK_ERROR(thr, DUK_ERR_RANGE_ERROR, DUK_STR_BUFFER_TOO_LONG);
+	}
+
+	h = duk_hbuffer_alloc(thr->heap, size, dynamic);
+	if (!h) {
+		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, DUK_STR_BUFFER_ALLOC_FAILED);
+	}
+
+	tv_slot = thr->valstack_top;
+	DUK_TVAL_SET_BUFFER(tv_slot, h);
+	DUK_HBUFFER_INCREF(thr, h);
+	thr->valstack_top++;
+
+	return DUK_HBUFFER_GET_DATA_PTR(h);
+}
+
+void *duk_push_fixed_buffer(duk_context *ctx, duk_size_t size) {
+	return duk_push_buffer(ctx, size, 0);
+}
+
+void *duk_push_dynamic_buffer(duk_context *ctx, duk_size_t size) {
+	return duk_push_buffer(ctx, size, 1);
+}
+
+duk_idx_t duk_push_object_internal(duk_context *ctx) {
+	return duk_push_object_helper(ctx,
+	                              DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                              DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT),
+	                              -1);  /* no prototype */
+}
+
+void duk_push_hstring(duk_context *ctx, duk_hstring *h) {
+	duk_tval tv;
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(h != NULL);
+	DUK_TVAL_SET_STRING(&tv, h);
+	duk_push_tval(ctx, &tv);
+}
+
+void duk_push_hstring_stridx(duk_context *ctx, duk_small_int_t stridx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	DUK_ASSERT(stridx >= 0 && stridx < DUK_HEAP_NUM_STRINGS);
+	duk_push_hstring(ctx, thr->strs[stridx]);
+}
+
+void duk_push_hobject(duk_context *ctx, duk_hobject *h) {
+	duk_tval tv;
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(h != NULL);
+	DUK_TVAL_SET_OBJECT(&tv, h);
+	duk_push_tval(ctx, &tv);
+}
+
+void duk_push_hbuffer(duk_context *ctx, duk_hbuffer *h) {
+	duk_tval tv;
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(h != NULL);
+	DUK_TVAL_SET_BUFFER(&tv, h);
+	duk_push_tval(ctx, &tv);
+}
+
+void duk_push_hobject_bidx(duk_context *ctx, duk_small_int_t builtin_idx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(builtin_idx >= 0 && builtin_idx < DUK_NUM_BUILTINS);
+	DUK_ASSERT(thr->builtins[builtin_idx] != NULL);
+	duk_push_hobject(ctx, thr->builtins[builtin_idx]);
+}
+
+/*
+ *  Poppers
+ */
+
+void duk_pop_n(duk_context *ctx, duk_idx_t count) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	DUK_ASSERT(ctx != NULL);
+
+	if (count < 0) {
+		DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_COUNT);
+		return;
+	}
+
+	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
+	if ((duk_size_t) (thr->valstack_top - thr->valstack_bottom) < (duk_size_t) count) {
+		DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_POP_TOO_MANY);
+	}
+
+	/*
+	 *  Must be very careful here, every DECREF may cause reallocation
+	 *  of our valstack.
+	 */
+
+	/* XXX: inlined DECREF macro would be nice here: no NULL check,
+	 * refzero queueing but no refzero algorithm run (= no pointer
+	 * instability), inline code.
+	 */
+	
+#ifdef DUK_USE_REFERENCE_COUNTING
+	while (count > 0) {
+		duk_tval tv_tmp;
+		duk_tval *tv;
+
+		tv = --thr->valstack_top;  /* tv points to element just below prev top */
+		DUK_ASSERT(tv >= thr->valstack_bottom);
+		DUK_TVAL_SET_TVAL(&tv_tmp, tv);
+		DUK_TVAL_SET_UNDEFINED_UNUSED(tv);
+		DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+		count--;
+	}
+#else
+	while (count > 0) {
+		duk_tval *tv;
+
+		tv = --thr->valstack_top;
+		DUK_ASSERT(tv >= thr->valstack_bottom);
+		DUK_TVAL_SET_UNDEFINED_UNUSED(tv);
+		count--;
+	}
+#endif
+
+	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
+}
+
+void duk_pop(duk_context *ctx) {
+	duk_pop_n(ctx, 1);
+}
+
+void duk_pop_2(duk_context *ctx) {
+	duk_pop_n(ctx, 2);
+}
+
+void duk_pop_3(duk_context *ctx) {
+	duk_pop_n(ctx, 3);
+}
+
+/*
+ *  Error throwing
+ */
+
+void duk_throw(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+
+	DUK_ASSERT(thr->valstack_bottom >= thr->valstack);
+	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
+	DUK_ASSERT(thr->valstack_end >= thr->valstack_top);
+
+	if (thr->valstack_top == thr->valstack_bottom) {
+		DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_CALL_ARGS);
+	}
+
+	/* Errors are augmented when they are created, not when they are
+	 * thrown or re-thrown.  The current error handler, however, runs
+	 * just before an error is thrown.
+	 */
+
+#if defined(DUK_USE_AUGMENT_ERROR_THROW)
+	DUK_DDD(DUK_DDDPRINT("THROW ERROR (API): %!dT (before throw augment)", (duk_tval *) duk_get_tval(ctx, -1)));
+	duk_err_augment_error_throw(thr);
+#endif
+	DUK_DDD(DUK_DDDPRINT("THROW ERROR (API): %!dT (after throw augment)", (duk_tval *) duk_get_tval(ctx, -1)));
+
+	duk_err_setup_heap_ljstate(thr, DUK_LJ_TYPE_THROW);
+
+	/* thr->heap->lj.jmpbuf_ptr is checked by duk_err_longjmp() so we don't
+	 * need to check that here.  If the value is NULL, a panic occurs because
+	 * we can't return.
+	 */
+
+	duk_err_longjmp(thr);
+	DUK_UNREACHABLE();
+}
+
+void duk_fatal(duk_context *ctx, duk_errcode_t err_code, const char *err_msg) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->heap != NULL);
+	DUK_ASSERT(thr->heap->fatal_func != NULL);
+
+	DUK_D(DUK_DPRINT("fatal error occurred, code %ld, message %s",
+	                 (long) err_code, (const char *) err_msg));
+
+	/* fatal_func should be noreturn, but noreturn declarations on function
+	 * pointers has a very spotty support apparently so it's not currently
+	 * done.
+	 */
+	thr->heap->fatal_func(ctx, err_code, err_msg);
+
+	DUK_PANIC(DUK_ERR_API_ERROR, "fatal handler returned");
+}
+
+void duk_error_raw(duk_context *ctx, duk_errcode_t err_code, const char *filename, duk_int_t line, const char *fmt, ...) {
+	va_list ap;
+	va_start(ap, fmt);
+	duk__push_error_object_vsprintf(ctx, err_code, filename, line, fmt, ap);
+	va_end(ap);
+	duk_throw(ctx);
+}
+
+#ifndef DUK_USE_VARIADIC_MACROS
+void duk_error_stash(duk_context *ctx, duk_errcode_t err_code, const char *fmt, ...) {
+	const char *filename = duk_api_global_filename;
+	duk_int_t line = duk_api_global_line;
+	va_list ap;
+
+	duk_api_global_filename = NULL;
+	duk_api_global_line = 0;
+
+	va_start(ap, fmt);
+	duk__push_error_object_vsprintf(ctx, err_code, filename, line, fmt, ap);
+	va_end(ap);
+	duk_throw(ctx);
+}
+#endif
+
+duk_bool_t duk_equals(duk_context *ctx, duk_idx_t index1, duk_idx_t index2) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv1, *tv2;
+
+	tv1 = duk_get_tval(ctx, index1);
+	if (!tv1) {
+		return 0;
+	}
+	tv2 = duk_get_tval(ctx, index2);
+	if (!tv2) {
+		return 0;
+	}
+
+	/* Coercion may be needed, the helper handles that by pushing the
+	 * tagged values to the stack.
+	 */
+	return duk_js_equals(thr, tv1, tv2);
+}
+
+duk_bool_t duk_strict_equals(duk_context *ctx, duk_idx_t index1, duk_idx_t index2) {
+	duk_tval *tv1, *tv2;
+
+	tv1 = duk_get_tval(ctx, index1);
+	if (!tv1) {
+		return 0;
+	}
+	tv2 = duk_get_tval(ctx, index2);
+	if (!tv2) {
+		return 0;
+	}
+
+	/* No coercions or other side effects, so safe */
+	return duk_js_strict_equals(tv1, tv2);
+}
+
+/*
+ *  Heap creation
+ */
+
+duk_context *duk_create_heap(duk_alloc_function alloc_func,
+                             duk_realloc_function realloc_func,
+                             duk_free_function free_func,
+                             void *alloc_udata,
+                             duk_fatal_function fatal_handler) {
+	duk_heap *heap = NULL;
+	duk_context *ctx;
+
+	/* Assume that either all memory funcs are NULL or non-NULL, mixed
+	 * cases will now be unsafe.
+	 */
+
+	/* XXX: just assert non-NULL values here and make caller arguments
+	 * do the defaulting to the default implementations (smaller code)?
+	 */
+
+	if (!alloc_func) {
+		DUK_ASSERT(realloc_func == NULL);
+		DUK_ASSERT(free_func == NULL);
+		alloc_func = duk_default_alloc_function;
+		realloc_func = duk_default_realloc_function;
+		free_func = duk_default_free_function;
+	} else {
+		DUK_ASSERT(realloc_func != NULL);
+		DUK_ASSERT(free_func != NULL);
+	}
+
+	if (!fatal_handler) {
+		fatal_handler = duk_default_fatal_handler;
+	}
+
+	DUK_ASSERT(alloc_func != NULL);
+	DUK_ASSERT(realloc_func != NULL);
+	DUK_ASSERT(free_func != NULL);
+	DUK_ASSERT(fatal_handler != NULL);
+
+	heap = duk_heap_alloc(alloc_func, realloc_func, free_func, alloc_udata, fatal_handler);
+	if (!heap) {
+		return NULL;
+	}
+	ctx = (duk_context *) heap->heap_thread;
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(((duk_hthread *) ctx)->heap != NULL);
+	return ctx;
+}
+
+void duk_destroy_heap(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_heap *heap;
+
+	if (!ctx) {
+		return;
+	}
+	heap = thr->heap;
+	DUK_ASSERT(heap != NULL);
+
+	duk_heap_free(heap);
+}
+#line 1 "duk_api_buffer.c"
+/*
+ *  Buffer
+ */
+
+/* include removed: duk_internal.h */
+
+void *duk_resize_buffer(duk_context *ctx, duk_idx_t index, duk_size_t new_size) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hbuffer_dynamic *h;
+
+	DUK_ASSERT(ctx != NULL);
+
+	h = (duk_hbuffer_dynamic *) duk_require_hbuffer(ctx, index);
+	DUK_ASSERT(h != NULL);
+
+	if (!DUK_HBUFFER_HAS_DYNAMIC(h)) {
+		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "buffer is not dynamic");
+	}
+
+	/* maximum size check is handled by callee */
+	duk_hbuffer_resize(thr, h, new_size, new_size);  /* snug */
+
+	return DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(h);
+}
+#line 1 "duk_api_call.c"
+/*
+ *  Calls.
+ *
+ *  Protected variants should avoid ever throwing an error.
+ */
+
+/* include removed: duk_internal.h */
+
+/* Prepare value stack for a method call through an object property.
+ * May currently throw an error e.g. when getting the property.
+ */
+static void duk__call_prop_prep_stack(duk_context *ctx, duk_idx_t normalized_obj_index, duk_idx_t nargs) {
+	DUK_DDD(DUK_DDDPRINT("duk__call_prop_prep_stack, normalized_obj_index=%ld, nargs=%ld, stacktop=%ld",
+	                     (long) normalized_obj_index, (long) nargs, (long) duk_get_top(ctx)));
+
+	/* [... key arg1 ... argN] */
+
+	/* duplicate key */
+	duk_dup(ctx, -nargs - 1);  /* Note: -nargs alone would fail for nargs == 0, this is OK */
+	duk_get_prop(ctx, normalized_obj_index);
+
+	DUK_DDD(DUK_DDDPRINT("func: %!T", (duk_tval *) duk_get_tval(ctx, -1)));
+
+	/* [... key arg1 ... argN func] */
+
+	duk_replace(ctx, -nargs - 2);
+
+	/* [... func arg1 ... argN] */
+
+	duk_dup(ctx, normalized_obj_index);
+	duk_insert(ctx, -nargs - 1);
+
+	/* [... func this arg1 ... argN] */
+}
+
+void duk_call(duk_context *ctx, duk_idx_t nargs) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_small_uint_t call_flags;
+	duk_idx_t idx_func;
+	duk_int_t rc;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(thr != NULL);
+
+	idx_func = duk_get_top(ctx) - nargs - 1;
+	if (idx_func < 0 || nargs < 0) {
+		/* note that we can't reliably pop anything here */
+		DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_CALL_ARGS);
+	}
+
+	/* XXX: awkward; we assume there is space for this, overwrite
+	 * directly instead?
+	 */
+	duk_push_undefined(ctx);
+	duk_insert(ctx, idx_func + 1);
+
+	call_flags = 0;  /* not protected, respect reclimit, not constructor */
+
+	rc = duk_handle_call(thr,           /* thread */
+	                     nargs,         /* num_stack_args */
+	                     call_flags);   /* call_flags */
+	DUK_UNREF(rc);
+}
+
+void duk_call_method(duk_context *ctx, duk_idx_t nargs) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_small_uint_t call_flags;
+	duk_idx_t idx_func;
+	duk_int_t rc;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(thr != NULL);
+
+	idx_func = duk_get_top(ctx) - nargs - 2;  /* must work for nargs <= 0 */
+	if (idx_func < 0 || nargs < 0) {
+		/* note that we can't reliably pop anything here */
+		DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_CALL_ARGS);
+	}
+
+	call_flags = 0;  /* not protected, respect reclimit, not constructor */
+
+	rc = duk_handle_call(thr,           /* thread */
+	                     nargs,         /* num_stack_args */
+	                     call_flags);   /* call_flags */
+	DUK_UNREF(rc);
+}
+
+void duk_call_prop(duk_context *ctx, duk_idx_t obj_index, duk_idx_t nargs) {
+	/*
+	 *  XXX: if duk_handle_call() took values through indices, this could be
+	 *  made much more sensible.  However, duk_handle_call() needs to fudge
+	 *  the 'this' and 'func' values to handle bound function chains, which
+	 *  is now done "in-place", so this is not a trivial change.
+	 */
+
+	obj_index = duk_require_normalize_index(ctx, obj_index);  /* make absolute */
+
+	duk__call_prop_prep_stack(ctx, obj_index, nargs);
+
+	duk_call_method(ctx, nargs);
+}
+
+duk_int_t duk_pcall(duk_context *ctx, duk_idx_t nargs) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_small_uint_t call_flags;
+	duk_idx_t idx_func;
+	duk_int_t rc;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(thr != NULL);
+
+	idx_func = duk_get_top(ctx) - nargs - 1;  /* must work for nargs <= 0 */
+	if (idx_func < 0 || nargs < 0) {
+		/* We can't reliably pop anything here because the stack input
+		 * shape is incorrect.  So we throw an error; if the caller has
+		 * no catch point for this, a fatal error will occur.  Another
+		 * alternative would be to just return an error.  But then the
+		 * stack would be in an unknown state which might cause some
+		 * very hard to diagnose problems later on.  Also note that even
+		 * if we did not throw an error here, the underlying call handler
+		 * might STILL throw an out-of-memory error or some other internal
+		 * fatal error.
+		 */
+		DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_CALL_ARGS);
+		return DUK_EXEC_ERROR;  /* unreachable */
+	}
+
+	/* awkward; we assume there is space for this */
+	duk_push_undefined(ctx);
+	duk_insert(ctx, idx_func + 1);
+
+	call_flags = DUK_CALL_FLAG_PROTECTED;  /* protected, respect reclimit, not constructor */
+
+	rc = duk_handle_call(thr,           /* thread */
+	                     nargs,         /* num_stack_args */
+	                     call_flags);   /* call_flags */
+
+	return rc;
+}
+
+duk_int_t duk_pcall_method(duk_context *ctx, duk_idx_t nargs) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_small_uint_t call_flags;
+	duk_idx_t idx_func;
+	duk_int_t rc;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(thr != NULL);
+
+	idx_func = duk_get_top(ctx) - nargs - 2;  /* must work for nargs <= 0 */
+	if (idx_func < 0 || nargs < 0) {
+		/* See comments in duk_pcall(). */
+		DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_CALL_ARGS);
+		return DUK_EXEC_ERROR;  /* unreachable */
+	}
+
+	call_flags = DUK_CALL_FLAG_PROTECTED;  /* protected, respect reclimit, not constructor */
+
+	rc = duk_handle_call(thr,           /* thread */
+	                     nargs,         /* num_stack_args */
+	                     call_flags);   /* call_flags */
+
+	return rc;
+}
+
+static duk_ret_t duk__pcall_prop_raw(duk_context *ctx) {
+	duk_idx_t obj_index;
+	duk_idx_t nargs;
+
+	/* Get the original arguments.  Note that obj_index may be a relative
+	 * index so the stack must have the same top when we use it.
+	 */
+
+	obj_index = (duk_idx_t) duk_get_int(ctx, -2);
+	nargs = (duk_idx_t) duk_get_int(ctx, -1);
+	duk_pop_2(ctx);
+
+	obj_index = duk_require_normalize_index(ctx, obj_index);  /* make absolute */
+	duk__call_prop_prep_stack(ctx, obj_index, nargs);
+	duk_call_method(ctx, nargs);
+	return 1;
+}
+
+duk_int_t duk_pcall_prop(duk_context *ctx, duk_idx_t obj_index, duk_idx_t nargs) {
+	/*
+	 *  Must be careful to catch errors related to value stack manipulation
+	 *  and property lookup, not just the call itself.
+	 */
+
+	duk_push_idx(ctx, obj_index);
+	duk_push_idx(ctx, nargs);
+
+	/* Inputs: explicit arguments (nargs), +1 for key, +2 for obj_index/nargs passing.
+	 * If the value stack does not contain enough args, an error is thrown; this matches
+	 * behavior of the other protected call API functions.
+	 */
+	return duk_safe_call(ctx, duk__pcall_prop_raw, nargs + 1 + 2 /*nargs*/, 1 /*nrets*/);
+}
+
+duk_int_t duk_safe_call(duk_context *ctx, duk_safe_call_function func, duk_idx_t nargs, duk_idx_t nrets) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_int_t rc;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(thr != NULL);
+
+	if (duk_get_top(ctx) < nargs || nrets < 0) {
+		/* See comments in duk_pcall(). */
+		DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_CALL_ARGS);
+		return DUK_EXEC_ERROR;  /* unreachable */
+	}
+
+	rc = duk_handle_safe_call(thr,           /* thread */
+	                          func,          /* func */
+	                          nargs,         /* num_stack_args */
+	                          nrets);        /* num_stack_res */
+
+	return rc;
+}
+
+void duk_new(duk_context *ctx, duk_idx_t nargs) {
+	/*
+	 *  There are two [[Construct]] operations in the specification:
+	 *
+	 *    - E5 Section 13.2.2: for Function objects
+	 *    - E5 Section 15.3.4.5.2: for "bound" Function objects
+	 *
+	 *  The chain of bound functions is resolved in Section 15.3.4.5.2,
+	 *  with arguments "piling up" until the [[Construct]] internal
+	 *  method is called on the final, actual Function object.  Note
+	 *  that the "prototype" property is looked up *only* from the
+	 *  final object, *before* calling the constructor.
+	 *
+	 *  Currently we follow the bound function chain here to get the
+	 *  "prototype" property value from the final, non-bound function.
+	 *  However, we let duk_handle_call() handle the argument "piling"
+	 *  when the constructor is called.  The bound function chain is
+	 *  thus now processed twice.
+	 *
+	 *  When constructing new Array instances, an unnecessary object is
+	 *  created and discarded now: the standard [[Construct]] creates an
+	 *  object, and calls the Array constructor.  The Array constructor
+	 *  returns an Array instance, which is used as the result value for
+	 *  the "new" operation; the object created before the Array constructor
+	 *  call is discarded.
+	 *
+	 *  This would be easy to fix, e.g. by knowing that the Array constructor
+	 *  will always create a replacement object and skip creating the fallback
+	 *  object in that case.
+	 *
+	 *  Note: functions called via "new" need to know they are called as a
+	 *  constructor.  For instance, built-in constructors behave differently
+	 *  depending on how they are called.
+	 */
+
+	/* XXX: merge this with duk_js_call.c, as this function implements
+	 * core semantics (or perhaps merge the two files altogether).
+	 */
+
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hobject *proto;
+	duk_hobject *cons;
+	duk_hobject *fallback;
+	duk_idx_t idx_cons;
+	duk_small_uint_t call_flags;
+	duk_int_t rc;
+
+	/* [... constructor arg1 ... argN] */
+
+	idx_cons = duk_require_normalize_index(ctx, -nargs - 1);
+
+	DUK_DDD(DUK_DDDPRINT("top=%ld, nargs=%ld, idx_cons=%ld",
+	                     (long) duk_get_top(ctx), (long) nargs, (long) idx_cons));
+
+	/* XXX: code duplication */
+
+	/*
+	 *  Figure out the final, non-bound constructor, to get "prototype"
+	 *  property.
+	 */
+
+	duk_dup(ctx, idx_cons);
+	for (;;) {
+		cons = duk_get_hobject(ctx, -1);
+		if (cons == NULL || !DUK_HOBJECT_HAS_CONSTRUCTABLE(cons)) {
+			/* Checking constructability from anything else than the
+			 * initial constructor is not strictly necessary, but a
+			 * nice sanity check.
+			 */
+			goto not_constructable;
+		}
+		if (!DUK_HOBJECT_HAS_BOUND(cons)) {
+			break;
+		}
+		duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_TARGET);  /* -> [... cons target] */
+		duk_remove(ctx, -2);                                  /* -> [... target] */
+	}
+	DUK_ASSERT(cons != NULL && !DUK_HOBJECT_HAS_BOUND(cons));
+
+	/* [... constructor arg1 ... argN final_cons] */
+
+	/*
+	 *  Create "fallback" object to be used as the object instance,
+	 *  unless the constructor returns a replacement value.
+	 *  Its internal prototype needs to be set based on "prototype"
+	 *  property of the constructor.
+	 */
+
+	duk_push_object(ctx);  /* class Object, extensible */
+
+	/* [... constructor arg1 ... argN final_cons fallback] */
+
+	duk_get_prop_stridx(ctx, -2, DUK_STRIDX_PROTOTYPE);
+	proto = duk_get_hobject(ctx, -1);
+	if (!proto) {
+		DUK_DDD(DUK_DDDPRINT("constructor has no 'prototype' property, or value not an object "
+		                     "-> leave standard Object prototype as fallback prototype"));
+	} else {
+		DUK_DDD(DUK_DDDPRINT("constructor has 'prototype' property with object value "
+		                     "-> set fallback prototype to that value: %!iO", (duk_heaphdr *) proto));
+		fallback = duk_get_hobject(ctx, -2);
+		DUK_ASSERT(fallback != NULL);
+		DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, fallback, proto);
+	}
+	duk_pop(ctx);
+
+	/* [... constructor arg1 ... argN final_cons fallback] */
+
+	/*
+	 *  Manipulate callstack for the call.
+	 */
+
+	duk_dup_top(ctx);
+	duk_insert(ctx, idx_cons + 1);  /* use fallback as 'this' value */
+	duk_insert(ctx, idx_cons);      /* also stash it before constructor,
+	                                 * in case we need it (as the fallback value)
+	                                 */
+	duk_pop(ctx);                   /* pop final_cons */
+
+
+	/* [... fallback constructor fallback(this) arg1 ... argN];
+	 * Note: idx_cons points to first 'fallback', not 'constructor'.
+	 */
+
+	DUK_DDD(DUK_DDDPRINT("before call, idx_cons+1 (constructor) -> %!T, idx_cons+2 (fallback/this) -> %!T, "
+	                     "nargs=%ld, top=%ld",
+	                     (duk_tval *) duk_get_tval(ctx, idx_cons + 1),
+	                     (duk_tval *) duk_get_tval(ctx, idx_cons + 2),
+	                     (long) nargs,
+	                     (long) duk_get_top(ctx)));
+
+	/*
+	 *  Call the constructor function (called in "constructor mode").
+	 */
+
+	call_flags = DUK_CALL_FLAG_CONSTRUCTOR_CALL;  /* not protected, respect reclimit, is a constructor call */
+
+	rc = duk_handle_call(thr,           /* thread */
+	                     nargs,         /* num_stack_args */
+	                     call_flags);   /* call_flags */
+	DUK_UNREF(rc);
+
+	/* [... fallback retval] */
+
+	DUK_DDD(DUK_DDDPRINT("constructor call finished, rc=%ld, fallback=%!iT, retval=%!iT",
+	                     (long) rc,
+	                     (duk_tval *) duk_get_tval(ctx, -2),
+	                     (duk_tval *) duk_get_tval(ctx, -1)));
+
+	/*
+	 *  Determine whether to use the constructor return value as the created
+	 *  object instance or not.
+	 */
+
+	if (duk_is_object(ctx, -1)) {
+		duk_remove(ctx, -2);
+	} else {
+		duk_pop(ctx);
+	}
+
+	/*
+	 *  Augment created errors upon creation (not when they are thrown or
+	 *  rethrown).  __FILE__ and __LINE__ are not desirable here; the call
+	 *  stack reflects the caller which is correct.
+	 */
+
+#ifdef DUK_USE_AUGMENT_ERROR_CREATE
+	duk_err_augment_error_create(thr, thr, NULL, 0, 1 /*noblame_fileline*/);
+#endif
+
+	/* [... retval] */
+
+	return;
+
+ not_constructable:
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_CONSTRUCTABLE);
+}
+
+duk_bool_t duk_is_constructor_call(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_activation *act;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT_DISABLE(thr->callstack_top >= 0);
+
+	act = duk_hthread_get_current_activation(thr);
+	return (act != NULL && (act->flags & DUK_ACT_FLAG_CONSTRUCT) != 0 ? 1 : 0);
+}
+
+duk_bool_t duk_is_strict_call(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_activation *act;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT_DISABLE(thr->callstack_top >= 0);
+
+	act = duk_hthread_get_current_activation(thr);
+	return (act != NULL && (act->flags & DUK_ACT_FLAG_STRICT) != 0 ? 1 : 0);
+}
+
+/*
+ *  Duktape/C function magic
+ */
+
+duk_int_t duk_get_magic(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_activation *act;
+	duk_hobject *func;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT_DISABLE(thr->callstack_top >= 0);
+
+	act = duk_hthread_get_current_activation(thr);
+	if (act) {
+		func = act->func;
+		DUK_ASSERT(func != NULL);
+
+		if (DUK_HOBJECT_IS_NATIVEFUNCTION(func)) {
+			duk_hnativefunction *nf = (duk_hnativefunction *) func;
+			return (duk_int_t) nf->magic;
+		}
+	}
+	return 0;
+}
+#line 1 "duk_api_codec.c"
+/*
+ *  Encoding and decoding basic formats: hex, base64.
+ *
+ *  These are in-place operations which may allow an optimized implementation.
+ */
+
+/* include removed: duk_internal.h */
+
+/* dst length must be exactly ceil(len/3)*4 */
+static void duk__base64_encode_helper(const duk_uint8_t *src, const duk_uint8_t *src_end,
+                                      duk_uint8_t *dst, duk_uint8_t *dst_end) {
+	duk_small_uint_t i, snip;
+	duk_uint_fast32_t t;
+	duk_uint_fast8_t x, y;
+
+	DUK_UNREF(dst_end);
+
+	while (src < src_end) {
+		/* read 3 bytes into 't', padded by zero */
+		snip = 4;
+		t = 0;
+		for (i = 0; i < 3; i++) {
+			t = t << 8;
+			if (src >= src_end) {
+				snip--;
+			} else {
+				t += (duk_uint_fast32_t) (*src++);
+			}
+		}
+
+		/*
+		 *  Missing bytes    snip     base64 example
+		 *    0               4         XXXX
+		 *    1               3         XXX=
+		 *    2               2         XX==
+		 */
+
+		DUK_ASSERT(snip >= 2 && snip <= 4);
+
+		for (i = 0; i < 4; i++) {
+			x = (duk_uint_fast8_t) ((t >> 18) & 0x3f);
+			t = t << 6;
+
+			/* A straightforward 64-byte lookup would be faster
+			 * and cleaner, but this is shorter.
+			 */
+			if (i >= snip) {
+				y = '=';
+			} else if (x <= 25) {
+				y = x + 'A';
+			} else if (x <= 51) {
+				y = x - 26 + 'a';
+			} else if (x <= 61) {
+				y = x - 52 + '0';
+			} else if (x == 62) {
+				y = '+';
+			} else {
+				y = '/';
+			}
+
+			DUK_ASSERT(dst < dst_end);
+			*dst++ = (duk_uint8_t) y;
+		}
+	}
+}
+
+static duk_bool_t duk__base64_decode_helper(const duk_uint8_t *src, const duk_uint8_t *src_end,
+                                            duk_uint8_t *dst, duk_uint8_t *dst_end, duk_uint8_t **out_dst_final) {
+	duk_uint_fast32_t t;
+	duk_uint_fast8_t x, y;
+	duk_small_uint_t group_idx;
+
+	DUK_UNREF(dst_end);
+
+	t = 0;
+	group_idx = 0;
+
+	while (src < src_end) {
+		x = *src++;
+
+		if (x >= 'A' && x <= 'Z') {
+			y = x - 'A' + 0;
+		} else if (x >= 'a' && x <= 'z') {
+			y = x - 'a' + 26;
+		} else if (x >= '0' && x <= '9') {
+			y = x - '0' + 52;
+		} else if (x == '+') {
+			y = 62;
+		} else if (x == '/') {
+			y = 63;
+		} else if (x == '=') {
+			/* We don't check the zero padding bytes here right now.
+			 * This seems to be common behavior for base-64 decoders.
+			 */
+
+			if (group_idx == 2) {
+				/* xx== -> 1 byte, t contains 12 bits, 4 on right are zero */
+				t = t >> 4;
+				DUK_ASSERT(dst < dst_end);
+				*dst++ = (duk_uint8_t) t;
+
+				if (src >= src_end) {
+					goto error;
+				}
+				x = *src++;
+				if (x != '=') {
+					goto error;
+				}
+			} else if (group_idx == 3) {
+				/* xxx= -> 2 bytes, t contains 18 bits, 2 on right are zero */
+				t = t >> 2;
+				DUK_ASSERT(dst < dst_end);
+				*dst++ = (duk_uint8_t) ((t >> 8) & 0xff);
+				DUK_ASSERT(dst < dst_end);
+				*dst++ = (duk_uint8_t) (t & 0xff);
+			} else {
+				goto error;
+			}
+
+			/* Here we can choose either to end parsing and ignore
+			 * whatever follows, or to continue parsing in case
+			 * multiple (possibly padded) base64 strings have been
+			 * concatenated.  Currently, keep on parsing.
+			 */
+			t = 0;
+			group_idx = 0;
+			continue;
+		} else if (x == 0x09 || x == 0x0a || x == 0x0d || x == 0x20) {
+			/* allow basic ASCII whitespace */
+			continue;
+		} else {
+			goto error;
+		}
+
+		t = (t << 6) + y;
+
+		if (group_idx == 3) {
+			/* output 3 bytes from 't' */
+			DUK_ASSERT(dst < dst_end);
+			*dst++ = (duk_uint8_t) ((t >> 16) & 0xff);
+			DUK_ASSERT(dst < dst_end);
+			*dst++ = (duk_uint8_t) ((t >> 8) & 0xff);
+			DUK_ASSERT(dst < dst_end);
+			*dst++ = (duk_uint8_t) (t & 0xff);
+			t = 0;
+			group_idx = 0;
+		} else {
+			group_idx++;
+		}
+	}
+
+	if (group_idx != 0) {
+		/* Here we'd have the option of decoding unpadded base64
+		 * (e.g. "xxxxyy" instead of "xxxxyy==".  Currently not
+		 * accepted.
+		 */
+		goto error;
+	}
+
+	*out_dst_final = dst;
+	return 1;
+
+ error:
+	return 0;
+}
+
+const char *duk_base64_encode(duk_context *ctx, duk_idx_t index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_uint8_t *src;
+	duk_size_t srclen;
+	duk_size_t dstlen;
+	duk_uint8_t *dst;
+	const char *ret;
+
+	/* XXX: optimize for string inputs: no need to coerce to a buffer
+	 * which makes a copy of the input.
+	 */
+
+	index = duk_require_normalize_index(ctx, index);
+	src = (duk_uint8_t *) duk_to_buffer(ctx, index, &srclen);
+	/* Note: for srclen=0, src may be NULL */
+
+	/* Computation must not wrap; this limit works for 32-bit size_t:
+	 * >>> srclen = 3221225469
+	 * >>> '%x' % ((srclen + 2) / 3 * 4)
+	 * 'fffffffc'
+	 */
+	if (srclen > 3221225469UL) {
+		goto type_error;
+	}
+	dstlen = (srclen + 2) / 3 * 4;
+	dst = (duk_uint8_t *) duk_push_fixed_buffer(ctx, dstlen);
+
+	duk__base64_encode_helper((const duk_uint8_t *) src, (const duk_uint8_t *) (src + srclen),
+	                          dst, (dst + dstlen));
+
+	ret = duk_to_string(ctx, -1);
+	duk_replace(ctx, index);
+	return ret;
+
+ type_error:
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "base64 encode failed");
+	return NULL;  /* never here */
+}
+
+void duk_base64_decode(duk_context *ctx, duk_idx_t index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	const duk_uint8_t *src;
+	duk_size_t srclen;
+	duk_size_t dstlen;
+	duk_uint8_t *dst;
+	duk_uint8_t *dst_final;
+	duk_bool_t retval;
+
+	/* XXX: optimize for buffer inputs: no need to coerce to a string
+	 * which causes an unnecessary interning.
+	 */
+
+	index = duk_require_normalize_index(ctx, index);
+	src = (const duk_uint8_t *) duk_to_lstring(ctx, index, &srclen);
+
+	/* Computation must not wrap, only srclen + 3 is at risk of
+	 * wrapping because after that the number gets smaller.
+	 * This limit works for 32-bit size_t:
+	 * 0x100000000 - 3 - 1 = 4294967292
+	 */
+	if (srclen > 4294967292UL) {
+		goto type_error;
+	}
+	dstlen = (srclen + 3) / 4 * 3;  /* upper limit */
+	dst = (duk_uint8_t *) duk_push_dynamic_buffer(ctx, dstlen);
+	/* Note: for dstlen=0, dst may be NULL */
+
+	retval = duk__base64_decode_helper((const duk_uint8_t *) src, (const duk_uint8_t *) (src + srclen),
+	                                   dst, dst + dstlen, &dst_final);
+	if (!retval) {
+		goto type_error;
+	}
+
+	/* XXX: convert to fixed buffer? */
+	(void) duk_resize_buffer(ctx, -1, (duk_size_t) (dst_final - dst));
+	duk_replace(ctx, index);
+	return;
+
+ type_error:
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "base64 decode failed");
+}
+
+const char *duk_hex_encode(duk_context *ctx, duk_idx_t index) {
+	duk_uint8_t *data;
+	duk_size_t len;
+	duk_size_t i;
+	duk_uint_fast8_t t;
+	duk_uint8_t *buf;
+	const char *ret;
+
+	/* XXX: special case for input string, no need to coerce to buffer */
+
+	index = duk_require_normalize_index(ctx, index);
+	data = (duk_uint8_t *) duk_to_buffer(ctx, index, &len);
+	DUK_ASSERT(data != NULL);
+
+	buf = (unsigned char *) duk_push_fixed_buffer(ctx, len * 2);
+	DUK_ASSERT(buf != NULL);
+	/* buf is always zeroed */
+
+	for (i = 0; i < len; i++) {
+		t = (duk_uint_fast8_t) data[i];
+		buf[i*2 + 0] = duk_lc_digits[t >> 4];
+		buf[i*2 + 1] = duk_lc_digits[t & 0x0f];
+	}
+
+	ret = duk_to_string(ctx, -1);
+	duk_replace(ctx, index);
+	return ret;
+}
+
+void duk_hex_decode(duk_context *ctx, duk_idx_t index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	const duk_uint8_t *str;
+	duk_size_t len;
+	duk_size_t i;
+	duk_small_int_t t;
+	duk_uint8_t *buf;
+
+	/* XXX: optimize for buffer inputs: no need to coerce to a string
+	 * which causes an unnecessary interning.
+	 */
+
+	index = duk_require_normalize_index(ctx, index);
+	str = (const duk_uint8_t *) duk_to_lstring(ctx, index, &len);
+	DUK_ASSERT(str != NULL);
+
+	if (len & 0x01) {
+		goto type_error;
+	}
+
+	buf = (duk_uint8_t *) duk_push_fixed_buffer(ctx, len / 2);
+	DUK_ASSERT(buf != NULL);
+	/* buf is always zeroed */
+
+	for (i = 0; i < len; i++) {
+		t = str[i];
+		DUK_ASSERT(t >= 0 && t <= 0xff);
+		t = duk_hex_dectab[t];
+		if (DUK_UNLIKELY(t < 0)) {
+			goto type_error;
+		}
+
+		if (i & 0x01) {
+			buf[i >> 1] += (duk_uint8_t) t;
+		} else {
+			buf[i >> 1] = (duk_uint8_t) (t << 4);
+		}
+	}
+
+	duk_replace(ctx, index);
+	return;
+
+ type_error:
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "hex decode failed");
+}
+
+const char *duk_json_encode(duk_context *ctx, duk_idx_t index) {
+#ifdef DUK_USE_ASSERTIONS
+	duk_idx_t top_at_entry = duk_get_top(ctx);
+#endif
+	const char *ret;
+
+	index = duk_require_normalize_index(ctx, index);
+	duk_bi_json_stringify_helper(ctx,
+	                             index /*idx_value*/,
+	                             DUK_INVALID_INDEX /*idx_replacer*/,
+	                             DUK_INVALID_INDEX /*idx_space*/,
+	                             0 /*flags*/);
+	DUK_ASSERT(duk_is_string(ctx, -1));
+	duk_replace(ctx, index);
+	ret = duk_get_string(ctx, index);
+
+	DUK_ASSERT(duk_get_top(ctx) == top_at_entry);
+
+	return ret;
+}
+
+void duk_json_decode(duk_context *ctx, duk_idx_t index) {
+#ifdef DUK_USE_ASSERTIONS
+	duk_idx_t top_at_entry = duk_get_top(ctx);
+#endif
+
+	index = duk_require_normalize_index(ctx, index);
+	duk_bi_json_parse_helper(ctx,
+	                         index /*idx_value*/,
+	                         DUK_INVALID_INDEX /*idx_reviver*/,
+	                         0 /*flags*/);
+	duk_replace(ctx, index);
+
+	DUK_ASSERT(duk_get_top(ctx) == top_at_entry);
+}
+#line 1 "duk_api_compile.c"
+/*
+ *  Compilation and evaluation
+ */
+
+/* include removed: duk_internal.h */
+
+typedef struct duk__compile_raw_args duk__compile_raw_args;
+struct duk__compile_raw_args {
+	duk_size_t src_length;  /* should be first on 64-bit platforms */
+	const duk_uint8_t *src_buffer;
+	duk_uint_t flags;
+};
+
+/* Eval is just a wrapper now. */
+duk_int_t duk_eval_raw(duk_context *ctx, const char *src_buffer, duk_size_t src_length, duk_uint_t flags) {
+	duk_uint_t comp_flags;
+	duk_int_t rc;
+
+	/* [ ... source? filename ] (depends on flags) */
+
+	comp_flags = flags;
+	comp_flags |= DUK_COMPILE_EVAL;
+	if (duk_is_strict_call(ctx)) {
+		comp_flags |= DUK_COMPILE_STRICT;
+	}
+	rc = duk_compile_raw(ctx, src_buffer, src_length, comp_flags);  /* may be safe, or non-safe depending on flags */
+
+	/* [ ... closure/error ] */
+
+	if (rc != DUK_EXEC_SUCCESS) {
+		rc = DUK_EXEC_ERROR;
+		goto got_rc;
+	}
+
+	if (flags & DUK_COMPILE_SAFE) {
+		rc = duk_pcall(ctx, 0);
+	} else {
+		duk_call(ctx, 0);
+		rc = DUK_EXEC_SUCCESS;
+	}
+
+	/* [ ... result/error ] */
+
+ got_rc:
+	if (flags & DUK_COMPILE_NORESULT) {
+		duk_pop(ctx);
+	}
+
+	return rc;
+}
+
+/* Helper which can be called both directly and with duk_safe_call(). */
+static duk_ret_t duk__do_compile(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk__compile_raw_args *comp_args;
+	duk_uint_t flags;
+	duk_small_uint_t comp_flags;
+	duk_hcompiledfunction *h_templ;
+
+	/* [ ... source? filename &comp_args ] (depends on flags) */
+
+	comp_args = (duk__compile_raw_args *) duk_require_pointer(ctx, -1);
+	flags = comp_args->flags;
+	duk_pop(ctx);
+
+	/* [ ... source? filename ] */
+
+	if (!comp_args->src_buffer) {
+		duk_hstring *h_sourcecode;
+
+		if (flags & DUK_COMPILE_NOSOURCE) {
+			DUK_ERROR(thr, DUK_ERR_API_ERROR, "no sourcecode");
+		}
+		h_sourcecode = duk_require_hstring(ctx, -2);
+		comp_args->src_buffer = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h_sourcecode);
+		comp_args->src_length = (duk_size_t) DUK_HSTRING_GET_BYTELEN(h_sourcecode);
+	}
+	DUK_ASSERT(comp_args->src_buffer != NULL);
+
+	/* XXX: unnecessary translation of flags */
+	comp_flags = 0;
+	if (flags & DUK_COMPILE_EVAL) {
+		comp_flags |= DUK_JS_COMPILE_FLAG_EVAL;
+	}
+	if (flags & DUK_COMPILE_FUNCTION) {
+		comp_flags |= DUK_JS_COMPILE_FLAG_EVAL |
+		              DUK_JS_COMPILE_FLAG_FUNCEXPR;
+	}
+	if (flags & DUK_COMPILE_STRICT) {
+		comp_flags |= DUK_JS_COMPILE_FLAG_STRICT;
+	}
+
+	/* [ ... source? filename ] */
+
+	duk_js_compile(thr, comp_args->src_buffer, comp_args->src_length, comp_flags);
+
+	/* [ ... source? func_template ] */
+
+	if (flags & DUK_COMPILE_NOSOURCE) {
+		;
+	} else {
+		duk_remove(ctx, -2);
+	}
+
+	/* [ ... func_template ] */
+
+	h_templ = (duk_hcompiledfunction *) duk_get_hobject(ctx, -1);
+	DUK_ASSERT(h_templ != NULL);
+	duk_js_push_closure(thr,
+	                   h_templ,
+	                   thr->builtins[DUK_BIDX_GLOBAL_ENV],
+	                   thr->builtins[DUK_BIDX_GLOBAL_ENV]);
+	duk_remove(ctx, -2);   /* -> [ ... closure ] */
+
+	/* [ ... closure ] */
+
+	return 1;
+}
+
+duk_int_t duk_compile_raw(duk_context *ctx, const char *src_buffer, duk_size_t src_length, duk_uint_t flags) {
+	duk__compile_raw_args comp_args_alloc;
+	duk__compile_raw_args *comp_args = &comp_args_alloc;
+
+	if ((flags & DUK_COMPILE_STRLEN) && (src_buffer != NULL)) {
+		/* String length is computed here to avoid multiple evaluation
+		 * of a macro argument in the calling side.
+		 */
+		src_length = DUK_STRLEN(src_buffer);
+	}
+
+	comp_args->src_buffer = (const duk_uint8_t *) src_buffer;
+	comp_args->src_length = src_length;
+	comp_args->flags = flags;
+	duk_push_pointer(ctx, (void *) comp_args);
+
+	/* [ ... source? filename &comp_args ] (depends on flags) */
+
+	if (flags & DUK_COMPILE_SAFE) {
+		duk_int_t rc;
+		duk_int_t nargs;
+		duk_int_t nrets = 1;
+
+		/* Arguments are either: [ filename &comp_args ] or [ source filename &comp_args ] */
+		nargs = (flags & DUK_COMPILE_NOSOURCE) ? 2 : 3;
+		rc = duk_safe_call(ctx, duk__do_compile, nargs, nrets);
+
+		/* [ ... closure ] */
+		return rc;
+	}
+
+	(void) duk__do_compile(ctx);
+
+	/* [ ... closure ] */
+	return DUK_EXEC_SUCCESS;
+}
+#line 1 "duk_api_debug.c"
+/*
+ *  Debugging related API calls
+ */
+
+/* include removed: duk_internal.h */
+
+void duk_push_context_dump(duk_context *ctx) {
+	duk_idx_t idx;
+	duk_idx_t top;
+
+	/* We don't duk_require_stack() here now, but rely on the caller having
+	 * enough space.
+	 */
+
+	top = duk_get_top(ctx);
+	duk_push_array(ctx);
+	for (idx = 0; idx < top; idx++) {
+		duk_dup(ctx, idx);
+		duk_put_prop_index(ctx, -2, idx);
+	}
+
+	/* FIXME: conversion errors should not propagate outwards.
+	 * Perhaps values need to be coerced individually?
+	 */
+	duk_bi_json_stringify_helper(ctx,
+	                             duk_get_top_index(ctx),  /*idx_value*/
+	                             DUK_INVALID_INDEX,  /*idx_replacer*/
+	                             DUK_INVALID_INDEX,  /*idx_space*/
+	                             DUK_JSON_FLAG_EXT_CUSTOM |
+	                             DUK_JSON_FLAG_ASCII_ONLY |
+	                             DUK_JSON_FLAG_AVOID_KEY_QUOTES /*flags*/);
+
+	duk_push_sprintf(ctx, "ctx: top=%ld, stack=%s", (long) top, (const char *) duk_safe_to_string(ctx, -1));
+	duk_replace(ctx, -3);  /* [ ... arr jsonx(arr) res ] -> [ ... res jsonx(arr) ] */
+	duk_pop(ctx);
+	DUK_ASSERT(duk_is_string(ctx, -1));
+}
+#line 1 "duk_api_logging.c"
+/*
+ *  Logging
+ *
+ *  Current logging primitive is a sprintf-style log which is convenient
+ *  for most C code.  Another useful primitive would be to log N arguments
+ *  from value stack (like the Ecmascript binding does).
+ */
+
+/* include removed: duk_internal.h */
+
+void duk_log(duk_context *ctx, duk_int_t level, const char *fmt, ...) {
+	va_list ap;
+	/* stridx_logfunc[] must be static to allow initializer with old compilers like BCC */
+	static const duk_uint16_t stridx_logfunc[6] = {
+		DUK_STRIDX_LC_TRACE, DUK_STRIDX_LC_DEBUG, DUK_STRIDX_LC_INFO,
+		DUK_STRIDX_LC_WARN, DUK_STRIDX_LC_ERROR, DUK_STRIDX_LC_FATAL
+	};
+
+	if (level < 0) {
+		level = 0;
+	} else if (level > (int) (sizeof(stridx_logfunc) / sizeof(duk_uint16_t)) - 1) {
+		level = (int) (sizeof(stridx_logfunc) / sizeof(duk_uint16_t)) - 1;
+	}
+
+	duk_push_hobject_bidx(ctx, DUK_BIDX_LOGGER_CONSTRUCTOR);
+	duk_get_prop_stridx(ctx, -1, DUK_STRIDX_CLOG);
+	duk_get_prop_stridx(ctx, -1, stridx_logfunc[level]);
+	duk_dup(ctx, -2);
+
+	/* [ ... Logger clog logfunc clog ] */
+
+	va_start(ap, fmt);
+	duk_push_vsprintf(ctx, fmt, ap);
+	va_end(ap);
+
+	/* [ ... Logger clog logfunc clog(=this) msg ] */
+
+	duk_call_method(ctx, 1 /*nargs*/);
+
+	/* [ ... Logger clog res ] */
+
+	duk_pop_3(ctx);
+}
+#line 1 "duk_api_memory.c"
+/*
+ *  Memory calls.
+ */
+
+/* include removed: duk_internal.h */
+
+void *duk_alloc_raw(duk_context *ctx, duk_size_t size) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+
+	DUK_ASSERT(ctx != NULL);
+
+	return DUK_ALLOC_RAW(thr->heap, size);
+}
+
+void duk_free_raw(duk_context *ctx, void *ptr) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+
+	DUK_ASSERT(ctx != NULL);
+
+	DUK_FREE_RAW(thr->heap, ptr);
+}
+
+void *duk_realloc_raw(duk_context *ctx, void *ptr, duk_size_t size) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+
+	DUK_ASSERT(ctx != NULL);
+
+	return DUK_REALLOC_RAW(thr->heap, ptr, size);
+}
+
+void *duk_alloc(duk_context *ctx, duk_size_t size) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+
+	DUK_ASSERT(ctx != NULL);
+
+	return DUK_ALLOC(thr->heap, size);
+}
+
+void duk_free(duk_context *ctx, void *ptr) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+
+	DUK_ASSERT(ctx != NULL);
+
+	DUK_FREE(thr->heap, ptr);
+}
+
+void *duk_realloc(duk_context *ctx, void *ptr, duk_size_t size) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+
+	DUK_ASSERT(ctx != NULL);
+
+	/*
+	 *  Note: since this is an exposed API call, there should be
+	 *  no way a mark-and-sweep could have a side effect on the
+	 *  memory allocation behind 'ptr'; the pointer should never
+	 *  be something that Duktape wants to change.
+	 *
+	 *  Thus, no need to use DUK_REALLOC_INDIRECT (and we don't
+	 *  have the storage location here anyway).
+	 */
+
+	return DUK_REALLOC(thr->heap, ptr, size);
+}
+
+void duk_get_memory_functions(duk_context *ctx, duk_memory_functions *out_funcs) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_heap *heap;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(out_funcs != NULL);
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->heap != NULL);
+
+	heap = thr->heap;
+	out_funcs->alloc = heap->alloc_func;
+	out_funcs->realloc = heap->realloc_func;
+	out_funcs->free = heap->free_func;
+	out_funcs->udata = heap->alloc_udata;
+}
+
+void duk_gc(duk_context *ctx, duk_uint_t flags) {
+#ifdef DUK_USE_MARK_AND_SWEEP
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_heap *heap;
+
+	DUK_UNREF(flags);
+
+	if (!ctx) {
+		return;
+	}
+	heap = thr->heap;
+	DUK_ASSERT(heap != NULL);
+
+	DUK_D(DUK_DPRINT("mark-and-sweep requested by application"));
+	duk_heap_mark_and_sweep(heap, 0);
+#else
+	DUK_D(DUK_DPRINT("mark-and-sweep requested by application but mark-and-sweep not enabled, ignoring"));
+	DUK_UNREF(ctx);
+	DUK_UNREF(flags);
+#endif
+}
+#line 1 "duk_api_object.c"
+/*
+ *  Object handling: property access and other support functions.
+ */
+
+/* include removed: duk_internal.h */
+
+/*
+ *  Property handling
+ *
+ *  The API exposes only the most common property handling functions.
+ *  The caller can invoke Ecmascript built-ins for full control (e.g.
+ *  defineProperty, getOwnPropertyDescriptor).
+ */
+
+duk_bool_t duk_get_prop(duk_context *ctx, duk_idx_t obj_index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv_obj;
+	duk_tval *tv_key;
+	duk_bool_t rc;
+
+	DUK_ASSERT(ctx != NULL);
+
+	/* Note: copying tv_obj and tv_key to locals to shield against a valstack
+	 * resize is not necessary for a property get right now.
+	 */
+
+	tv_obj = duk_require_tval(ctx, obj_index);
+	tv_key = duk_require_tval(ctx, -1);
+
+	rc = duk_hobject_getprop(thr, tv_obj, tv_key);
+	DUK_ASSERT(rc == 0 || rc == 1);
+	/* a value is left on stack regardless of rc */
+
+	duk_remove(ctx, -2);  /* remove key */
+	return rc;  /* 1 if property found, 0 otherwise */
+}
+
+duk_bool_t duk_get_prop_string(duk_context *ctx, duk_idx_t obj_index, const char *key) {
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(key != NULL);
+
+	obj_index = duk_require_normalize_index(ctx, obj_index);
+	duk_push_string(ctx, key);
+	return duk_get_prop(ctx, obj_index);
+}
+
+duk_bool_t duk_get_prop_index(duk_context *ctx, duk_idx_t obj_index, duk_uarridx_t arr_index) {
+	DUK_ASSERT(ctx != NULL);
+
+	obj_index = duk_require_normalize_index(ctx, obj_index);
+	duk_push_number(ctx, (double) arr_index);
+	return duk_get_prop(ctx, obj_index);
+}
+
+duk_bool_t duk_get_prop_stridx(duk_context *ctx, duk_idx_t obj_index, duk_small_int_t stridx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT_DISABLE(stridx >= 0);
+	DUK_ASSERT(stridx < DUK_HEAP_NUM_STRINGS);
+
+	obj_index = duk_require_normalize_index(ctx, obj_index);
+	duk_push_hstring(ctx, thr->strs[stridx]);
+	return duk_get_prop(ctx, obj_index);
+}
+
+duk_bool_t duk_get_prop_stridx_boolean(duk_context *ctx, duk_idx_t obj_index, duk_small_int_t stridx, duk_bool_t *out_has_prop) {
+	duk_bool_t rc;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT_DISABLE(stridx >= 0);
+	DUK_ASSERT(stridx < DUK_HEAP_NUM_STRINGS);
+
+	rc = duk_get_prop_stridx(ctx, obj_index, stridx);
+	if (out_has_prop) {
+		*out_has_prop = rc;
+	}
+	rc = duk_to_boolean(ctx, -1);
+	DUK_ASSERT(rc == 0 || rc == 1);
+	duk_pop(ctx);
+	return rc;
+}
+
+duk_bool_t duk_put_prop(duk_context *ctx, duk_idx_t obj_index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv_obj;
+	duk_tval *tv_key;
+	duk_tval *tv_val;
+	duk_small_int_t throw_flag;
+	duk_bool_t rc;
+
+	DUK_ASSERT(ctx != NULL);
+
+	/* Note: copying tv_obj and tv_key to locals to shield against a valstack
+	 * resize is not necessary for a property put right now (putprop protects
+	 * against it internally).
+	 */
+
+	tv_obj = duk_require_tval(ctx, obj_index);
+	tv_key = duk_require_tval(ctx, -2);
+	tv_val = duk_require_tval(ctx, -1);
+	throw_flag = duk_is_strict_call(ctx);  /* FIXME */
+
+	rc = duk_hobject_putprop(thr, tv_obj, tv_key, tv_val, throw_flag);
+	DUK_ASSERT(rc == 0 || rc == 1);
+
+	duk_pop_2(ctx);  /* remove key and value */
+	return rc;  /* 1 if property found, 0 otherwise */
+}
+
+duk_bool_t duk_put_prop_string(duk_context *ctx, duk_idx_t obj_index, const char *key) {
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(key != NULL);
+
+	obj_index = duk_require_normalize_index(ctx, obj_index);
+	duk_push_string(ctx, key);
+	duk_swap_top(ctx, -2);  /* [val key] -> [key val] */
+	return duk_put_prop(ctx, obj_index);
+}
+
+duk_bool_t duk_put_prop_index(duk_context *ctx, duk_idx_t obj_index, duk_uarridx_t arr_index) {
+	DUK_ASSERT(ctx != NULL);
+
+	obj_index = duk_require_normalize_index(ctx, obj_index);
+	duk_push_number(ctx, (double) arr_index);
+	duk_swap_top(ctx, -2);  /* [val key] -> [key val] */
+	return duk_put_prop(ctx, obj_index);
+}
+
+duk_bool_t duk_put_prop_stridx(duk_context *ctx, duk_idx_t obj_index, duk_small_int_t stridx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT_DISABLE(stridx >= 0);
+	DUK_ASSERT(stridx < DUK_HEAP_NUM_STRINGS);
+
+	obj_index = duk_require_normalize_index(ctx, obj_index);
+	duk_push_hstring(ctx, thr->strs[stridx]);
+	duk_swap_top(ctx, -2);  /* [val key] -> [key val] */
+	return duk_put_prop(ctx, obj_index);
+}
+
+duk_bool_t duk_del_prop(duk_context *ctx, duk_idx_t obj_index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv_obj;
+	duk_tval *tv_key;
+	duk_small_int_t throw_flag;
+	duk_bool_t rc;
+
+	DUK_ASSERT(ctx != NULL);
+
+	/* Note: copying tv_obj and tv_key to locals to shield against a valstack
+	 * resize is not necessary for a property delete right now.
+	 */
+
+	tv_obj = duk_require_tval(ctx, obj_index);
+	tv_key = duk_require_tval(ctx, -1);
+	throw_flag = duk_is_strict_call(ctx);  /* FIXME */
+
+	rc = duk_hobject_delprop(thr, tv_obj, tv_key, throw_flag);
+	DUK_ASSERT(rc == 0 || rc == 1);
+
+	duk_pop(ctx);  /* remove key */
+	return rc;
+}
+
+duk_bool_t duk_del_prop_string(duk_context *ctx, duk_idx_t obj_index, const char *key) {
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(key != NULL);
+
+	obj_index = duk_require_normalize_index(ctx, obj_index);
+	duk_push_string(ctx, key);
+	return duk_del_prop(ctx, obj_index);
+}
+
+duk_bool_t duk_del_prop_index(duk_context *ctx, duk_idx_t obj_index, duk_uarridx_t arr_index) {
+	DUK_ASSERT(ctx != NULL);
+
+	obj_index = duk_require_normalize_index(ctx, obj_index);
+	duk_push_number(ctx, (double) arr_index);
+	return duk_del_prop(ctx, obj_index);
+}
+
+duk_bool_t duk_del_prop_stridx(duk_context *ctx, duk_idx_t obj_index, duk_small_int_t stridx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT_DISABLE(stridx >= 0);
+	DUK_ASSERT(stridx < DUK_HEAP_NUM_STRINGS);
+
+	obj_index = duk_require_normalize_index(ctx, obj_index);
+	duk_push_hstring(ctx, thr->strs[stridx]);
+	return duk_del_prop(ctx, obj_index);
+}
+
+duk_bool_t duk_has_prop(duk_context *ctx, duk_idx_t obj_index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval *tv_obj;
+	duk_tval *tv_key;
+	duk_bool_t rc;
+
+	DUK_ASSERT(ctx != NULL);
+
+	/* Note: copying tv_obj and tv_key to locals to shield against a valstack
+	 * resize is not necessary for a property existence check right now.
+	 */
+
+	tv_obj = duk_require_tval(ctx, obj_index);
+	tv_key = duk_require_tval(ctx, -1);
+
+	rc = duk_hobject_hasprop(thr, tv_obj, tv_key);
+	DUK_ASSERT(rc == 0 || rc == 1);
+
+	duk_pop(ctx);  /* remove key */
+	return rc;  /* 1 if property found, 0 otherwise */
+}
+
+duk_bool_t duk_has_prop_string(duk_context *ctx, duk_idx_t obj_index, const char *key) {
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(key != NULL);
+
+	obj_index = duk_require_normalize_index(ctx, obj_index);
+	duk_push_string(ctx, key);
+	return duk_has_prop(ctx, obj_index);
+}
+
+duk_bool_t duk_has_prop_index(duk_context *ctx, duk_idx_t obj_index, duk_uarridx_t arr_index) {
+	DUK_ASSERT(ctx != NULL);
+
+	obj_index = duk_require_normalize_index(ctx, obj_index);
+	duk_push_number(ctx, (double) arr_index);
+	return duk_has_prop(ctx, obj_index);
+}
+
+duk_bool_t duk_has_prop_stridx(duk_context *ctx, duk_idx_t obj_index, duk_small_int_t stridx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT_DISABLE(stridx >= 0);
+	DUK_ASSERT(stridx < DUK_HEAP_NUM_STRINGS);
+
+	obj_index = duk_require_normalize_index(ctx, obj_index);
+	duk_push_hstring(ctx, thr->strs[stridx]);
+	return duk_has_prop(ctx, obj_index);
+}
+
+/* Define own property without inheritance looks and such.  This differs from
+ * [[DefineOwnProperty]] because special behaviors (like Array 'length') are
+ * not invoked by this method.  The caller must be careful to invoke any such
+ * behaviors if necessary.
+ */
+void duk_def_prop(duk_context *ctx, duk_idx_t obj_index, duk_small_uint_t desc_flags) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hobject *obj;
+	duk_hstring *key;
+
+	DUK_ASSERT(ctx != NULL);
+
+	obj = duk_require_hobject(ctx, obj_index);
+	DUK_ASSERT(obj != NULL);
+	key = duk_to_hstring(ctx, -2);
+	DUK_ASSERT(key != NULL);
+	DUK_ASSERT(duk_require_tval(ctx, -1) != NULL);
+
+	duk_hobject_define_property_internal(thr, obj, key, desc_flags);
+
+	duk_pop(ctx);  /* pop key */
+}
+
+void duk_def_prop_index(duk_context *ctx, duk_idx_t obj_index, duk_uarridx_t arr_index, duk_small_uint_t desc_flags) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hobject *obj;
+
+	DUK_ASSERT(ctx != NULL);
+
+	obj = duk_require_hobject(ctx, obj_index);
+	DUK_ASSERT(obj != NULL);
+
+	duk_hobject_define_property_internal_arridx(thr, obj, arr_index, desc_flags);
+	/* value popped by call */
+}
+
+void duk_def_prop_stridx(duk_context *ctx, duk_idx_t obj_index, duk_small_int_t stridx, duk_small_uint_t desc_flags) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hobject *obj;
+	duk_hstring *key;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT_DISABLE(stridx >= 0);
+	DUK_ASSERT(stridx < DUK_HEAP_NUM_STRINGS);
+
+	obj = duk_require_hobject(ctx, obj_index);
+	DUK_ASSERT(obj != NULL);
+	key = thr->strs[stridx];
+	DUK_ASSERT(key != NULL);
+	DUK_ASSERT(duk_require_tval(ctx, -1) != NULL);
+
+	duk_hobject_define_property_internal(thr, obj, key, desc_flags);
+	/* value popped by call */
+}
+
+void duk_def_prop_stridx_builtin(duk_context *ctx, duk_idx_t obj_index, duk_small_int_t stridx, duk_small_int_t builtin_idx, duk_small_uint_t desc_flags) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hobject *obj;
+	duk_hstring *key;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT_DISABLE(stridx >= 0);
+	DUK_ASSERT(stridx < DUK_HEAP_NUM_STRINGS);
+	DUK_ASSERT_DISABLE(builtin_idx >= 0);
+	DUK_ASSERT(builtin_idx < DUK_NUM_BUILTINS);
+
+	obj = duk_require_hobject(ctx, obj_index);
+	DUK_ASSERT(obj != NULL);
+	key = thr->strs[stridx];
+	DUK_ASSERT(key != NULL);
+
+	duk_push_hobject(ctx, thr->builtins[builtin_idx]);
+	duk_hobject_define_property_internal(thr, obj, key, desc_flags);
+	/* value popped by call */
+}
+
+/* This is a rare property helper; it sets the global thrower (E5 Section 13.2.3)
+ * setter/getter into an object property.  This is needed by the 'arguments'
+ * object creation code, function instance creation code, and Function.prototype.bind().
+ */
+
+void duk_def_prop_stridx_thrower(duk_context *ctx, duk_idx_t obj_index, duk_small_int_t stridx, duk_small_uint_t desc_flags) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hobject *obj = duk_require_hobject(ctx, obj_index);
+	duk_hobject *thrower = thr->builtins[DUK_BIDX_TYPE_ERROR_THROWER];
+	duk_hobject_define_accessor_internal(thr, obj, DUK_HTHREAD_GET_STRING(thr, stridx), thrower, thrower, desc_flags);
+}
+
+/*
+ *  Object related
+ *
+ *  Note: seal() and freeze() are accessible through Ecmascript bindings,
+ *  and are not exposed through the API.
+ */
+
+void duk_compact(duk_context *ctx, duk_idx_t obj_index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hobject *obj;
+
+	DUK_ASSERT(ctx != NULL);
+
+	obj = duk_get_hobject(ctx, obj_index);
+	if (obj) {
+		/* Note: this may fail, caller should protect the call if necessary */
+		duk_hobject_compact_props(thr, obj);
+	}
+}
+
+/* FIXME: the duk_hobject_enum.c stack APIs should be reworked */
+
+void duk_enum(duk_context *ctx, duk_idx_t obj_index, duk_uint_t enum_flags) {
+	DUK_ASSERT(ctx != NULL);
+
+	duk_require_hobject(ctx, obj_index);
+	duk_dup(ctx, obj_index);
+	duk_hobject_enumerator_create(ctx, enum_flags);   /* [target] -> [enum] */
+}
+
+duk_bool_t duk_next(duk_context *ctx, duk_idx_t enum_index, duk_bool_t get_value) {
+	DUK_ASSERT(ctx != NULL);
+
+	duk_require_hobject(ctx, enum_index);
+	duk_dup(ctx, enum_index);
+	return duk_hobject_enumerator_next(ctx, get_value);
+}
+
+/*
+ *  Helpers for writing multiple properties
+ */
+
+void duk_put_function_list(duk_context *ctx, duk_idx_t obj_index, const duk_function_list_entry *funcs) {
+	const duk_function_list_entry *ent = funcs;
+
+	DUK_ASSERT(ctx != NULL);
+
+	obj_index = duk_require_normalize_index(ctx, obj_index);
+	if (ent != NULL) {
+		while (ent->key != NULL) {
+			duk_push_c_function(ctx, ent->value, ent->nargs);
+			duk_put_prop_string(ctx, obj_index, ent->key);
+			ent++;
+		}
+	}
+}
+
+void duk_put_number_list(duk_context *ctx, duk_idx_t obj_index, const duk_number_list_entry *numbers) {
+	const duk_number_list_entry *ent = numbers;
+
+	DUK_ASSERT(ctx != NULL);
+
+	obj_index = duk_require_normalize_index(ctx, obj_index);
+	if (ent != NULL) {
+		while (ent->key != NULL) {
+			duk_push_number(ctx, ent->value);
+			duk_put_prop_string(ctx, obj_index, ent->key);
+			ent++;
+		}
+	}
+}
+
+/*
+ *  Shortcut for accessing global object properties
+ */
+
+duk_bool_t duk_get_global_string(duk_context *ctx, const char *key) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_bool_t ret;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(thr->builtins[DUK_BIDX_GLOBAL] != NULL);
+
+	/* XXX: direct implementation */
+
+	duk_push_hobject(ctx, thr->builtins[DUK_BIDX_GLOBAL]);
+	ret = duk_get_prop_string(ctx, -1, key);
+	duk_remove(ctx, -2);
+	return ret;
+}
+#line 1 "duk_api_string.c"
+/*
+ *  String manipulation
+ */
+
+/* include removed: duk_internal.h */
+
+static void duk__concat_and_join_helper(duk_context *ctx, duk_idx_t count_in, duk_bool_t is_join) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_uint_t count;
+	duk_uint_t i;
+	duk_size_t idx;
+	duk_size_t len;
+	duk_hstring *h;
+	duk_uint8_t *buf;
+
+	DUK_ASSERT(ctx != NULL);
+
+	if (DUK_UNLIKELY(count_in <= 0)) {
+		if (count_in < 0) {
+			DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_COUNT);
+			return;
+		}
+		DUK_ASSERT(count_in == 0);
+		duk_push_hstring_stridx(ctx, DUK_STRIDX_EMPTY_STRING);
+		return;
+	}
+	count = (duk_uint_t) count_in;
+
+	if (is_join) {
+		duk_size_t t1, t2, limit;
+		h = duk_to_hstring(ctx, -((duk_idx_t) count) - 1);
+		DUK_ASSERT(h != NULL);
+
+		/* A bit tricky overflow test, see doc/code-issues.txt. */
+		t1 = (duk_size_t) DUK_HSTRING_GET_BYTELEN(h);
+		t2 = (duk_size_t) (count - 1);
+		limit = (duk_size_t) DUK_HSTRING_MAX_BYTELEN;
+		if (DUK_UNLIKELY(t2 != 0 && t1 > limit / t2)) {
+			/* Combined size of separators already overflows */
+			goto error_overflow;
+		}
+		len = (duk_size_t) (t1 * t2);
+	} else {
+		len = (duk_size_t) 0;
+	}
+
+	for (i = count; i >= 1; i--) {
+		duk_size_t new_len;
+		duk_to_string(ctx, -((duk_idx_t) i));
+		h = duk_require_hstring(ctx, -((duk_idx_t) i));
+		new_len = len + (duk_size_t) DUK_HSTRING_GET_BYTELEN(h);
+
+		/* Impose a string maximum length, need to handle overflow
+		 * correctly.
+		 */
+		if (new_len < len ||  /* wrapped */
+		    new_len > (duk_size_t) DUK_HSTRING_MAX_BYTELEN) {
+			goto error_overflow;
+		}
+		len = new_len;
+	}
+
+	DUK_DDD(DUK_DDDPRINT("join/concat %lu strings, total length %lu bytes",
+	                     (unsigned long) count, (unsigned long) len));
+
+	/* use stack allocated buffer to ensure reachability in errors (e.g. intern error) */
+	buf = (duk_uint8_t *) duk_push_fixed_buffer(ctx, len);
+	DUK_ASSERT(buf != NULL);
+
+	/* [... (sep) str1 str2 ... strN buf] */
+
+	idx = 0;
+	for (i = count; i >= 1; i--) {
+		if (is_join && i != count) {
+			h = duk_require_hstring(ctx, -((duk_idx_t) count) - 2);  /* extra -1 for buffer */
+			DUK_MEMCPY(buf + idx, DUK_HSTRING_GET_DATA(h), DUK_HSTRING_GET_BYTELEN(h));
+			idx += DUK_HSTRING_GET_BYTELEN(h);
+		}
+		h = duk_require_hstring(ctx, -((duk_idx_t) i) - 1);  /* extra -1 for buffer */
+		DUK_MEMCPY(buf + idx, DUK_HSTRING_GET_DATA(h), DUK_HSTRING_GET_BYTELEN(h));
+		idx += DUK_HSTRING_GET_BYTELEN(h);
+	}
+
+	DUK_ASSERT(idx == len);
+
+	/* [... (sep) str1 str2 ... strN buf] */
+
+	/* get rid of the strings early to minimize memory use before intern */
+
+	if (is_join) {
+		duk_replace(ctx, -((duk_idx_t) count) - 2);  /* overwrite sep */
+		duk_pop_n(ctx, count);
+	} else {
+		duk_replace(ctx, -((duk_idx_t) count) - 1);  /* overwrite str1 */
+		duk_pop_n(ctx, count-1);
+	}
+
+	/* [... buf] */
+
+	(void) duk_to_string(ctx, -1);
+
+	/* [... res] */
+	return;
+
+ error_overflow:
+	DUK_ERROR(thr, DUK_ERR_RANGE_ERROR, "concat result too long");
+}
+
+void duk_concat(duk_context *ctx, duk_idx_t count) {
+	duk__concat_and_join_helper(ctx, count, 0 /*is_join*/);
+}
+
+void duk_join(duk_context *ctx, duk_idx_t count) {
+	duk__concat_and_join_helper(ctx, count, 1 /*is_join*/);
+}
+
+/* XXX: could map/decode be unified with duk_unicode_support.c code?
+ * Case conversion needs also the character surroundings though.
+ */
+
+void duk_decode_string(duk_context *ctx, duk_idx_t index, duk_decode_char_function callback, void *udata) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hstring *h_input;
+	duk_uint8_t *p, *p_start, *p_end;
+	duk_codepoint_t cp;
+
+	h_input = duk_require_hstring(ctx, index);
+	DUK_ASSERT(h_input != NULL);
+
+	p_start = (duk_uint8_t *) DUK_HSTRING_GET_DATA(h_input);
+	p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_input);
+	p = p_start;
+
+	for (;;) {
+		if (p >= p_end) {
+			break;
+		}
+		cp = (int) duk_unicode_decode_xutf8_checked(thr, &p, p_start, p_end);
+		callback(udata, cp);
+	}
+}
+
+void duk_map_string(duk_context *ctx, duk_idx_t index, duk_map_char_function callback, void *udata) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hstring *h_input;
+	duk_hbuffer_dynamic *h_buf;
+	duk_uint8_t *p, *p_start, *p_end;
+	duk_codepoint_t cp;
+
+	index = duk_normalize_index(ctx, index);
+
+	h_input = duk_require_hstring(ctx, index);
+	DUK_ASSERT(h_input != NULL);
+
+	/* XXX: should init with a spare of at least h_input->blen? */
+	duk_push_dynamic_buffer(ctx, 0);
+	h_buf = (duk_hbuffer_dynamic *) duk_get_hbuffer(ctx, -1);
+	DUK_ASSERT(h_buf != NULL);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(h_buf));
+
+	p_start = (duk_uint8_t *) DUK_HSTRING_GET_DATA(h_input);
+	p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_input);
+	p = p_start;
+
+	for (;;) {
+		if (p >= p_end) {
+			break;
+		}
+		cp = (int) duk_unicode_decode_xutf8_checked(thr, &p, p_start, p_end);
+		cp = callback(udata, cp);
+		duk_hbuffer_append_xutf8(thr, h_buf, cp);
+	}
+
+	duk_to_string(ctx, -1);  /* invalidates h_buf pointer */
+	duk_replace(ctx, index);
+}
+
+void duk_substring(duk_context *ctx, duk_idx_t index, duk_size_t start_offset, duk_size_t end_offset) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hstring *h;
+	duk_hstring *res;
+	duk_size_t start_byte_offset;
+	duk_size_t end_byte_offset;
+
+	DUK_ASSERT(ctx != NULL);
+
+	index = duk_require_normalize_index(ctx, index);
+	h = duk_require_hstring(ctx, index);
+	DUK_ASSERT(h != NULL);
+
+	if (end_offset >= DUK_HSTRING_GET_CHARLEN(h)) {
+		end_offset = DUK_HSTRING_GET_CHARLEN(h);
+	}
+	if (start_offset > end_offset) {
+		start_offset = end_offset;
+	}
+
+	DUK_ASSERT_DISABLE(start_offset >= 0);
+	DUK_ASSERT(start_offset <= end_offset && start_offset <= DUK_HSTRING_GET_CHARLEN(h));
+	DUK_ASSERT_DISABLE(end_offset >= 0);
+	DUK_ASSERT(end_offset >= start_offset && end_offset <= DUK_HSTRING_GET_CHARLEN(h));
+
+	/* guaranteed by string limits */
+	DUK_ASSERT(start_offset <= DUK_UINT32_MAX);
+	DUK_ASSERT(end_offset <= DUK_UINT32_MAX);
+
+	start_byte_offset = (duk_size_t) duk_heap_strcache_offset_char2byte(thr, h, (duk_uint_fast32_t) start_offset);
+	end_byte_offset = (duk_size_t) duk_heap_strcache_offset_char2byte(thr, h, (duk_uint_fast32_t) end_offset);
+
+	DUK_ASSERT(end_byte_offset >= start_byte_offset);
+	DUK_ASSERT(end_byte_offset - start_byte_offset <= DUK_UINT32_MAX);  /* guaranteed by string limits */
+
+	/* no size check is necessary */
+	res = duk_heap_string_intern_checked(thr,
+	                                     DUK_HSTRING_GET_DATA(h) + start_byte_offset,
+	                                     (duk_uint32_t) (end_byte_offset - start_byte_offset));
+
+	duk_push_hstring(ctx, res);
+	duk_replace(ctx, index);
+}
+
+/* XXX: this is quite clunky.  Add Unicode helpers to scan backwards and
+ * forwards with a callback to process codepoints?
+ */
+void duk_trim(duk_context *ctx, duk_idx_t index) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hstring *h;
+	duk_uint8_t *p, *p_start, *p_end, *p_tmp1, *p_tmp2;  /* pointers for scanning */
+	duk_uint8_t *q_start, *q_end;  /* start (incl) and end (excl) of trimmed part */
+	duk_codepoint_t cp;
+
+	index = duk_require_normalize_index(ctx, index);
+	h = duk_require_hstring(ctx, index);
+	DUK_ASSERT(h != NULL);
+
+	p_start = DUK_HSTRING_GET_DATA(h);
+	p_end = p_start + DUK_HSTRING_GET_BYTELEN(h);
+
+	p = p_start;
+	while (p < p_end) {
+		p_tmp1 = p;
+		cp = (duk_codepoint_t) duk_unicode_decode_xutf8_checked(thr, &p_tmp1, p_start, p_end);
+		if (!(duk_unicode_is_whitespace(cp) || duk_unicode_is_line_terminator(cp))) {
+			break;
+		}
+		p = p_tmp1;
+	}
+	q_start = p;
+	if (p == p_end) {
+		/* entire string is whitespace */
+		q_end = p;
+		goto scan_done;
+	}
+
+	p = p_end;
+	while (p > p_start) {
+		p_tmp1 = p;
+		while (p > p_start) {
+			p--;
+			if (((*p) & 0xc0) != 0x80) {
+				break;
+			}
+		}
+		p_tmp2 = p;
+
+		cp = (duk_codepoint_t) duk_unicode_decode_xutf8_checked(thr, &p_tmp2, p_start, p_end);
+		if (!(duk_unicode_is_whitespace(cp) || duk_unicode_is_line_terminator(cp))) {
+			p = p_tmp1;
+			break;
+		}
+	}
+	q_end = p;
+
+ scan_done:
+	/* This may happen when forward and backward scanning disagree
+	 * (possible for non-extended-UTF-8 strings).
+	 */
+	if (q_end < q_start) {
+		q_end = q_start;
+	}
+
+	DUK_ASSERT(q_start >= p_start && q_start <= p_end);
+	DUK_ASSERT(q_end >= p_start && q_end <= p_end);
+	DUK_ASSERT(q_end >= q_start);
+
+	DUK_DDD(DUK_DDDPRINT("trim: p_start=%p, p_end=%p, q_start=%p, q_end=%p",
+	                     (void *) p_start, (void *) p_end, (void *) q_start, (void *) q_end));
+
+	if (q_start == p_start && q_end == p_end) {
+		DUK_DDD(DUK_DDDPRINT("nothing was trimmed: avoid interning (hashing etc)"));
+		return;
+	}
+
+	duk_push_lstring(ctx, (const char *) q_start, (duk_size_t) (q_end - q_start));
+	duk_replace(ctx, index);
+}
+
+duk_codepoint_t duk_char_code_at(duk_context *ctx, duk_idx_t index, duk_size_t char_offset) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hstring *h;
+	duk_ucodepoint_t cp;
+
+	h = duk_require_hstring(ctx, index);
+	DUK_ASSERT(h != NULL);
+
+	DUK_ASSERT_DISABLE(char_offset >= 0);  /* always true, arg is unsigned */
+	if (char_offset >= DUK_HSTRING_GET_CHARLEN(h)) {
+		return 0;
+	}
+
+	DUK_ASSERT(char_offset <= DUK_UINT_MAX);  /* guaranteed by string limits */
+	cp = duk_hstring_char_code_at_raw(thr, h, (duk_uint_t) char_offset);
+	return (duk_codepoint_t) cp;
+}
+#line 1 "duk_api_thread.c"
+/*
+ *  Thread handling
+ */
+
+/* include removed: duk_internal.h */
+
+/* FIXME */
+#line 1 "duk_api_var.c"
+/*
+ *  Variable access
+ */
+
+/* include removed: duk_internal.h */
+
+void duk_get_var(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_activation *act;
+	duk_hstring *h_varname;
+	duk_small_int_t throw_flag = 1;  /* always throw ReferenceError for unresolvable */
+
+	DUK_ASSERT(ctx != NULL);
+
+	h_varname = duk_require_hstring(ctx, -1);  /* XXX: tostring? */
+	DUK_ASSERT(h_varname != NULL);
+
+	act = duk_hthread_get_current_activation(thr);
+	if (act) {
+		(void) duk_js_getvar_activation(thr, act, h_varname, throw_flag);  /* -> [ ... varname val this ] */
+	} else {
+		/* Outside any activation -> look up from global. */
+		DUK_ASSERT(thr->builtins[DUK_BIDX_GLOBAL_ENV] != NULL);
+		(void) duk_js_getvar_envrec(thr, thr->builtins[DUK_BIDX_GLOBAL_ENV], h_varname, throw_flag);
+	}
+
+	/* [ ... varname val this ]  (because throw_flag == 1, always resolved) */
+
+	duk_pop(ctx);
+	duk_remove(ctx, -2);
+
+	/* [ ... val ] */
+
+	/* Return value would be pointless: because throw_flag==1, we always
+	 * throw if the identifier doesn't resolve.
+	 */
+	return;
+}
+
+void duk_put_var(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_activation *act;
+	duk_hstring *h_varname;
+	duk_tval *tv_val;
+	duk_small_int_t throw_flag;
+
+	DUK_ASSERT(ctx != NULL);
+
+	h_varname = duk_require_hstring(ctx, -2);  /* XXX: tostring? */
+	DUK_ASSERT(h_varname != NULL);
+
+	tv_val = duk_require_tval(ctx, -1);
+
+	throw_flag = duk_is_strict_call(ctx);
+
+	act = duk_hthread_get_current_activation(thr);
+	if (act) {
+		duk_js_putvar_activation(thr, act, h_varname, tv_val, throw_flag);  /* -> [ ... varname val this ] */
+	} else {
+		/* Outside any activation -> put to global. */
+		DUK_ASSERT(thr->builtins[DUK_BIDX_GLOBAL_ENV] != NULL);
+		duk_js_putvar_envrec(thr, thr->builtins[DUK_BIDX_GLOBAL_ENV], h_varname, tv_val, throw_flag);
+	}
+
+	/* [ ... varname val ] */
+
+	duk_pop_2(ctx);
+
+	/* [ ... ] */
+
+	return;
+}
+
+duk_bool_t duk_del_var(duk_context *ctx) {
+	DUK_ERROR((duk_hthread *) ctx, DUK_ERR_UNIMPLEMENTED_ERROR, "unimplemented");
+	return 0;
+}
+
+duk_bool_t duk_has_var(duk_context *ctx) {
+	DUK_ERROR((duk_hthread *) ctx, DUK_ERR_UNIMPLEMENTED_ERROR, "unimplemented");
+	return 0;
+}
+
+#line 1 "duk_bi_array.c"
+/*
+ *  Array built-ins
+ *
+ *  Note that most Array built-ins are intentionally generic and work even
+ *  when the 'this' binding is not an Array instance.  To ensure this,
+ *  Array algorithms do not assume "magical" Array behavior for the "length"
+ *  property, for instance.
+ *
+ *  XXX: the "Throw" flag should be set for (almost?) all [[Put]] and
+ *  [[Delete]] operations, but it's currently false throughout.  Go through
+ *  all put/delete cases and check throw flag use.  Need a new API primitive
+ *  which allows throws flag to be specified.
+ *
+ *  XXX: array lengths above 2G won't work reliably.  There are many places
+ *  where one needs a full signed 32-bit range ([-0xffffffff, 0xffffffff],
+ *  i.e. -33- bits).  Further, some valid array length values may be above
+ *  2**32-1, and this is not always correctly handled (duk_uint32_t is not enough).
+ *
+ *  On using "put" vs. "def" prop
+ *  =============================
+ *
+ *  Code below must be careful to use the appropriate primitive as it matters
+ *  for compliance.  When using "put" there may be inherited properties in
+ *  Array.prototype which cause side effects when values are written.  When
+ *  using "define" there are no such side effects, and many test262 test cases
+ *  check for this (for real world code, such side effects are very rare).
+ *  Both "put" and "define" are used in the E5.1 specification; as a rule,
+ *  "put" is used when modifying an existing array (or a non-array 'this'
+ *  binding) and "define" for setting values into a fresh result array.
+ *
+ *  Also note that Array instance 'length' should be writable, but not
+ *  enumerable and definitely not configurable: even Duktape code internally
+ *  assumes that an Array instance will always have a 'length' property.
+ *  Preventing deletion of the property is critical.
+ */
+
+/* include removed: duk_internal.h */
+
+/* Perform an intermediate join when this many elements have been pushed
+ * on the value stack.
+ */
+#define  DUK__ARRAY_MID_JOIN_LIMIT  4096
+
+/* Shared entry code for many Array built-ins.  Note that length is left
+ * on stack (it could be popped, but that's not necessary).
+ */
+static duk_uint32_t duk__push_this_obj_len_u32(duk_context *ctx) {
+	duk_uint32_t len;
+
+	(void) duk_push_this_coercible_to_object(ctx);
+	duk_get_prop_stridx(ctx, -1, DUK_STRIDX_LENGTH);
+	len = duk_to_uint32(ctx, -1);
+
+	/* -> [ ... ToObject(this) ToUint32(length) ] */
+	return len;
+}
+
+static duk_uint32_t duk__push_this_obj_len_u32_limited(duk_context *ctx) {
+	/* Range limited to [0, 0x7fffffff] range, i.e. range that can be
+	 * represented with duk_int32_t.  Use this when the method doesn't
+	 * handle the full 32-bit unsigned range correctly.
+	 */
+	duk_uint32_t ret = duk__push_this_obj_len_u32(ctx);
+	if (DUK_UNLIKELY(ret >= 0x80000000UL)) {
+		DUK_ERROR((duk_hthread *) ctx, DUK_ERR_INTERNAL_ERROR, "array length above 2G");
+	}
+	return ret;
+}
+
+/*
+ *  Constructor
+ */
+
+duk_ret_t duk_bi_array_constructor(duk_context *ctx) {
+	duk_idx_t nargs;
+	duk_double_t d;
+	duk_uint32_t len;
+	duk_idx_t i;
+
+	nargs = duk_get_top(ctx);
+	duk_push_array(ctx);
+
+	if (nargs == 1 && duk_is_number(ctx, 0)) {
+		/* XXX: expensive check (also shared elsewhere - so add a shared internal API call?) */
+		d = duk_get_number(ctx, 0);
+		len = duk_to_uint32(ctx, 0);
+		if (((duk_double_t) len) != d) {
+			return DUK_RET_RANGE_ERROR;
+		}
+
+		/* XXX: if 'len' is low, may want to ensure array part is kept:
+		 * the caller is likely to want a dense array.
+		 */
+		duk_dup(ctx, 0);
+		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_W);  /* [ ToUint32(len) array ToUint32(len) ] -> [ ToUint32(len) array ] */
+		return 1;
+	}
+
+	/* XXX: optimize by creating array into correct size directly, and
+	 * operating on the array part directly; values can be memcpy()'d from
+	 * value stack directly as long as refcounts are increased.
+	 */
+	for (i = 0; i < nargs; i++) {
+		duk_dup(ctx, i);
+		duk_def_prop_index_wec(ctx, -2, (duk_uarridx_t) i);
+	}
+
+	duk_push_u32(ctx, (duk_uint32_t) nargs);
+	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_W);
+	return 1;
+}
+
+/*
+ *  isArray()
+ */
+
+duk_ret_t duk_bi_array_constructor_is_array(duk_context *ctx) {
+	duk_hobject *h;
+
+	h = duk_get_hobject_with_class(ctx, 0, DUK_HOBJECT_CLASS_ARRAY);
+	duk_push_boolean(ctx, (h != NULL));
+	return 1;
+}
+
+/*
+ *  toString()
+ */
+
+duk_ret_t duk_bi_array_prototype_to_string(duk_context *ctx) {
+	(void) duk_push_this_coercible_to_object(ctx);
+	duk_get_prop_stridx(ctx, -1, DUK_STRIDX_JOIN);
+
+	/* [ ... this func ] */
+	if (!duk_is_callable(ctx, -1)) {
+		/* Fall back to the initial (original) Object.toString().  We don't
+		 * currently have pointers to the built-in functions, only the top
+		 * level global objects (like "Array") so this is now done in a bit
+		 * of a hacky manner.  It would be cleaner to push the (original)
+		 * function and use duk_call_method().
+		 */
+
+		/* XXX: 'this' will be ToObject() coerced twice, which is incorrect
+		 * but should have no visible side effects.
+		 */
+		DUK_DDD(DUK_DDDPRINT("this.join is not callable, fall back to (original) Object.toString"));
+		duk_set_top(ctx, 0);
+		return duk_bi_object_prototype_to_string(ctx);  /* has access to 'this' binding */
+	}
+
+	/* [ ... this func ] */
+
+	duk_insert(ctx, -2);
+
+	/* [ ... func this ] */
+
+	DUK_DDD(DUK_DDDPRINT("calling: func=%!iT, this=%!iT",
+	                     (duk_tval *) duk_get_tval(ctx, -2),
+	                     (duk_tval *) duk_get_tval(ctx, -1)));
+	duk_call_method(ctx, 0);
+
+	return 1;
+}
+
+/*
+ *  concat()
+ */
+
+duk_ret_t duk_bi_array_prototype_concat(duk_context *ctx) {
+	duk_idx_t i, n;
+	duk_uarridx_t idx, idx_last;
+	duk_uarridx_t j, len;
+	duk_hobject *h;
+
+	/* XXX: the insert here is a bit expensive if there are a lot of items.
+	 * It could also be special cased in the outermost for loop quite easily
+	 * (as the element is dup()'d anyway).
+	 */
+
+	(void) duk_push_this_coercible_to_object(ctx);
+	duk_insert(ctx, 0);
+	n = duk_get_top(ctx);
+	duk_push_array(ctx);  /* -> [ ToObject(this) item1 ... itemN arr ] */
+
+	/* NOTE: The Array special behaviors are NOT invoked by duk_def_prop_index()
+	 * (which differs from the official algorithm).  If no error is thrown, this
+	 * doesn't matter as the length is updated at the end.  However, if an error
+	 * is thrown, the length will be unset.  That shouldn't matter because the
+	 * caller won't get a reference to the intermediate value.
+	 */
+
+	idx = 0;
+	idx_last = 0;
+	for (i = 0; i < n; i++) {
+		DUK_ASSERT_TOP(ctx, n + 1);
+
+		/* [ ToObject(this) item1 ... itemN arr ] */
+
+		duk_dup(ctx, i);
+		h = duk_get_hobject_with_class(ctx, -1, DUK_HOBJECT_CLASS_ARRAY);
+		if (!h) {
+			duk_def_prop_index_wec(ctx, -2, idx++);
+			idx_last = idx;
+			continue;
+		}
+
+		/* [ ToObject(this) item1 ... itemN arr item(i) ] */
+
+		/* XXX: an array can have length higher than 32 bits; this is not handled
+		 * correctly now.
+		 */
+		len = (duk_uarridx_t) duk_get_length(ctx, -1);
+		for (j = 0; j < len; j++) {
+			if (duk_get_prop_index(ctx, -1, j)) {
+				/* [ ToObject(this) item1 ... itemN arr item(i) item(i)[j] ] */
+				duk_def_prop_index_wec(ctx, -3, idx++);
+				idx_last = idx;
+			} else {
+				/* XXX: according to E5.1 Section 15.4.4.4 nonexistent trailing
+				 * elements do not affect 'length' but test262 disagrees.  Work
+				 * as E5.1 mandates for now and don't touch idx_last.
+				 */
+				idx++;
+				duk_pop(ctx);
+			}
+		}
+		duk_pop(ctx);
+	}
+
+	duk_push_uarridx(ctx, idx_last);
+	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_W);
+
+	DUK_ASSERT_TOP(ctx, n + 1);
+	return 1;
+}
+
+/*
+ *  join(), toLocaleString()
+ *
+ *  Note: checking valstack is necessary, but only in the per-element loop.
+ *
+ *  Note: the trivial approach of pushing all the elements on the value stack
+ *  and then calling duk_join() fails when the array contains a large number
+ *  of elements.  This problem can't be offloaded to duk_join() because the
+ *  elements to join must be handled here and have special handling.  Current
+ *  approach is to do intermediate joins with very large number of elements.
+ *  There is no fancy handling; the prefix gets re-joined multiple times.
+ */
+
+duk_ret_t duk_bi_array_prototype_join_shared(duk_context *ctx) {
+	duk_uint32_t len, count;
+	duk_uint32_t idx;
+	duk_small_int_t to_locale_string = duk_get_magic(ctx);
+	duk_idx_t valstack_required;
+
+	/* For join(), nargs is 1.  For toLocaleString(), nargs is 0 and
+	 * setting the top essentially pushes an undefined to the stack,
+	 * thus defaulting to a comma separator.
+	 */
+	duk_set_top(ctx, 1);
+	if (duk_is_undefined(ctx, 0)) {
+		duk_pop(ctx);
+		duk_push_hstring_stridx(ctx, DUK_STRIDX_COMMA);
+	} else {
+		duk_to_string(ctx, 0);
+	}
+
+	len = duk__push_this_obj_len_u32(ctx);
+
+	/* [ sep ToObject(this) len ] */
+
+	DUK_DDD(DUK_DDDPRINT("sep=%!T, this=%!T, len=%lu",
+	                     (duk_tval *) duk_get_tval(ctx, 0),
+	                     (duk_tval *) duk_get_tval(ctx, 1),
+	                     (unsigned long) len));
+
+	valstack_required = (len >= DUK__ARRAY_MID_JOIN_LIMIT ?
+	                     DUK__ARRAY_MID_JOIN_LIMIT : len) + 1;
+	duk_require_stack(ctx, valstack_required);
+
+	duk_dup(ctx, 0);
+
+	/* [ sep ToObject(this) len sep ] */
+
+	count = 0;
+	idx = 0;
+	for (;;) {
+		if (count >= DUK__ARRAY_MID_JOIN_LIMIT ||   /* intermediate join to avoid valstack overflow */
+		    idx >= len) { /* end of loop (careful with len==0) */
+			/* [ sep ToObject(this) len sep str0 ... str(count-1) ] */
+			DUK_DDD(DUK_DDDPRINT("mid/final join, count=%ld, idx=%ld, len=%ld",
+			                     (long) count, (long) idx, (long) len));
+			duk_join(ctx, (duk_idx_t) count);  /* -> [ sep ToObject(this) len str ] */
+			duk_dup(ctx, 0);                   /* -> [ sep ToObject(this) len str sep ] */
+			duk_insert(ctx, -2);               /* -> [ sep ToObject(this) len sep str ] */
+			count = 1;
+		}
+		if (idx >= len) {
+			/* if true, the stack already contains the final result */
+			break;
+		}
+
+		duk_get_prop_index(ctx, 1, (duk_uarridx_t) idx);
+		if (duk_is_null_or_undefined(ctx, -1)) {
+			duk_pop(ctx);
+			duk_push_hstring_stridx(ctx, DUK_STRIDX_EMPTY_STRING);
+		} else {
+			if (to_locale_string) {
+				duk_to_object(ctx, -1);
+				duk_get_prop_stridx(ctx, -1, DUK_STRIDX_TO_LOCALE_STRING);
+				duk_insert(ctx, -2);  /* -> [ ... toLocaleString ToObject(val) ] */
+				duk_call_method(ctx, 0);
+				duk_to_string(ctx, -1);
+			} else {
+				duk_to_string(ctx, -1);
+			}
+		}
+
+		count++;
+		idx++;
+	}
+
+	/* [ sep ToObject(this) len sep result ] */
+
+	return 1;
+}
+
+/*
+ *  pop(), push()
+ */
+
+duk_ret_t duk_bi_array_prototype_pop(duk_context *ctx) {
+	duk_uint32_t len;
+	duk_uint32_t idx;
+
+	DUK_ASSERT_TOP(ctx, 0);
+	len = duk__push_this_obj_len_u32(ctx);
+	if (len == 0) {
+		duk_push_int(ctx, 0);
+		duk_put_prop_stridx(ctx, 0, DUK_STRIDX_LENGTH);
+		return 0;
+	}
+	idx = len - 1;
+
+	duk_get_prop_index(ctx, 0, (duk_uarridx_t) idx);
+	duk_del_prop_index(ctx, 0, (duk_uarridx_t) idx);
+	duk_push_u32(ctx, idx);
+	duk_put_prop_stridx(ctx, 0, DUK_STRIDX_LENGTH);
+	return 1;
+}
+
+duk_ret_t duk_bi_array_prototype_push(duk_context *ctx) {
+	/* Note: 'this' is not necessarily an Array object.  The push()
+	 * algorithm is supposed to work for other kinds of objects too,
+	 * so the algorithm has e.g. an explicit update for the 'length'
+	 * property which is normally "magical" in arrays.
+	 */
+
+	duk_double_t len;
+	duk_idx_t i, n;
+
+	n = duk_get_top(ctx);
+	len = (duk_double_t) duk__push_this_obj_len_u32(ctx);
+
+	/* [ arg1 ... argN obj length ] */
+
+	/* Note: we keep track of length with a double instead of a 32-bit
+	 * (unsigned) int because the length can go beyond 32 bits and the
+	 * final length value is NOT wrapped to 32 bits on this call.
+	 */
+
+	for (i = 0; i < n; i++) {
+		duk_push_number(ctx, len);
+		duk_dup(ctx, i);
+		duk_put_prop(ctx, -4);
+		len += 1.0;
+	}
+
+	duk_push_number(ctx, len);
+	duk_dup_top(ctx);
+	duk_put_prop_stridx(ctx, -4, DUK_STRIDX_LENGTH);
+
+	/* [ arg1 ... argN obj length new_length ] */
+	return 1;
+}
+
+/*
+ *  sort()
+ *
+ *  Currently qsort with random pivot.  This is now really, really slow,
+ *  because there is no fast path for array parts.
+ *
+ *  Signed indices are used because qsort() leaves and degenerate cases
+ *  may use a negative offset.
+ */
+
+static duk_small_int_t duk__array_sort_compare(duk_context *ctx, duk_int_t idx1, duk_int_t idx2) {
+	duk_bool_t have1, have2;
+	duk_bool_t undef1, undef2;
+	duk_small_int_t ret;
+	duk_idx_t idx_obj = 1;  /* fixed offsets in valstack */
+	duk_idx_t idx_fn = 0;
+	duk_hstring *h1, *h2;
+
+	/* Fast exit if indices are identical.  This is valid for a non-existent property,
+	 * for an undefined value, and almost always for ToString() coerced comparison of
+	 * arbitrary values (corner cases where this is not the case include e.g. a an
+	 * object with varying ToString() coercion).
+	 *
+	 * The specification does not prohibit "caching" of values read from the array, so
+	 * assuming equality for comparing an index with itself falls into the category of
+	 * "caching".
+	 *
+	 * Also, compareFn may be inconsistent, so skipping a call to compareFn here may
+	 * have an effect on the final result.  The specification does not require any
+	 * specific behavior for inconsistent compare functions, so again, this fast path
+	 * is OK.
+	 */
+
+	if (idx1 == idx2) {
+		DUK_DDD(DUK_DDDPRINT("duk__array_sort_compare: idx1=%ld, idx2=%ld -> indices identical, quick exit",
+		                     (long) idx1, (long) idx2));
+		return 0;
+	}
+
+	have1 = duk_get_prop_index(ctx, idx_obj, (duk_uarridx_t) idx1);
+	have2 = duk_get_prop_index(ctx, idx_obj, (duk_uarridx_t) idx2);
+
+	DUK_DDD(DUK_DDDPRINT("duk__array_sort_compare: idx1=%ld, idx2=%ld, have1=%ld, have2=%ld, val1=%!T, val2=%!T",
+	                     (long) idx1, (long) idx2, (long) have1, (long) have2,
+	                     (duk_tval *) duk_get_tval(ctx, -2), (duk_tval *) duk_get_tval(ctx, -1)));
+
+	if (have1) {
+		if (have2) {
+			;
+		} else {
+			ret = -1;
+			goto pop_ret;
+		}
+	} else {
+		if (have2) {
+			ret = 1;
+			goto pop_ret;
+		} else {
+			ret = 0;
+			goto pop_ret;
+		}
+	}
+
+	undef1 = duk_is_undefined(ctx, -2);
+	undef2 = duk_is_undefined(ctx, -1);
+	if (undef1) {
+		if (undef2) {
+			ret = 0;
+			goto pop_ret;
+		} else {
+			ret = 1;
+			goto pop_ret;
+		}
+	} else {
+		if (undef2) {
+			ret = -1;
+			goto pop_ret;
+		} else {
+			;
+		}
+	}
+
+	if (!duk_is_undefined(ctx, idx_fn)) {
+		duk_double_t d;
+
+		/* no need to check callable; duk_call() will do that */
+		duk_dup(ctx, idx_fn);    /* -> [ ... x y fn ] */
+		duk_insert(ctx, -3);     /* -> [ ... fn x y ] */
+		duk_call(ctx, 2);        /* -> [ ... res ] */
+
+		/* The specification is a bit vague what to do if the return
+		 * value is not a number.  Other implementations seem to
+		 * tolerate non-numbers but e.g. V8 won't apparently do a
+		 * ToNumber().
+		 */
+
+		/* XXX: best behavior for real world compatibility? */
+
+		d = duk_to_number(ctx, -1);
+		if (d < 0.0) {
+			ret = -1;
+		} else if (d > 0.0) {
+			ret = 1;
+		} else {
+			ret = 0;
+		}
+
+		duk_pop(ctx);
+		DUK_DDD(DUK_DDDPRINT("-> result %ld (from comparefn, after coercion)", (long) ret));
+		return ret;
+	}
+
+	/* string compare is the default (a bit oddly) */
+
+	h1 = duk_to_hstring(ctx, -2);
+	h2 = duk_to_hstring(ctx, -1);
+	DUK_ASSERT(h1 != NULL);
+	DUK_ASSERT(h2 != NULL);
+
+	ret = duk_js_string_compare(h1, h2);  /* retval is directly usable */
+	goto pop_ret;
+
+ pop_ret:
+	duk_pop_2(ctx);
+	DUK_DDD(DUK_DDDPRINT("-> result %ld", (long) ret));
+	return ret;
+}
+
+static void duk__array_sort_swap(duk_context *ctx, duk_int_t l, duk_int_t r) {
+	duk_bool_t have_l, have_r;
+	duk_idx_t idx_obj = 1;  /* fixed offset in valstack */
+
+	if (l == r) {
+		return;
+	}
+
+	/* swap elements; deal with non-existent elements correctly */
+	have_l = duk_get_prop_index(ctx, idx_obj, (duk_uarridx_t) l);
+	have_r = duk_get_prop_index(ctx, idx_obj, (duk_uarridx_t) r);
+
+	if (have_r) {
+		/* right exists, [[Put]] regardless whether or not left exists */
+		duk_put_prop_index(ctx, idx_obj, (duk_uarridx_t) l);
+	} else {
+		duk_del_prop_index(ctx, idx_obj, (duk_uarridx_t) l);
+		duk_pop(ctx);
+	}
+
+	if (have_l) {
+		duk_put_prop_index(ctx, idx_obj, (duk_uarridx_t) r);
+	} else {
+		duk_del_prop_index(ctx, idx_obj, (duk_uarridx_t) r);
+		duk_pop(ctx);
+	}
+}
+
+#if defined(DUK_USE_DDDPRINT)
+/* Debug print which visualizes the qsort partitioning process. */
+static void duk__debuglog_qsort_state(duk_context *ctx, duk_int_t lo, duk_int_t hi, duk_int_t pivot) {
+	char buf[4096];
+	char *ptr = buf;
+	duk_int_t i, n;
+	n = (duk_int_t) duk_get_length(ctx, 1);
+	if (n > 4000) {
+		n = 4000;
+	}
+	*ptr++ = '[';
+	for (i = 0; i < n; i++) {
+		if (i == pivot) {
+			*ptr++ = '|';
+		} else if (i == lo) {
+			*ptr++ = '<';
+		} else if (i == hi) {
+			*ptr++ = '>';
+		} else if (i >= lo && i <= hi) {
+			*ptr++ = '-';
+		} else {
+			*ptr++ = ' ';
+		}
+	}
+	*ptr++ = ']';
+	*ptr++ = '\0';
+
+	DUK_DDD(DUK_DDDPRINT("%s   (lo=%ld, hi=%ld, pivot=%ld)",
+	                     (const char *) buf, (long) lo, (long) hi, (long) pivot));
+}
+#endif
+
+static void duk__array_qsort(duk_context *ctx, duk_int_t lo, duk_int_t hi) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_int_t p, l, r;
+
+	/* The lo/hi indices may be crossed and hi < 0 is possible at entry. */
+
+	DUK_DDD(DUK_DDDPRINT("duk__array_qsort: lo=%ld, hi=%ld, obj=%!T",
+	                     (long) lo, (long) hi, (duk_tval *) duk_get_tval(ctx, 1)));
+
+	DUK_ASSERT_TOP(ctx, 3);
+
+	/* In some cases it may be that lo > hi, or hi < 0; these
+	 * degenerate cases happen e.g. for empty arrays, and in
+	 * recursion leaves.
+	 */
+
+	/* trivial cases */
+	if (hi - lo < 1) {
+		DUK_DDD(DUK_DDDPRINT("degenerate case, return immediately"));
+		return;
+	}
+	DUK_ASSERT(hi > lo);
+	DUK_ASSERT(hi - lo + 1 >= 2);
+
+	/* randomized pivot selection */
+	p = lo + (duk_util_tinyrandom_get_bits(thr, 30) % (hi - lo + 1));  /* rnd in [lo,hi] */
+	DUK_ASSERT(p >= lo && p <= hi);
+	DUK_DDD(DUK_DDDPRINT("lo=%ld, hi=%ld, chose pivot p=%ld",
+	                     (long) lo, (long) hi, (long) p));
+
+	/* move pivot out of the way */
+	duk__array_sort_swap(ctx, p, lo);
+	p = lo;
+	DUK_DDD(DUK_DDDPRINT("pivot moved out of the way: %!T", (duk_tval *) duk_get_tval(ctx, 1)));
+
+	l = lo + 1;
+	r = hi;
+	for (;;) {
+		/* find elements to swap */
+		for (;;) {
+			DUK_DDD(DUK_DDDPRINT("left scan: l=%ld, r=%ld, p=%ld",
+			                     (long) l, (long) r, (long) p));
+			if (l >= hi) {
+				break;
+			}
+			if (duk__array_sort_compare(ctx, l, p) >= 0) {  /* !(l < p) */
+				break;
+			}
+			l++;
+		}
+		for (;;) {
+			DUK_DDD(DUK_DDDPRINT("right scan: l=%ld, r=%ld, p=%ld",
+			                     (long) l, (long) r, (long) p));
+			if (r <= lo) {
+				break;
+			}
+			if (duk__array_sort_compare(ctx, p, r) >= 0) {  /* !(p < r) */
+				break;
+			}
+			r--;
+		}
+		if (l >= r) {
+			goto done;
+		}
+		DUK_ASSERT(l < r);
+
+		DUK_DDD(DUK_DDDPRINT("swap %ld and %ld", (long) l, (long) r));
+
+		duk__array_sort_swap(ctx, l, r);
+
+		DUK_DDD(DUK_DDDPRINT("after swap: %!T", (duk_tval *) duk_get_tval(ctx, 1)));
+		l++;
+		r--;
+	}
+ done:
+	/* Note that 'l' and 'r' may cross, i.e. r < l */
+	DUK_ASSERT(l >= lo && l <= hi);
+	DUK_ASSERT(r >= lo && r <= hi);
+
+	/* XXX: there's no explicit recursion bound here now.  For the average
+	 * qsort recursion depth O(log n) that's not really necessary: e.g. for
+	 * 2**32 recursion depth would be about 32 which is OK.  However, qsort
+	 * worst case recursion depth is O(n) which may be a problem.
+	 */
+
+	/* move pivot to its final place */
+	DUK_DDD(DUK_DDDPRINT("before final pivot swap: %!T", (duk_tval *) duk_get_tval(ctx, 1)));
+	duk__array_sort_swap(ctx, lo, r);	
+
+#if defined(DUK_USE_DDDPRINT)
+	duk__debuglog_qsort_state(ctx, lo, hi, r);
+#endif
+
+	DUK_DDD(DUK_DDDPRINT("recurse: pivot=%ld, obj=%!T", (long) r, (duk_tval *) duk_get_tval(ctx, 1)));
+	duk__array_qsort(ctx, lo, r - 1);
+	duk__array_qsort(ctx, r + 1, hi);
+}
+
+duk_ret_t duk_bi_array_prototype_sort(duk_context *ctx) {
+	duk_uint32_t len;
+
+	/* XXX: len >= 0x80000000 won't work below because a signed type
+	 * is needed by qsort.
+	 */
+	len = duk__push_this_obj_len_u32_limited(ctx);
+
+	/* stack[0] = compareFn
+	 * stack[1] = ToObject(this)
+	 * stack[2] = ToUint32(length)
+	 */
+
+	if (len > 0) {
+		/* avoid degenerate cases, so that (len - 1) won't underflow */
+		duk__array_qsort(ctx, (duk_int_t) 0, (duk_int_t) (len - 1));
+	}
+
+	DUK_ASSERT_TOP(ctx, 3);
+	duk_pop(ctx);
+	return 1;  /* return ToObject(this) */
+}
+
+/*
+ *  splice()
+ */
+
+/* XXX: this compiles to over 500 bytes now, even without special handling
+ * for an array part.  Uses signed ints so does not handle full array range correctly.
+ */
+
+/* XXX: can shift() / unshift() use the same helper?
+ *   shift() is (close to?) <--> splice(0, 1)
+ *   unshift is (close to?) <--> splice(0, 0, [items])?
+ */
+
+duk_ret_t duk_bi_array_prototype_splice(duk_context *ctx) {
+	duk_idx_t nargs;
+	duk_uint32_t len;
+	duk_bool_t have_delcount;
+	duk_int_t item_count;
+	duk_int_t act_start;
+	duk_int_t del_count;
+	duk_int_t i, n;
+
+	DUK_UNREF(have_delcount);
+
+	nargs = duk_get_top(ctx);
+	if (nargs < 2) {
+		duk_set_top(ctx, 2);
+		nargs = 2;
+		have_delcount = 0;
+	} else {
+		have_delcount = 1;
+	}
+
+	/* XXX: len >= 0x80000000 won't work below because we need to be
+	 * able to represent -len.
+	 */
+	len = duk__push_this_obj_len_u32_limited(ctx);
+
+	act_start = duk_to_int_clamped(ctx, 0, -((duk_int_t) len), (duk_int_t) len);
+	if (act_start < 0) {
+		act_start = len + act_start;
+	}
+	DUK_ASSERT(act_start >= 0 && act_start <= (duk_int_t) len);
+
+#ifdef DUK_USE_NONSTD_ARRAY_SPLICE_DELCOUNT
+	if (have_delcount) {
+#endif
+		del_count = duk_to_int_clamped(ctx, 1, 0, len - act_start);
+#ifdef DUK_USE_NONSTD_ARRAY_SPLICE_DELCOUNT
+	} else {
+		/* E5.1 standard behavior when deleteCount is not given would be
+		 * to treat it just like if 'undefined' was given, which coerces
+		 * ultimately to 0.  Real world behavior is to splice to the end
+		 * of array, see test-bi-array-proto-splice-no-delcount.js.
+		 */
+		del_count = len - act_start;
+	}
+#endif
+
+	DUK_ASSERT(del_count >= 0 && del_count <= (duk_int_t) len - act_start);
+	DUK_ASSERT(del_count + act_start <= (duk_int_t) len);
+
+	duk_push_array(ctx);
+
+	/* stack[0] = start
+	 * stack[1] = deleteCount
+	 * stack[2...nargs-1] = items
+	 * stack[nargs] = ToObject(this)               -3
+	 * stack[nargs+1] = ToUint32(length)           -2
+	 * stack[nargs+2] = result array               -1
+	 */
+
+	DUK_ASSERT_TOP(ctx, nargs + 3);
+
+	/* Step 9: copy elements-to-be-deleted into the result array */
+
+	for (i = 0; i < del_count; i++) {
+		if (duk_get_prop_index(ctx, -3, (duk_uarridx_t) (act_start + i))) {
+			duk_def_prop_index_wec(ctx, -2, i);  /* throw flag irrelevant (false in std alg) */
+		} else {
+			duk_pop(ctx);
+		}
+	}
+	duk_push_u32(ctx, (duk_uint32_t) del_count);
+	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_W);
+
+	/* Steps 12 and 13: reorganize elements to make room for itemCount elements */
+
+	DUK_ASSERT(nargs >= 2);
+	item_count = (duk_int_t) (nargs - 2);
+	if (item_count < del_count) {
+		/*    [ A B C D E F G H ]    rel_index = 2, del_count 3, item count 1
+		 * -> [ A B F G H ]          (conceptual intermediate step)
+		 * -> [ A B . F G H ]        (placeholder marked)
+		 *    [ A B C F G H ]        (actual result at this point, C will be replaced)
+		 */
+
+		DUK_ASSERT_TOP(ctx, nargs + 3);
+
+		n = len - del_count;
+		for (i = act_start; i < n; i++) {
+			if (duk_get_prop_index(ctx, -3, (duk_uarridx_t) (i + del_count))) {
+				duk_put_prop_index(ctx, -4, (duk_uarridx_t) (i + item_count));
+			} else {
+				duk_pop(ctx);
+				duk_del_prop_index(ctx, -3, (duk_uarridx_t) (i + item_count));
+			}
+		}
+
+		DUK_ASSERT_TOP(ctx, nargs + 3);
+
+		/* loop iterator init and limit changed from standard algorithm */
+		n = len - del_count + item_count;
+		for (i = len - 1; i >= n; i--) {
+			duk_del_prop_index(ctx, -3, (duk_uarridx_t) i);
+		}
+
+		DUK_ASSERT_TOP(ctx, nargs + 3);
+	} else if (item_count > del_count) {
+		/*    [ A B C D E F G H ]    rel_index = 2, del_count 3, item count 4
+		 * -> [ A B F G H ]          (conceptual intermediate step)
+		 * -> [ A B . . . . F G H ]  (placeholder marked)
+		 *    [ A B C D E F F G H ]  (actual result at this point)
+		 */
+
+		DUK_ASSERT_TOP(ctx, nargs + 3);
+
+		/* loop iterator init and limit changed from standard algorithm */
+		for (i = len - del_count - 1; i >= act_start; i--) {
+			if (duk_get_prop_index(ctx, -3, (duk_uarridx_t) (i + del_count))) {
+				duk_put_prop_index(ctx, -4, (duk_uarridx_t) (i + item_count));
+			} else {
+				duk_pop(ctx);
+				duk_del_prop_index(ctx, -3, (duk_uarridx_t) (i + item_count));
+			}
+		}
+
+		DUK_ASSERT_TOP(ctx, nargs + 3);
+	} else {
+		/*    [ A B C D E F G H ]    rel_index = 2, del_count 3, item count 3
+		 * -> [ A B F G H ]          (conceptual intermediate step)
+		 * -> [ A B . . . F G H ]    (placeholder marked)
+		 *    [ A B C D E F G H ]    (actual result at this point)
+		 */
+	}
+	DUK_ASSERT_TOP(ctx, nargs + 3);
+
+	/* Step 15: insert itemCount elements into the hole made above */
+
+	for (i = 0; i < item_count; i++) {
+		duk_dup(ctx, i + 2);  /* args start at index 2 */
+		duk_put_prop_index(ctx, -4, (duk_uarridx_t) (act_start + i));
+	}
+
+	/* Step 16: update length; note that the final length may be above 32 bit range */
+
+	duk_push_number(ctx, ((duk_double_t) len) - ((duk_double_t) del_count) + ((duk_double_t) item_count));
+	duk_put_prop_stridx(ctx, -4, DUK_STRIDX_LENGTH);
+
+	/* result array is already at the top of stack */
+	DUK_ASSERT_TOP(ctx, nargs + 3);
+	return 1;
+}
+
+/*
+ *  reverse()
+ */
+
+duk_ret_t duk_bi_array_prototype_reverse(duk_context *ctx) {
+	duk_uint32_t len;
+	duk_uint32_t middle;
+	duk_uint32_t lower, upper;
+	duk_bool_t have_lower, have_upper;
+
+	len = duk__push_this_obj_len_u32(ctx);
+	middle = len / 2;
+
+	/* If len <= 1, middle will be 0 and for-loop bails out
+	 * immediately (0 < 0 -> false).
+	 */
+
+	for (lower = 0; lower < middle; lower++) {
+		DUK_ASSERT(len >= 2);
+		DUK_ASSERT_TOP(ctx, 2);
+
+		DUK_ASSERT(len >= lower + 1);
+		upper = len - lower - 1;
+
+		have_lower = duk_get_prop_index(ctx, -2, (duk_uarridx_t) lower);
+		have_upper = duk_get_prop_index(ctx, -3, (duk_uarridx_t) upper);
+
+		/* [ ToObject(this) ToUint32(length) lowerValue upperValue ] */
+
+		if (have_upper) {
+			duk_put_prop_index(ctx, -4, (duk_uarridx_t) lower);
+		} else {
+			duk_del_prop_index(ctx, -4, (duk_uarridx_t) lower);
+			duk_pop(ctx);
+		}
+
+		if (have_lower) {
+			duk_put_prop_index(ctx, -3, (duk_uarridx_t) upper);
+		} else {
+			duk_del_prop_index(ctx, -3, (duk_uarridx_t) upper);
+			duk_pop(ctx);
+		}
+
+		DUK_ASSERT_TOP(ctx, 2);
+	}
+
+	DUK_ASSERT_TOP(ctx, 2);
+	duk_pop(ctx);  /* -> [ ToObject(this) ] */
+	return 1;
+}
+
+/*
+ *  slice()
+ */
+
+duk_ret_t duk_bi_array_prototype_slice(duk_context *ctx) {
+	duk_uint32_t len;
+	duk_int_t start, end;
+	duk_int_t i;
+	duk_uarridx_t idx;
+	duk_uint32_t res_length = 0;
+
+	/* XXX: len >= 0x80000000 won't work below because we need to be
+	 * able to represent -len.
+	 */
+	len = duk__push_this_obj_len_u32_limited(ctx);
+	duk_push_array(ctx);
+
+	/* stack[0] = start
+	 * stack[1] = end
+	 * stack[2] = ToObject(this)
+	 * stack[3] = ToUint32(length)
+	 * stack[4] = result array
+	 */
+
+	start = duk_to_int_clamped(ctx, 0, -((duk_int_t) len), (duk_int_t) len);
+	if (start < 0) {
+		start = len + start;
+	}
+	/* XXX: could duk_is_undefined() provide defaulting undefined to 'len'
+	 * (the upper limit)?
+	 */
+	if (duk_is_undefined(ctx, 1)) {
+		end = len;
+	} else {
+		end = duk_to_int_clamped(ctx, 1, -((duk_int_t) len), (duk_int_t) len);
+		if (end < 0) {
+			end = len + end;
+		}
+	}
+	DUK_ASSERT(start >= 0 && (duk_uint32_t) start <= len);
+	DUK_ASSERT(end >= 0 && (duk_uint32_t) end <= len);
+
+	idx = 0;
+	for (i = start; i < end; i++) {
+		DUK_ASSERT_TOP(ctx, 5);
+		if (duk_get_prop_index(ctx, 2, (duk_uarridx_t) i)) {
+			duk_def_prop_index_wec(ctx, 4, idx);
+			res_length = idx + 1;
+		} else {
+			duk_pop(ctx);
+		}
+		idx++;
+		DUK_ASSERT_TOP(ctx, 5);
+	}
+
+	duk_push_u32(ctx, res_length);
+	duk_def_prop_stridx(ctx, 4, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_W);
+
+	DUK_ASSERT_TOP(ctx, 5);
+	return 1;
+}
+
+/*
+ *  shift()
+ */
+
+duk_ret_t duk_bi_array_prototype_shift(duk_context *ctx) {
+	duk_uint32_t len;
+	duk_uint32_t i;
+
+	len = duk__push_this_obj_len_u32(ctx);
+	if (len == 0) {
+		duk_push_int(ctx, 0);
+		duk_put_prop_stridx(ctx, 0, DUK_STRIDX_LENGTH);
+		return 0;
+	}
+
+	duk_get_prop_index(ctx, 0, 0);
+
+	/* stack[0] = object (this)
+	 * stack[1] = ToUint32(length)
+	 * stack[2] = elem at index 0 (retval)
+	 */
+
+	for (i = 1; i < len; i++) {
+		DUK_ASSERT_TOP(ctx, 3);
+		if (duk_get_prop_index(ctx, 0, (duk_uarridx_t) i)) {
+			/* fromPresent = true */
+			duk_put_prop_index(ctx, 0, (duk_uarridx_t) (i - 1));
+		} else {
+			/* fromPresent = false */
+			duk_del_prop_index(ctx, 0, (duk_uarridx_t) (i - 1));
+			duk_pop(ctx);
+		}
+	}
+	duk_del_prop_index(ctx, 0, (duk_uarridx_t) (len - 1));
+
+	duk_push_u32(ctx, (duk_uint32_t) (len - 1));
+	duk_put_prop_stridx(ctx, 0, DUK_STRIDX_LENGTH);
+
+	DUK_ASSERT_TOP(ctx, 3);
+	return 1;
+}
+
+/*
+ *  unshift()
+ */
+
+duk_ret_t duk_bi_array_prototype_unshift(duk_context *ctx) {
+	duk_idx_t nargs;
+	duk_uint32_t len;
+	duk_uint32_t i;
+	duk_double_t final_len;
+
+	nargs = duk_get_top(ctx);
+	len = duk__push_this_obj_len_u32(ctx);
+
+	/* stack[0...nargs-1] = unshift args (vararg)
+	 * stack[nargs] = ToObject(this)
+	 * stack[nargs+1] = ToUint32(length)
+	 */
+
+	DUK_ASSERT_TOP(ctx, nargs + 2);
+
+	/* Note: unshift() may operate on indices above unsigned 32-bit range
+	 * and the final length may be >= 2**32.  Hence we use 'double' vars
+	 * here, when appropriate.
+	 */
+
+	i = len;
+	while (i > 0) {
+		DUK_ASSERT_TOP(ctx, nargs + 2);
+		i--;
+		/* k+argCount-1; note that may be above 32-bit range */
+		duk_push_number(ctx, ((duk_double_t) i) + ((duk_double_t) nargs));
+		if (duk_get_prop_index(ctx, -3, (duk_uarridx_t) i)) {
+			/* fromPresent = true */
+			/* [ ... ToObject(this) ToUint32(length) to val ] */
+			duk_put_prop(ctx, -4);  /* -> [ ... ToObject(this) ToUint32(length) ] */
+		} else {
+			/* fromPresent = false */
+			/* [ ... ToObject(this) ToUint32(length) to val ] */
+			duk_pop(ctx);
+			duk_del_prop(ctx, -3);  /* -> [ ... ToObject(this) ToUint32(length) ] */
+		}
+		DUK_ASSERT_TOP(ctx, nargs + 2);
+	}
+
+	for (i = 0; i < (duk_uint32_t) nargs; i++) {
+		DUK_ASSERT_TOP(ctx, nargs + 2);
+		duk_dup(ctx, i);  /* -> [ ... ToObject(this) ToUint32(length) arg[i] ] */
+		duk_put_prop_index(ctx, -3, (duk_uarridx_t) i);
+		DUK_ASSERT_TOP(ctx, nargs + 2);
+	}
+
+	DUK_ASSERT_TOP(ctx, nargs + 2);
+	final_len = ((duk_double_t) len) + ((duk_double_t) nargs);
+	duk_push_number(ctx, final_len);
+	duk_dup_top(ctx);  /* -> [ ... ToObject(this) ToUint32(length) final_len final_len ] */
+	duk_put_prop_stridx(ctx, -4, DUK_STRIDX_LENGTH);
+	return 1;
+}
+
+/*
+ *  indexOf(), lastIndexOf()
+ */
+
+duk_ret_t duk_bi_array_prototype_indexof_shared(duk_context *ctx) {
+	duk_idx_t nargs;
+	duk_int_t i, len;
+	duk_int_t from_index;
+	duk_small_int_t idx_step = duk_get_magic(ctx);  /* idx_step is +1 for indexOf, -1 for lastIndexOf */
+
+	/* lastIndexOf() needs to be a vararg function because we must distinguish
+	 * between an undefined fromIndex and a "not given" fromIndex; indexOf() is
+	 * made vararg for symmetry although it doesn't strictly need to be.
+	 */
+
+	nargs = duk_get_top(ctx);
+	duk_set_top(ctx, 2);
+
+	/* XXX: must be able to represent -len */
+	len = (duk_int_t) duk__push_this_obj_len_u32_limited(ctx);
+	if (len == 0) {
+		goto not_found;
+	}
+
+	/* Index clamping is a bit tricky, we must ensure that we'll only iterate
+	 * through elements that exist and that the specific requirements from E5.1
+	 * Sections 15.4.4.14 and 15.4.4.15 are fulfilled; especially:
+	 *
+	 *   - indexOf: clamp to [-len,len], negative handling -> [0,len],
+	 *     if clamped result is len, for-loop bails out immediately
+	 *
+	 *   - lastIndexOf: clamp to [-len-1, len-1], negative handling -> [-1, len-1],
+	 *     if clamped result is -1, for-loop bails out immediately
+	 *
+	 * If fromIndex is not given, ToInteger(undefined) = 0, which is correct
+	 * for indexOf() but incorrect for lastIndexOf().  Hence special handling,
+	 * and why lastIndexOf() needs to be a vararg function.
+	 */
+
+	if (nargs >= 2) {
+		/* indexOf: clamp fromIndex to [-len, len]
+		 * (if fromIndex == len, for-loop terminates directly)
+		 *
+		 * lastIndexOf: clamp fromIndex to [-len - 1, len - 1]
+		 * (if clamped to -len-1 -> fromIndex becomes -1, terminates for-loop directly)
+		 */
+		from_index = duk_to_int_clamped(ctx,
+		                                1,
+		                                (idx_step > 0 ? -len : -len - 1),
+		                                (idx_step > 0 ? len : len - 1));
+		if (from_index < 0) {
+			/* for lastIndexOf, result may be -1 (mark immediate termination) */
+			from_index = len + from_index;
+		}
+	} else {
+		/* for indexOf, ToInteger(undefined) would be 0, i.e. correct, but
+		 * handle both indexOf and lastIndexOf specially here.
+		 */
+		if (idx_step > 0) {
+			from_index = 0;
+		} else {
+			from_index = len - 1;
+		}
+	}
+
+	/* stack[0] = searchElement
+	 * stack[1] = fromIndex
+	 * stack[2] = object
+	 * stack[3] = length (not needed, but not popped above)
+	 */
+
+	for (i = from_index; i >= 0 && i < len; i += idx_step) {
+		DUK_ASSERT_TOP(ctx, 4);
+
+		if (duk_get_prop_index(ctx, 2, (duk_uarridx_t) i)) {
+			DUK_ASSERT_TOP(ctx, 5);
+			if (duk_strict_equals(ctx, 0, 4)) {
+				duk_push_int(ctx, i);
+				return 1;
+			}
+		}
+
+		duk_pop(ctx);
+	}
+
+ not_found:
+	duk_push_int(ctx, -1);
+	return 1;
+}
+
+/*
+ *  every(), some(), forEach(), map(), filter()
+ */
+
+#define DUK__ITER_EVERY    0
+#define DUK__ITER_SOME     1
+#define DUK__ITER_FOREACH  2
+#define DUK__ITER_MAP      3
+#define DUK__ITER_FILTER   4
+
+/* XXX: This helper is a bit awkward because the handling for the different iteration
+ * callers is quite different.  This now compiles to a bit less than 500 bytes, so with
+ * 5 callers the net result is about 100 bytes / caller.
+ */
+
+duk_ret_t duk_bi_array_prototype_iter_shared(duk_context *ctx) {
+	duk_uint32_t len;
+	duk_uint32_t i;
+	duk_uarridx_t k;
+	duk_bool_t bval;
+	duk_small_int_t iter_type = duk_get_magic(ctx);
+	duk_uint32_t res_length = 0;
+
+	/* each call this helper serves has nargs==2 */
+	DUK_ASSERT_TOP(ctx, 2);
+
+	len = duk__push_this_obj_len_u32(ctx);
+	if (!duk_is_callable(ctx, 0)) {
+		goto type_error;
+	}
+	/* if thisArg not supplied, behave as if undefined was supplied */
+
+	if (iter_type == DUK__ITER_MAP || iter_type == DUK__ITER_FILTER) {
+		duk_push_array(ctx);
+	} else {
+		duk_push_undefined(ctx);
+	}
+
+	/* stack[0] = callback
+	 * stack[1] = thisArg
+	 * stack[2] = object
+	 * stack[3] = ToUint32(length)  (unused, but avoid unnecessary pop)
+	 * stack[4] = result array (or undefined)
+	 */
+
+	k = 0;  /* result index for filter() */
+	for (i = 0; i < len; i++) {
+		DUK_ASSERT_TOP(ctx, 5);
+
+		if (!duk_get_prop_index(ctx, 2, (duk_uarridx_t) i)) {
+			duk_pop(ctx);
+			continue;
+		}
+
+		/* The original value needs to be preserved for filter(), hence
+		 * this funny order.  We can't re-get the value because of side
+		 * effects.
+		 */
+
+		duk_dup(ctx, 0);
+		duk_dup(ctx, 1);
+		duk_dup(ctx, -3);
+		duk_push_u32(ctx, i);
+		duk_dup(ctx, 2);  /* [ ... val callback thisArg val i obj ] */
+		duk_call_method(ctx, 3); /* -> [ ... val retval ] */
+
+		switch (iter_type) {
+		case DUK__ITER_EVERY:
+			bval = duk_to_boolean(ctx, -1);
+			if (!bval) {
+				/* stack top contains 'false' */
+				return 1;
+			}
+			break;
+		case DUK__ITER_SOME:
+			bval = duk_to_boolean(ctx, -1);
+			if (bval) {
+				/* stack top contains 'true' */
+				return 1;
+			}
+			break;
+		case DUK__ITER_FOREACH:
+			/* nop */
+			break;
+		case DUK__ITER_MAP:
+			duk_dup(ctx, -1);
+			duk_def_prop_index_wec(ctx, 4, (duk_uarridx_t) i);  /* retval to result[i] */
+			res_length = i + 1;
+			break;
+		case DUK__ITER_FILTER:
+			bval = duk_to_boolean(ctx, -1);
+			if (bval) {
+				duk_dup(ctx, -2);  /* orig value */
+				duk_def_prop_index_wec(ctx, 4, (duk_uarridx_t) k);
+				k++;
+				res_length = k;
+			}
+			break;
+		default:
+			DUK_UNREACHABLE();
+			break;
+		}
+		duk_pop_2(ctx);
+
+		DUK_ASSERT_TOP(ctx, 5);
+	}
+
+	switch (iter_type) {
+	case DUK__ITER_EVERY:
+		duk_push_true(ctx);
+		break;
+	case DUK__ITER_SOME:
+		duk_push_false(ctx);
+		break;
+	case DUK__ITER_FOREACH:
+		duk_push_undefined(ctx);
+		break;
+	case DUK__ITER_MAP:
+	case DUK__ITER_FILTER:
+		DUK_ASSERT_TOP(ctx, 5);
+		DUK_ASSERT(duk_is_array(ctx, -1));  /* topmost element is the result array already */
+		duk_push_u32(ctx, res_length);
+		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_W);
+		break;
+	default:
+		DUK_UNREACHABLE();
+		break;
+	}
+
+	return 1;
+
+ type_error:
+	return DUK_RET_TYPE_ERROR;
+}
+
+/*
+ *  reduce(), reduceRight()
+ */
+
+duk_ret_t duk_bi_array_prototype_reduce_shared(duk_context *ctx) {
+	duk_idx_t nargs;
+	duk_bool_t have_acc;
+	duk_uint32_t i, len;
+	duk_small_int_t idx_step = duk_get_magic(ctx);  /* idx_step is +1 for reduce, -1 for reduceRight */
+
+	/* We're a varargs function because we need to detect whether
+	 * initialValue was given or not.
+	 */
+	nargs = duk_get_top(ctx);
+	DUK_DDD(DUK_DDDPRINT("nargs=%ld", (long) nargs));
+
+	duk_set_top(ctx, 2);
+	len = duk__push_this_obj_len_u32(ctx);
+	if (!duk_is_callable(ctx, 0)) {
+		goto type_error;
+	}
+
+	/* stack[0] = callback fn
+	 * stack[1] = initialValue
+	 * stack[2] = object (coerced this)
+	 * stack[3] = length (not needed, but not popped above)
+	 * stack[4] = accumulator
+	 */
+
+	have_acc = 0;
+	if (nargs >= 2) {
+		duk_dup(ctx, 1);
+		have_acc = 1;
+	}
+	DUK_DDD(DUK_DDDPRINT("have_acc=%ld, acc=%!T",
+	                     (long) have_acc, (duk_tval *) duk_get_tval(ctx, 3)));
+
+	/* For len == 0, i is initialized to len - 1 which underflows.
+	 * The condition (i < len) will then exit the for-loop on the
+	 * first round which is correct.  Similarly, loop termination
+	 * happens by i underflowing.
+	 */
+
+	for (i = (idx_step >= 0 ? 0 : len - 1);
+	     i < len;  /* i >= 0 would always be true */
+	     i += idx_step) {
+		DUK_DDD(DUK_DDDPRINT("i=%ld, len=%ld, have_acc=%ld, top=%ld, acc=%!T",
+		                     (long) i, (long) len, (long) have_acc,
+		                     (long) duk_get_top(ctx),
+		                     (duk_tval *) duk_get_tval(ctx, 4)));
+
+		DUK_ASSERT((have_acc && duk_get_top(ctx) == 5) ||
+		           (!have_acc && duk_get_top(ctx) == 4));
+
+		if (!duk_has_prop_index(ctx, 2, (duk_uarridx_t) i)) {
+			continue;
+		}
+
+		if (!have_acc) {
+			DUK_ASSERT_TOP(ctx, 4);
+			duk_get_prop_index(ctx, 2, (duk_uarridx_t) i);
+			have_acc = 1;
+			DUK_ASSERT_TOP(ctx, 5);
+		} else {
+			DUK_ASSERT_TOP(ctx, 5);
+			duk_dup(ctx, 0);
+			duk_dup(ctx, 4);
+			duk_get_prop_index(ctx, 2, (duk_uarridx_t) i);
+			duk_push_u32(ctx, i);
+			duk_dup(ctx, 2);
+			DUK_DDD(DUK_DDDPRINT("calling reduce function: func=%!T, prev=%!T, curr=%!T, idx=%!T, obj=%!T",
+			                     (duk_tval *) duk_get_tval(ctx, -5), (duk_tval *) duk_get_tval(ctx, -4),
+			                     (duk_tval *) duk_get_tval(ctx, -3), (duk_tval *) duk_get_tval(ctx, -2),
+			                     (duk_tval *) duk_get_tval(ctx, -1)));
+			duk_call(ctx, 4);
+			DUK_DDD(DUK_DDDPRINT("-> result: %!T", (duk_tval *) duk_get_tval(ctx, -1)));
+			duk_replace(ctx, 4);
+			DUK_ASSERT_TOP(ctx, 5);
+		}
+	}
+
+	if (!have_acc) {
+		goto type_error;
+	}
+
+	DUK_ASSERT_TOP(ctx, 5);
+	return 1;
+
+ type_error:
+	return DUK_RET_TYPE_ERROR;
+}
+#line 1 "duk_bi_boolean.c"
+/*
+ *  Boolean built-ins
+ */
+
+/* include removed: duk_internal.h */
+
+/* Shared helper to provide toString() and valueOf().  Checks 'this', gets
+ * the primitive value to stack top, and optionally coerces with ToString().
+ */
+duk_ret_t duk_bi_boolean_prototype_tostring_shared(duk_context *ctx) {
+	duk_tval *tv;
+	duk_hobject *h;
+	duk_small_int_t coerce_tostring = duk_get_magic(ctx);
+
+	/* FIXME: there is room to use a shared helper here, many built-ins
+	 * check the 'this' type, and if it's an object, check its class,
+	 * then get its internal value, etc.
+	 */
+
+	duk_push_this(ctx);
+	tv = duk_get_tval(ctx, -1);
+	DUK_ASSERT(tv != NULL);
+
+	if (DUK_TVAL_IS_BOOLEAN(tv)) {
+		goto type_ok;
+	} else if (DUK_TVAL_IS_OBJECT(tv)) {
+		h = DUK_TVAL_GET_OBJECT(tv);
+		DUK_ASSERT(h != NULL);
+
+		if (DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_BOOLEAN) {
+			duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_VALUE);
+			DUK_ASSERT(duk_is_boolean(ctx, -1));
+			goto type_ok;
+		}
+	}
+
+	return DUK_RET_TYPE_ERROR;
+
+ type_ok:
+	if (coerce_tostring) {
+		duk_to_string(ctx, -1);
+	}
+	return 1;
+}
+
+duk_ret_t duk_bi_boolean_constructor(duk_context *ctx) {
+	duk_hobject *h_this;
+
+	duk_to_boolean(ctx, 0);
+
+	if (duk_is_constructor_call(ctx)) {
+		/* FIXME: helper; rely on Boolean.prototype as being non-writable, non-configurable */
+		duk_push_this(ctx);
+		h_this = duk_get_hobject(ctx, -1);
+		DUK_ASSERT(h_this != NULL);
+		DUK_ASSERT(h_this->prototype == ((duk_hthread *) ctx)->builtins[DUK_BIDX_BOOLEAN_PROTOTYPE]);
+
+		DUK_HOBJECT_SET_CLASS_NUMBER(h_this, DUK_HOBJECT_CLASS_BOOLEAN);
+
+		duk_dup(ctx, 0);  /* -> [ val obj val ] */
+		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_NONE);  /* FIXME: proper flags? */
+	}  /* unbalanced stack */
+
+	return 1;
+}
+#line 1 "duk_bi_buffer.c"
+/*
+ *  Buffer built-ins
+ */
+
+/* include removed: duk_internal.h */
+
+/*
+ *  Constructor
+ */
+
+duk_ret_t duk_bi_buffer_constructor(duk_context *ctx) {
+	duk_size_t buf_size;
+	duk_small_int_t buf_dynamic;
+	duk_uint8_t *buf_data;
+	const duk_uint8_t *src_data;
+	duk_hobject *h_obj;
+
+	/*
+	 *  Constructor arguments are currently somewhat compatible with
+	 *  (keep it that way if possible):
+	 *
+	 *    http://nodejs.org/api/buffer.html
+	 *
+	 */
+
+	buf_dynamic = duk_get_boolean(ctx, 1);  /* default to false */
+
+	switch (duk_get_type(ctx, 0)) {
+	case DUK_TYPE_NUMBER:
+		/* new buffer of specified size */
+		buf_size = (duk_size_t) duk_to_int(ctx, 0);
+		(void) duk_push_buffer(ctx, buf_size, buf_dynamic);
+		break;
+	case DUK_TYPE_BUFFER:
+		/* return input buffer, converted to a Buffer object if called as a
+		 * constructor (no change if called as a function).
+		 */
+		duk_set_top(ctx, 1);
+		break;
+	case DUK_TYPE_STRING:
+		/* new buffer with string contents */
+		src_data = (const duk_uint8_t *) duk_get_lstring(ctx, 0, &buf_size);
+		DUK_ASSERT(src_data != NULL);  /* even for zero-length string */
+		buf_data = (duk_uint8_t *) duk_push_buffer(ctx, buf_size, buf_dynamic);
+		DUK_MEMCPY((void *) buf_data, (const void *) src_data, (size_t) buf_size);
+		break;
+	case DUK_TYPE_OBJECT:
+		/* Buffer object: get the plain buffer inside.  If called as as
+		 * constructor, a new Buffer object pointing to the same plain
+		 * buffer is created below.
+		 */
+		h_obj = duk_get_hobject(ctx, 0);
+		DUK_ASSERT(h_obj != NULL);
+		if (DUK_HOBJECT_GET_CLASS_NUMBER(h_obj) != DUK_HOBJECT_CLASS_BUFFER) {
+			return DUK_RET_TYPE_ERROR;
+		}
+		duk_get_prop_stridx(ctx, 0, DUK_STRIDX_INT_VALUE);
+		DUK_ASSERT(duk_is_buffer(ctx, -1));
+		break;
+	case DUK_TYPE_NONE:
+	default:
+		return DUK_RET_TYPE_ERROR;
+	}
+
+	/* stack is unbalanced, but: [ <something> buf ] */
+
+	if (duk_is_constructor_call(ctx)) {
+		duk_push_object_helper(ctx,
+		                       DUK_HOBJECT_FLAG_EXTENSIBLE |
+		                       DUK_HOBJECT_FLAG_EXOTIC_BUFFEROBJ |
+		                       DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_BUFFER),
+		                       DUK_BIDX_BUFFER_PROTOTYPE);
+
+		/* Buffer object internal value is immutable */
+		duk_dup(ctx, -2);
+		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_NONE);
+	}
+	/* Note: unbalanced stack on purpose */
+
+	return 1;
+}
+
+/*
+ *  toString(), valueOf()
+ */
+
+duk_ret_t duk_bi_buffer_prototype_tostring_shared(duk_context *ctx) {
+	duk_tval *tv;
+	duk_small_int_t to_string = duk_get_magic(ctx);
+
+	duk_push_this(ctx);
+	tv = duk_require_tval(ctx, -1);
+	DUK_ASSERT(tv != NULL);
+
+	if (DUK_TVAL_IS_BUFFER(tv)) {
+		/* nop */
+	} else if (DUK_TVAL_IS_OBJECT(tv)) {
+		duk_hobject *h = DUK_TVAL_GET_OBJECT(tv);
+		DUK_ASSERT(h != NULL);
+
+		/* Must be a "buffer object", i.e. class "Buffer" */
+		if (DUK_HOBJECT_GET_CLASS_NUMBER(h) != DUK_HOBJECT_CLASS_BUFFER) {
+			goto type_error;
+		}
+
+		duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_VALUE);
+	} else {
+		goto type_error;
+	}
+
+	if (to_string) {
+		duk_to_string(ctx, -1);
+	}
+	return 1;
+
+ type_error:
+	return DUK_RET_TYPE_ERROR;
+}
+#line 1 "duk_bi_date.c"
+/*
+ *  Date built-ins
+ *
+ *  Unlike most built-ins, Date has a lot of platform dependencies for
+ *  getting UTC time, converting between UTC and local time, and parsing
+ *  and formatting time values.
+ *
+ *  See doc/datetime.txt.
+ *
+ *  Platform specific links:
+ *
+ *    - http://msdn.microsoft.com/en-us/library/windows/desktop/ms725473(v=vs.85).aspx
+ */
+
+/* include removed: duk_internal.h */
+
+/*
+ *  Platform specific includes and defines
+ *
+ *  Note that necessary system headers (like <sys/time.h>) are included
+ *  by duk_internal.h (or duk_features.h, which is included by duk_internal.h)
+ *  because the header locations vary between systems and we don't want
+ *  that clutter here.
+ */
+
+#define DUK__GET_NOW_TIMEVAL      duk_bi_date_get_now
+#define DUK__GET_LOCAL_TZOFFSET   duk__get_local_tzoffset
+
+/* Buffer sizes for some UNIX calls.  Larger than strictly necessary
+ * to avoid Valgrind errors.
+ */
+#define DUK__STRPTIME_BUF_SIZE  64
+#define DUK__STRFTIME_BUF_SIZE  64
+
+/*
+ *  Other file level defines
+ */
+
+/* Forward declarations. */
+static duk_double_t duk__push_this_get_timeval_tzoffset(duk_context *ctx, duk_small_uint_t flags, duk_int_t *out_tzoffset);
+static duk_double_t duk__push_this_get_timeval(duk_context *ctx, duk_small_uint_t flags);
+static void duk__timeval_to_parts(duk_double_t d, duk_int_t *parts, duk_double_t *dparts, duk_small_uint_t flags);
+static duk_double_t duk__get_timeval_from_dparts(duk_double_t *dparts, duk_small_uint_t flags);
+static void duk__twodigit_year_fixup(duk_context *ctx, duk_idx_t idx_val);
+
+/* Millisecond count constants. */
+#define DUK__MS_SECOND          1000L
+#define DUK__MS_MINUTE          (60L * 1000L)
+#define DUK__MS_HOUR            (60L * 60L * 1000L)
+#define DUK__MS_DAY             (24L * 60L * 60L * 1000L)
+
+/* Part indices for internal breakdowns.  Part order from DUK__IDX_YEAR to
+ * DUK__IDX_MILLISECOND matches argument ordering of Ecmascript API calls
+ * (like Date constructor call).  A few functions in this file depend
+ * on the specific ordering, so change with care.  16 bits are not enough
+ * for all parts (year, specifically).
+ *
+ * (Must be in-sync with genbuiltins.py.)
+ */
+#define DUK__IDX_YEAR           0  /* year */
+#define DUK__IDX_MONTH          1  /* month: 0 to 11 */
+#define DUK__IDX_DAY            2  /* day within month: 0 to 30 */
+#define DUK__IDX_HOUR           3
+#define DUK__IDX_MINUTE         4
+#define DUK__IDX_SECOND         5
+#define DUK__IDX_MILLISECOND    6
+#define DUK__IDX_WEEKDAY        7  /* weekday: 0 to 6, 0=sunday, 1=monday, etc */
+#define DUK__NUM_PARTS          8
+
+/* Internal API call flags, used for various functions in this file.
+ * Certain flags are used by only certain functions, but since the flags
+ * don't overlap, a single flags value can be passed around to multiple
+ * functions.
+ *
+ * The unused top bits of the flags field are also used to pass values
+ * to helpers (duk__get_part_helper() and duk__set_part_helper()).
+ *
+ * (Must be in-sync with genbuiltins.py.)
+ */
+#define DUK__FLAG_NAN_TO_ZERO          (1 << 0)  /* timeval breakdown: internal time value NaN -> zero */
+#define DUK__FLAG_NAN_TO_RANGE_ERROR   (1 << 1)  /* timeval breakdown: internal time value NaN -> RangeError (toISOString) */
+#define DUK__FLAG_ONEBASED             (1 << 2)  /* timeval breakdown: convert month and day-of-month parts to one-based (default is zero-based) */
+#define DUK__FLAG_LOCALTIME            (1 << 3)  /* convert time value to local time */
+#define DUK__FLAG_SUB1900              (1 << 4)  /* getter: subtract 1900 from year when getting year part */
+#define DUK__FLAG_TOSTRING_DATE        (1 << 5)  /* include date part in string conversion result */
+#define DUK__FLAG_TOSTRING_TIME        (1 << 6)  /* include time part in string conversion result */
+#define DUK__FLAG_TOSTRING_LOCALE      (1 << 7)  /* use locale specific formatting if available */
+#define DUK__FLAG_TIMESETTER           (1 << 8)  /* setter: call is a time setter (affects hour, min, sec, ms); otherwise date setter (affects year, month, day-in-month) */
+#define DUK__FLAG_YEAR_FIXUP           (1 << 9)  /* setter: perform 2-digit year fixup (00...99 -> 1900...1999) */
+#define DUK__FLAG_SEP_T                (1 << 10) /* string conversion: use 'T' instead of ' ' as a separator */
+
+/*
+ *  Platform specific helpers
+ */
+
+#ifdef DUK_USE_DATE_NOW_GETTIMEOFDAY
+/* Get current Ecmascript time (= UNIX/Posix time, but in milliseconds). */
+duk_double_t duk_bi_date_get_now(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	struct timeval tv;
+	duk_double_t d;
+
+	if (gettimeofday(&tv, NULL) != 0) {
+		DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "gettimeofday failed");
+	}
+
+	d = ((duk_double_t) tv.tv_sec) * 1000.0 +
+	    ((duk_double_t) (tv.tv_usec / 1000));
+	DUK_ASSERT(DUK_FLOOR(d) == d);  /* no fractions */
+
+	return d;
+}
+#endif  /* DUK_USE_DATE_NOW_GETTIMEOFDAY */
+
+#ifdef DUK_USE_DATE_NOW_TIME
+/* Not a very good provider: only full seconds are available. */
+duk_double_t duk_bi_date_get_now(duk_context *ctx) {
+	time_t t = time(NULL);
+	return ((duk_double_t) t) * 1000.0;
+}
+#endif  /* DUK_USE_DATE_NOW_TIME */
+
+#if defined(DUK_USE_DATE_NOW_WINDOWS) || defined(DUK_USE_DATE_TZO_WINDOWS)
+/* Shared Windows helpers. */
+static void duk__convert_systime_to_ularge(const SYSTEMTIME *st, ULARGE_INTEGER *res) {
+	FILETIME ft;
+	if (SystemTimeToFileTime(st, &ft) == 0) {
+		DUK_D(DUK_DPRINT("SystemTimeToFileTime() failed, returning 0"));
+		res->QuadPart = 0;
+	} else {
+		res->LowPart = ft.dwLowDateTime;
+		res->HighPart = ft.dwHighDateTime;
+	}
+}
+static void duk__set_systime_jan1970(SYSTEMTIME *st) {
+	DUK_MEMZERO((void *) st, sizeof(*st));
+	st->wYear = 1970;
+	st->wMonth = 1;
+	st->wDayOfWeek = 4;  /* not sure whether or not needed; Thursday */
+	st->wDay = 1;
+	DUK_ASSERT(st->wHour == 0);
+	DUK_ASSERT(st->wMinute == 0);
+	DUK_ASSERT(st->wSecond == 0);
+	DUK_ASSERT(st->wMilliseconds == 0);
+}
+#endif  /* defined(DUK_USE_DATE_NOW_WINDOWS) || defined(DUK_USE_DATE_TZO_WINDOWS) */
+
+#ifdef DUK_USE_DATE_NOW_WINDOWS
+duk_double_t duk_bi_date_get_now(duk_context *ctx) {
+	/* Suggested step-by-step method from documentation of RtlTimeToSecondsSince1970:
+	 * http://msdn.microsoft.com/en-us/library/windows/desktop/ms724928(v=vs.85).aspx
+	 */
+	SYSTEMTIME st1, st2;
+	ULARGE_INTEGER tmp1, tmp2;
+
+	DUK_UNREF(ctx);
+
+	GetSystemTime(&st1);
+	duk__convert_systime_to_ularge((const SYSTEMTIME *) &st1, &tmp1);
+
+	duk__set_systime_jan1970(&st2);
+	duk__convert_systime_to_ularge((const SYSTEMTIME *) &st2, &tmp2);
+
+	/* Difference is in 100ns units, convert to milliseconds w/o fractions */
+	return (duk_double_t) ((tmp1.QuadPart - tmp2.QuadPart) / 10000LL);
+}
+#endif  /* DUK_USE_DATE_NOW_WINDOWS */
+
+#if defined(DUK_USE_DATE_TZO_GMTIME) || defined(DUK_USE_DATE_TZO_GMTIME_R)
+/* Get local time offset (in seconds) for a certain (UTC) instant 'd'. */
+static duk_int_t duk__get_local_tzoffset(duk_double_t d) {
+	time_t t, t1, t2;
+	duk_int_t parts[DUK__NUM_PARTS];
+	duk_double_t dparts[DUK__NUM_PARTS];
+	struct tm tms[2];
+#ifdef DUK_USE_DATE_TZO_GMTIME
+	struct tm *tm_ptr;
+#endif
+
+	/* For NaN/inf, the return value doesn't matter. */
+	if (!DUK_ISFINITE(d)) {
+		return 0;
+	}
+
+	/*
+	 *  This is a bit tricky to implement portably.  The result depends
+	 *  on the timestamp (specifically, DST depends on the timestamp).
+	 *  If e.g. UNIX APIs are used, they'll have portability issues with
+	 *  very small and very large years.
+	 *
+	 *  Current approach:
+	 *
+	 *  - Clamp year to stay within portable UNIX limits.  Avoid 2038 as
+	 *    some conversions start to fail.  Avoid 1970, as some conversions
+	 *    in January 1970 start to fail (verified in practice).
+	 *
+	 *  - Create a UTC time breakdown from 't', and then pretend it is a
+	 *    local time breakdown and build a UTC time from it.  The timestamp
+	 *    will effectively shift backwards by time the time offset (e.g. -2h
+	 *    or -3h for EET/EEST).  Convert with mktime() twice to get the DST
+	 *    flag for the final conversion.
+	 *
+	 *  FIXME: this is probably not entirely correct nor clear, but is
+	 *  good enough for now.
+	 */
+
+	duk__timeval_to_parts(d, parts, dparts, 0 /*flags*/);
+
+	/*
+	 *  FIXME: must choose 'equivalent year', E5 Section 15.9.1.8, instead
+	 *  of just clamping.
+	 */
+	if (parts[DUK__IDX_YEAR] < 1971) {
+		dparts[DUK__IDX_YEAR] = 1971.0;
+	} else if (parts[DUK__IDX_YEAR] > 2037) {
+		dparts[DUK__IDX_YEAR] = 2037.0;
+	}
+
+	d = duk__get_timeval_from_dparts(dparts, 0 /*flags*/);
+	DUK_ASSERT(d >= 0 && d < 2147483648.0 * 1000.0);  /* unsigned 31-bit range */
+	t = (time_t) (d / 1000.0);
+	DUK_DDD(DUK_DDDPRINT("timeval: %lf -> time_t %ld", (double) d, (long) t));
+
+	t1 = t;
+
+	DUK_MEMZERO((void *) tms, sizeof(struct tm) * 2);
+
+#if defined(DUK_USE_DATE_TZO_GMTIME_R)
+	(void) gmtime_r(&t, &tms[0]);
+#elif defined(DUK_USE_DATE_TZO_GMTIME)
+	tm_ptr = gmtime(&t);
+	DUK_MEMCPY((void *) &tms[0], tm_ptr, sizeof(struct tm));
+#else
+#error internal error
+#endif
+	DUK_MEMCPY((void *) &tms[1], &tms[0], sizeof(struct tm));
+
+	DUK_DDD(DUK_DDDPRINT("before mktime: tm={sec:%ld,min:%ld,hour:%ld,mday:%ld,mon:%ld,year:%ld,"
+	                     "wday:%ld,yday:%ld,isdst:%ld}",
+	                     (long) tms[0].tm_sec, (long) tms[0].tm_min, (long) tms[0].tm_hour,
+	                     (long) tms[0].tm_mday, (long) tms[0].tm_mon, (long) tms[0].tm_year,
+	                     (long) tms[0].tm_wday, (long) tms[0].tm_yday, (long) tms[0].tm_isdst));
+
+	(void) mktime(&tms[0]);
+	tms[1].tm_isdst = tms[0].tm_isdst;
+	t2 = mktime(&tms[1]);
+	DUK_ASSERT_DISABLE(t2 >= 0);  /* On some platforms time_t is unsigned and this would cause a warning */
+	if (t2 == (time_t) -1) {
+		/* This check used to be for (t2 < 0) but on some platforms
+		 * time_t is unsigned and apparently the proper way to detect
+		 * an mktime() error return is the cast above.  See e.g.:
+		 * http://pubs.opengroup.org/onlinepubs/009695299/functions/mktime.html
+		 */
+		goto error;
+	}
+
+	DUK_DDD(DUK_DDDPRINT("after mktime: tm={sec:%ld,min:%ld,hour:%ld,mday:%ld,mon:%ld,year:%ld,"
+	                     "wday:%ld,yday:%ld,isdst:%ld}",
+	                     (long) tms[1].tm_sec, (long) tms[1].tm_min, (long) tms[1].tm_hour,
+	                     (long) tms[1].tm_mday, (long) tms[1].tm_mon, (long) tms[1].tm_year,
+	                     (long) tms[1].tm_wday, (long) tms[1].tm_yday, (long) tms[1].tm_isdst));
+	DUK_DDD(DUK_DDDPRINT("t2=%ld", (long) t2));
+
+	/* Positive if local time ahead of UTC. */
+
+	/* difftime() returns a double, so coercion to int generates quite
+	 * a lot of code.  Direct subtraction is not portable, however.
+	 *
+	 * XXX: allow direct subtraction on known platforms.
+	 */
+#if 0
+	return (duk_int_t) (t1 - t2);
+#endif
+	return (duk_int_t) difftime(t1, t2);
+
+ error:
+	/* FIXME: return something more useful, so that caller can throw? */
+	DUK_D(DUK_DPRINT("mktime() failed, d=%lf", (double) d));
+	return 0;
+}
+#endif  /* DUK_USE_DATE_TZO_GMTIME */
+
+#if defined(DUK_USE_DATE_TZO_WINDOWS)
+static duk_int_t duk__get_local_tzoffset(duk_double_t d) {
+	SYSTEMTIME st1;
+	SYSTEMTIME st2;
+	SYSTEMTIME st3;
+	ULARGE_INTEGER tmp1;
+	ULARGE_INTEGER tmp2;
+	ULARGE_INTEGER tmp3;
+	FILETIME ft1;
+
+	/* Use the approach described in "Remarks" of FileTimeToLocalFileTime:
+	 * http://msdn.microsoft.com/en-us/library/windows/desktop/ms724277(v=vs.85).aspx
+	 */
+
+	duk__set_systime_jan1970(&st1);
+	duk__convert_systime_to_ularge((const SYSTEMTIME *) &st1, &tmp1);
+	tmp2.QuadPart = (ULONGLONG) (d * 10000.0);  /* millisec -> 100ns units since jan 1, 1970 */
+	tmp2.QuadPart += tmp1.QuadPart;             /* input 'd' in Windows UTC, 100ns units */
+
+	ft1.dwLowDateTime = tmp2.LowPart;
+	ft1.dwHighDateTime = tmp2.HighPart;
+	FileTimeToSystemTime((const FILETIME *) &ft1, &st2);
+	if (SystemTimeToTzSpecificLocalTime((LPTIME_ZONE_INFORMATION) NULL, &st2, &st3) == 0) {
+		DUK_D(DUK_DPRINT("SystemTimeToTzSpecificLocalTime() failed, return tzoffset 0"));
+		return 0;
+	}
+	duk__convert_systime_to_ularge((const SYSTEMTIME *) &st3, &tmp3);
+
+	/* Positive if local time ahead of UTC. */
+	return (duk_int_t) (((LONGLONG) tmp3.QuadPart - (LONGLONG) tmp2.QuadPart) / 10000000LL);  /* seconds */
+}
+#endif  /* DUK_USE_DATE_TZO_WINDOWS */
+
+#ifdef DUK_USE_DATE_PRS_STRPTIME
+static duk_bool_t duk__parse_string_strptime(duk_context *ctx, const char *str) {
+	struct tm tm;
+	time_t t;
+	char buf[DUK__STRPTIME_BUF_SIZE];
+
+	/* copy to buffer with spare to avoid Valgrind gripes from strptime */
+	DUK_ASSERT(str != NULL);
+	DUK_MEMZERO(buf, sizeof(buf));  /* valgrind whine without this */
+	DUK_SNPRINTF(buf, sizeof(buf), "%s", (const char *) str);
+	buf[sizeof(buf) - 1] = (char) 0;
+
+	DUK_DDD(DUK_DDDPRINT("parsing: '%s'", (const char *) buf));
+
+	DUK_MEMZERO(&tm, sizeof(tm));
+	if (strptime((const char *) buf, "%c", &tm) != NULL) {
+		DUK_DDD(DUK_DDDPRINT("before mktime: tm={sec:%ld,min:%ld,hour:%ld,mday:%ld,mon:%ld,year:%ld,"
+		                     "wday:%ld,yday:%ld,isdst:%ld}",
+		                     (long) tm.tm_sec, (long) tm.tm_min, (long) tm.tm_hour,
+		                     (long) tm.tm_mday, (long) tm.tm_mon, (long) tm.tm_year,
+		                      (long) tm.tm_wday, (long) tm.tm_yday, (long) tm.tm_isdst));
+		tm.tm_isdst = -1;  /* negative: dst info not available */
+
+		t = mktime(&tm);
+		DUK_DDD(DUK_DDDPRINT("mktime() -> %ld", (long) t));
+		if (t >= 0) {
+			duk_push_number(ctx, ((duk_double_t) t) * 1000.0);
+			return 1;
+		}
+	}
+
+	return 0;
+}
+#endif  /* DUK_USE_DATE_PRS_STRPTIME */
+
+#ifdef DUK_USE_DATE_PRS_GETDATE
+static duk_bool_t duk__parse_string_getdate(duk_context *ctx, const char *str) {
+	struct tm tm;
+	duk_small_int_t rc;
+	time_t t;
+
+	/* For this to work, DATEMSK must be set, so this is not very
+	 * convenient for an embeddable interpreter.
+	 */
+
+	DUK_MEMZERO(&tm, sizeof(struct tm));
+	rc = (duk_small_int_t) getdate_r(str, &tm);
+	DUK_DDD(DUK_DDDPRINT("getdate_r() -> %ld", (long) rc));
+
+	if (rc == 0) {
+		t = mktime(&tm);
+		DUK_DDD(DUK_DDDPRINT("mktime() -> %ld", (long) t));
+		if (t >= 0) {
+			duk_push_number(ctx, (duk_double_t) t);
+			return 1;
+		}
+	}
+
+	return 0;
+}
+#endif  /* DUK_USE_DATE_PRS_GETDATE */
+
+#ifdef DUK_USE_DATE_FMT_STRFTIME
+static duk_bool_t duk__format_parts_strftime(duk_context *ctx, duk_int_t *parts, duk_int_t tzoffset, duk_small_uint_t flags) {
+	char buf[DUK__STRFTIME_BUF_SIZE];
+	struct tm tm;
+	const char *fmt;
+
+	DUK_UNREF(tzoffset);
+
+	/* If the platform doesn't support the entire Ecmascript range, we need
+	 * to return 0 so that the caller can fall back to the default formatter.
+	 *
+	 * For now, assume that if time_t is 8 bytes or more, the whole Ecmascript
+	 * range is supported.  For smaller time_t values (4 bytes in practice),
+	 * assumes that the signed 32-bit range is supported.
+	 *
+	 * XXX: detect this more correctly per platform.  The size of time_t is
+	 * probably not an accurate guarantee of strftime() supporting or not
+	 * supporting a large time range (the full Ecmascript range).
+	 */
+	if (sizeof(time_t) < 8 &&
+	   (parts[DUK__IDX_YEAR] < 1970 || parts[DUK__IDX_YEAR] > 2037)) {
+		/* be paranoid for 32-bit time values (even avoiding negative ones) */
+		return 0;
+	}
+
+	DUK_MEMZERO(&tm, sizeof(tm));
+	tm.tm_sec = parts[DUK__IDX_SECOND];
+	tm.tm_min = parts[DUK__IDX_MINUTE];
+	tm.tm_hour = parts[DUK__IDX_HOUR];
+	tm.tm_mday = parts[DUK__IDX_DAY];       /* already one-based */
+	tm.tm_mon = parts[DUK__IDX_MONTH] - 1;  /* one-based -> zero-based */
+	tm.tm_year = parts[DUK__IDX_YEAR] - 1900;
+	tm.tm_wday = parts[DUK__IDX_WEEKDAY];
+	tm.tm_isdst = 0;
+
+	DUK_MEMZERO(buf, sizeof(buf));
+	if ((flags & DUK__FLAG_TOSTRING_DATE) && (flags & DUK__FLAG_TOSTRING_TIME)) {
+		fmt = "%c";
+	} else if (flags & DUK__FLAG_TOSTRING_DATE) {
+		fmt = "%x";
+	} else {
+		DUK_ASSERT(flags & DUK__FLAG_TOSTRING_TIME);
+		fmt = "%X";
+	}
+	(void) strftime(buf, sizeof(buf) - 1, fmt, &tm);
+	DUK_ASSERT(buf[sizeof(buf) - 1] == 0);
+
+	duk_push_string(ctx, buf);
+	return 1;
+}
+#endif  /* DUK_USE_DATE_FMT_STRFTIME */
+
+/*
+ *  ISO 8601 subset parser.
+ */
+
+/* Parser part count. */
+#define DUK__NUM_ISO8601_PARSER_PARTS  9
+
+/* Parser part indices. */
+#define DUK__PI_YEAR         0
+#define DUK__PI_MONTH        1
+#define DUK__PI_DAY          2
+#define DUK__PI_HOUR         3
+#define DUK__PI_MINUTE       4
+#define DUK__PI_SECOND       5
+#define DUK__PI_MILLISECOND  6
+#define DUK__PI_TZHOUR       7
+#define DUK__PI_TZMINUTE     8
+
+/* Parser part masks. */
+#define DUK__PM_YEAR         (1 << DUK__PI_YEAR)
+#define DUK__PM_MONTH        (1 << DUK__PI_MONTH)
+#define DUK__PM_DAY          (1 << DUK__PI_DAY)
+#define DUK__PM_HOUR         (1 << DUK__PI_HOUR)
+#define DUK__PM_MINUTE       (1 << DUK__PI_MINUTE)
+#define DUK__PM_SECOND       (1 << DUK__PI_SECOND)
+#define DUK__PM_MILLISECOND  (1 << DUK__PI_MILLISECOND)
+#define DUK__PM_TZHOUR       (1 << DUK__PI_TZHOUR)
+#define DUK__PM_TZMINUTE     (1 << DUK__PI_TZMINUTE)
+
+/* Parser separator indices. */
+#define DUK__SI_PLUS         0
+#define DUK__SI_MINUS        1
+#define DUK__SI_T            2
+#define DUK__SI_SPACE        3
+#define DUK__SI_COLON        4
+#define DUK__SI_PERIOD       5
+#define DUK__SI_Z            6
+#define DUK__SI_NUL          7
+
+/* Parser separator masks. */
+#define DUK__SM_PLUS         (1 << DUK__SI_PLUS)
+#define DUK__SM_MINUS        (1 << DUK__SI_MINUS)
+#define DUK__SM_T            (1 << DUK__SI_T)
+#define DUK__SM_SPACE        (1 << DUK__SI_SPACE)
+#define DUK__SM_COLON        (1 << DUK__SI_COLON)
+#define DUK__SM_PERIOD       (1 << DUK__SI_PERIOD)
+#define DUK__SM_Z            (1 << DUK__SI_Z)
+#define DUK__SM_NUL          (1 << DUK__SI_NUL)
+
+/* Rule control flags. */
+#define DUK__CF_NEG          (1 << 0)  /* continue matching, set neg_tzoffset flag */
+#define DUK__CF_ACCEPT       (1 << 1)  /* accept string */
+#define DUK__CF_ACCEPT_NUL   (1 << 2)  /* accept string if next char is NUL (otherwise reject) */
+
+#define DUK__PACK_RULE(partmask,sepmask,nextpart,flags)  \
+	((duk_uint32_t) (partmask) + \
+	 (((duk_uint32_t) (sepmask)) << 9) + \
+	 (((duk_uint32_t) (nextpart)) << 17) + \
+	 (((duk_uint32_t) (flags)) << 21))
+
+#define DUK__UNPACK_RULE(rule,var_nextidx,var_flags)  do { \
+		(var_nextidx) = (duk_small_uint_t) (((rule) >> 17) & 0x0f); \
+		(var_flags) = (duk_small_uint_t) ((rule) >> 21); \
+	} while (0)
+
+#define DUK__RULE_MASK_PART_SEP  0x1ffffUL
+
+/* Matching separator index is used in the control table */
+static const duk_uint8_t duk__parse_iso8601_seps[] = {
+	DUK_ASC_PLUS /*0*/, DUK_ASC_MINUS /*1*/, DUK_ASC_UC_T /*2*/, DUK_ASC_SPACE /*3*/,
+	DUK_ASC_COLON /*4*/, DUK_ASC_PERIOD /*5*/, DUK_ASC_UC_Z /*6*/, DUK_ASC_NUL /*7*/
+};
+
+/* Rule table: first matching rule is used to determine what to do next. */
+static const duk_uint32_t duk__parse_iso8601_control[] = {
+	DUK__PACK_RULE(DUK__PM_YEAR, DUK__SM_MINUS, DUK__PI_MONTH, 0),
+	DUK__PACK_RULE(DUK__PM_MONTH, DUK__SM_MINUS, DUK__PI_DAY, 0),
+	DUK__PACK_RULE(DUK__PM_YEAR | DUK__PM_MONTH | DUK__PM_DAY, DUK__SM_T | DUK__SM_SPACE, DUK__PI_HOUR, 0),
+	DUK__PACK_RULE(DUK__PM_HOUR, DUK__SM_COLON, DUK__PI_MINUTE, 0),
+	DUK__PACK_RULE(DUK__PM_MINUTE, DUK__SM_COLON, DUK__PI_SECOND, 0),
+	DUK__PACK_RULE(DUK__PM_SECOND, DUK__SM_PERIOD, DUK__PI_MILLISECOND, 0),
+	DUK__PACK_RULE(DUK__PM_TZHOUR, DUK__SM_COLON, DUK__PI_TZMINUTE, 0),
+	DUK__PACK_RULE(DUK__PM_YEAR | DUK__PM_MONTH | DUK__PM_DAY | DUK__PM_HOUR /*Note1*/ | DUK__PM_MINUTE | DUK__PM_SECOND | DUK__PM_MILLISECOND, DUK__SM_PLUS, DUK__PI_TZHOUR, 0),
+	DUK__PACK_RULE(DUK__PM_YEAR | DUK__PM_MONTH | DUK__PM_DAY | DUK__PM_HOUR /*Note1*/ | DUK__PM_MINUTE | DUK__PM_SECOND | DUK__PM_MILLISECOND, DUK__SM_MINUS, DUK__PI_TZHOUR, DUK__CF_NEG),
+	DUK__PACK_RULE(DUK__PM_YEAR | DUK__PM_MONTH | DUK__PM_DAY | DUK__PM_HOUR /*Note1*/ | DUK__PM_MINUTE | DUK__PM_SECOND | DUK__PM_MILLISECOND, DUK__SM_Z, 0, DUK__CF_ACCEPT_NUL),
+	DUK__PACK_RULE(DUK__PM_YEAR | DUK__PM_MONTH | DUK__PM_DAY | DUK__PM_HOUR /*Note1*/ | DUK__PM_MINUTE | DUK__PM_SECOND | DUK__PM_MILLISECOND | DUK__PM_TZHOUR /*Note2*/ | DUK__PM_TZMINUTE, DUK__SM_NUL, 0, DUK__CF_ACCEPT)
+
+	/* Note1: the specification doesn't require matching a time form with
+	 *        just hours ("HH"), but we accept it here, e.g. "2012-01-02T12Z".
+	 *
+	 * Note2: the specification doesn't require matching a timezone offset
+	 *        with just hours ("HH"), but accept it here, e.g. "2012-01-02T03:04:05+02"
+	 */
+};
+
+static duk_bool_t duk__parse_string_iso8601_subset(duk_context *ctx, const char *str) {
+	duk_int_t parts[DUK__NUM_ISO8601_PARSER_PARTS];
+	duk_double_t dparts[DUK__NUM_PARTS];
+	duk_double_t d;
+	const duk_uint8_t *p;
+	duk_small_uint_t part_idx = 0;
+	duk_int_t accum = 0;
+	duk_small_uint_t ndigits = 0;
+	duk_bool_t neg_year = 0;
+	duk_bool_t neg_tzoffset = 0;
+	duk_uint_fast8_t ch;
+	duk_small_uint_t i;
+
+	/* During parsing, month and day are one-based; set defaults here. */
+	DUK_MEMZERO(parts, sizeof(parts));
+	DUK_ASSERT(parts[DUK__IDX_YEAR] == 0);  /* don't care value, year is mandatory */
+	parts[DUK__IDX_MONTH] = 1;
+	parts[DUK__IDX_DAY] = 1;
+
+	/* Special handling for year sign. */
+	p = (const duk_uint8_t *) str;
+	ch = p[0];
+	if (ch == DUK_ASC_PLUS) {
+		p++;
+	} else if (ch == DUK_ASC_MINUS) {
+		neg_year = 1;
+		p++;
+	}
+
+	for (;;) {
+		ch = *p++;
+		DUK_DDD(DUK_DDDPRINT("parsing, part_idx=%ld, char=%ld ('%c')",
+		                     (long) part_idx, (long) ch,
+		                     (int) ((ch >= 0x20 && ch <= 0x7e) ? ch : DUK_ASC_QUESTION)));
+
+		if (ch >= DUK_ASC_0 && ch <= DUK_ASC_9) {
+			if (ndigits >= 9) {
+				DUK_DDD(DUK_DDDPRINT("too many digits -> reject"));
+				goto reject;
+			}
+			if (part_idx == DUK__PI_MILLISECOND /*msec*/ && ndigits >= 3) {
+				/* ignore millisecond fractions after 3 */
+			} else {
+				accum = accum * 10 + ((duk_int_t) ch) - ((duk_int_t) DUK_ASC_0) + 0x00;
+				ndigits++;
+			}
+		} else {
+			duk_uint_fast32_t match_val;
+			duk_small_int_t sep_idx;
+
+			if (ndigits <= 0) {
+				goto reject;
+			}
+			if (part_idx == DUK__PI_MILLISECOND) {
+				/* complete the millisecond field */
+				while (ndigits < 3) {
+					accum *= 10;
+					ndigits++;
+				}
+			}
+			parts[part_idx] = accum;
+			DUK_DDD(DUK_DDDPRINT("wrote part %ld -> value %ld", (long) part_idx, (long) accum));
+
+			accum = 0;
+			ndigits = 0;
+
+			for (i = 0; i < (duk_small_uint_t) (sizeof(duk__parse_iso8601_seps) / sizeof(duk_uint8_t)); i++) {
+				if (duk__parse_iso8601_seps[i] == ch) {
+					break;
+				}
+			}
+			if (i == (duk_small_uint_t) (sizeof(duk__parse_iso8601_seps) / sizeof(duk_uint8_t))) {
+				DUK_DDD(DUK_DDDPRINT("separator character doesn't match -> reject"));
+				goto reject;
+			}
+
+			sep_idx = i;
+			match_val = (1UL << part_idx) + (1UL << (sep_idx + 9));  /* match against rule part/sep bits */
+
+			for (i = 0; i < (duk_small_uint_t) (sizeof(duk__parse_iso8601_control) / sizeof(duk_uint32_t)); i++) {
+				duk_uint_fast32_t rule = duk__parse_iso8601_control[i];
+				duk_small_uint_t nextpart;
+				duk_small_uint_t cflags;
+
+				DUK_DDD(DUK_DDDPRINT("part_idx=%ld, sep_idx=%ld, match_val=0x%08lx, considering rule=0x%08lx",
+				                     (long) part_idx, (long) sep_idx,
+				                     (unsigned long) match_val, (unsigned long) rule));
+
+				if ((rule & match_val) != match_val) {
+					continue;
+				}
+
+				DUK__UNPACK_RULE(rule, nextpart, cflags);
+
+				DUK_DDD(DUK_DDDPRINT("rule match -> part_idx=%ld, sep_idx=%ld, match_val=0x%08lx, "
+				                     "rule=0x%08lx -> nextpart=%ld, cflags=0x%02lx",
+				                     (long) part_idx, (long) sep_idx,
+				                     (unsigned long) match_val, (unsigned long) rule,
+				                     (long) nextpart, (unsigned long) cflags));
+
+				if (cflags & DUK__CF_NEG) {
+					neg_tzoffset = 1;
+				}
+
+				if (cflags & DUK__CF_ACCEPT) {
+					goto accept;
+				}
+
+				if (cflags & DUK__CF_ACCEPT_NUL) {
+					DUK_ASSERT(*(p - 1) != (char) 0);
+					if (*p == DUK_ASC_NUL) {
+						goto accept;
+					}
+					goto reject;
+				}
+
+				part_idx = nextpart;
+				break;
+			}  /* rule match */
+
+			if (i == (duk_small_uint_t) (sizeof(duk__parse_iso8601_control) / sizeof(duk_uint32_t))) {
+				DUK_DDD(DUK_DDDPRINT("no rule matches -> reject"));
+				goto reject;
+			}
+
+			if (ch == 0) {
+				/* This shouldn't be necessary, but check just in case
+				 * to avoid any chance of overruns.
+				 */
+				DUK_DDD(DUK_DDDPRINT("NUL after rule matching (should not happen) -> reject"));
+				goto reject;
+			}
+		}  /* if-digit-else-ctrl */
+	}  /* char loop */
+
+	/* We should never exit the loop above, but if we do, reject
+	 * by falling through.
+	 */
+	DUK_DDD(DUK_DDDPRINT("fell out of char loop without explicit accept/reject -> reject"));
+
+ reject:
+	DUK_DDD(DUK_DDDPRINT("reject"));
+	return 0;
+
+ accept:
+	DUK_DDD(DUK_DDDPRINT("accept"));
+
+	/* Apply timezone offset to get the main parts in UTC */
+	if (neg_year) {
+		parts[DUK__PI_YEAR] = -parts[DUK__PI_YEAR];
+	}
+	if (neg_tzoffset) {
+		parts[DUK__PI_HOUR] += parts[DUK__PI_TZHOUR];
+		parts[DUK__PI_MINUTE] += parts[DUK__PI_TZMINUTE];
+	} else {
+		parts[DUK__PI_HOUR] -= parts[DUK__PI_TZHOUR];
+		parts[DUK__PI_MINUTE] -= parts[DUK__PI_TZMINUTE];
+	}
+	parts[DUK__PI_MONTH] -= 1;  /* zero-based month */
+	parts[DUK__PI_DAY] -= 1;  /* zero-based day */
+
+	/* Use double parts, they tolerate unnormalized time.
+	 *
+	 * Note: DUK__IDX_WEEKDAY is initialized with a bogus value (DUK__PI_TZHOUR)
+	 * on purpose.  It won't be actually used by duk__get_timeval_from_dparts(),
+	 * but will make the value initialized just in case, and avoid any
+	 * potential for Valgrind issues.
+	 */
+	for (i = 0; i < DUK__NUM_PARTS; i++) {
+		DUK_DDD(DUK_DDDPRINT("part[%ld] = %ld", (long) i, (long) parts[i]));
+		dparts[i] = parts[i];
+	}
+
+	d = duk__get_timeval_from_dparts(dparts, 0 /*flags*/);
+	duk_push_number(ctx, d);
+	return 1;
+}
+
+/*
+ *  Date/time parsing helper.
+ *
+ *  Parse a datetime string into a time value.  We must first try to parse
+ *  the input according to the standard format in E5.1 Section 15.9.1.15.
+ *  If that fails, we can try to parse using custom parsing, which can
+ *  either be platform neutral (custom code) or platform specific (using
+ *  existing platform API calls).
+ *
+ *  Note in particular that we must parse whatever toString(), toUTCString(),
+ *  and toISOString() can produce; see E5.1 Section 15.9.4.2.
+ *
+ *  Returns 1 to allow tailcalling.
+ */
+
+/*
+ *  FIXME: check standard behavior and also usual behavior in other
+ *  implementations.  For instance, V8 parses '2012-01-01' as UTC and
+ *  '2012/01/01' as local time.
+ */
+
+static duk_ret_t duk__parse_string(duk_context *ctx, const char *str) {
+	/* XXX: there is a small risk here: because the ISO 8601 parser is
+	 * very loose, it may end up parsing some datetime values which
+	 * would be better parsed with a platform specific parser.
+	 */
+
+	DUK_ASSERT(str != NULL);
+	DUK_DDD(DUK_DDDPRINT("parse datetime from string '%s'", (const char *) str));
+
+	if (duk__parse_string_iso8601_subset(ctx, str) != 0) {
+		return 1;
+	}
+
+#if defined(DUK_USE_DATE_PRS_STRPTIME)
+	if (duk__parse_string_strptime(ctx, str) != 0) {
+		return 1;
+	}
+#elif defined(DUK_USE_DATE_PRS_GETDATE)
+	if (duk__parse_string_getdate(ctx, str) != 0) {
+		return 1;
+	}
+#else
+	/* No platform-specific parsing, this is not an error. */
+#endif
+
+	duk_push_nan(ctx);
+	return 1;
+}
+
+/*
+ *  Calendar helpers
+ *
+ *  Some helpers are used for getters and can operate on normalized values
+ *  which can be represented with 32-bit signed integers.  Other helpers are
+ *  needed by setters and operate on un-normalized double values, must watch
+ *  out for non-finite numbers etc.
+ */
+
+static duk_uint8_t duk__days_in_month[12] = {
+	(duk_uint8_t) 31, (duk_uint8_t) 28, (duk_uint8_t) 31, (duk_uint8_t) 30,
+	(duk_uint8_t) 31, (duk_uint8_t) 30, (duk_uint8_t) 31, (duk_uint8_t) 31,
+	(duk_uint8_t) 30, (duk_uint8_t) 31, (duk_uint8_t) 30, (duk_uint8_t) 31
+};
+
+static duk_bool_t duk__is_leap_year(duk_int_t year) {
+	if ((year % 4) != 0) {
+		return 0;
+	}
+	if ((year % 100) != 0) {
+		return 1;
+	}
+	if ((year % 400) != 0) {
+		return 0;
+	}
+	return 1;
+}
+
+static duk_double_t duk__timeclip(duk_double_t x) {
+	if (!DUK_ISFINITE(x)) {
+		return DUK_DOUBLE_NAN;
+	}
+
+	if (x > 8.64e15 || x < -8.64e15) {
+		return DUK_DOUBLE_NAN;
+	}
+
+	x = duk_js_tointeger_number(x);
+
+	/* Here we'd have the option to normalize -0 to +0. */
+	return x;
+}
+
+/* Integer division which floors also negative values correctly. */
+static duk_int_t duk__div_floor(duk_int_t a, duk_int_t b) {
+	DUK_ASSERT(b > 0);
+	if (a >= 0) {
+		return a / b;
+	} else {
+		/* e.g. a = -4, b = 5  -->  -4 - 5 + 1 / 5  -->  -8 / 5  -->  -1
+		 *      a = -5, b = 5  -->  -5 - 5 + 1 / 5  -->  -9 / 5  -->  -1
+		 *      a = -6, b = 5  -->  -6 - 5 + 1 / 5  -->  -10 / 5  -->  -2
+		 */
+		return (a - b + 1) / b;
+	}
+}
+
+/* Compute day number of the first day of a given year. */
+static duk_int_t duk__day_from_year(duk_int_t year) {
+	/* Note: in integer arithmetic, (x / 4) is same as floor(x / 4) for non-negative
+	 * values, but is incorrect for negative ones.
+	 */
+	return 365 * (year - 1970)
+	       + duk__div_floor(year - 1969, 4)
+	       - duk__div_floor(year - 1901, 100)
+	       + duk__div_floor(year - 1601, 400);
+}
+
+/* Given a day number, determine year and day-within-year. */
+static duk_int_t duk__year_from_day(duk_int_t day, duk_small_int_t *out_day_within_year) {
+	duk_int_t year;
+	duk_int_t diff_days;
+
+	/* estimate year upwards (towards positive infinity), then back down;
+	 * two iterations should be enough
+	 */
+
+	if (day >= 0) {
+		year = 1970 + day / 365;
+	} else {
+		year = 1970 + day / 366;
+	}
+
+	for (;;) {
+		diff_days = duk__day_from_year(year) - day;
+		DUK_DDD(DUK_DDDPRINT("year=%ld day=%ld, diff_days=%ld", (long) year, (long) day, (long) diff_days));
+		if (diff_days <= 0) {
+			DUK_ASSERT(-diff_days <= 366);  /* fits into duk_small_int_t */
+			*out_day_within_year = -diff_days;
+			DUK_DDD(DUK_DDDPRINT("--> year=%ld, day-within-year=%ld",
+			                     (long) year, (long) *out_day_within_year));
+			DUK_ASSERT(*out_day_within_year >= 0);
+			DUK_ASSERT(*out_day_within_year <= (duk__is_leap_year(year) ? 366 : 365));
+			return year;
+		}
+
+		/* Note: this is very tricky; we must never 'overshoot' the
+		 * correction downwards.
+		 */
+		year -= 1 + (diff_days - 1) / 366;  /* conservative */
+	}
+}
+
+/* Given a (year, month, day-within-month) triple, compute day number.
+ * The input triple is un-normalized and may contain non-finite values.
+ */
+static duk_double_t duk__make_day(duk_double_t year, duk_double_t month, duk_double_t day) {
+	duk_int_t day_num;
+	duk_bool_t is_leap;
+	duk_small_int_t i, n;
+
+	/* Assume that year, month, day are all coerced to whole numbers.
+	 * They may also be NaN or infinity, in which case this function
+	 * must return NaN or infinity to ensure time value becomes NaN.
+	 * If 'day' is NaN, the final return will end up returning a NaN,
+	 * so it doesn't need to be checked here.
+	 */
+
+	if (!DUK_ISFINITE(year) || !DUK_ISFINITE(month)) {
+		return DUK_DOUBLE_NAN;
+	}
+	
+	year += DUK_FLOOR(month / 12.0);
+
+	month = DUK_FMOD(month, 12.0);
+	if (month < 0.0) {
+		/* handle negative values */
+		month += 12.0;
+	}
+
+	/* The algorithm in E5.1 Section 15.9.1.12 normalizes month, but
+	 * does not normalize the day-of-month (nor check whether or not
+	 * it is finite) because it's not necessary for finding the day
+	 * number which matches the (year,month) pair.
+	 *
+	 * We assume that duk__day_from_year() is exact here.
+	 *
+	 * Without an explicit infinity / NaN check in the beginning,
+	 * day_num would be a bogus integer here.
+	 */
+
+	day_num = duk__day_from_year((duk_int_t) year);
+	is_leap = duk__is_leap_year((duk_int_t) year);
+
+	n = (duk_small_int_t) month;
+	for (i = 0; i < n; i++) {
+		day_num += duk__days_in_month[i];
+		if (i == 1 && is_leap) {
+			day_num++;
+		}
+	}
+
+	/* If 'day' is NaN, returns NaN. */
+	return (duk_double_t) day_num + day;
+}
+
+/* Split time value into parts.  The time value is assumed to be an internal
+ * one, i.e. finite, no fractions.  Possible local time adjustment has already
+ * been applied when reading the time value.
+ */
+static void duk__timeval_to_parts(duk_double_t d, duk_int_t *parts, duk_double_t *dparts, duk_small_uint_t flags) {
+	duk_double_t d1, d2;
+	duk_int_t t1, t2;
+	duk_int_t year;  /* does not fit into 16 bits */
+	duk_small_int_t month;
+	duk_small_int_t day;
+	duk_small_int_t dim;
+	duk_small_uint_t i;
+	duk_bool_t is_leap;
+
+	DUK_ASSERT(DUK_ISFINITE(d));    /* caller checks */
+	DUK_ASSERT(DUK_FLOOR(d) == d);  /* no fractions in internal time */
+
+	/* these computations are guaranteed to be exact for the valid
+	 * E5 time value range, assuming milliseconds without fractions.
+	 */
+	d1 = (duk_double_t) DUK_FMOD(d, (double) DUK__MS_DAY);
+	if (d1 < 0.0) {
+		/* deal with negative values */
+		d1 += (duk_double_t) DUK__MS_DAY;
+	}
+	d2 = DUK_FLOOR(d / (duk_double_t) DUK__MS_DAY);
+	DUK_ASSERT(d2 * ((duk_double_t) DUK__MS_DAY) + d1 == d);
+
+	/* now expected to fit into a 32-bit integer */
+	t1 = (duk_int_t) d1;
+	t2 = (duk_int_t) d2;
+	DUK_ASSERT((duk_double_t) t1 == d1);
+	DUK_ASSERT((duk_double_t) t2 == d2);
+
+	/* t1 = milliseconds within day, t2 = day number */
+
+	parts[DUK__IDX_MILLISECOND] = t1 % 1000; t1 /= 1000;
+	parts[DUK__IDX_SECOND] = t1 % 60; t1 /= 60;
+	parts[DUK__IDX_MINUTE] = t1 % 60; t1 /= 60;
+	parts[DUK__IDX_HOUR] = t1;
+	DUK_ASSERT(parts[DUK__IDX_MILLISECOND] >= 0 && parts[DUK__IDX_MILLISECOND] <= 999);
+	DUK_ASSERT(parts[DUK__IDX_SECOND] >= 0 && parts[DUK__IDX_SECOND] <= 59);
+	DUK_ASSERT(parts[DUK__IDX_MINUTE] >= 0 && parts[DUK__IDX_MINUTE] <= 59);
+	DUK_ASSERT(parts[DUK__IDX_HOUR] >= 0 && parts[DUK__IDX_HOUR] <= 23);
+
+	parts[DUK__IDX_WEEKDAY] = (t2 + 4) % 7;  /* E5.1 Section 15.9.1.6 */
+	if (parts[DUK__IDX_WEEKDAY] < 0) {
+		/* deal with negative values */
+		parts[DUK__IDX_WEEKDAY] += 7;
+	}
+
+	year = duk__year_from_day(t2, &day);
+	is_leap = duk__is_leap_year(year);
+	for (month = 0; month < 12; month++) {
+		dim = duk__days_in_month[month];
+		if (month == 1 && is_leap) {
+			dim++;
+		}
+		DUK_DDD(DUK_DDDPRINT("month=%ld, dim=%ld, day=%ld",
+		                     (long) month, (long) dim, (long) day));
+		if (day < dim) {
+			break;
+		}
+		day -= dim;
+	}
+	DUK_DDD(DUK_DDDPRINT("final month=%ld", (long) month));
+	DUK_ASSERT(month >= 0 && month <= 11);
+	DUK_ASSERT(day >= 0 && day <= 31);
+
+	parts[DUK__IDX_YEAR] = year;
+	parts[DUK__IDX_MONTH] = month;
+	parts[DUK__IDX_DAY] = day;
+
+	if (flags & DUK__FLAG_ONEBASED) {
+		parts[DUK__IDX_MONTH]++;  /* zero-based -> one-based */
+		parts[DUK__IDX_DAY]++;    /* -""- */
+	}
+
+	if (dparts != NULL) {
+		for (i = 0; i < DUK__NUM_PARTS; i++) {
+			dparts[i] = (duk_double_t) parts[i];
+		}
+	}
+}
+
+/* Compute time value from (double) parts. */
+static duk_double_t duk__get_timeval_from_dparts(duk_double_t *dparts, duk_small_uint_t flags) {
+#if defined(DUK_USE_PARANOID_DATE_COMPUTATION)
+	/* See comments below on MakeTime why these are volatile. */
+	volatile duk_double_t tmp_time;
+	volatile duk_double_t tmp_day;
+	volatile duk_double_t d;
+#else
+	duk_double_t tmp_time;
+	duk_double_t tmp_day;
+	duk_double_t d;
+#endif
+	duk_small_uint_t i;
+
+	/* Expects 'this' at top of stack on entry. */
+
+	/* Coerce all finite parts with ToInteger().  ToInteger() must not
+	 * be called for NaN/Infinity because it will convert e.g. NaN to
+	 * zero.  If ToInteger() has already been called, this has no side
+	 * effects and is idempotent.
+	 *
+	 * Don't read dparts[DUK__IDX_WEEKDAY]; it will cause Valgrind issues
+	 * if the value is uninitialized.
+	 */
+	for (i = 0; i <= DUK__IDX_MILLISECOND; i++) {
+		/* SCANBUILD: scan-build complains here about assigned value
+		 * being garbage or undefined.  This is correct but operating
+		 * on undefined values has no ill effect and is ignored by the
+		 * caller in the case where this happens.
+		 */
+		d = dparts[i];
+		if (DUK_ISFINITE(d)) {
+			dparts[i] = duk_js_tointeger_number(d);
+		}
+	}
+
+	/* Use explicit steps in computation to try to ensure that
+	 * computation happens with intermediate results coerced to
+	 * double values (instead of using something more accurate).
+	 * E.g. E5.1 Section 15.9.1.11 requires use of IEEE 754
+	 * rules (= Ecmascript '+' and '*' operators).
+	 *
+	 * Without 'volatile' even this approach fails on some platform
+	 * and compiler combinations.  For instance, gcc 4.8.1 on Ubuntu
+	 * 64-bit, with -m32 and without -std=c99, test-bi-date-canceling.js
+	 * would fail because of some optimizations when computing tmp_time
+	 * (MakeTime below).  Adding 'volatile' to tmp_time solved this
+	 * particular problem (annoyingly, also adding debug prints or
+	 * running the executable under valgrind hides it).
+	 */
+	
+	/* MakeTime */
+	tmp_time = 0.0;
+	tmp_time += dparts[DUK__IDX_HOUR] * ((duk_double_t) DUK__MS_HOUR);
+	tmp_time += dparts[DUK__IDX_MINUTE] * ((duk_double_t) DUK__MS_MINUTE);
+	tmp_time += dparts[DUK__IDX_SECOND] * ((duk_double_t) DUK__MS_SECOND);
+	tmp_time += dparts[DUK__IDX_MILLISECOND];
+
+	/* MakeDay */
+	tmp_day = duk__make_day(dparts[DUK__IDX_YEAR], dparts[DUK__IDX_MONTH], dparts[DUK__IDX_DAY]);
+
+	/* MakeDate */
+	d = tmp_day * ((duk_double_t) DUK__MS_DAY) + tmp_time;
+
+	DUK_DDD(DUK_DDDPRINT("time=%lf day=%lf --> timeval=%lf",
+	                     (double) tmp_time, (double) tmp_day, (double) d));
+
+	/* Optional UTC conversion followed by TimeClip().
+	 * Note that this also handles Infinity -> NaN conversion.
+	 */
+	if (flags & DUK__FLAG_LOCALTIME) {
+		/* FIXME: this is now incorrect.  'd' is local time here (as
+		 * we're converting to UTC), but DUK__GET_LOCAL_TZOFFSET() should
+		 * be called with UTC time.  This needs to be reworked to avoid
+		 * the chicken-and-egg problem.
+		 *
+		 * See E5.1 Section 15.9.1.9:
+		 * UTC(t) = t - LocalTZA - DaylightSavingTA(t - LocalTZA)
+		 *
+		 * For NaN/inf, DUK__GET_LOCAL_TZOFFSET() returns 0.
+		 */
+
+		d -= DUK__GET_LOCAL_TZOFFSET(d) * 1000L;
+	}
+	d = duk__timeclip(d);
+
+	return d;
+}
+
+/*
+ *  API oriented helpers
+ */
+
+/* Push 'this' binding, check that it is a Date object; then push the
+ * internal time value.  At the end, stack is: [ ... this timeval ].
+ * Returns the time value.  Local time adjustment is done if requested.
+ */
+static duk_double_t duk__push_this_get_timeval_tzoffset(duk_context *ctx, duk_small_uint_t flags, duk_int_t *out_tzoffset) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hobject *h;
+	duk_double_t d;
+	duk_int_t tzoffset = 0;
+
+	duk_push_this(ctx);
+	h = duk_get_hobject(ctx, -1);  /* FIXME: getter with class check, useful in built-ins */
+	if (h == NULL || DUK_HOBJECT_GET_CLASS_NUMBER(h) != DUK_HOBJECT_CLASS_DATE) {
+		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "expected Date");
+	}
+
+	duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_VALUE);
+	d = duk_to_number(ctx, -1);
+	duk_pop(ctx);
+
+	if (DUK_ISNAN(d)) {
+		if (flags & DUK__FLAG_NAN_TO_ZERO) {
+			d = 0.0;
+		}
+		if (flags & DUK__FLAG_NAN_TO_RANGE_ERROR) {
+			DUK_ERROR(thr, DUK_ERR_RANGE_ERROR, "Invalid Date");
+		}
+	}
+	/* if no NaN handling flag, may still be NaN here, but not Inf */
+	DUK_ASSERT(!DUK_ISINF(d));
+
+	if (flags & DUK__FLAG_LOCALTIME) {
+		/* Note: DST adjustment is determined using UTC time.
+		 * If 'd' is NaN, tzoffset will be 0.
+		 */
+		tzoffset = DUK__GET_LOCAL_TZOFFSET(d);  /* seconds */
+		d += tzoffset * 1000L;
+	}
+	if (out_tzoffset) {
+		*out_tzoffset = tzoffset;
+	}
+
+	/* [ ... this ] */
+	return d;
+}
+
+static duk_double_t duk__push_this_get_timeval(duk_context *ctx, duk_small_uint_t flags) {
+	return duk__push_this_get_timeval_tzoffset(ctx, flags, NULL);
+}
+
+/* Set timeval to 'this' from dparts, push the new time value onto the
+ * value stack and return 1 (caller can then tailcall us).  Expects
+ * the value stack to contain 'this' on the stack top.
+ */
+static duk_ret_t duk__set_this_timeval_from_dparts(duk_context *ctx, duk_double_t *dparts, duk_small_uint_t flags) {
+	duk_double_t d;
+
+	/* [ ... this ] */
+
+	d = duk__get_timeval_from_dparts(dparts, flags);
+	duk_push_number(ctx, d);  /* -> [ ... this timeval_new ] */
+	duk_dup_top(ctx);         /* -> [ ... this timeval_new timeval_new ] */
+	duk_put_prop_stridx(ctx, -3, DUK_STRIDX_INT_VALUE);
+
+	/* stack top: new time value, return 1 to allow tailcalls */
+	return 1;
+}
+
+/* 'out_buf' must be at least DUK_BI_DATE_ISO8601_BUFSIZE long. */
+static void duk__format_parts_iso8601(duk_int_t *parts, duk_int_t tzoffset, duk_small_uint_t flags, duk_uint8_t *out_buf) {
+	char yearstr[8];   /* "-123456\0" */
+	char tzstr[8];     /* "+11:22\0" */
+	char sep = (flags & DUK__FLAG_SEP_T) ? DUK_ASC_UC_T : DUK_ASC_SPACE;
+
+	DUK_ASSERT(parts[DUK__IDX_MONTH] >= 1 && parts[DUK__IDX_MONTH] <= 12);
+	DUK_ASSERT(parts[DUK__IDX_DAY] >= 1 && parts[DUK__IDX_DAY] <= 31);
+	DUK_ASSERT(parts[DUK__IDX_YEAR] >= -999999 && parts[DUK__IDX_YEAR] <= 999999);
+
+	/* Note: %06d for positive value, %07d for negative value to include
+	 * sign and 6 digits.
+	 */
+	DUK_SNPRINTF(yearstr,
+	             sizeof(yearstr),
+	             (parts[DUK__IDX_YEAR] >= 0 && parts[DUK__IDX_YEAR] <= 9999) ? "%04ld" :
+	                    ((parts[DUK__IDX_YEAR] >= 0) ? "+%06ld" : "%07ld"),
+	             (long) parts[DUK__IDX_YEAR]);
+	yearstr[sizeof(yearstr) - 1] = (char) 0;
+
+	if (flags & DUK__FLAG_LOCALTIME) {
+		/* tzoffset seconds are dropped; 16 bits suffice for
+		 * time offset in minutes
+		 */
+		if (tzoffset >= 0) {
+			duk_small_int_t tmp = tzoffset / 60;
+			DUK_SNPRINTF(tzstr, sizeof(tzstr), "+%02d:%02d", (int) (tmp / 60), (int) (tmp % 60));
+		} else {
+			duk_small_int_t tmp = -tzoffset / 60;
+			DUK_SNPRINTF(tzstr, sizeof(tzstr), "-%02d:%02d", (int) (tmp / 60), (int) (tmp % 60));
+		}
+		tzstr[sizeof(tzstr) - 1] = (char) 0;
+	} else {
+		tzstr[0] = DUK_ASC_UC_Z;
+		tzstr[1] = (char) 0;
+	}
+
+	/* Unlike year, the other parts fit into 16 bits so %d format
+	 * is portable.
+	 */
+	if ((flags & DUK__FLAG_TOSTRING_DATE) && (flags & DUK__FLAG_TOSTRING_TIME)) {
+		DUK_SPRINTF((char *) out_buf, "%s-%02d-%02d%c%02d:%02d:%02d.%03d%s",
+		            (const char *) yearstr, (int) parts[DUK__IDX_MONTH], (int) parts[DUK__IDX_DAY], (int) sep,
+		            (int) parts[DUK__IDX_HOUR], (int) parts[DUK__IDX_MINUTE],
+		            (int) parts[DUK__IDX_SECOND], (int) parts[DUK__IDX_MILLISECOND], (const char *) tzstr);
+	} else if (flags & DUK__FLAG_TOSTRING_DATE) {
+		DUK_SPRINTF((char *) out_buf, "%s-%02d-%02d",
+		            (const char *) yearstr, (int) parts[DUK__IDX_MONTH], (int) parts[DUK__IDX_DAY]);
+	} else {
+		DUK_ASSERT(flags & DUK__FLAG_TOSTRING_TIME);
+		DUK_SPRINTF((char *) out_buf, "%02d:%02d:%02d.%03d%s",
+		            (int) parts[DUK__IDX_HOUR], (int) parts[DUK__IDX_MINUTE],
+		            (int) parts[DUK__IDX_SECOND], (int) parts[DUK__IDX_MILLISECOND],
+		            (const char *) tzstr);
+	}
+}
+
+/* Helper for string conversion calls: check 'this' binding, get the
+ * internal time value, and format date and/or time in a few formats.
+ * Return value allows tail calls.
+ */
+static duk_ret_t duk__to_string_helper(duk_context *ctx, duk_small_uint_t flags) {
+	duk_double_t d;
+	duk_int_t parts[DUK__NUM_PARTS];
+	duk_int_t tzoffset;  /* seconds, doesn't fit into 16 bits */
+	duk_bool_t rc;
+	duk_uint8_t buf[DUK_BI_DATE_ISO8601_BUFSIZE];
+
+	DUK_UNREF(rc);  /* unreferenced with some options */
+
+	d = duk__push_this_get_timeval_tzoffset(ctx, flags, &tzoffset);
+	if (DUK_ISNAN(d)) {
+		duk_push_hstring_stridx(ctx, DUK_STRIDX_INVALID_DATE);
+		return 1;
+	}
+	DUK_ASSERT(DUK_ISFINITE(d));
+
+	/* formatters always get one-based month/day-of-month */
+	duk__timeval_to_parts(d, parts, NULL, DUK__FLAG_ONEBASED);
+	DUK_ASSERT(parts[DUK__IDX_MONTH] >= 1 && parts[DUK__IDX_MONTH] <= 12);
+	DUK_ASSERT(parts[DUK__IDX_DAY] >= 1 && parts[DUK__IDX_DAY] <= 31);
+
+	if (flags & DUK__FLAG_TOSTRING_LOCALE) {
+		/* try locale specific formatter; if it refuses to format the
+		 * string, fall back to an ISO 8601 formatted value in local
+		 * time.
+		 */
+#ifdef DUK_USE_DATE_FMT_STRFTIME
+		rc = duk__format_parts_strftime(ctx, parts, tzoffset, flags);
+		if (rc != 0) {
+			return 1;
+		}
+#else
+		/* No locale specific formatter; this is OK, we fall back
+		 * to ISO 8601.
+		 */
+#endif
+	}
+
+	/* Different calling convention than above used because the helper
+	 * is shared.
+	 */
+	duk__format_parts_iso8601(parts, tzoffset, flags, buf);
+	duk_push_string(ctx, (const char *) buf);
+	return 1;
+}
+
+/* Helper for component getter calls: check 'this' binding, get the
+ * internal time value, split it into parts (either as UTC time or
+ * local time), push a specified component as a return value to the
+ * value stack and return 1 (caller can then tailcall us).
+ */
+static duk_ret_t duk__get_part_helper(duk_context *ctx, duk_small_uint_t flags_and_idx) {
+	duk_double_t d;
+	duk_int_t parts[DUK__NUM_PARTS];
+	duk_small_uint_t idx_part = (duk_small_uint_t) (flags_and_idx >> 12);  /* unpack args */
+
+	DUK_ASSERT_DISABLE(idx_part >= 0);  /* unsigned */
+	DUK_ASSERT(idx_part < DUK__NUM_PARTS);
+
+	d = duk__push_this_get_timeval(ctx, flags_and_idx);
+	if (DUK_ISNAN(d)) {
+		duk_push_nan(ctx);
+		return 1;
+	}
+	DUK_ASSERT(DUK_ISFINITE(d));
+
+	duk__timeval_to_parts(d, parts, NULL, flags_and_idx);  /* no need to mask idx portion */
+
+	/* Setter APIs detect special year numbers (0...99) and apply a +1900
+	 * only in certain cases.  The legacy getYear() getter applies -1900
+	 * unconditionally.
+	 */
+	duk_push_int(ctx, (flags_and_idx & DUK__FLAG_SUB1900) ? parts[idx_part] - 1900 : parts[idx_part]);
+	return 1;
+}
+
+/* Helper for component setter calls: check 'this' binding, get the
+ * internal time value, split it into parts (either as UTC time or
+ * local time), modify one or more components as specified, recompute
+ * the time value, set it as the internal value.  Finally, push the
+ * new time value as a return value to the value stack and return 1
+ * (caller can then tailcall us).
+ */
+static duk_ret_t duk__set_part_helper(duk_context *ctx, duk_small_uint_t flags_and_maxnargs) {
+	duk_double_t d;
+	duk_int_t parts[DUK__NUM_PARTS];
+	duk_double_t dparts[DUK__NUM_PARTS];
+	duk_idx_t nargs;
+	duk_small_uint_t maxnargs = (duk_small_uint_t) (flags_and_maxnargs >> 12);  /* unpack args */
+	duk_small_uint_t idx_first, idx;
+	duk_small_uint_t i;
+
+	nargs = duk_get_top(ctx);
+	d = duk__push_this_get_timeval(ctx, flags_and_maxnargs);
+	DUK_ASSERT(DUK_ISFINITE(d) || DUK_ISNAN(d));
+
+	if (DUK_ISFINITE(d)) {
+		duk__timeval_to_parts(d, parts, dparts, flags_and_maxnargs);
+	} else {
+		/* NaN timevalue: we need to coerce the arguments, but
+		 * the resulting internal timestamp needs to remain NaN.
+		 * This works but is not pretty: parts and dparts will
+		 * be partially uninitialized, but we only write to it.
+		 */
+	}
+
+	/*
+	 *  Determining which datetime components to overwrite based on
+	 *  stack arguments is a bit complicated, but important to factor
+	 *  out from setters themselves for compactness.
+	 *
+	 *  If DUK__FLAG_TIMESETTER, maxnargs indicates setter type:
+	 *
+	 *   1 -> millisecond
+	 *   2 -> second, [millisecond]
+	 *   3 -> minute, [second], [millisecond]
+	 *   4 -> hour, [minute], [second], [millisecond]
+	 *
+	 *  Else:
+	 *
+	 *   1 -> date
+	 *   2 -> month, [date]
+	 *   3 -> year, [month], [date]
+	 *
+	 *  By comparing nargs and maxnargs (and flags) we know which
+	 *  components to override.  We rely on part index ordering.
+	 */
+
+	if (flags_and_maxnargs & DUK__FLAG_TIMESETTER) {
+		DUK_ASSERT(maxnargs >= 1 && maxnargs <= 4);
+		idx_first = DUK__IDX_MILLISECOND - (maxnargs - 1);
+	} else {
+		DUK_ASSERT(maxnargs >= 1 && maxnargs <= 3);
+		idx_first = DUK__IDX_DAY - (maxnargs - 1);
+	}
+	DUK_ASSERT_DISABLE(idx_first >= 0);  /* unsigned */
+	DUK_ASSERT(idx_first < DUK__NUM_PARTS);
+
+	for (i = 0; i < maxnargs; i++) {
+		if ((duk_idx_t) i >= nargs) {
+			/* no argument given -> leave components untouched */
+			break;
+		}
+		idx = idx_first + i;
+		DUK_ASSERT_DISABLE(idx >= 0);  /* unsigned */
+		DUK_ASSERT(idx < DUK__NUM_PARTS);
+
+		if (idx == DUK__IDX_YEAR && (flags_and_maxnargs & DUK__FLAG_YEAR_FIXUP)) {
+			duk__twodigit_year_fixup(ctx, (duk_idx_t) i);
+		}
+
+		dparts[idx] = duk_to_number(ctx, i);
+
+		if (idx == DUK__IDX_DAY) {
+			/* Day-of-month is one-based in the API, but zero-based
+			 * internally, so fix here.  Note that month is zero-based
+			 * both in the API and internally.
+			 */
+			/* SCANBUILD: complains about use of uninitialized values.
+			 * The complaint is correct, but operating in undefined
+			 * values here is intentional in some cases and the caller
+			 * ignores the results.
+			 */
+			dparts[idx] -= 1.0;
+		}
+	}
+
+	/* Leaves new timevalue on stack top and returns 1, which is correct
+	 * for part setters.
+	 */
+	if (DUK_ISFINITE(d)) {
+		return duk__set_this_timeval_from_dparts(ctx, dparts, flags_and_maxnargs);
+	} else {
+		/* Internal timevalue is already NaN, so don't touch it. */
+		duk_push_nan(ctx);
+		return 1;
+	}
+}
+
+/* Apply ToNumber() to specified index; if ToInteger(val) in [0,99], add
+ * 1900 and replace value at idx_val.
+ */
+static void duk__twodigit_year_fixup(duk_context *ctx, duk_idx_t idx_val) {
+	duk_double_t d;
+
+	/* XXX: idx_val would fit into 16 bits, but using duk_small_uint_t
+	 * might not generate better code due to casting.
+	 */
+
+	/* E5 Sections 15.9.3.1, B.2.4, B.2.5 */
+	duk_to_number(ctx, idx_val);
+	if (duk_is_nan(ctx, idx_val)) {
+		return;
+	}
+	duk_dup(ctx, idx_val);
+	duk_to_int(ctx, -1);
+	d = duk_get_number(ctx, -1);  /* get as double to handle huge numbers correctly */
+	if (d >= 0.0 && d <= 99.0) {
+		d += 1900.0;
+		duk_push_number(ctx, d);
+		duk_replace(ctx, idx_val);
+	}
+	duk_pop(ctx);
+}
+
+/* Set datetime parts from stack arguments, defaulting any missing values.
+ * Day-of-week is not set; it is not required when setting the time value.
+ */
+static void duk__set_parts_from_args(duk_context *ctx, duk_double_t *dparts, duk_idx_t nargs) {
+	duk_double_t d;
+	duk_small_uint_t i;
+	duk_small_uint_t idx;
+
+	/* Causes a ToNumber() coercion, but doesn't break coercion order since
+	 * year is coerced first anyway.
+	 */
+	duk__twodigit_year_fixup(ctx, 0);
+
+	/* There are at most 7 args, but we use 8 here so that also
+	 * DUK__IDX_WEEKDAY gets initialized (to zero) to avoid the potential
+	 * for any Valgrind gripes later.
+	 */
+	for (i = 0; i < 8; i++) {
+		/* Note: rely on index ordering */
+		idx = DUK__IDX_YEAR + i;
+		if ((duk_idx_t) i < nargs) {
+			d = duk_to_number(ctx, (duk_idx_t) i);
+			if (idx == DUK__IDX_DAY) {
+				/* Convert day from one-based to zero-based (internal).  This may
+				 * cause the day part to be negative, which is OK.
+				 */
+				d -= 1.0;
+			}
+		} else {
+			/* All components default to 0 except day-of-month which defaults
+			 * to 1.  However, because our internal day-of-month is zero-based,
+			 * it also defaults to zero here.
+			 */
+			d = 0.0;
+		}
+		dparts[idx] = d;
+	}
+
+	DUK_DDD(DUK_DDDPRINT("parts from args -> %lf %lf %lf %lf %lf %lf %lf %lf",
+	                     (double) dparts[0], (double) dparts[1],
+	                     (double) dparts[2], (double) dparts[3],
+	                     (double) dparts[4], (double) dparts[5],
+	                     (double) dparts[6], (double) dparts[7]));
+}
+
+/*
+ *  Helper to format a time value into caller buffer, used by logging.
+ *  'out_buf' must be at least DUK_BI_DATE_ISO8601_BUFSIZE long.
+ */
+
+void duk_bi_date_format_timeval(duk_double_t timeval, duk_uint8_t *out_buf) {
+	duk_int_t parts[DUK__NUM_PARTS];
+
+	duk__timeval_to_parts(timeval,
+	                      parts,
+	                      NULL,
+	                      DUK__FLAG_ONEBASED);
+
+	duk__format_parts_iso8601(parts,
+	                          0 /*tzoffset*/,
+	                          DUK__FLAG_TOSTRING_DATE |
+	                          DUK__FLAG_TOSTRING_TIME |
+	                          DUK__FLAG_SEP_T /*flags*/,
+	                          out_buf);
+}
+
+/*
+ *  Constructor calls
+ */
+
+duk_ret_t duk_bi_date_constructor(duk_context *ctx) {
+	duk_idx_t nargs = duk_get_top(ctx);
+	duk_bool_t is_cons = duk_is_constructor_call(ctx);
+	duk_double_t dparts[DUK__NUM_PARTS];
+	duk_double_t d;
+
+	DUK_DDD(DUK_DDDPRINT("Date constructor, nargs=%ld, is_cons=%ld", (long) nargs, (long) is_cons));
+
+	duk_push_object_helper(ctx,
+	                       DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                       DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DATE),
+	                       DUK_BIDX_DATE_PROTOTYPE);
+
+	/* Unlike most built-ins, the internal [[PrimitiveValue]] of a Date
+	 * is mutable.
+	 */
+
+	if (nargs == 0 || !is_cons) {
+		d = duk__timeclip(DUK__GET_NOW_TIMEVAL(ctx));
+		duk_push_number(ctx, d);
+		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_W);
+		if (!is_cons) {
+			/* called as a normal function: return new Date().toString() */
+			duk_to_string(ctx, -1);
+		}
+		return 1;
+	} else if (nargs == 1) {
+		duk_to_primitive(ctx, 0, DUK_HINT_NONE);
+		if (duk_is_string(ctx, 0)) {
+			duk__parse_string(ctx, duk_to_string(ctx, 0));
+			duk_replace(ctx, 0);  /* may be NaN */
+		}
+		d = duk__timeclip(duk_to_number(ctx, 0));
+		duk_push_number(ctx, d);
+		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_W);
+		return 1;
+	}
+
+	duk__set_parts_from_args(ctx, dparts, nargs);
+
+	/* Parts are in local time, convert when setting. */
+
+	(void) duk__set_this_timeval_from_dparts(ctx, dparts, DUK__FLAG_LOCALTIME /*flags*/);  /* -> [ ... this timeval ] */
+	duk_pop(ctx);  /* -> [ ... this ] */
+	return 1;
+}
+
+duk_ret_t duk_bi_date_constructor_parse(duk_context *ctx) {
+	return duk__parse_string(ctx, duk_to_string(ctx, 0));
+}
+
+duk_ret_t duk_bi_date_constructor_utc(duk_context *ctx) {
+	duk_idx_t nargs = duk_get_top(ctx);
+	duk_double_t dparts[DUK__NUM_PARTS];
+	duk_double_t d;
+
+	/* Behavior for nargs < 2 is implementation dependent: currently we'll
+	 * set a NaN time value (matching V8 behavior) in this case.
+	 */
+
+	if (nargs < 2) {
+		duk_push_nan(ctx);
+	} else {
+		duk__set_parts_from_args(ctx, dparts, nargs);
+		d = duk__get_timeval_from_dparts(dparts, 0 /*flags*/);
+		duk_push_number(ctx, d);
+	}
+	return 1;
+}
+
+duk_ret_t duk_bi_date_constructor_now(duk_context *ctx) {
+	duk_double_t d;
+
+	d = DUK__GET_NOW_TIMEVAL(ctx);
+	DUK_ASSERT(duk__timeclip(d) == d);  /* TimeClip() should never be necessary */
+	duk_push_number(ctx, d);
+	return 1;
+}
+
+/*
+ *  String/JSON conversions
+ *
+ *  Human readable conversions are now basically ISO 8601 with a space
+ *  (instead of 'T') as the date/time separator.  This is a good baseline
+ *  and is platform independent.
+ *
+ *  A shared native helper to provide many conversions.  Magic value contains
+ *  a set of flags.  The helper provides:
+ *
+ *    toString()
+ *    toDateString()
+ *    toTimeString()
+ *    toLocaleString()
+ *    toLocaleDateString()
+ *    toLocaleTimeString()
+ *    toUTCString()
+ *    toISOString()
+ *
+ *  Notes:
+ *
+ *    - Date.prototype.toGMTString() and Date.prototype.toUTCString() are
+ *      required to be the same Ecmascript function object (!), so it is
+ *      omitted from here.
+ *
+ *    - Date.prototype.toUTCString(): E5.1 specification does not require a
+ *      specific format, but result should be human readable.  The
+ *      specification suggests using ISO 8601 format with a space (instead
+ *      of 'T') separator if a more human readable format is not available.
+ *
+ *    - Date.prototype.toISOString(): unlike other conversion functions,
+ *      toISOString() requires a RangeError for invalid date values.
+ */
+
+duk_ret_t duk_bi_date_prototype_tostring_shared(duk_context *ctx) {
+	duk_small_uint_t flags = (duk_small_uint_t) duk_get_magic(ctx);
+	return duk__to_string_helper(ctx, flags);
+}
+
+duk_ret_t duk_bi_date_prototype_value_of(duk_context *ctx) {
+	/* This native function is also used for Date.prototype.getTime()
+	 * as their behavior is identical.
+	 */
+
+	duk_double_t d = duk__push_this_get_timeval(ctx, 0 /*flags*/);  /* -> [ this ] */
+	DUK_ASSERT(DUK_ISFINITE(d) || DUK_ISNAN(d));
+	duk_push_number(ctx, d);
+	return 1;
+}
+
+duk_ret_t duk_bi_date_prototype_to_json(duk_context *ctx) {
+	/* Note: toJSON() is a generic function which works even if 'this'
+	 * is not a Date.  The sole argument is ignored.
+	 */
+
+	duk_push_this(ctx);
+	duk_to_object(ctx, -1);
+
+	duk_dup_top(ctx);
+	duk_to_primitive(ctx, -1, DUK_HINT_NUMBER);
+	if (duk_is_number(ctx, -1)) {
+		duk_double_t d = duk_get_number(ctx, -1);
+		if (!DUK_ISFINITE(d)) {
+			duk_push_null(ctx);
+			return 1;
+		}
+	}
+	duk_pop(ctx);
+
+	duk_get_prop_stridx(ctx, -1, DUK_STRIDX_TO_ISO_STRING);
+	duk_dup(ctx, -2);  /* -> [ O toIsoString O ] */
+	duk_call_method(ctx, 0);
+	return 1;
+}
+
+/*
+ *  Getters.
+ *
+ *  Implementing getters is quite easy.  The internal time value is either
+ *  NaN, or represents milliseconds (without fractions) from Jan 1, 1970.
+ *  The internal time value can be converted to integer parts, and each
+ *  part will be normalized and will fit into a 32-bit signed integer.
+ *
+ *  A shared native helper to provide all getters.  Magic value contains
+ *  a set of flags and also packs the date component index argument.  The
+ *  helper provides:
+ *
+ *    getFullYear()
+ *    getUTCFullYear()
+ *    getMonth()
+ *    getUTCMonth()
+ *    getDate()
+ *    getUTCDate()
+ *    getDay()
+ *    getUTCDay()
+ *    getHours()
+ *    getUTCHours()
+ *    getMinutes()
+ *    getUTCMinutes()
+ *    getSeconds()
+ *    getUTCSeconds()
+ *    getMilliseconds()
+ *    getUTCMilliseconds()
+ *    getYear()
+ *
+ *  Notes:
+ *
+ *    - Date.prototype.getDate(): 'date' means day-of-month, and is
+ *      zero-based in internal calculations but public API expects it to
+ *      be one-based.
+ *
+ *    - Date.prototype.getTime() and Date.prototype.valueOf() have identical
+ *      behavior.  They have separate function objects, but share the same C
+ *      function (duk_bi_date_prototype_value_of).
+ */
+
+duk_ret_t duk_bi_date_prototype_get_shared(duk_context *ctx) {
+	duk_small_uint_t flags_and_idx = (duk_small_uint_t) duk_get_magic(ctx);
+	return duk__get_part_helper(ctx, flags_and_idx);
+}
+
+duk_ret_t duk_bi_date_prototype_get_timezone_offset(duk_context *ctx) {
+	/*
+	 *  Return (t - LocalTime(t)) in minutes:
+	 *
+	 *    t - LocalTime(t) = t - (t + LocalTZA + DaylightSavingTA(t))
+	 *                     = -(LocalTZA + DaylightSavingTA(t))
+	 *
+	 *  where DaylightSavingTA() is checked for time 't'.
+	 *
+	 *  Note that the sign of the result is opposite to common usage,
+	 *  e.g. for EE(S)T which normally is +2h or +3h from UTC, this
+	 *  function returns -120 or -180.
+	 *
+	 */
+
+	duk_double_t d;
+	duk_int_t tzoffset;
+
+	/* Note: DST adjustment is determined using UTC time. */
+	d = duk__push_this_get_timeval(ctx, 0 /*flags*/);
+	DUK_ASSERT(DUK_ISFINITE(d) || DUK_ISNAN(d));
+	if (DUK_ISNAN(d)) {
+		duk_push_nan(ctx);
+	} else {
+		DUK_ASSERT(DUK_ISFINITE(d));
+		tzoffset = DUK__GET_LOCAL_TZOFFSET(d);
+		duk_push_int(ctx, -tzoffset / 60);
+	}
+	return 1;
+}
+
+/*
+ *  Setters.
+ *
+ *  Setters are a bit more complicated than getters.  Component setters
+ *  break down the current time value into its (normalized) component
+ *  parts, replace one or more components with -unnormalized- new values,
+ *  and the components are then converted back into a time value.  As an
+ *  example of using unnormalized values:
+ *
+ *    var d = new Date(1234567890);
+ *
+ *  is equivalent to:
+ *
+ *    var d = new Date(0);
+ *    d.setUTCMilliseconds(1234567890);
+ *
+ *  A shared native helper to provide almost all setters.  Magic value
+ *  contains a set of flags and also packs the "maxnargs" argument.  The
+ *  helper provides:
+ *
+ *    setMilliseconds()
+ *    setUTCMilliseconds()
+ *    setSeconds()
+ *    setUTCSeconds()
+ *    setMinutes()
+ *    setUTCMinutes()
+ *    setHours()
+ *    setUTCHours()
+ *    setDate()
+ *    setUTCDate()
+ *    setMonth()
+ *    setUTCMonth()
+ *    setFullYear()
+ *    setUTCFullYear()
+ *    setYear()
+ *
+ *  Notes:
+ *
+ *    - Date.prototype.setYear() (Section B addition): special year check
+ *      is omitted.  NaN / Infinity will just flow through and ultimately
+ *      result in a NaN internal time value.
+ *
+ *    - Date.prototype.setYear() does not have optional arguments for
+ *      setting month and day-in-month (like setFullYear()), but we indicate
+ *      'maxnargs' to be 3 to get the year written to the correct component
+ *      index in duk__set_part_helper().  The function has nargs == 1, so only
+ *      the year will be set regardless of actual argument count.
+ */
+
+duk_ret_t duk_bi_date_prototype_set_shared(duk_context *ctx) {
+	duk_small_uint_t flags_and_maxnargs = (duk_small_uint_t) duk_get_magic(ctx);
+	return duk__set_part_helper(ctx, flags_and_maxnargs);
+}
+
+duk_ret_t duk_bi_date_prototype_set_time(duk_context *ctx) {
+	duk_double_t d;
+
+	(void) duk__push_this_get_timeval(ctx, 0 /*flags*/); /* -> [ timeval this ] */
+	d = duk__timeclip(duk_to_number(ctx, 0));
+	duk_push_number(ctx, d);
+	duk_dup_top(ctx);
+	duk_put_prop_stridx(ctx, -3, DUK_STRIDX_INT_VALUE); /* -> [ timeval this timeval ] */
+
+	return 1;
+}
+#line 1 "duk_bi_duktape.c"
+/*
+ *  Duktape built-ins
+ *
+ *  Size optimization note: it might seem that vararg multipurpose functions
+ *  like fin(), enc(), and dec() are not very size optimal, but using a single
+ *  user-visible Ecmascript function saves a lot of run-time footprint; each
+ *  Function instance takes >100 bytes.  Using a shared native helper and a
+ *  'magic' value won't save much if there are multiple Function instances
+ *  anyway.
+ */
+
+/* include removed: duk_internal.h */
+
+/* Raw helper to extract internal information / statistics about a value.
+ * The return values are version specific and must not expose anything
+ * that would lead to security issues (e.g. exposing compiled function
+ * 'data' buffer might be an issue).  Currently only counts and sizes and
+ * such are given so there should not be a security impact.
+ */
+duk_ret_t duk_bi_duktape_object_info(duk_context *ctx) {
+	duk_tval *tv;
+	duk_heaphdr *h;
+	duk_int_t i, n;
+
+	tv = duk_get_tval(ctx, 0);
+	DUK_ASSERT(tv != NULL);  /* because arg count is 1 */
+
+	duk_push_array(ctx);  /* -> [ val arr ] */
+
+	/* type tag (public) */
+	duk_push_int(ctx, duk_get_type(ctx, 0));
+
+	/* address */
+	if (DUK_TVAL_IS_HEAP_ALLOCATED(tv)) {
+		h = DUK_TVAL_GET_HEAPHDR(tv);
+		duk_push_pointer(ctx, (void *) h);
+	} else {
+		goto done;
+	}
+	DUK_ASSERT(h != NULL);
+
+	/* refcount */
+#ifdef DUK_USE_REFERENCE_COUNTING
+	duk_push_size_t(ctx, DUK_HEAPHDR_GET_REFCOUNT(h));
+#else
+	duk_push_undefined(ctx);
+#endif
+
+	/* heaphdr size and additional allocation size, followed by
+	 * type specific stuff (with varying value count)
+	 */
+	switch ((duk_small_int_t) DUK_HEAPHDR_GET_TYPE(h)) {
+	case DUK_HTYPE_STRING: {
+		duk_hstring *h_str = (duk_hstring *) h;
+		duk_push_uint(ctx, (duk_uint_t) (sizeof(duk_hstring) + DUK_HSTRING_GET_BYTELEN(h_str) + 1));
+		break;
+	}
+	case DUK_HTYPE_OBJECT: {
+		duk_hobject *h_obj = (duk_hobject *) h;
+		duk_small_uint_t hdr_size;
+		if (DUK_HOBJECT_IS_COMPILEDFUNCTION(h_obj)) {
+			hdr_size = (duk_small_uint_t) sizeof(duk_hcompiledfunction);
+		} else if (DUK_HOBJECT_IS_NATIVEFUNCTION(h_obj)) {
+			hdr_size = (duk_small_uint_t) sizeof(duk_hnativefunction);
+		} else if (DUK_HOBJECT_IS_THREAD(h_obj)) {
+			hdr_size = (duk_small_uint_t) sizeof(duk_hthread);
+		} else {
+			hdr_size = (duk_small_uint_t) sizeof(duk_hobject);
+		}
+		duk_push_uint(ctx, (duk_uint_t) hdr_size);
+		duk_push_uint(ctx, (duk_uint_t) DUK_HOBJECT_E_ALLOC_SIZE(h_obj));
+		duk_push_uint(ctx, (duk_uint_t) h_obj->e_size);
+		duk_push_uint(ctx, (duk_uint_t) h_obj->e_used);
+		duk_push_uint(ctx, (duk_uint_t) h_obj->a_size);
+		duk_push_uint(ctx, (duk_uint_t) h_obj->h_size);
+		if (DUK_HOBJECT_IS_COMPILEDFUNCTION(h_obj)) {
+			duk_hbuffer *h_data = ((duk_hcompiledfunction *) h_obj)->data;
+			if (h_data) {
+				duk_push_uint(ctx, (duk_uint_t) DUK_HBUFFER_GET_SIZE(h_data));
+			} else {
+				duk_push_uint(ctx, 0);
+			}
+		}
+		break;
+	}
+	case DUK_HTYPE_BUFFER: {
+		duk_hbuffer *h_buf = (duk_hbuffer *) h;
+		if (DUK_HBUFFER_HAS_DYNAMIC(h_buf)) {
+			/* XXX: when usable_size == 0, dynamic buf ptr may now be NULL, in which case
+			 * the second allocation does not exist.
+			 */
+			duk_hbuffer_dynamic *h_dyn = (duk_hbuffer_dynamic *) h;
+			duk_push_uint(ctx, (duk_uint_t) (sizeof(duk_hbuffer_dynamic)));
+			duk_push_uint(ctx, (duk_uint_t) (DUK_HBUFFER_DYNAMIC_GET_ALLOC_SIZE(h_dyn)));
+		} else {
+			duk_push_uint(ctx, (duk_uint_t) (sizeof(duk_hbuffer_fixed) + DUK_HBUFFER_GET_SIZE(h_buf) + 1));
+		}
+		break;
+
+	}
+	}
+
+ done:
+	/* set values into ret array */
+	/* FIXME: primitive to make array from valstack slice */
+	n = duk_get_top(ctx);
+	for (i = 2; i < n; i++) {
+		duk_dup(ctx, i);
+		duk_put_prop_index(ctx, 1, i - 2);
+	}
+	duk_dup(ctx, 1);
+	return 1;
+}
+
+duk_ret_t duk_bi_duktape_object_act(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_activation *act;
+	duk_hobject *h_func;
+	duk_uint_fast32_t pc;
+	duk_uint_fast32_t line;
+	duk_int_t level;
+
+	/* -1             = top callstack entry, callstack[callstack_top - 1]
+	 * -callstack_top = bottom callstack entry, callstack[0]
+	 */
+	level = duk_to_int(ctx, 0);
+	if (level >= 0 || -level > (duk_int_t) thr->callstack_top) {
+		return 0;
+	}
+	DUK_ASSERT(level >= -((duk_int_t) thr->callstack_top) && level <= -1);
+	act = thr->callstack + thr->callstack_top + level;
+
+	duk_push_object(ctx);
+
+	h_func = act->func;
+	DUK_ASSERT(h_func != NULL);
+	duk_push_hobject(ctx, h_func);
+
+	pc = (duk_uint_fast32_t) act->pc;
+	duk_push_uint(ctx, (duk_uint_t) pc);
+
+	line = duk_hobject_pc2line_query(ctx, -2, pc);
+	duk_push_uint(ctx, (duk_uint_t) line);
+
+	/* Providing access to e.g. act->lex_env would be dangerous: these
+	 * internal structures must never be accessible to the application.
+	 * Duktape relies on them having consistent data, and this consistency
+	 * is only asserted for, not checked for.
+	 */
+
+	/* [ level obj func pc line ] */
+
+	/* FIXME: version specific array format instead? */
+	duk_def_prop_stridx_wec(ctx, -4, DUK_STRIDX_LINE_NUMBER);
+	duk_def_prop_stridx_wec(ctx, -3, DUK_STRIDX_PC);
+	duk_def_prop_stridx_wec(ctx, -2, DUK_STRIDX_LC_FUNCTION);
+	return 1;
+}
+
+duk_ret_t duk_bi_duktape_object_gc(duk_context *ctx) {
+#ifdef DUK_USE_MARK_AND_SWEEP
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_small_uint_t flags;
+	duk_bool_t rc;
+
+	flags = (duk_small_uint_t) duk_get_uint(ctx, 0);
+	rc = duk_heap_mark_and_sweep(thr->heap, flags);
+	duk_push_boolean(ctx, rc);
+	return 1;
+#else
+	DUK_UNREF(ctx);
+	return 0;
+#endif
+}
+
+duk_ret_t duk_bi_duktape_object_fin(duk_context *ctx) {
+	(void) duk_require_hobject(ctx, 0);
+	if (duk_get_top(ctx) >= 2) {
+		/* Set: currently a finalizer is disabled by setting it to
+		 * undefined; this does not remove the property at the moment.
+		 * The value could be type checked to be either a function
+		 * or something else; if something else, the property could
+		 * be deleted.
+		 */
+		duk_set_top(ctx, 2);
+		(void) duk_put_prop_stridx(ctx, 0, DUK_STRIDX_INT_FINALIZER);
+		return 0;
+	} else {
+		/* Get. */
+		DUK_ASSERT(duk_get_top(ctx) == 1);
+		duk_get_prop_stridx(ctx, 0, DUK_STRIDX_INT_FINALIZER);
+		return 1;
+	}
+}
+
+duk_ret_t duk_bi_duktape_object_enc(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hstring *h_str;
+
+	/* Vararg function: must be careful to check/require arguments.
+	 * The JSON helpers accept invalid indices and treat them like
+	 * non-existent optional parameters.
+	 */
+
+	h_str = duk_require_hstring(ctx, 0);
+	duk_require_valid_index(ctx, 1);
+
+	if (h_str == DUK_HTHREAD_STRING_HEX(thr)) {
+		duk_set_top(ctx, 2);
+		duk_hex_encode(ctx, 1);
+		DUK_ASSERT_TOP(ctx, 2);
+	} else if (h_str == DUK_HTHREAD_STRING_BASE64(thr)) {
+		duk_set_top(ctx, 2);
+		duk_base64_encode(ctx, 1);
+		DUK_ASSERT_TOP(ctx, 2);
+#ifdef DUK_USE_JX
+	} else if (h_str == DUK_HTHREAD_STRING_JX(thr)) {
+		duk_bi_json_stringify_helper(ctx,
+		                             1 /*idx_value*/,
+		                             2 /*idx_replacer*/,
+		                             3 /*idx_space*/,
+		                             DUK_JSON_FLAG_EXT_CUSTOM |
+		                             DUK_JSON_FLAG_ASCII_ONLY |
+		                             DUK_JSON_FLAG_AVOID_KEY_QUOTES /*flags*/);
+#endif
+#ifdef DUK_USE_JC
+	} else if (h_str == DUK_HTHREAD_STRING_JC(thr)) {
+		duk_bi_json_stringify_helper(ctx,
+		                             1 /*idx_value*/,
+		                             2 /*idx_replacer*/,
+		                             3 /*idx_space*/,
+		                             DUK_JSON_FLAG_EXT_COMPATIBLE |
+		                             DUK_JSON_FLAG_ASCII_ONLY /*flags*/);
+#endif
+	} else {
+		return DUK_RET_TYPE_ERROR;
+	}
+	return 1;
+}
+
+duk_ret_t duk_bi_duktape_object_dec(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hstring *h_str;
+
+	/* Vararg function: must be careful to check/require arguments.
+	 * The JSON helpers accept invalid indices and treat them like
+	 * non-existent optional parameters.
+	 */
+
+	h_str = duk_require_hstring(ctx, 0);
+	duk_require_valid_index(ctx, 1);
+
+	if (h_str == DUK_HTHREAD_STRING_HEX(thr)) {
+		duk_set_top(ctx, 2);
+		duk_hex_decode(ctx, 1);
+		DUK_ASSERT_TOP(ctx, 2);
+	} else if (h_str == DUK_HTHREAD_STRING_BASE64(thr)) {
+		duk_set_top(ctx, 2);
+		duk_base64_decode(ctx, 1);
+		DUK_ASSERT_TOP(ctx, 2);
+#ifdef DUK_USE_JX
+	} else if (h_str == DUK_HTHREAD_STRING_JX(thr)) {
+		duk_bi_json_parse_helper(ctx,
+		                         1 /*idx_value*/,
+		                         2 /*idx_replacer*/,
+		                         DUK_JSON_FLAG_EXT_CUSTOM /*flags*/);
+#endif
+#ifdef DUK_USE_JC
+	} else if (h_str == DUK_HTHREAD_STRING_JC(thr)) {
+		duk_bi_json_parse_helper(ctx,
+		                         1 /*idx_value*/,
+		                         2 /*idx_replacer*/,
+		                         DUK_JSON_FLAG_EXT_COMPATIBLE /*flags*/);
+#endif
+	} else {
+		return DUK_RET_TYPE_ERROR;
+	}
+	return 1;
+}
+
+/*
+ *  Compact an object
+ */
+
+duk_ret_t duk_bi_duktape_object_compact(duk_context *ctx) {
+	DUK_ASSERT_TOP(ctx, 1);
+	duk_compact(ctx, 0);
+	return 1;  /* return the argument object */
+}
+#line 1 "duk_bi_error.c"
+/*
+ *  Error built-ins
+ */
+
+/* include removed: duk_internal.h */
+
+duk_ret_t duk_bi_error_constructor_shared(duk_context *ctx) {
+	/* Behavior for constructor and non-constructor call is
+	 * the same except for augmenting the created error.  When
+	 * called as a constructor, the caller (duk_new()) will handle
+	 * augmentation; when called as normal function, we need to do
+	 * it here.
+	 */
+
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_small_int_t bidx_prototype = duk_get_magic(ctx);
+
+	/* same for both error and each subclass like TypeError */
+	duk_uint_t flags_and_class = DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                             DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ERROR);
+	
+	DUK_UNREF(thr);
+
+	duk_push_object_helper(ctx, flags_and_class, bidx_prototype);
+
+	/* If message is undefined, the own property 'message' is not set at
+	 * all to save property space.  An empty message is inherited anyway.
+	 */
+	if (!duk_is_undefined(ctx, 0)) {
+		duk_to_string(ctx, 0);
+		duk_dup(ctx, 0);  /* [ message error message ] */
+		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_MESSAGE, DUK_PROPDESC_FLAGS_WC);
+	}
+
+	/* Augment the error if called as a normal function.  __FILE__ and __LINE__
+	 * are not desirable in this case.
+	 */
+
+#ifdef DUK_USE_AUGMENT_ERROR_CREATE
+	if (!duk_is_constructor_call(ctx)) {
+		duk_err_augment_error_create(thr, thr, NULL, 0, 1 /*noblame_fileline*/);
+	}
+#endif
+
+	return 1;
+}
+
+duk_ret_t duk_bi_error_prototype_to_string(duk_context *ctx) {
+	/* FIXME: optimize with more direct internal access */
+
+	duk_push_this(ctx);
+	if (!duk_is_object(ctx, -1)) {
+		goto type_error;
+	}
+
+	/* [ ... this ] */
+
+	duk_get_prop_stridx(ctx, -1, DUK_STRIDX_NAME);
+	if (duk_is_undefined(ctx, -1)) {
+		duk_pop(ctx);
+		duk_push_string(ctx, "Error");
+	} else {
+		duk_to_string(ctx, -1);
+	}
+
+	/* [ ... this name ] */
+
+	/* FIXME: Are steps 6 and 7 in E5 Section 15.11.4.4 duplicated by
+	 * accident or are they actually needed?  The first ToString()
+	 * could conceivably return 'undefined'.
+	 */
+	duk_get_prop_stridx(ctx, -2, DUK_STRIDX_MESSAGE);
+	if (duk_is_undefined(ctx, -1)) {
+		duk_pop(ctx);
+		duk_push_string(ctx, "");
+	} else {
+		duk_to_string(ctx, -1);
+	}
+
+	/* [ ... this name message ] */
+
+	if (duk_get_length(ctx, -2) == 0) {
+		/* name is empty -> return message */
+		return 1;
+	}
+	if (duk_get_length(ctx, -1) == 0) {
+		/* message is empty -> return name */
+		duk_pop(ctx);
+		return 1;
+	}
+	duk_push_string(ctx, ": ");
+	duk_insert(ctx, -2);  /* ... name ': ' message */
+	duk_concat(ctx, 3);
+
+	return 1;
+
+ type_error:
+	return DUK_RET_TYPE_ERROR;
+}
+
+#ifdef DUK_USE_TRACEBACKS
+
+/*
+ *  Traceback handling
+ *
+ *  The unified helper decodes the traceback and produces various requested
+ *  outputs.  It should be optimized for size, and may leave garbage on stack,
+ *  only the topmost return value matters.  For instance, traceback separator
+ *  and decoded strings are pushed even when looking for filename only.
+ *
+ *  NOTE: because user code can currently write to the tracedata array (or
+ *  replace it with something other than an array), the code below must
+ *  tolerate arbitrary tracedata.  It can throw errors etc, but cannot cause
+ *  a segfault or memory unsafe behavior.
+ */
+
+/* constants arbitrary, chosen for small loads */
+#define DUK__OUTPUT_TYPE_TRACEBACK   (-1)
+#define DUK__OUTPUT_TYPE_FILENAME    0
+#define DUK__OUTPUT_TYPE_LINENUMBER  1
+
+static duk_ret_t duk__traceback_getter_helper(duk_context *ctx, duk_small_int_t output_type) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_idx_t idx_td;
+	duk_small_int_t i;  /* traceback depth fits into 16 bits */
+	duk_small_int_t t;  /* stack type fits into 16 bits */
+	const char *str_tailcalled = " tailcalled";
+	const char *str_strict = " strict";
+	const char *str_construct = " construct";
+	const char *str_prevyield = " preventsyield";
+	const char *str_directeval = " directeval";
+	const char *str_empty = "";
+
+	DUK_ASSERT_TOP(ctx, 0);  /* fixed arg count */
+
+	duk_push_this(ctx);
+	duk_get_prop_stridx(ctx, -1, DUK_STRIDX_TRACEDATA);
+	idx_td = duk_get_top_index(ctx);
+
+	duk_push_hstring_stridx(ctx, DUK_STRIDX_NEWLINE_TAB);
+	duk_push_this(ctx);
+	duk_to_string(ctx, -1);
+
+	/* [ ... this tracedata sep ToString(this) ] */
+
+	/* FIXME: skip null filename? */
+
+	if (duk_check_type(ctx, idx_td, DUK_TYPE_OBJECT)) {
+		/* Current tracedata contains 2 entries per callstack entry. */
+		for (i = 0; ; i += 2) {
+			duk_int_t pc;
+			duk_int_t line;
+			duk_int_t flags;
+			duk_double_t d;
+			const char *funcname;
+			const char *filename;
+			duk_hobject *h_func;
+			duk_hstring *h_name;
+
+			duk_require_stack(ctx, 5);
+			duk_get_prop_index(ctx, idx_td, i);
+			duk_get_prop_index(ctx, idx_td, i + 1);
+			d = duk_to_number(ctx, -1);
+			pc = (duk_int_t) DUK_FMOD(d, DUK_DOUBLE_2TO32);
+			flags = (duk_int_t) DUK_FLOOR(d / DUK_DOUBLE_2TO32);
+			t = (duk_small_int_t) duk_get_type(ctx, -2);
+
+			if (t == DUK_TYPE_OBJECT) {
+				/*
+				 *  Ecmascript/native function call
+				 */
+
+				/* [ ... v1(func) v2(pc+flags) ] */
+
+				h_func = duk_get_hobject(ctx, -2);
+				DUK_ASSERT(h_func != NULL);
+
+				duk_get_prop_stridx(ctx, -2, DUK_STRIDX_NAME);
+				duk_get_prop_stridx(ctx, -3, DUK_STRIDX_FILE_NAME);
+
+#if defined(DUK_USE_PC2LINE)
+				line = duk_hobject_pc2line_query(ctx, -4, (duk_uint_fast32_t) pc);
+#else
+				line = 0;
+#endif
+
+				/* [ ... v1 v2 name filename ] */
+
+				if (output_type == DUK__OUTPUT_TYPE_FILENAME) {
+					return 1;
+				} else if (output_type == DUK__OUTPUT_TYPE_LINENUMBER) {
+					duk_push_int(ctx, line);
+					return 1;
+				}
+
+				h_name = duk_get_hstring(ctx, -2);  /* may be NULL */
+				funcname = (h_name == NULL || h_name == DUK_HTHREAD_STRING_EMPTY_STRING(thr)) ?
+				           "anon" : (const char *) DUK_HSTRING_GET_DATA(h_name);
+				filename = duk_get_string(ctx, -1);
+				filename = filename ? filename : "";
+				DUK_ASSERT(funcname != NULL);
+				DUK_ASSERT(filename != NULL);
+
+				if (DUK_HOBJECT_HAS_NATIVEFUNCTION(h_func)) {
+					duk_push_sprintf(ctx, "%s %s native%s%s%s%s%s",
+					                 (const char *) funcname,
+					                 (const char *) filename,
+					                 (const char *) ((flags & DUK_ACT_FLAG_STRICT) ? str_strict : str_empty),
+					                 (const char *) ((flags & DUK_ACT_FLAG_TAILCALLED) ? str_tailcalled : str_empty),
+					                 (const char *) ((flags & DUK_ACT_FLAG_CONSTRUCT) ? str_construct : str_empty),
+					                 (const char *) ((flags & DUK_ACT_FLAG_DIRECT_EVAL) ? str_directeval : str_empty),
+					                 (const char *) ((flags & DUK_ACT_FLAG_PREVENT_YIELD) ? str_prevyield : str_empty));
+
+				} else {
+					duk_push_sprintf(ctx, "%s %s:%ld%s%s%s%s%s",
+					                 (const char *) funcname,
+					                 (const char *) filename,
+					                 (long) line,
+					                 (const char *) ((flags & DUK_ACT_FLAG_STRICT) ? str_strict : str_empty),
+					                 (const char *) ((flags & DUK_ACT_FLAG_TAILCALLED) ? str_tailcalled : str_empty),
+					                 (const char *) ((flags & DUK_ACT_FLAG_CONSTRUCT) ? str_construct : str_empty),
+					                 (const char *) ((flags & DUK_ACT_FLAG_DIRECT_EVAL) ? str_directeval : str_empty),
+					                 (const char *) ((flags & DUK_ACT_FLAG_PREVENT_YIELD) ? str_prevyield : str_empty));
+				}
+				duk_replace(ctx, -5);   /* [ ... v1 v2 name filename str ] -> [ ... str v2 name filename ] */
+				duk_pop_n(ctx, 3);      /* -> [ ... str ] */
+			} else if (t == DUK_TYPE_STRING) {
+				/*
+				 *  __FILE__ / __LINE__ entry, here 'pc' is line number directly.
+				 *  Sometimes __FILE__ / __LINE__ is reported as the source for
+				 *  the error (fileName, lineNumber), sometimes not.
+				 */
+
+				/* [ ... v1(filename) v2(line+flags) ] */
+
+				if (!(flags & DUK_TB_FLAG_NOBLAME_FILELINE)) {
+					if (output_type == DUK__OUTPUT_TYPE_FILENAME) {
+						duk_pop(ctx);
+						return 1;
+					} else if (output_type == DUK__OUTPUT_TYPE_LINENUMBER) {
+						duk_push_int(ctx, pc);
+						return 1;
+					}
+				}
+
+				duk_push_sprintf(ctx, "%s:%ld",
+				                 (const char *) duk_get_string(ctx, -2), (long) pc);
+				duk_replace(ctx, -3);  /* [ ... v1 v2 str ] -> [ ... str v2 ] */
+				duk_pop(ctx);          /* -> [ ... str ] */
+			} else {
+				/* unknown, ignore */
+				duk_pop_2(ctx);
+				break;
+			}
+		}
+
+		if (i >= DUK_USE_TRACEBACK_DEPTH * 2) {
+			/* Possibly truncated; there is no explicit truncation
+			 * marker so this is the best we can do.
+			 */
+
+			duk_push_hstring_stridx(ctx, DUK_STRIDX_BRACKETED_ELLIPSIS);
+		}
+	}
+
+	/* [ ... this tracedata sep ToString(this) str1 ... strN ] */
+
+	if (output_type != DUK__OUTPUT_TYPE_TRACEBACK) {
+		return 0;
+	} else {
+		duk_join(ctx, duk_get_top(ctx) - (idx_td + 2) /*count, not including sep*/);
+		return 1;
+	}
+}
+
+/* FIXME: output type could be encoded into native function 'magic' value to
+ * save space.
+ */
+
+duk_ret_t duk_bi_error_prototype_stack_getter(duk_context *ctx) {
+	return duk__traceback_getter_helper(ctx, DUK__OUTPUT_TYPE_TRACEBACK);
+}
+
+duk_ret_t duk_bi_error_prototype_filename_getter(duk_context *ctx) {
+	return duk__traceback_getter_helper(ctx, DUK__OUTPUT_TYPE_FILENAME);
+}
+
+duk_ret_t duk_bi_error_prototype_linenumber_getter(duk_context *ctx) {
+	return duk__traceback_getter_helper(ctx, DUK__OUTPUT_TYPE_LINENUMBER);
+}
+
+#undef DUK__OUTPUT_TYPE_TRACEBACK
+#undef DUK__OUTPUT_TYPE_FILENAME
+#undef DUK__OUTPUT_TYPE_LINENUMBER
+
+#else  /* DUK_USE_TRACEBACKS */
+
+/*
+ *  Traceback handling when tracebacks disabled.
+ *
+ *  The fileName / lineNumber stubs are now necessary because built-in
+ *  data will include the accessor properties in Error.prototype.  If those
+ *  are removed for builds without tracebacks, these can also be removed.
+ *  'stack' should still be present and produce a ToString() equivalent:
+ *  this is useful for user code which prints a stacktrace and expects to
+ *  see something useful.  A normal stacktrace also begins with a ToString()
+ *  of the error so this makes sense.
+ */
+
+duk_ret_t duk_bi_error_prototype_stack_getter(duk_context *ctx) {
+	/* FIXME: remove this native function and map 'stack' accessor
+	 * to the toString() implementation directly.
+	 */
+	return duk_bi_error_prototype_to_string(ctx);
+}
+
+duk_ret_t duk_bi_error_prototype_filename_getter(duk_context *ctx) {
+	DUK_UNREF(ctx);
+	return 0;
+}
+
+duk_ret_t duk_bi_error_prototype_linenumber_getter(duk_context *ctx) {
+	DUK_UNREF(ctx);
+	return 0;
+}
+
+#endif  /* DUK_USE_TRACEBACKS */
+
+duk_ret_t duk_bi_error_prototype_nop_setter(duk_context *ctx) {
+	/* Attempt to write 'stack', 'fileName', 'lineNumber' is a silent no-op.
+	 * User can use Object.defineProperty() to override this behavior.
+	 */
+	DUK_ASSERT_TOP(ctx, 1);  /* fixed arg count */
+	DUK_UNREF(ctx);
+	return 0;
+}
+#line 1 "duk_bi_function.c"
+/*
+ *  Function built-ins
+ */
+
+/* include removed: duk_internal.h */
+
+/* FIXME: shared string */
+const char *duk__str_anon = "anon";
+
+duk_ret_t duk_bi_function_constructor(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hstring *h_sourcecode;
+	duk_idx_t nargs;
+	duk_idx_t i;
+	duk_small_uint_t comp_flags;
+	duk_hcompiledfunction *func;
+	duk_hobject *outer_lex_env;
+	duk_hobject *outer_var_env;
+
+	/* normal and constructor calls have identical semantics */
+
+	nargs = duk_get_top(ctx);
+	for (i = 0; i < nargs; i++) {
+		duk_to_string(ctx, i);
+	}
+
+	if (nargs == 0) {
+		duk_push_string(ctx, "");
+		duk_push_string(ctx, "");
+	} else if (nargs == 1) {
+		/* XXX: cover this with the generic >1 case? */
+		duk_push_string(ctx, "");
+	} else {
+		duk_insert(ctx, 0);   /* [ arg1 ... argN-1 body] -> [body arg1 ... argN-1] */
+		duk_push_string(ctx, ",");
+		duk_insert(ctx, 1);
+		duk_join(ctx, nargs - 1);
+	}
+
+	/* [ body formals ], formals is comma separated list that needs to be parsed */
+
+	DUK_ASSERT_TOP(ctx, 2);
+
+	/* FIXME: this placeholder is not always correct, but use for now.
+	 * It will fail in corner cases; see test-dev-func-cons-args.js.
+	 */
+	duk_push_string(ctx, "function(");
+	duk_dup(ctx, 1);
+	duk_push_string(ctx, "){");
+	duk_dup(ctx, 0);
+	duk_push_string(ctx, "}");
+	duk_concat(ctx, 5);
+
+	/* [ body formals source ] */
+
+	DUK_ASSERT_TOP(ctx, 3);
+
+	/* strictness is not inherited, intentional */
+	comp_flags = DUK_JS_COMPILE_FLAG_FUNCEXPR;
+
+	duk_push_hstring_stridx(ctx, DUK_STRIDX_COMPILE);  /* XXX: copy from caller? */  /* FIXME: ignored now */
+	h_sourcecode = duk_require_hstring(ctx, -2);
+	duk_js_compile(thr,
+	               (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h_sourcecode),
+	               (duk_size_t) DUK_HSTRING_GET_BYTELEN(h_sourcecode),
+	               comp_flags);
+	func = (duk_hcompiledfunction *) duk_get_hobject(ctx, -1);
+	DUK_ASSERT(func != NULL);
+	DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION((duk_hobject *) func));
+
+	/* [ body formals source template ] */
+
+	/* only outer_lex_env matters, as functions always get a new
+	 * variable declaration environment.
+	 */
+
+	outer_lex_env = thr->builtins[DUK_BIDX_GLOBAL_ENV];
+	outer_var_env = thr->builtins[DUK_BIDX_GLOBAL_ENV];
+
+	duk_js_push_closure(thr, func, outer_var_env, outer_lex_env);
+
+	/* [ body formals source template closure ] */
+
+	return 1;
+}
+
+duk_ret_t duk_bi_function_prototype(duk_context *ctx) {
+	/* ignore arguments, return undefined (E5 Section 15.3.4) */
+	DUK_UNREF(ctx);
+	return 0;
+}
+
+duk_ret_t duk_bi_function_prototype_to_string(duk_context *ctx) {
+	duk_tval *tv;
+
+	/*
+	 *  E5 Section 15.3.4.2 places few requirements on the output of
+	 *  this function:
+	 *
+	 *    - The result is an implementation dependent representation
+	 *      of the function; in particular
+	 *
+	 *    - The result must follow the syntax of a FunctionDeclaration.
+	 *      In particular, the function must have a name (even in the
+	 *      case of an anonymous function or a function with an empty
+	 *      name).
+	 *
+	 *    - Note in particular that the output does NOT need to compile
+	 *      into anything useful.
+	 */
+
+
+	/* FIXME: faster internal way to get this */
+	duk_push_this(ctx);
+	tv = duk_get_tval(ctx, -1);
+	DUK_ASSERT(tv != NULL);
+
+	if (DUK_TVAL_IS_OBJECT(tv)) {
+		duk_hobject *obj = DUK_TVAL_GET_OBJECT(tv);
+		const char *func_name = duk__str_anon;
+
+		/* FIXME: rework, it would be nice to avoid C formatting functions to
+		 * ensure there are no Unicode issues.
+		 */
+
+		duk_get_prop_stridx(ctx, -1, DUK_STRIDX_NAME);
+		if (!duk_is_undefined(ctx, -1)) {
+			func_name = duk_to_string(ctx, -1);
+			DUK_ASSERT(func_name != NULL);
+
+			if (func_name[0] == (char) 0) {
+				func_name = duk__str_anon;
+			}
+		}
+
+		if (DUK_HOBJECT_HAS_COMPILEDFUNCTION(obj)) {
+			/* XXX: actual source, if available */
+			duk_push_sprintf(ctx, "function %s() {/* source code */}", (const char *) func_name);
+		} else if (DUK_HOBJECT_HAS_NATIVEFUNCTION(obj)) {
+			duk_push_sprintf(ctx, "function %s() {/* native code */}", (const char *) func_name);
+		} else if (DUK_HOBJECT_HAS_BOUND(obj)) {
+			duk_push_sprintf(ctx, "function %s() {/* bound */}", (const char *) func_name);
+		} else {
+			goto type_error;
+		}
+	} else {
+		goto type_error;
+	}
+
+	return 1;
+
+ type_error:
+	return DUK_RET_TYPE_ERROR;
+}
+
+duk_ret_t duk_bi_function_prototype_apply(duk_context *ctx) {
+	duk_idx_t len;
+	duk_idx_t i;
+
+	DUK_ASSERT_TOP(ctx, 2);  /* not a vararg function */
+
+	duk_push_this(ctx);
+	if (!duk_is_callable(ctx, -1)) {
+		DUK_DDD(DUK_DDDPRINT("func is not callable"));
+		goto type_error;
+	}
+	duk_insert(ctx, 0);
+	DUK_ASSERT_TOP(ctx, 3);
+
+	DUK_DDD(DUK_DDDPRINT("func=%!iT, thisArg=%!iT, argArray=%!iT",
+	                     (duk_tval *) duk_get_tval(ctx, 0),
+	                     (duk_tval *) duk_get_tval(ctx, 1),
+	                     (duk_tval *) duk_get_tval(ctx, 2)));
+
+	/* [ func thisArg argArray ] */
+
+	if (duk_is_null_or_undefined(ctx, 2)) {
+		DUK_DDD(DUK_DDDPRINT("argArray is null/undefined, no args"));
+		len = 0;
+	} else if (!duk_is_object(ctx, 2)) {
+		goto type_error;
+	} else {
+		DUK_DDD(DUK_DDDPRINT("argArray is an object"));
+
+		/* FIXME: make this an internal helper */
+		duk_get_prop_stridx(ctx, 2, DUK_STRIDX_LENGTH);
+		len = (duk_idx_t) duk_to_uint32(ctx, -1);  /* ToUint32() coercion required */
+		duk_pop(ctx);
+
+		duk_require_stack(ctx, len);
+
+		DUK_DDD(DUK_DDDPRINT("argArray length is %ld", (long) len));
+		for (i = 0; i < len; i++) {
+			duk_get_prop_index(ctx, 2, i);
+		}
+	}
+	duk_remove(ctx, 2);
+	DUK_ASSERT_TOP(ctx, 2 + len);
+
+	/* [ func thisArg arg1 ... argN ] */
+	
+	DUK_DDD(DUK_DDDPRINT("apply, func=%!iT, thisArg=%!iT, len=%ld",
+	                     (duk_tval *) duk_get_tval(ctx, 0),
+	                     (duk_tval *) duk_get_tval(ctx, 1),
+	                     (long) len));
+	duk_call_method(ctx, len);
+	return 1;
+
+ type_error:
+	return DUK_RET_TYPE_ERROR;
+}
+
+duk_ret_t duk_bi_function_prototype_call(duk_context *ctx) {
+	duk_idx_t nargs;
+
+	/* Step 1 is not necessary because duk_call_method() will take
+	 * care of it.
+	 */
+
+	/* vararg function, thisArg needs special handling */
+	nargs = duk_get_top(ctx);  /* = 1 + arg count */
+	if (nargs == 0) {
+		duk_push_undefined(ctx);
+		nargs++;
+	}
+	DUK_ASSERT(nargs >= 1);
+
+	/* [ thisArg arg1 ... argN ] */
+
+	duk_push_this(ctx);  /* 'func' in the algorithm */
+	duk_insert(ctx, 0);
+
+	/* [ func thisArg arg1 ... argN ] */
+
+	DUK_DDD(DUK_DDDPRINT("func=%!iT, thisArg=%!iT, argcount=%ld, top=%ld",
+	                     (duk_tval *) duk_get_tval(ctx, 0),
+	                     (duk_tval *) duk_get_tval(ctx, 1),
+	                     (long) (nargs - 1),
+	                     (long) duk_get_top(ctx)));
+	duk_call_method(ctx, nargs - 1);	
+	return 1;
+}
+
+/* FIXME: the implementation now assumes "chained" bound functions,
+ * whereas "collapsed" bound functions (where there is ever only
+ * one bound function which directly points to a non-bound, final
+ * function) would require a "collapsing" implementation which
+ * merges argument lists etc here.
+ */
+duk_ret_t duk_bi_function_prototype_bind(duk_context *ctx) {
+	duk_hobject *h_target;
+	duk_idx_t nargs;
+	duk_idx_t i;
+
+	/* vararg function, careful arg handling (e.g. thisArg may not be present) */
+	nargs = duk_get_top(ctx);  /* = 1 + arg count */
+	if (nargs == 0) {
+		duk_push_undefined(ctx);
+		nargs++;
+	}
+	DUK_ASSERT(nargs >= 1);
+
+	duk_push_this(ctx);
+	if (!duk_is_callable(ctx, -1)) {
+		DUK_DDD(DUK_DDDPRINT("func is not callable"));
+		goto type_error;
+	}
+
+	/* [ thisArg arg1 ... argN func ]  (thisArg+args == nargs total) */
+	DUK_ASSERT_TOP(ctx, nargs + 1);
+
+	/* create bound function object */
+	duk_push_object_helper(ctx,
+	                       DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                       DUK_HOBJECT_FLAG_BOUND |
+	                       DUK_HOBJECT_FLAG_CONSTRUCTABLE |
+	                       DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION),
+	                       DUK_BIDX_FUNCTION_PROTOTYPE);
+
+	/* FIXME: check hobject flags (e.g. strict) */
+
+	/* [ thisArg arg1 ... argN func boundFunc ] */
+	duk_dup(ctx, -2);  /* func */
+	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_TARGET, DUK_PROPDESC_FLAGS_NONE);
+
+	duk_dup(ctx, 0);   /* thisArg */
+	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_THIS, DUK_PROPDESC_FLAGS_NONE);
+
+	duk_push_array(ctx);
+
+	/* [ thisArg arg1 ... argN func boundFunc argArray ] */
+
+	for (i = 0; i < nargs - 1; i++) {
+		duk_dup(ctx, 1 + i);
+		duk_put_prop_index(ctx, -2, i);
+	}
+	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_ARGS, DUK_PROPDESC_FLAGS_NONE);
+
+	/* [ thisArg arg1 ... argN func boundFunc ] */
+
+	/* bound function 'length' property is interesting */
+	h_target = duk_get_hobject(ctx, -2);
+	DUK_ASSERT(h_target != NULL);
+	if (DUK_HOBJECT_GET_CLASS_NUMBER(h_target) == DUK_HOBJECT_CLASS_FUNCTION) {
+		duk_int_t tmp;
+		duk_get_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH);
+		tmp = duk_to_int(ctx, -1) - (nargs - 1);  /* step 15.a */
+		duk_pop(ctx);
+		duk_push_int(ctx, (tmp < 0 ? 0 : tmp));
+	} else {
+		duk_push_int(ctx, 0);
+	}
+	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_NONE);  /* attrs in E5 Section 15.3.5.1 */
+
+	/* caller and arguments must use the same thrower, [[ThrowTypeError]] */
+	duk_def_prop_stridx_thrower(ctx, -1, DUK_STRIDX_CALLER, DUK_PROPDESC_FLAGS_NONE);
+	duk_def_prop_stridx_thrower(ctx, -1, DUK_STRIDX_LC_ARGUMENTS, DUK_PROPDESC_FLAGS_NONE);
+
+	/* these non-standard properties are copied for convenience */
+	/* XXX: 'copy properties' API call? */
+	duk_get_prop_stridx(ctx, -2, DUK_STRIDX_NAME);
+	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_WC);
+	duk_get_prop_stridx(ctx, -2, DUK_STRIDX_FILE_NAME);
+	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_WC);
+
+	DUK_DDD(DUK_DDDPRINT("created bound function: %!iT", (duk_tval *) duk_get_tval(ctx, -1)));
+
+	return 1;
+
+ type_error:
+	return DUK_RET_TYPE_ERROR;
+}
+#line 1 "duk_bi_global.c"
+/*
+ *  Global object built-ins
+ */
+
+/* include removed: duk_internal.h */
+
+/*
+ *  Encoding/decoding helpers
+ */
+
+/* Macros for creating and checking bitmasks for character encoding.
+ * Bit number is a bit counterintuitive, but minimizes code size.
+ */
+#define DUK__MKBITS(a,b,c,d,e,f,g,h)  ((duk_uint8_t) ( \
+	((a) << 0) | ((b) << 1) | ((c) << 2) | ((d) << 3) | \
+	((e) << 4) | ((f) << 5) | ((g) << 6) | ((h) << 7) \
+	))
+#define DUK__CHECK_BITMASK(table,cp)  ((table)[(cp) >> 3] & (1 << ((cp) & 0x07)))
+
+/* E5.1 Section 15.1.3.3: uriReserved + uriUnescaped + '#' */
+static duk_uint8_t duk__encode_uriunescaped_table[16] = {
+	DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x00-0x0f */
+	DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x10-0x1f */
+	DUK__MKBITS(0, 1, 0, 1, 1, 0, 1, 1), DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1),  /* 0x20-0x2f */
+	DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 1, 1, 0, 1, 0, 1),  /* 0x30-0x3f */
+	DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1),  /* 0x40-0x4f */
+	DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 1, 0, 0, 0, 0, 1),  /* 0x50-0x5f */
+	DUK__MKBITS(0, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1),  /* 0x60-0x6f */
+	DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 1, 0, 0, 0, 1, 0),  /* 0x70-0x7f */
+};
+
+/* E5.1 Section 15.1.3.4: uriUnescaped */
+static duk_uint8_t duk__encode_uricomponent_unescaped_table[16] = {
+	DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x00-0x0f */
+	DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x10-0x1f */
+	DUK__MKBITS(0, 1, 0, 0, 0, 0, 0, 1), DUK__MKBITS(1, 1, 1, 0, 0, 1, 1, 0),  /* 0x20-0x2f */
+	DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 0, 0, 0, 0, 0, 0),  /* 0x30-0x3f */
+	DUK__MKBITS(0, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1),  /* 0x40-0x4f */
+	DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 1, 0, 0, 0, 0, 1),  /* 0x50-0x5f */
+	DUK__MKBITS(0, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1),  /* 0x60-0x6f */
+	DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 1, 0, 0, 0, 1, 0),  /* 0x70-0x7f */
+};
+
+/* E5.1 Section 15.1.3.1: uriReserved + '#' */
+static duk_uint8_t duk__decode_uri_reserved_table[16] = {
+	DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x00-0x0f */
+	DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x10-0x1f */
+	DUK__MKBITS(0, 0, 0, 1, 1, 0, 1, 0), DUK__MKBITS(0, 0, 0, 1, 1, 0, 0, 1),  /* 0x20-0x2f */
+	DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 1, 1, 0, 1, 0, 1),  /* 0x30-0x3f */
+	DUK__MKBITS(1, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x40-0x4f */
+	DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x50-0x5f */
+	DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x60-0x6f */
+	DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x70-0x7f */
+};
+
+/* E5.1 Section 15.1.3.2: empty */
+static duk_uint8_t duk__decode_uri_component_reserved_table[16] = {
+	DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x00-0x0f */
+	DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x10-0x1f */
+	DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x20-0x2f */
+	DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x30-0x3f */
+	DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x40-0x4f */
+	DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x50-0x5f */
+	DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x60-0x6f */
+	DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x70-0x7f */
+};
+
+#ifdef DUK_USE_SECTION_B
+/* E5.1 Section B.2.2, step 7. */
+static duk_uint8_t duk__escape_unescaped_table[16] = {
+	DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x00-0x0f */
+	DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x10-0x1f */
+	DUK__MKBITS(0, 0, 0, 0, 0, 0, 0, 0), DUK__MKBITS(0, 0, 1, 1, 0, 1, 1, 1),  /* 0x20-0x2f */
+	DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 0, 0, 0, 0, 0, 0),  /* 0x30-0x3f */
+	DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1),  /* 0x40-0x4f */
+	DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 1, 0, 0, 0, 0, 1),  /* 0x50-0x5f */
+	DUK__MKBITS(0, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1),  /* 0x60-0x6f */
+	DUK__MKBITS(1, 1, 1, 1, 1, 1, 1, 1), DUK__MKBITS(1, 1, 1, 0, 0, 0, 0, 0)   /* 0x70-0x7f */
+};
+#endif  /* DUK_USE_SECTION_B */
+
+typedef struct {
+	duk_hthread *thr;
+	duk_hstring *h_str;
+	duk_hbuffer_dynamic *h_buf;
+	duk_uint8_t *p;
+	duk_uint8_t *p_start;
+	duk_uint8_t *p_end;
+} duk__transform_context;
+
+typedef void (*duk__transform_callback)(duk__transform_context *tfm_ctx, void *udata, duk_codepoint_t cp);
+
+/* FIXME: refactor and share with other code */
+static duk_small_int_t duk__decode_hex_escape(duk_uint8_t *p, duk_small_int_t n) {
+	duk_small_int_t ch;
+	duk_small_int_t t = 0;
+
+	while (n > 0) {
+		t = t * 16;
+		ch = (duk_small_int_t) duk_hex_dectab[*p++];
+		if (DUK_LIKELY(ch >= 0)) {
+			t += ch;
+		} else {
+			return -1;
+		}
+		n--;
+	}
+	return t;
+}
+
+static int duk__transform_helper(duk_context *ctx, duk__transform_callback callback, void *udata) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk__transform_context tfm_ctx_alloc;
+	duk__transform_context *tfm_ctx = &tfm_ctx_alloc;
+	duk_codepoint_t cp;
+
+	tfm_ctx->thr = thr;
+
+	tfm_ctx->h_str = duk_to_hstring(ctx, 0);
+	DUK_ASSERT(tfm_ctx->h_str != NULL);
+
+	(void) duk_push_dynamic_buffer(ctx, 0);
+	tfm_ctx->h_buf = (duk_hbuffer_dynamic *) duk_get_hbuffer(ctx, -1);
+	DUK_ASSERT(tfm_ctx->h_buf != NULL);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(tfm_ctx->h_buf));
+
+	tfm_ctx->p_start = DUK_HSTRING_GET_DATA(tfm_ctx->h_str);
+	tfm_ctx->p_end = tfm_ctx->p_start + DUK_HSTRING_GET_BYTELEN(tfm_ctx->h_str);
+	tfm_ctx->p = tfm_ctx->p_start;
+
+	while (tfm_ctx->p < tfm_ctx->p_end) {
+		cp = (duk_codepoint_t) duk_unicode_decode_xutf8_checked(thr, &tfm_ctx->p, tfm_ctx->p_start, tfm_ctx->p_end);
+		callback(tfm_ctx, udata, cp);
+	}
+
+	duk_to_string(ctx, -1);
+	return 1;
+}
+
+static void duk__transform_callback_encode_uri(duk__transform_context *tfm_ctx, void *udata, duk_codepoint_t cp) {
+	duk_uint8_t xutf8_buf[DUK_UNICODE_MAX_XUTF8_LENGTH];
+	duk_uint8_t buf[3];
+	duk_small_int_t len;
+	duk_codepoint_t cp1, cp2;
+	duk_small_int_t i, t;
+	duk_uint8_t *unescaped_table = (duk_uint8_t *) udata;
+
+	if (cp < 0) {
+		goto uri_error;
+	} else if ((cp < 0x80L) && DUK__CHECK_BITMASK(unescaped_table, cp)) {
+		duk_hbuffer_append_byte(tfm_ctx->thr, tfm_ctx->h_buf, (duk_uint8_t) cp);
+		return;
+	} else if (cp >= 0xdc00L && cp <= 0xdfffL) {
+		goto uri_error;
+	} else if (cp >= 0xd800L && cp <= 0xdbffL) {
+		/* Needs lookahead */
+		if (duk_unicode_decode_xutf8(tfm_ctx->thr, &tfm_ctx->p, tfm_ctx->p_start, tfm_ctx->p_end, (duk_ucodepoint_t *) &cp2) == 0) {
+			goto uri_error;
+		}
+		if (!(cp2 >= 0xdc00L && cp2 <= 0xdfffL)) {
+			goto uri_error;
+		}
+		cp1 = cp;
+		cp = ((cp1 - 0xd800L) << 10) + (cp2 - 0xdc00L) + 0x10000L;
+	} else if (cp > 0x10ffffL) {
+		/* Although we can allow non-BMP characters (they'll decode
+		 * back into surrogate pairs), we don't allow extended UTF-8
+		 * characters; they would encode to URIs which won't decode
+		 * back because of strict UTF-8 checks in URI decoding.
+		 * (However, we could just as well allow them here.)
+		 */
+		goto uri_error;
+	} else {
+		/* Non-BMP characters within valid UTF-8 range: encode as is.
+		 * They'll decode back into surrogate pairs.
+		 */
+		;
+	}
+
+	len = duk_unicode_encode_xutf8((duk_ucodepoint_t) cp, xutf8_buf);
+	buf[0] = (duk_uint8_t) '%';
+	for (i = 0; i < len; i++) {
+		t = (int) xutf8_buf[i];
+		buf[1] = (duk_uint8_t) duk_uc_nybbles[t >> 4];
+		buf[2] = (duk_uint8_t) duk_uc_nybbles[t & 0x0f];
+		duk_hbuffer_append_bytes(tfm_ctx->thr, tfm_ctx->h_buf, buf, 3);
+	}
+	return;
+
+ uri_error:
+	DUK_ERROR(tfm_ctx->thr, DUK_ERR_URI_ERROR, "invalid input");
+}
+
+static void duk__transform_callback_decode_uri(duk__transform_context *tfm_ctx, void *udata, duk_codepoint_t cp) {
+	duk_uint8_t *reserved_table = (duk_uint8_t *) udata;
+	duk_small_uint_t utf8_blen;
+	duk_codepoint_t min_cp;
+	duk_small_int_t t;  /* must be signed */
+	duk_small_uint_t i;
+
+	if (cp == (duk_codepoint_t) '%') {
+		duk_uint8_t *p = tfm_ctx->p;
+		duk_size_t left = (duk_size_t) (tfm_ctx->p_end - p);  /* bytes left */
+
+		DUK_DDD(DUK_DDDPRINT("percent encoding, left=%ld", (long) left));
+
+		if (left < 2) {
+			goto uri_error;
+		}
+
+		t = duk__decode_hex_escape(p, 2);
+		DUK_DDD(DUK_DDDPRINT("first byte: %ld", (long) t));
+		if (t < 0) {
+			goto uri_error;
+		}
+
+		if (t < 0x80) {
+			if (DUK__CHECK_BITMASK(reserved_table, t)) {
+				/* decode '%xx' to '%xx' if decoded char in reserved set */
+				DUK_ASSERT(tfm_ctx->p - 1 >= tfm_ctx->p_start);
+				duk_hbuffer_append_bytes(tfm_ctx->thr, tfm_ctx->h_buf, (duk_uint8_t *) (p - 1), 3);
+			} else {
+				duk_hbuffer_append_byte(tfm_ctx->thr, tfm_ctx->h_buf, (duk_uint8_t) t);
+			}
+			tfm_ctx->p += 2;
+			return;
+		}
+
+		/* Decode UTF-8 codepoint from a sequence of hex escapes.  The
+		 * first byte of the sequence has been decoded to 't'.
+		 *
+		 * Note that UTF-8 validation must be strict according to the
+		 * specification: E5.1 Section 15.1.3, decode algorithm step
+		 * 4.d.vii.8.  URIError from non-shortest encodings is also
+		 * specifically noted in the spec.
+		 */
+
+		DUK_ASSERT(t >= 0x80);
+		if (t < 0xc0) {
+			/* continuation byte */
+			goto uri_error;
+		} else if (t < 0xe0) {
+			/* 110x xxxx; 2 bytes */
+			utf8_blen = 2;
+			min_cp = 0x80L;
+			cp = t & 0x1f;
+		} else if (t < 0xf0) {
+			/* 1110 xxxx; 3 bytes */
+			utf8_blen = 3;
+			min_cp = 0x800L;
+			cp = t & 0x0f;
+		} else if (t < 0xf8) {
+			/* 1111 0xxx; 4 bytes */
+			utf8_blen = 4;
+			min_cp = 0x10000L;
+			cp = t & 0x07;
+		} else {
+			/* extended utf-8 not allowed for URIs */
+			goto uri_error;
+		}
+
+		if (left < utf8_blen * 3 - 1) {
+			/* '%xx%xx...%xx', p points to char after first '%' */
+			goto uri_error;
+		}
+
+		p += 3;
+		for (i = 1; i < utf8_blen; i++) {
+			/* p points to digit part ('%xy', p points to 'x') */
+			t = duk__decode_hex_escape(p, 2);
+			DUK_DDD(DUK_DDDPRINT("i=%ld utf8_blen=%ld cp=%ld t=0x%02lx",
+			                     (long) i, (long) utf8_blen, (long) cp, (unsigned long) t));
+			if (t < 0) {
+				goto uri_error;
+			}
+			if ((t & 0xc0) != 0x80) {
+				goto uri_error;
+			}
+			cp = (cp << 6) + (t & 0x3f);
+			p += 3;
+		}
+		p--;  /* p overshoots */
+		tfm_ctx->p = p;
+
+		DUK_DDD(DUK_DDDPRINT("final cp=%ld, min_cp=%ld", (long) cp, (long) min_cp));
+
+		if (cp < min_cp || cp > 0x10ffffL || (cp >= 0xd800L && cp <= 0xdfffL)) {
+			goto uri_error;
+		}
+
+		/* The E5.1 algorithm checks whether or not a decoded codepoint
+		 * is below 0x80 and perhaps may be in the "reserved" set.
+		 * This seems pointless because the single byte UTF-8 case is
+		 * handled separately, and non-shortest encodings are rejected.
+		 * So, 'cp' cannot be below 0x80 here, and thus cannot be in
+		 * the reserved set.
+		 */
+
+		/* utf-8 validation ensures these */
+		DUK_ASSERT(cp >= 0x80L && cp <= 0x10ffffL);
+
+		if (cp >= 0x10000L) {
+			cp -= 0x10000L;
+			DUK_ASSERT(cp < 0x100000L);
+			duk_hbuffer_append_xutf8(tfm_ctx->thr, tfm_ctx->h_buf, (duk_ucodepoint_t) ((cp >> 10) + 0xd800L));
+			duk_hbuffer_append_xutf8(tfm_ctx->thr, tfm_ctx->h_buf, (duk_ucodepoint_t) ((cp & 0x03ffUL) + 0xdc00L));
+		} else {
+			duk_hbuffer_append_xutf8(tfm_ctx->thr, tfm_ctx->h_buf, (duk_ucodepoint_t) cp);
+		}
+	} else {
+		duk_hbuffer_append_xutf8(tfm_ctx->thr, tfm_ctx->h_buf, (duk_ucodepoint_t) cp);
+	}
+	return;
+
+ uri_error:
+	DUK_ERROR(tfm_ctx->thr, DUK_ERR_URI_ERROR, "invalid input");
+}
+
+#ifdef DUK_USE_SECTION_B
+static void duk__transform_callback_escape(duk__transform_context *tfm_ctx, void *udata, duk_codepoint_t cp) {
+	duk_uint8_t buf[6];
+	duk_small_int_t len;
+
+	DUK_UNREF(udata);
+
+	if (cp < 0) {
+		goto esc_error;
+	} else if ((cp < 0x80L) && DUK__CHECK_BITMASK(duk__escape_unescaped_table, cp)) {
+		buf[0] = (duk_uint8_t) cp;
+		len = 1;
+	} else if (cp < 0x100L) {
+		buf[0] = (duk_uint8_t) '%';
+		buf[1] = (duk_uint8_t) duk_uc_nybbles[cp >> 4];
+		buf[2] = (duk_uint8_t) duk_uc_nybbles[cp & 0x0f];
+		len = 3;
+	} else if (cp < 0x10000L) {
+		buf[0] = (duk_uint8_t) '%';
+		buf[1] = (duk_uint8_t) 'u';
+		buf[2] = (duk_uint8_t) duk_uc_nybbles[cp >> 12];
+		buf[3] = (duk_uint8_t) duk_uc_nybbles[(cp >> 8) & 0x0f];
+		buf[4] = (duk_uint8_t) duk_uc_nybbles[(cp >> 4) & 0x0f];
+		buf[5] = (duk_uint8_t) duk_uc_nybbles[cp & 0x0f];
+		len = 6;
+	} else {
+		/* Characters outside BMP cannot be escape()'d.  We could
+		 * encode them as surrogate pairs (for codepoints inside
+		 * valid UTF-8 range, but not extended UTF-8).  Because
+		 * escape() and unescape() are legacy functions, we don't.
+		 */
+		goto esc_error;
+	}
+
+	duk_hbuffer_append_bytes(tfm_ctx->thr, tfm_ctx->h_buf, buf, len);
+	return;
+
+ esc_error:
+	DUK_ERROR(tfm_ctx->thr, DUK_ERR_TYPE_ERROR, "invalid input");
+}
+
+static void duk__transform_callback_unescape(duk__transform_context *tfm_ctx, void *udata, duk_codepoint_t cp) {
+	duk_small_int_t t;
+
+	DUK_UNREF(udata);
+
+	if (cp == (duk_codepoint_t) '%') {
+		duk_uint8_t *p = tfm_ctx->p;
+		duk_size_t left = (duk_size_t) (tfm_ctx->p_end - p);  /* bytes left */
+
+		if (left >= 5 && p[0] == 'u' &&
+		    ((t = duk__decode_hex_escape(p + 1, 4)) >= 0)) {
+			cp = (duk_codepoint_t) t;
+			tfm_ctx->p += 5;
+		} else if (left >= 2 &&
+		           ((t = duk__decode_hex_escape(p, 2)) >= 0)) {
+			cp = (duk_codepoint_t) t;
+			tfm_ctx->p += 2;
+		}
+	}
+
+	duk_hbuffer_append_xutf8(tfm_ctx->thr, tfm_ctx->h_buf, cp);
+}
+#endif  /* DUK_USE_SECTION_B */
+
+/*
+ *  Eval
+ *
+ *  Eval needs to handle both a "direct eval" and an "indirect eval".
+ *  Direct eval handling needs access to the caller's activation so that its
+ *  lexical environment can be accessed.  A direct eval is only possible from
+ *  Ecmascript code; an indirect eval call is possible also from C code.
+ *  When an indirect eval call is made from C code, there may not be a
+ *  calling activation at all which needs careful handling.
+ */
+
+duk_ret_t duk_bi_global_object_eval(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hstring *h;
+	duk_activation *act_caller;
+	duk_activation *act_eval;
+	duk_activation *act;
+	duk_hcompiledfunction *func;
+	duk_hobject *outer_lex_env;
+	duk_hobject *outer_var_env;
+	duk_bool_t this_to_global = 1;
+	duk_small_uint_t comp_flags;
+
+	DUK_ASSERT_TOP(ctx, 1);
+	DUK_ASSERT(thr->callstack_top >= 1);  /* at least this function exists */
+	DUK_ASSERT(((thr->callstack + thr->callstack_top - 1)->flags & DUK_ACT_FLAG_DIRECT_EVAL) == 0 || /* indirect eval */
+	           (thr->callstack_top >= 2));  /* if direct eval, calling activation must exist */
+
+	/*
+	 *  callstack_top - 1 --> this function
+	 *  callstack_top - 2 --> caller (may not exist)
+	 *
+	 *  If called directly from C, callstack_top might be 1.  If calling
+	 *  activation doesn't exist, call must be indirect.
+	 */
+
+	h = duk_get_hstring(ctx, 0);
+	if (!h) {
+		return 1;  /* return arg as-is */
+	}
+
+	/* [ source ] */
+
+	comp_flags = DUK_JS_COMPILE_FLAG_EVAL;
+	act_eval = thr->callstack + thr->callstack_top - 1;    /* this function */
+	if (thr->callstack_top >= 2) {
+		/* Have a calling activation, check for direct eval (otherwise
+		 * assume indirect eval.
+		 */
+		act_caller = thr->callstack + thr->callstack_top - 2;  /* caller */
+		if ((act_caller->flags & DUK_ACT_FLAG_STRICT) &&
+		    (act_eval->flags & DUK_ACT_FLAG_DIRECT_EVAL)) {
+			/* Only direct eval inherits strictness from calling code
+			 * (E5.1 Section 10.1.1).
+			 */
+			comp_flags |= DUK_JS_COMPILE_FLAG_STRICT;
+		}
+	} else {
+		DUK_ASSERT((act_eval->flags & DUK_ACT_FLAG_DIRECT_EVAL) == 0);
+	}
+	act_caller = NULL;  /* avoid dereference after potential callstack realloc */
+	act_eval = NULL;
+
+	duk_push_hstring_stridx(ctx, DUK_STRIDX_INPUT);  /* XXX: copy from caller? */
+	duk_js_compile(thr,
+	               (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h),
+	               (duk_size_t) DUK_HSTRING_GET_BYTELEN(h),
+	               comp_flags);
+	func = (duk_hcompiledfunction *) duk_get_hobject(ctx, -1);
+	DUK_ASSERT(func != NULL);
+	DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION((duk_hobject *) func));
+
+	/* [ source template ] */
+
+	/* E5 Section 10.4.2 */
+	DUK_ASSERT(thr->callstack_top >= 1);
+	act = thr->callstack + thr->callstack_top - 1;  /* this function */
+	if (act->flags & DUK_ACT_FLAG_DIRECT_EVAL) {	
+		DUK_ASSERT(thr->callstack_top >= 2);
+		act = thr->callstack + thr->callstack_top - 2;  /* caller */
+		if (act->lex_env == NULL) {
+			DUK_ASSERT(act->var_env == NULL);
+			DUK_DDD(DUK_DDDPRINT("delayed environment initialization"));
+
+			/* this may have side effects, so re-lookup act */
+			duk_js_init_activation_environment_records_delayed(thr, act);
+			act = thr->callstack + thr->callstack_top - 2;
+		}
+		DUK_ASSERT(act->lex_env != NULL);
+		DUK_ASSERT(act->var_env != NULL);
+
+		this_to_global = 0;
+
+		if (DUK_HOBJECT_HAS_STRICT((duk_hobject *) func)) {
+			duk_hobject *new_env;
+			duk_hobject *act_lex_env;
+
+			DUK_DDD(DUK_DDDPRINT("direct eval call to a strict function -> "
+			                     "var_env and lex_env to a fresh env, "
+			                     "this_binding to caller's this_binding"));
+
+			act = thr->callstack + thr->callstack_top - 2;  /* caller */
+			act_lex_env = act->lex_env;
+			act = NULL;  /* invalidated */
+
+			(void) duk_push_object_helper_proto(ctx,
+			                                    DUK_HOBJECT_FLAG_EXTENSIBLE |
+			                                    DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DECENV),
+			                                    act_lex_env);
+			new_env = duk_require_hobject(ctx, -1);
+			DUK_ASSERT(new_env != NULL);
+			DUK_DDD(DUK_DDDPRINT("new_env allocated: %!iO",
+			                     (duk_heaphdr *) new_env));
+
+			outer_lex_env = new_env;
+			outer_var_env = new_env;
+
+			duk_insert(ctx, 0);  /* stash to bottom of value stack to keep new_env reachable */
+
+			/* compiler's responsibility */
+			DUK_ASSERT(DUK_HOBJECT_HAS_NEWENV((duk_hobject *) func));
+		} else {
+			DUK_DDD(DUK_DDDPRINT("direct eval call to a non-strict function -> "
+			                     "var_env and lex_env to caller's envs, "
+			                     "this_binding to caller's this_binding"));
+
+			outer_lex_env = act->lex_env;
+			outer_var_env = act->var_env;
+
+			/* compiler's responsibility */
+			DUK_ASSERT(!DUK_HOBJECT_HAS_NEWENV((duk_hobject *) func));
+		}
+	} else {
+		DUK_DDD(DUK_DDDPRINT("indirect eval call -> var_env and lex_env to "
+		                     "global object, this_binding to global object"));
+
+		this_to_global = 1;
+		outer_lex_env = thr->builtins[DUK_BIDX_GLOBAL_ENV];
+		outer_var_env = thr->builtins[DUK_BIDX_GLOBAL_ENV];
+	}
+	act = NULL;
+
+	duk_js_push_closure(thr, func, outer_var_env, outer_lex_env);
+
+	/* [ source template closure ] */
+
+	if (this_to_global) {
+		DUK_ASSERT(thr->builtins[DUK_BIDX_GLOBAL] != NULL);
+		duk_push_hobject_bidx(ctx, DUK_BIDX_GLOBAL);
+	} else {
+		duk_tval *tv;
+		DUK_ASSERT(thr->callstack_top >= 2);
+		act = thr->callstack + thr->callstack_top - 2;  /* caller */
+		tv = thr->valstack + act->idx_bottom - 1;  /* this is just beneath bottom */
+		DUK_ASSERT(tv >= thr->valstack);
+		duk_push_tval(ctx, tv);
+	}
+
+	DUK_DDD(DUK_DDDPRINT("eval -> lex_env=%!iO, var_env=%!iO, this_binding=%!T",
+	                     (duk_heaphdr *) outer_lex_env,
+	                     (duk_heaphdr *) outer_var_env,
+	                     (duk_tval *) duk_get_tval(ctx, -1)));
+
+	/* [ source template closure this ] */
+
+	duk_call_method(ctx, 0);
+
+	/* [ source template result ] */
+
+	return 1;
+}
+
+/*
+ *  Parsing of ints and floats
+ */
+
+duk_ret_t duk_bi_global_object_parse_int(duk_context *ctx) {
+	duk_bool_t strip_prefix;
+	duk_int32_t radix;
+	duk_small_uint_t s2n_flags;
+
+	DUK_ASSERT_TOP(ctx, 2);
+	duk_to_string(ctx, 0);
+
+	strip_prefix = 1;
+	radix = duk_to_int32(ctx, 1);
+	if (radix != 0) {
+		if (radix < 2 || radix > 36) {
+			goto ret_nan;
+		}
+		/* For octal, setting strip_prefix=0 is not necessary, as zero
+		 * is tolerated anyway:
+		 *
+		 *   parseInt('123', 8) === parseInt('0123', 8)     with or without strip_prefix
+		 *   parseInt('123', 16) === parseInt('0x123', 16)  requires strip_prefix = 1
+		 */
+		if (radix != 16) {
+			strip_prefix = 0;
+		}
+	} else {
+		radix = 10;
+	}
+
+	s2n_flags = DUK_S2N_FLAG_TRIM_WHITE |
+	            DUK_S2N_FLAG_ALLOW_GARBAGE |
+	            DUK_S2N_FLAG_ALLOW_PLUS |
+	            DUK_S2N_FLAG_ALLOW_MINUS |
+	            DUK_S2N_FLAG_ALLOW_LEADING_ZERO |
+#ifdef DUK_USE_OCTAL_SUPPORT
+	            (strip_prefix ? (DUK_S2N_FLAG_ALLOW_AUTO_HEX_INT | DUK_S2N_FLAG_ALLOW_AUTO_OCT_INT) : 0)
+#else
+	            (strip_prefix ? DUK_S2N_FLAG_ALLOW_AUTO_HEX_INT : 0)
+#endif
+	            ;
+
+	duk_dup(ctx, 0);
+	duk_numconv_parse(ctx, radix, s2n_flags);
+	return 1;
+
+ ret_nan:
+	duk_push_nan(ctx);
+	return 1;
+}
+
+duk_ret_t duk_bi_global_object_parse_float(duk_context *ctx) {
+	duk_small_uint_t s2n_flags;
+	duk_int32_t radix;
+
+	DUK_ASSERT_TOP(ctx, 1);
+	duk_to_string(ctx, 0);
+
+	radix = 10;
+
+	/* XXX: check flags */
+	s2n_flags = DUK_S2N_FLAG_TRIM_WHITE |
+	            DUK_S2N_FLAG_ALLOW_EXP |
+	            DUK_S2N_FLAG_ALLOW_GARBAGE |
+	            DUK_S2N_FLAG_ALLOW_PLUS |
+	            DUK_S2N_FLAG_ALLOW_MINUS |
+	            DUK_S2N_FLAG_ALLOW_INF |
+	            DUK_S2N_FLAG_ALLOW_FRAC |
+	            DUK_S2N_FLAG_ALLOW_NAKED_FRAC |
+	            DUK_S2N_FLAG_ALLOW_EMPTY_FRAC |
+	            DUK_S2N_FLAG_ALLOW_LEADING_ZERO;
+
+	duk_numconv_parse(ctx, radix, s2n_flags);
+	return 1;
+}
+
+/*
+ *  Number checkers
+ */
+
+duk_ret_t duk_bi_global_object_is_nan(duk_context *ctx) {
+	duk_double_t d = duk_to_number(ctx, 0);
+	duk_push_boolean(ctx, DUK_ISNAN(d));
+	return 1;
+}
+
+duk_ret_t duk_bi_global_object_is_finite(duk_context *ctx) {
+	duk_double_t d = duk_to_number(ctx, 0);
+	duk_push_boolean(ctx, DUK_ISFINITE(d));
+	return 1;
+}
+
+/*
+ *  URI handling
+ */
+
+duk_ret_t duk_bi_global_object_decode_uri(duk_context *ctx) {
+	return duk__transform_helper(ctx, duk__transform_callback_decode_uri, (void *) duk__decode_uri_reserved_table);
+}
+
+duk_ret_t duk_bi_global_object_decode_uri_component(duk_context *ctx) {
+	return duk__transform_helper(ctx, duk__transform_callback_decode_uri, (void *) duk__decode_uri_component_reserved_table);
+}
+
+duk_ret_t duk_bi_global_object_encode_uri(duk_context *ctx) {
+	return duk__transform_helper(ctx, duk__transform_callback_encode_uri, (void *) duk__encode_uriunescaped_table);
+}
+
+duk_ret_t duk_bi_global_object_encode_uri_component(duk_context *ctx) {
+	return duk__transform_helper(ctx, duk__transform_callback_encode_uri, (void *) duk__encode_uricomponent_unescaped_table);
+}
+
+#ifdef DUK_USE_SECTION_B
+duk_ret_t duk_bi_global_object_escape(duk_context *ctx) {
+	return duk__transform_helper(ctx, duk__transform_callback_escape, (void *) NULL);
+}
+
+duk_ret_t duk_bi_global_object_unescape(duk_context *ctx) {
+	return duk__transform_helper(ctx, duk__transform_callback_unescape, (void *) NULL);
+}
+#else  /* DUK_USE_SECTION_B */
+duk_ret_t duk_bi_global_object_escape(duk_context *ctx) {
+	DUK_UNREF(ctx);
+	return DUK_RET_UNSUPPORTED_ERROR;
+}
+
+duk_ret_t duk_bi_global_object_unescape(duk_context *ctx) {
+	DUK_UNREF(ctx);
+	return DUK_RET_UNSUPPORTED_ERROR;
+}
+#endif  /* DUK_USE_SECTION_B */
+
+#ifdef DUK_USE_BROWSER_LIKE
+#ifdef DUK_USE_FILE_IO
+static duk_ret_t duk__print_alert_helper(duk_context *ctx, duk_file *f_out) {
+	duk_idx_t nargs;
+	duk_idx_t i;
+	const char *str;
+	duk_size_t len;
+	char nl = '\n';
+
+	/* If argument count is 1 and first argument is a buffer, write the buffer
+	 * as raw data into the file without a newline; this allows exact control
+	 * over stdout/stderr without an additional entrypoint (useful for now).
+	 */
+
+	nargs = duk_get_top(ctx);
+	if (nargs == 1 && duk_is_buffer(ctx, 0)) {
+		const char *buf = NULL;
+		duk_size_t sz = 0;
+		buf = (const char *) duk_get_buffer(ctx, 0, &sz);
+		if (buf && sz > 0) {
+			DUK_FWRITE(buf, 1, sz, f_out);
+		}
+		goto flush;
+	}
+
+	/* XXX: What are the best semantics / specification for print()?
+	 * Now apply ToString() to arguments and join with a single space.
+	 */
+	/* XXX: ToString() coerce inplace instead? */
+
+	if (nargs > 0) {
+		for (i = 0; i < nargs; i++) {
+			if (i != 0) {
+				duk_push_hstring_stridx(ctx, DUK_STRIDX_SPACE);
+			}
+			duk_dup(ctx, i);
+			duk_to_string(ctx, -1);
+		}
+
+		duk_concat(ctx, 2 * nargs - 1);
+
+		str = duk_get_lstring(ctx, -1, &len);
+		if (str) {
+			DUK_FWRITE(str, 1, len, f_out);
+		}
+	}
+
+	DUK_FWRITE((const char *) &nl, 1, 1, f_out);
+
+ flush:
+	DUK_FFLUSH(f_out);
+	return 0;
+}
+
+duk_ret_t duk_bi_global_object_print(duk_context *ctx) {
+	return duk__print_alert_helper(ctx, DUK_STDOUT);
+}
+
+duk_ret_t duk_bi_global_object_alert(duk_context *ctx) {
+	return duk__print_alert_helper(ctx, DUK_STDERR);
+}
+#else  /* DUK_USE_FILE_IO */
+/* Supported but no file I/O -> silently ignore, no error */
+duk_ret_t duk_bi_global_object_print(duk_context *ctx) {
+	DUK_UNREF(ctx);
+	return 0;
+}
+
+duk_ret_t duk_bi_global_object_alert(duk_context *ctx) {
+	DUK_UNREF(ctx);
+	return 0;
+}
+#endif  /* DUK_USE_FILE_IO */
+#else  /* DUK_USE_BROWSER_LIKE */
+duk_ret_t duk_bi_global_object_print(duk_context *ctx) {
+	DUK_UNREF(ctx);
+	return DUK_RET_UNSUPPORTED_ERROR;
+}
+
+duk_ret_t duk_bi_global_object_alert(duk_context *ctx) {
+	DUK_UNREF(ctx);
+	return DUK_RET_UNSUPPORTED_ERROR;
+}
+#endif  /* DUK_USE_BROWSER_LIKE */
+
+/*
+ *  CommonJS require() and modules support
+ */
+
+#if defined(DUK_USE_COMMONJS_MODULES)
+static void duk__bi_global_resolve_module_id(duk_context *ctx, const char *req_id, const char *mod_id) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_size_t mod_id_len;
+	duk_size_t req_id_len;
+	duk_uint8_t buf_in[DUK_BI_COMMONJS_MODULE_ID_LIMIT];
+	duk_uint8_t buf_out[DUK_BI_COMMONJS_MODULE_ID_LIMIT];
+	duk_uint8_t *p;
+	duk_uint8_t *q;
+
+	DUK_ASSERT(req_id != NULL);
+	/* mod_id may be NULL */
+	DUK_ASSERT(sizeof(buf_out) >= sizeof(buf_in));  /* bound checking requires this */
+
+	/*
+	 *  A few notes on the algorithm:
+	 *
+	 *    - Terms are not allowed to begin with a period unless the term
+	 *      is either '.' or '..'.  This simplifies implementation (and
+	 *      is within CommonJS modules specification).
+	 *
+	 *    - There are few output bound checks here.  This is on purpose:
+	 *      we check the input length and rely on the output never being
+	 *      longer than the input, so we cannot run out of output space.
+	 *
+	 *    - Non-ASCII characters are processed as individual bytes and
+	 *      need no special treatment.  However, U+0000 terminates the
+	 *      algorithm; this is not an issue because U+0000 is not a
+	 *      desirable term character anyway.
+	 */
+
+	/*
+	 *  Set up the resolution input which is the requested ID directly
+	 *  (if absolute or no current module path) or with current module
+	 *  ID prepended (if relative and current module path exists).
+	 */
+
+	req_id_len = DUK_STRLEN(req_id);
+	if (mod_id != NULL && req_id[0] == '.') {
+		mod_id_len = DUK_STRLEN(mod_id);
+		if (mod_id_len + 1 + req_id_len + 1 >= sizeof(buf_in)) {
+			DUK_DD(DUK_DDPRINT("resolve error: current and requested module ID don't fit into resolve input buffer"));
+			goto resolve_error;
+		}
+		(void) DUK_SNPRINTF((char *) buf_in, sizeof(buf_in), "%s/%s", (const char *) mod_id, (const char *) req_id);
+	} else {
+		if (req_id_len + 1 >= sizeof(buf_in)) {
+			DUK_DD(DUK_DDPRINT("resolve error: requested module ID doesn't fit into resolve input buffer"));
+			goto resolve_error;
+		}
+		(void) DUK_SNPRINTF((char *) buf_in, sizeof(buf_in), "%s", (const char *) req_id);
+	}
+	buf_in[sizeof(buf_in) - 1] = (duk_uint8_t) 0;
+
+	DUK_DDD(DUK_DDDPRINT("input module id: '%s'", (const char *) buf_in));
+
+	/*
+	 *  Resolution loop.  At the top of the loop we're expecting a valid
+	 *  term: '.', '..', or a non-empty identifier not starting with a period.
+	 */
+
+	p = buf_in;
+	q = buf_out;
+	for (;;) {
+		duk_uint_fast8_t c;
+
+		/* Here 'p' always points to the start of a term. */
+		DUK_DDD(DUK_DDDPRINT("resolve loop top: p -> '%s', q=%p, buf_out=%p",
+		                     (const char *) p, (void *) q, (void *) buf_out));
+
+		c = *p++;
+		if (DUK_UNLIKELY(c == 0)) {
+			DUK_DD(DUK_DDPRINT("resolve error: requested ID must end with a non-empty term"));
+			goto resolve_error;
+		} else if (DUK_UNLIKELY(c == '.')) {
+			c = *p++;
+			if (c == '/') {
+				/* Term was '.' and is eaten entirely (including dup slashes). */
+				goto eat_dup_slashes;
+			}
+			if (c == '.' && *p == '/') {
+				/* Term was '..', backtrack resolved name by one component.
+				 *  q[-1] = previous slash (or beyond start of buffer)
+				 *  q[-2] = last char of previous component (or beyond start of buffer)
+				 */
+				p++;  /* eat (first) input slash */
+				DUK_ASSERT(q >= buf_out);
+				if (q == buf_out) {
+					DUK_DD(DUK_DDPRINT("resolve error: term was '..' but nothing to backtrack"));
+					goto resolve_error;
+				}
+				DUK_ASSERT(*(q - 1) == '/');
+				q--;  /* backtrack to last output slash */
+				for (;;) {
+					/* Backtrack to previous slash or start of buffer. */
+					DUK_ASSERT(q >= buf_out);
+					if (q == buf_out) {
+						break;
+					}
+					if (*(q - 1) == '/') {
+						break;
+					}
+					q--;
+				}
+				goto eat_dup_slashes;
+			}
+			DUK_DD(DUK_DDPRINT("resolve error: term begins with '.' but is not '.' or '..' (not allowed now)"));
+			goto resolve_error;
+		} else if (DUK_UNLIKELY(c == '/')) {
+			/* e.g. require('/foo'), empty terms not allowed */
+			DUK_DD(DUK_DDPRINT("resolve error: empty term (not allowed now)"));
+			goto resolve_error;
+		} else {
+			for (;;) {
+				/* Copy term name until end or '/'. */
+				*q++ = c;
+				c = *p++;
+				if (DUK_UNLIKELY(c == 0)) {
+					goto loop_done;
+				} else if (DUK_UNLIKELY(c == '/')) {
+					*q++ = '/';
+					break;
+				} else {
+					/* write on next loop */
+				}
+			}
+		}
+
+	 eat_dup_slashes:
+		for (;;) {
+			/* eat dup slashes */
+			c = *p;
+			if (DUK_LIKELY(c != '/')) {
+				break;
+			}
+			p++;
+		}
+	}
+ loop_done:
+
+	duk_push_lstring(ctx, (const char *) buf_out, (size_t) (q - buf_out));
+	return;
+
+ resolve_error:
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "cannot resolve module id: %s", (const char *) req_id);
+}
+#endif  /* DUK_USE_COMMONJS_MODULES */
+
+#if defined(DUK_USE_COMMONJS_MODULES)
+duk_ret_t duk_bi_global_object_require(duk_context *ctx) {
+	const char *str_req_id;  /* requested identifier */
+	const char *str_mod_id;  /* require.id of current module */
+
+	/* NOTE: we try to minimize code size by avoiding unnecessary pops,
+	 * so the stack looks a bit cluttered in this function.  DUK_ASSERT_TOP()
+	 * assertions are used to ensure stack configuration is correct at each
+	 * step.
+	 */
+
+	/*
+	 *  Resolve module identifier into canonical absolute form.
+	 */
+
+	str_req_id = duk_require_string(ctx, 0);
+	duk_push_current_function(ctx);
+	duk_get_prop_stridx(ctx, -1, DUK_STRIDX_ID);
+	str_mod_id = duk_get_string(ctx, 2);  /* ignore non-strings */
+	DUK_DDD(DUK_DDDPRINT("resolve module id: requested=%!T, currentmodule=%!T",
+	                     (duk_tval *) duk_get_tval(ctx, 0),
+	                     (duk_tval *) duk_get_tval(ctx, 2)));
+	duk__bi_global_resolve_module_id(ctx, str_req_id, str_mod_id);
+	str_req_id = NULL;
+	str_mod_id = NULL;
+	DUK_DDD(DUK_DDDPRINT("resolved module id: requested=%!T, currentmodule=%!T, result=%!T",
+	                     (duk_tval *) duk_get_tval(ctx, 0),
+	                     (duk_tval *) duk_get_tval(ctx, 2),
+	                     (duk_tval *) duk_get_tval(ctx, 3)));
+
+	/* [ requested_id require require.id resolved_id ] */
+	DUK_ASSERT_TOP(ctx, 4);
+
+	/*
+	 *  Cached module check.
+	 *
+	 *  If module has been loaded or its loading has already begun without
+	 *  finishing, return the same cached value ('exports').  The value is
+	 *  registered when module load starts so that circular references can
+	 *  be supported to some extent.
+	 */
+
+	/* [ requested_id require require.id resolved_id ] */
+	DUK_ASSERT_TOP(ctx, 4);
+
+	duk_push_hobject_bidx(ctx, DUK_BIDX_DUKTAPE);
+	duk_get_prop_stridx(ctx, 4, DUK_STRIDX_MOD_LOADED);  /* Duktape.modLoaded */
+	(void) duk_require_hobject(ctx, 5);
+
+	/* [ requested_id require require.id resolved_id Duktape Duktape.modLoaded ] */
+	DUK_ASSERT_TOP(ctx, 6);
+
+	duk_dup(ctx, 3);
+	if (duk_get_prop(ctx, 5)) {
+		/* [ requested_id require require.id resolved_id Duktape Duktape.modLoaded Duktape.modLoaded[id] ] */
+		DUK_DD(DUK_DDPRINT("module already loaded: %!T",
+		                   (duk_tval *) duk_get_tval(ctx, 3)));
+		return 1;
+	}
+
+	/* [ requested_id require require.id resolved_id Duktape Duktape.modLoaded undefined ] */
+	DUK_ASSERT_TOP(ctx, 7);
+
+	/*
+	 *  Module not loaded (and loading not started previously).
+	 *
+	 *  Create a new require() function with 'id' set to resolved ID
+	 *  of module being loaded.  Also create 'exports' and 'module'
+	 *  tables but don't register exports to the loaded table yet.
+	 *  We don't want to do that unless the user module search callbacks
+	 *  succeeds in finding the module.
+	 */
+
+	DUK_DD(DUK_DDPRINT("module not yet loaded: %!T",
+	                   (duk_tval *) duk_get_tval(ctx, 3)));
+
+	/* Fresh require: require.id is left configurable (but not writable)
+	 * so that is not easy to accidentally tweak it, but it can still be
+	 * done with Object.defineProperty().
+	 *
+	 * XXX: require.id could also be just made non-configurable, as there
+	 * is no practical reason to touch it.
+	 */
+	duk_push_c_function(ctx, duk_bi_global_object_require, 1 /*nargs*/);
+	duk_dup(ctx, 3);
+	duk_def_prop_stridx(ctx, 7, DUK_STRIDX_ID, DUK_PROPDESC_FLAGS_C);  /* a fresh require() with require.id = resolved target module id */
+
+	/* Exports table. */
+	duk_push_object(ctx);
+
+	/* Module table: module.id is non-writable and non-configurable, as
+	 * the CommonJS spec suggests this if possible.
+	 */
+	duk_push_object(ctx);
+	duk_dup(ctx, 3);  /* resolved id: require(id) must return this same module */
+	duk_def_prop_stridx(ctx, 9, DUK_STRIDX_ID, DUK_PROPDESC_FLAGS_NONE);
+
+	/* [ requested_id require require.id resolved_id Duktape Duktape.modLoaded undefined fresh_require exports module ] */
+	DUK_ASSERT_TOP(ctx, 10);
+
+	/*
+	 *  Call user provided module search function and build the wrapped
+	 *  module source code (if necessary).  The module search function
+	 *  can be used to implement pure Ecmacsript, pure C, and mixed
+	 *  Ecmascript/C modules.
+	 *
+	 *  The module search function can operate on the exports table directly
+	 *  (e.g. DLL code can register values to it).  It can also return a
+	 *  string which is interpreted as module source code (if a non-string
+	 *  is returned the module is assumed to be a pure C one).  If a module
+	 *  cannot be found, an error must be thrown by the user callback.
+	 *
+	 *  NOTE: the current arrangement allows C modules to be implemented
+	 *  but since the exports table is registered to Duktape.modLoaded only
+	 *  after the search function returns, circular requires / partially
+	 *  loaded modules don't work for C modules.  This is rarely an issue,
+	 *  as C modules usually simply expose a set of helper functions.
+	 */
+
+	duk_push_string(ctx, "(function(require,exports,module){");
+
+	/* Duktape.modSearch(resolved_id, fresh_require, exports, module). */
+	duk_get_prop_stridx(ctx, 4, DUK_STRIDX_MOD_SEARCH);  /* Duktape.modSearch */
+	duk_dup(ctx, 3);
+	duk_dup(ctx, 7);
+	duk_dup(ctx, 8);
+	duk_dup(ctx, 9);  /* [ ... Duktape.modSearch resolved_id fresh_require exports module ] */
+	duk_call(ctx, 4 /*nargs*/);  /* -> [ ... source ] */
+	DUK_ASSERT_TOP(ctx, 12);
+
+	/* Because user callback did not throw an error, remember exports table. */
+	duk_dup(ctx, 3);
+	duk_dup(ctx, 8);
+	duk_def_prop(ctx, 5, DUK_PROPDESC_FLAGS_EC);  /* Duktape.modLoaded[resolved_id] = exports */
+
+	/* If user callback did not return source code, module loading
+	 * is finished (user callback initialized exports table directly).
+	 */
+	if (!duk_is_string(ctx, 11)) {
+		/* User callback did not return source code, so
+		 * module loading is finished.
+		 */
+		duk_dup(ctx, 8);
+		return 1;
+	}
+
+	/* Finish the wrapped module source. */
+	duk_push_string(ctx, "})");
+	duk_concat(ctx, 3);
+	duk_eval(ctx);
+
+	/* Force 'fileName' property of the module function so that if the
+	 * module creates a logger, the logger name defaults to the module
+	 * name.
+	 */
+	duk_dup(ctx, 3);
+	duk_put_prop_stridx(ctx, -2, DUK_STRIDX_FILE_NAME);
+
+	/* XXX: The module wrapper function is currently anonymous and is shown
+	 * in stack traces.  It would be nice to force it to match the module
+	 * name (perhaps just the cleaned up last term).  At the moment 'name'
+	 * is write protected so we can't change it directly.  Note that we must
+	 * not introduce an actual name binding into the function scope (which
+	 * is usually the case with a named function) because it would affect
+	 * the scope seen by the module and shadow accesses to globals of the
+	 * same name.
+	 */
+
+	/*
+	 *  Call the wrapped module function.
+	 */
+
+	/* [ requested_id require require.id resolved_id Duktape Duktape.modLoaded undefined fresh_require exports module mod_func ] */
+	DUK_ASSERT_TOP(ctx, 11);
+
+	duk_dup(ctx, 8);  /* exports (this binding) */
+	duk_dup(ctx, 7);  /* fresh require (argument) */
+	duk_dup(ctx, 8);  /* exports (argument) */
+	duk_dup(ctx, 9);  /* module (argument) */
+
+	/* [ requested_id require require.id resolved_id Duktape Duktape.modLoaded undefined fresh_require exports module mod_func exports fresh_require exports module ] */
+	DUK_ASSERT_TOP(ctx, 15);
+
+	duk_call_method(ctx, 3 /*nargs*/);
+
+	/* [ requested_id require require.id resolved_id Duktape Duktape.modLoaded undefined fresh_require exports module result(ignored) ] */
+	DUK_ASSERT_TOP(ctx, 11);
+
+	duk_pop_2(ctx);
+	return 1;  /* return exports */
+}
+#else
+duk_ret_t duk_bi_global_object_require(duk_context *ctx) {
+	DUK_UNREF(ctx);
+	return DUK_RET_UNSUPPORTED_ERROR;
+}
+#endif  /* DUK_USE_COMMONJS_MODULES */
+#line 1 "duk_bi_json.c"
+/*
+ *  JSON built-ins.
+ *
+ *  See doc/json.txt.
+ *
+ *  Codepoints are handled as duk_uint_fast32_t to ensure that the full
+ *  unsigned 32-bit range is supported.  This matters to e.g. JX.
+ */
+
+/* include removed: duk_internal.h */
+
+/*
+ *  Local defines and forward declarations.
+ */
+
+static void duk__dec_syntax_error(duk_json_dec_ctx *js_ctx);
+static void duk__dec_eat_white(duk_json_dec_ctx *js_ctx);
+static duk_small_int_t duk__dec_peek(duk_json_dec_ctx *js_ctx);
+static duk_small_int_t duk__dec_get(duk_json_dec_ctx *js_ctx);
+static duk_small_int_t duk__dec_get_nonwhite(duk_json_dec_ctx *js_ctx);
+static duk_uint_fast32_t duk__dec_decode_hex_escape(duk_json_dec_ctx *js_ctx, duk_small_uint_t n);
+static void duk__dec_req_stridx(duk_json_dec_ctx *js_ctx, duk_small_uint_t stridx);
+static void duk__dec_string(duk_json_dec_ctx *js_ctx);
+#ifdef DUK_USE_JX
+static void duk__dec_plain_string(duk_json_dec_ctx *js_ctx);
+static void duk__dec_pointer(duk_json_dec_ctx *js_ctx);
+static void duk__dec_buffer(duk_json_dec_ctx *js_ctx);
+#endif
+static void duk__dec_number(duk_json_dec_ctx *js_ctx);
+static void duk__dec_objarr_entry(duk_json_dec_ctx *js_ctx);
+static void duk__dec_objarr_exit(duk_json_dec_ctx *js_ctx);
+static void duk__dec_object(duk_json_dec_ctx *js_ctx);
+static void duk__dec_array(duk_json_dec_ctx *js_ctx);
+static void duk__dec_value(duk_json_dec_ctx *js_ctx);
+static void duk__dec_reviver_walk(duk_json_dec_ctx *js_ctx);
+
+static void duk__emit_1(duk_json_enc_ctx *js_ctx, duk_uint_fast8_t ch);
+static void duk__emit_2(duk_json_enc_ctx *js_ctx, duk_uint_fast16_t packed_chars);
+static void duk__emit_esc_auto(duk_json_enc_ctx *js_ctx, duk_uint_fast32_t cp);
+static void duk__emit_xutf8(duk_json_enc_ctx *js_ctx, duk_uint_fast32_t cp);
+static void duk__emit_hstring(duk_json_enc_ctx *js_ctx, duk_hstring *h);
+#if defined(DUK_USE_JX) || defined(DUK_USE_JC)
+static void duk__emit_cstring(duk_json_enc_ctx *js_ctx, const char *p);
+#endif
+static void duk__emit_stridx(duk_json_enc_ctx *js_ctx, duk_small_uint_t stridx);
+static duk_bool_t duk__enc_key_quotes_needed(duk_hstring *h_key);
+static void duk__enc_quote_string(duk_json_enc_ctx *js_ctx, duk_hstring *h_str);
+static void duk__enc_objarr_entry(duk_json_enc_ctx *js_ctx, duk_hstring **h_stepback, duk_hstring **h_indent, duk_idx_t *entry_top);
+static void duk__enc_objarr_exit(duk_json_enc_ctx *js_ctx, duk_hstring **h_stepback, duk_hstring **h_indent, duk_idx_t *entry_top);
+static void duk__enc_object(duk_json_enc_ctx *js_ctx);
+static void duk__enc_array(duk_json_enc_ctx *js_ctx);
+static duk_bool_t duk__enc_value1(duk_json_enc_ctx *js_ctx, duk_idx_t idx_holder);
+static void duk__enc_value2(duk_json_enc_ctx *js_ctx);
+static duk_bool_t duk__enc_allow_into_proplist(duk_tval *tv);
+
+/*
+ *  Parsing implementation.
+ *
+ *  JSON lexer is now separate from duk_lexer.c because there are numerous
+ *  small differences making it difficult to share the lexer.
+ *
+ *  The parser here works with raw bytes directly; this works because all
+ *  JSON delimiters are ASCII characters.  Invalid xUTF-8 encoded values
+ *  inside strings will be passed on without normalization; this is not a
+ *  compliance concern because compliant inputs will always be valid
+ *  CESU-8 encodings.
+ */
+
+static void duk__dec_syntax_error(duk_json_dec_ctx *js_ctx) {
+	/* Shared handler to minimize parser size.  Cause will be
+	 * hidden, unfortunately.
+	 */
+	DUK_ERROR(js_ctx->thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_INVALID_JSON);
+}
+
+static void duk__dec_eat_white(duk_json_dec_ctx *js_ctx) {
+	duk_small_uint_t t;
+	for (;;) {
+		if (js_ctx->p >= js_ctx->p_end) {
+			break;
+		}
+		t = (*js_ctx->p);
+		if (!(t == 0x20 || t == 0x0a || t == 0x0d || t == 0x09)) {
+			break;
+		}
+		js_ctx->p++;
+	}
+}
+
+static duk_small_int_t duk__dec_peek(duk_json_dec_ctx *js_ctx) {
+	if (js_ctx->p >= js_ctx->p_end) {
+		return -1;
+	} else {
+		return (duk_small_int_t) (*js_ctx->p);
+	}
+}
+
+static duk_small_int_t duk__dec_get(duk_json_dec_ctx *js_ctx) {
+	/* Multiple EOFs will now be supplied to the caller.  This could also be
+	 * changed so that reading the second EOF would cause an error automatically.
+	 */
+	if (js_ctx->p >= js_ctx->p_end) {
+		return -1;
+	} else {
+		return (duk_small_int_t) (*js_ctx->p++);
+	}
+}
+
+static duk_small_int_t duk__dec_get_nonwhite(duk_json_dec_ctx *js_ctx) {
+	duk__dec_eat_white(js_ctx);
+	return duk__dec_get(js_ctx);
+}
+
+/* For JX, expressing the whole unsigned 32-bit range matters. */
+static duk_uint_fast32_t duk__dec_decode_hex_escape(duk_json_dec_ctx *js_ctx, duk_small_uint_t n) {
+	duk_small_uint_t i;
+	duk_uint_fast32_t res = 0;
+	duk_small_int_t x;
+
+	for (i = 0; i < n; i++) {
+		/* FIXME: share helper from lexer; duk_lexer.c / hexval(). */
+
+		x = duk__dec_get(js_ctx);
+		DUK_ASSERT((x >= 0 && x <= 0xff) || (x == -1));
+
+		DUK_DDD(DUK_DDDPRINT("decode_hex_escape: i=%ld, n=%ld, res=%ld, x=%ld",
+		                     (long) i, (long) n, (long) res, (long) x));
+
+		/* x == -1 will map to 0xff, dectab returns -1 which causes syntax_error */
+		x = duk_hex_dectab[x & 0xff];
+		if (DUK_LIKELY(x >= 0)) {
+			res = (res * 16) + x;
+		} else {
+			/* catches EOF and invalid digits */
+			goto syntax_error;
+		}
+	}
+
+	DUK_DDD(DUK_DDDPRINT("final hex decoded value: %ld", (long) res));
+	return res;
+
+ syntax_error:
+	duk__dec_syntax_error(js_ctx);
+	DUK_UNREACHABLE();
+	return 0;
+}
+
+static void duk__dec_req_stridx(duk_json_dec_ctx *js_ctx, duk_small_uint_t stridx) {
+	duk_hstring *h;
+	duk_uint8_t *p;
+	duk_uint8_t *p_end;
+	duk_small_int_t x;
+
+	/* First character has already been eaten and checked by the caller. */
+
+	DUK_ASSERT_DISABLE(stridx >= 0);  /* unsigned */
+	DUK_ASSERT(stridx < DUK_HEAP_NUM_STRINGS);
+	h = DUK_HTHREAD_GET_STRING(js_ctx->thr, stridx);
+	DUK_ASSERT(h != NULL);
+
+	p = (duk_uint8_t *) DUK_HSTRING_GET_DATA(h);
+	p_end = ((duk_uint8_t *) DUK_HSTRING_GET_DATA(h)) +
+	        DUK_HSTRING_GET_BYTELEN(h);
+
+	DUK_ASSERT(*(js_ctx->p - 1) == *p);  /* first character has been matched */
+	p++;  /* first char */
+
+	while (p < p_end) {
+		x = duk__dec_get(js_ctx);
+		if ((duk_small_int_t) (*p) != x) {
+			/* catches EOF */
+			goto syntax_error;
+		}
+		p++;
+	}
+
+	return;
+
+ syntax_error:
+	duk__dec_syntax_error(js_ctx);
+	DUK_UNREACHABLE();
+}
+
+static void duk__dec_string(duk_json_dec_ctx *js_ctx) {
+	duk_hthread *thr = js_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_hbuffer_dynamic *h_buf;
+	duk_small_int_t x;
+	duk_uint_fast32_t cp;
+
+	/* '"' was eaten by caller */
+
+	/* Note that we currently parse -bytes-, not codepoints.
+	 * All non-ASCII extended UTF-8 will encode to bytes >= 0x80,
+	 * so they'll simply pass through (valid UTF-8 or not).
+	 */
+
+	duk_push_dynamic_buffer(ctx, 0);
+	h_buf = (duk_hbuffer_dynamic *) duk_get_hbuffer(ctx, -1);
+	DUK_ASSERT(h_buf != NULL);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(h_buf));
+
+	for (;;) {
+		x = duk__dec_get(js_ctx);
+		if (x == DUK_ASC_DOUBLEQUOTE) {
+			break;
+		} else if (x == DUK_ASC_BACKSLASH) {
+			/* EOF (-1) will be cast to an unsigned value first
+			 * and then re-cast for the switch.  In any case, it
+			 * will match the default case (syntax error).
+			 */
+			cp = (duk_uint_fast32_t) duk__dec_get(js_ctx);
+			switch ((int) cp) {
+			case DUK_ASC_BACKSLASH: break;
+			case DUK_ASC_DOUBLEQUOTE: break;
+			case DUK_ASC_SLASH: break;
+			case DUK_ASC_LC_T: cp = 0x09; break;
+			case DUK_ASC_LC_N: cp = 0x0a; break;
+			case DUK_ASC_LC_R: cp = 0x0d; break;
+			case DUK_ASC_LC_F: cp = 0x0c; break;
+			case DUK_ASC_LC_B: cp = 0x08; break;
+			case DUK_ASC_LC_U: {
+				cp = duk__dec_decode_hex_escape(js_ctx, 4);
+				break;
+			}
+#ifdef DUK_USE_JX
+			case DUK_ASC_UC_U: {
+				if (js_ctx->flag_ext_custom) {
+					cp = duk__dec_decode_hex_escape(js_ctx, 8);
+				} else {
+					goto syntax_error;
+				}
+				break;
+			}
+			case DUK_ASC_LC_X: {
+				if (js_ctx->flag_ext_custom) {
+					cp = duk__dec_decode_hex_escape(js_ctx, 2);
+				} else {
+					goto syntax_error;
+				}
+				break;
+			}
+#endif  /* DUK_USE_JX */
+			default:
+				/* catches EOF (-1) */
+				goto syntax_error;
+			}
+			duk_hbuffer_append_xutf8(thr, h_buf, (duk_uint32_t) cp);
+		} else if (x < 0x20) {
+			/* catches EOF (-1) */
+			goto syntax_error;
+		} else {
+			duk_hbuffer_append_byte(thr, h_buf, (duk_uint8_t) x);
+		}
+	}
+
+	duk_to_string(ctx, -1);
+
+	/* [ ... str ] */
+
+	return;
+
+ syntax_error:
+	duk__dec_syntax_error(js_ctx);
+	DUK_UNREACHABLE();
+}
+
+#ifdef DUK_USE_JX
+/* Decode a plain string consisting entirely of identifier characters.
+ * Used to parse plain keys (e.g. "foo: 123").
+ */
+static void duk__dec_plain_string(duk_json_dec_ctx *js_ctx) {
+	duk_hthread *thr = js_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_uint8_t *p;
+	duk_small_int_t x;
+
+	/* Caller has already eaten the first char so backtrack one byte. */
+
+	js_ctx->p--;  /* safe */
+	p = js_ctx->p;
+
+	/* Here again we parse bytes, and non-ASCII UTF-8 will cause end of
+	 * parsing (which is correct except if there are non-shortest encodings).
+	 * There is also no need to check explicitly for end of input buffer as
+	 * the input is NUL padded and NUL will exit the parsing loop.
+	 *
+	 * Because no unescaping takes place, we can just scan to the end of the
+	 * plain string and intern from the input buffer.
+	 */
+
+	for (;;) {
+		x = *p;
+
+		/* There is no need to check the first character specially here
+		 * (i.e. reject digits): the caller only accepts valid initial
+		 * characters and won't call us if the first character is a digit.
+		 * This also ensures that the plain string won't be empty.
+		 */
+
+		if (!duk_unicode_is_identifier_part((duk_codepoint_t) x)) {
+			break;
+		}
+		p++;
+	}
+
+	duk_push_lstring(ctx, (const char *) js_ctx->p, (duk_size_t) (p - js_ctx->p));
+	js_ctx->p = p;
+
+	/* [ ... str ] */
+}
+#endif  /* DUK_USE_JX */
+
+#ifdef DUK_USE_JX
+static void duk__dec_pointer(duk_json_dec_ctx *js_ctx) {
+	duk_hthread *thr = js_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_uint8_t *p;
+	duk_small_int_t x;
+	void *voidptr;
+
+	/* Caller has already eaten the first character ('(') which we don't need. */
+
+	p = js_ctx->p;
+
+	for (;;) {
+		x = *p;
+
+		/* Assume that the native representation never contains a closing
+		 * parenthesis.
+		 */
+
+		if (x == DUK_ASC_RPAREN) {
+			break;
+		} else if (x <= 0) {
+			/* NUL term or -1 (EOF), NUL check would suffice */
+			goto syntax_error;
+		}
+		p++;
+	}
+
+	/* There is no need to NUL delimit the sscanf() call: trailing garbage is
+	 * ignored and there is always a NUL terminator which will force an error
+	 * if no error is encountered before it.  It's possible that the scan
+	 * would scan further than between [js_ctx->p,p[ though and we'd advance
+	 * by less than the scanned value.
+	 *
+	 * Because pointers are platform specific, a failure to scan a pointer
+	 * results in a null pointer which is a better placeholder than a missing
+	 * value or an error.
+	 */
+
+	voidptr = NULL;
+	(void) DUK_SSCANF((const char *) js_ctx->p, DUK_STR_FMT_PTR, &voidptr);
+	duk_push_pointer(ctx, voidptr);
+	js_ctx->p = p + 1;  /* skip ')' */
+
+	/* [ ... ptr ] */
+
+	return;
+
+ syntax_error:
+	duk__dec_syntax_error(js_ctx);
+	DUK_UNREACHABLE();
+}
+#endif  /* DUK_USE_JX */
+
+#ifdef DUK_USE_JX
+static void duk__dec_buffer(duk_json_dec_ctx *js_ctx) {
+	duk_hthread *thr = js_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_uint8_t *p;
+	duk_small_int_t x;
+
+	/* Caller has already eaten the first character ('|') which we don't need. */
+
+	p = js_ctx->p;
+
+	for (;;) {
+		x = *p;
+
+		/* This loop intentionally does not ensure characters are valid
+		 * ([0-9a-fA-F]) because the hex decode call below will do that.
+		 */
+		if (x == DUK_ASC_PIPE) {
+			break;
+		} else if (x <= 0) {
+			/* NUL term or -1 (EOF), NUL check would suffice */
+			goto syntax_error;
+		}
+		p++;
+	}
+
+	duk_push_lstring(ctx, (const char *) js_ctx->p, (duk_size_t) (p - js_ctx->p));
+	duk_hex_decode(ctx, -1);
+	js_ctx->p = p + 1;  /* skip '|' */
+
+	/* [ ... buf ] */
+
+	return;
+
+ syntax_error:
+	duk__dec_syntax_error(js_ctx);
+	DUK_UNREACHABLE();
+}
+#endif  /* DUK_USE_JX */
+
+/* Parse a number, other than NaN or +/- Infinity */
+static void duk__dec_number(duk_json_dec_ctx *js_ctx) {
+	duk_context *ctx = (duk_context *) js_ctx->thr;
+	duk_uint8_t *p_start;
+	duk_small_int_t x;
+	duk_small_uint_t s2n_flags;
+
+	DUK_DDD(DUK_DDDPRINT("parse_number"));
+
+	/* Caller has already eaten the first character so backtrack one
+	 * byte.  This is correct because the first character is either
+	 * '-' or a digit (i.e. an ASCII character).
+	 */
+
+	js_ctx->p--;  /* safe */
+	p_start = js_ctx->p;
+
+	/* First pass parse is very lenient (e.g. allows '1.2.3') and extracts a
+	 * string for strict number parsing.
+	 */
+
+	for (;;) {
+		x = duk__dec_peek(js_ctx);
+
+		DUK_DDD(DUK_DDDPRINT("parse_number: p_start=%p, p=%p, p_end=%p, x=%ld",
+		                     (void *) p_start, (void *) js_ctx->p,
+		                     (void *) js_ctx->p_end, (long) x));
+
+		if (!((x >= DUK_ASC_0 && x <= DUK_ASC_9) ||
+		      (x == DUK_ASC_PERIOD || x == DUK_ASC_LC_E ||
+		       x == DUK_ASC_UC_E || x == DUK_ASC_MINUS))) {
+			break;
+		}
+
+		js_ctx->p++;  /* safe, because matched char */
+	}
+
+	DUK_ASSERT(js_ctx->p > p_start);
+	duk_push_lstring(ctx, (const char *) p_start, (duk_size_t) (js_ctx->p - p_start));
+
+	s2n_flags = DUK_S2N_FLAG_ALLOW_EXP |
+	            DUK_S2N_FLAG_ALLOW_MINUS |  /* but don't allow leading plus */
+	            DUK_S2N_FLAG_ALLOW_FRAC;
+
+	DUK_DDD(DUK_DDDPRINT("parse_number: string before parsing: %!T",
+	                     (duk_tval *) duk_get_tval(ctx, -1)));
+	duk_numconv_parse(ctx, 10 /*radix*/, s2n_flags);
+	if (duk_is_nan(ctx, -1)) {
+		DUK_ERROR(js_ctx->thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_INVALID_NUMBER);
+	}
+	DUK_ASSERT(duk_is_number(ctx, -1));
+	DUK_DDD(DUK_DDDPRINT("parse_number: final number: %!T",
+	                     (duk_tval *) duk_get_tval(ctx, -1)));
+
+	/* [ ... num ] */
+}
+
+static void duk__dec_objarr_entry(duk_json_dec_ctx *js_ctx) {
+	duk_context *ctx = (duk_context *) js_ctx->thr;
+	duk_require_stack(ctx, DUK_JSON_DEC_REQSTACK);
+
+	/* c recursion check */
+
+	DUK_ASSERT(js_ctx->recursion_depth >= 0);
+	DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit);
+	if (js_ctx->recursion_depth >= js_ctx->recursion_limit) {
+		DUK_ERROR((duk_hthread *) ctx, DUK_ERR_RANGE_ERROR, DUK_STR_JSONDEC_RECLIMIT);
+	}
+	js_ctx->recursion_depth++;
+}
+
+static void duk__dec_objarr_exit(duk_json_dec_ctx *js_ctx) {
+	/* c recursion check */
+
+	DUK_ASSERT(js_ctx->recursion_depth > 0);
+	DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit);
+	js_ctx->recursion_depth--;
+}
+
+static void duk__dec_object(duk_json_dec_ctx *js_ctx) {
+	duk_context *ctx = (duk_context *) js_ctx->thr;
+	duk_int_t key_count;  /* XXX: a "first" flag would suffice */
+	duk_small_int_t x;
+
+	DUK_DDD(DUK_DDDPRINT("parse_object"));
+
+	duk__dec_objarr_entry(js_ctx);
+
+	duk_push_object(ctx);
+
+	/* Initial '{' has been checked and eaten by caller. */
+
+	key_count = 0;
+	for (;;) {
+		x = duk__dec_get_nonwhite(js_ctx);
+
+		DUK_DDD(DUK_DDDPRINT("parse_object: obj=%!T, x=%ld, key_count=%ld",
+		                     (duk_tval *) duk_get_tval(ctx, -1),
+		                     (long) x, (long) key_count));
+
+		/* handle comma and closing brace */
+
+		if (x == DUK_ASC_COMMA && key_count > 0) {
+			/* accept comma, expect new value */
+			x = duk__dec_get_nonwhite(js_ctx);
+		} else if (x == DUK_ASC_RCURLY) {
+			/* eat closing brace */
+			break;
+		} else if (key_count == 0) {
+			/* accept anything, expect first value (EOF will be
+			 * caught by key parsing below.
+			 */
+			;
+		} else {
+			/* catches EOF (and initial comma) */
+			goto syntax_error;
+		}
+
+		/* parse key and value */
+
+		if (x == DUK_ASC_DOUBLEQUOTE) {
+			duk__dec_string(js_ctx);
+#ifdef DUK_USE_JX
+		} else if (js_ctx->flag_ext_custom &&
+		           duk_unicode_is_identifier_start((duk_codepoint_t) x)) {
+			duk__dec_plain_string(js_ctx);
+#endif
+		} else {
+			goto syntax_error;
+		}
+
+		/* [ ... obj key ] */
+
+		x = duk__dec_get_nonwhite(js_ctx);
+		if (x != DUK_ASC_COLON) {
+			goto syntax_error;
+		}
+
+		duk__dec_value(js_ctx);
+
+		/* [ ... obj key val ] */
+
+		duk_def_prop_wec(ctx, -3);
+
+		/* [ ... obj ] */
+
+		key_count++;
+	}
+
+	/* [ ... obj ] */
+
+	DUK_DDD(DUK_DDDPRINT("parse_object: final object is %!T",
+	                     (duk_tval *) duk_get_tval(ctx, -1)));
+
+	duk__dec_objarr_exit(js_ctx);
+	return;
+
+ syntax_error:
+	duk__dec_syntax_error(js_ctx);
+	DUK_UNREACHABLE();
+}
+
+static void duk__dec_array(duk_json_dec_ctx *js_ctx) {
+	duk_context *ctx = (duk_context *) js_ctx->thr;
+	duk_uarridx_t arr_idx;
+	duk_small_int_t x;
+
+	DUK_DDD(DUK_DDDPRINT("parse_array"));
+
+	duk__dec_objarr_entry(js_ctx);
+
+	duk_push_array(ctx);
+
+	/* Initial '[' has been checked and eaten by caller. */
+
+	arr_idx = 0;
+	for (;;) {
+		x = duk__dec_get_nonwhite(js_ctx);
+
+		DUK_DDD(DUK_DDDPRINT("parse_array: arr=%!T, x=%ld, arr_idx=%ld",
+		                     (duk_tval *) duk_get_tval(ctx, -1),
+		                     (long) x, (long) arr_idx));
+
+		/* handle comma and closing bracket */
+
+		if ((x == DUK_ASC_COMMA) && (arr_idx != 0)) {
+			/* accept comma, expect new value */
+			;
+		} else if (x == DUK_ASC_RBRACKET) {
+			/* eat closing bracket */
+			break;
+		} else if (arr_idx == 0) {
+			/* accept anything, expect first value (EOF will be
+			 * caught by duk__dec_value() below.
+			 */
+			js_ctx->p--;  /* backtrack (safe) */
+		} else {
+			/* catches EOF (and initial comma) */
+			goto syntax_error;
+		}
+
+		/* parse value */
+
+		duk__dec_value(js_ctx);
+
+		/* [ ... arr val ] */
+
+		duk_def_prop_index_wec(ctx, -2, arr_idx);
+		arr_idx++;
+	}
+
+	/* Must set 'length' explicitly when using duk_def_prop_xxx() to
+	 * set the values.
+	 */
+
+	duk_set_length(ctx, -1, arr_idx);
+
+	/* [ ... arr ] */
+
+	DUK_DDD(DUK_DDDPRINT("parse_array: final array is %!T",
+	                     (duk_tval *) duk_get_tval(ctx, -1)));
+
+	duk__dec_objarr_exit(js_ctx);
+	return;
+
+ syntax_error:
+	duk__dec_syntax_error(js_ctx);
+	DUK_UNREACHABLE();
+}
+
+static void duk__dec_value(duk_json_dec_ctx *js_ctx) {
+	duk_context *ctx = (duk_context *) js_ctx->thr;
+	duk_small_int_t x;
+
+	x = duk__dec_get_nonwhite(js_ctx);
+
+	DUK_DDD(DUK_DDDPRINT("parse_value: initial x=%ld", (long) x));
+
+	/* Note: duk__dec_req_stridx() backtracks one char */
+
+	if (x == DUK_ASC_DOUBLEQUOTE) {
+		duk__dec_string(js_ctx);
+	} else if ((x >= DUK_ASC_0 && x <= DUK_ASC_9) || (x == DUK_ASC_MINUS)) {
+#ifdef DUK_USE_JX
+		if (js_ctx->flag_ext_custom && duk__dec_peek(js_ctx) == DUK_ASC_UC_I) {
+			duk__dec_req_stridx(js_ctx, DUK_STRIDX_MINUS_INFINITY);  /* "-Infinity" */
+			duk_push_number(ctx, -DUK_DOUBLE_INFINITY);
+		} else {
+#else
+		{  /* unconditional block */
+#endif
+			/* We already ate 'x', so duk__dec_number() will back up one byte. */
+			duk__dec_number(js_ctx);
+		}
+	} else if (x == DUK_ASC_LC_T) {
+		duk__dec_req_stridx(js_ctx, DUK_STRIDX_TRUE);
+		duk_push_true(ctx);
+	} else if (x == DUK_ASC_LC_F) {
+		duk__dec_req_stridx(js_ctx, DUK_STRIDX_FALSE);
+		duk_push_false(ctx);
+	} else if (x == DUK_ASC_LC_N) {
+		duk__dec_req_stridx(js_ctx, DUK_STRIDX_LC_NULL);
+		duk_push_null(ctx);
+#ifdef DUK_USE_JX
+	} else if (js_ctx->flag_ext_custom && x == DUK_ASC_LC_U) {
+		duk__dec_req_stridx(js_ctx, DUK_STRIDX_LC_UNDEFINED);
+		duk_push_undefined(ctx);
+	} else if (js_ctx->flag_ext_custom && x == DUK_ASC_UC_N) {
+		duk__dec_req_stridx(js_ctx, DUK_STRIDX_NAN);
+		duk_push_nan(ctx);
+	} else if (js_ctx->flag_ext_custom && x == DUK_ASC_UC_I) {
+		duk__dec_req_stridx(js_ctx, DUK_STRIDX_INFINITY);
+		duk_push_number(ctx, DUK_DOUBLE_INFINITY);
+	} else if (js_ctx->flag_ext_custom && x == DUK_ASC_LPAREN) {
+		duk__dec_pointer(js_ctx);
+	} else if (js_ctx->flag_ext_custom && x == DUK_ASC_PIPE) {
+		duk__dec_buffer(js_ctx);
+#endif
+	} else if (x == DUK_ASC_LCURLY) {
+		duk__dec_object(js_ctx);
+	} else if (x == DUK_ASC_LBRACKET) {
+		duk__dec_array(js_ctx);
+	} else {
+		/* catches EOF */
+		goto syntax_error;
+	}
+
+	duk__dec_eat_white(js_ctx);
+
+	/* [ ... val ] */
+	return;
+
+ syntax_error:
+	duk__dec_syntax_error(js_ctx);
+	DUK_UNREACHABLE();
+}
+
+/* Recursive value reviver, implements the Walk() algorithm.  No C recursion
+ * check is done here because the initial parsing step will already ensure
+ * there is a reasonable limit on C recursion depth and hence object depth.
+ */
+static void duk__dec_reviver_walk(duk_json_dec_ctx *js_ctx) {
+	duk_context *ctx = (duk_context *) js_ctx->thr;
+	duk_hobject *h;
+	duk_uarridx_t i, arr_len;
+
+	DUK_DDD(DUK_DDDPRINT("walk: top=%ld, holder=%!T, name=%!T",
+	                     (long) duk_get_top(ctx),
+	                     (duk_tval *) duk_get_tval(ctx, -2),
+	                     (duk_tval *) duk_get_tval(ctx, -1)));
+
+	duk_dup_top(ctx);
+	duk_get_prop(ctx, -3);  /* -> [ ... holder name val ] */
+
+	h = duk_get_hobject(ctx, -1);
+	if (h != NULL) {
+		if (DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_ARRAY) {
+			arr_len = (duk_uarridx_t) duk_get_length(ctx, -1);
+			for (i = 0; i < arr_len; i++) {
+				/* [ ... holder name val ] */
+
+				DUK_DDD(DUK_DDDPRINT("walk: array, top=%ld, i=%ld, arr_len=%ld, holder=%!T, name=%!T, val=%!T",
+				                     (long) duk_get_top(ctx), (long) i, (long) arr_len,
+				                     (duk_tval *) duk_get_tval(ctx, -3), (duk_tval *) duk_get_tval(ctx, -2),
+				                     (duk_tval *) duk_get_tval(ctx, -1)));
+
+				/* FIXME: push_uint_string / push_u32_string */
+				duk_dup_top(ctx);
+				duk_push_uint(ctx, (duk_uint_t) i);
+				duk_to_string(ctx, -1);  /* -> [ ... holder name val val ToString(i) ] */
+				duk__dec_reviver_walk(js_ctx);  /* -> [ ... holder name val new_elem ] */
+
+				if (duk_is_undefined(ctx, -1)) {
+					duk_pop(ctx);
+					duk_del_prop_index(ctx, -1, i);
+				} else {
+					/* XXX: duk_def_prop_index_wec() would be more appropriate
+					 * here but it currently makes some assumptions that might
+					 * not hold (e.g. that previous property is not an accessor).
+					 */
+					duk_put_prop_index(ctx, -2, i);
+				}
+			}
+		} else {
+			/* [ ... holder name val ] */
+			duk_enum(ctx, -1, DUK_ENUM_OWN_PROPERTIES_ONLY /*flags*/);
+			while (duk_next(ctx, -1 /*enum_index*/, 0 /*get_value*/)) {
+				DUK_DDD(DUK_DDDPRINT("walk: object, top=%ld, holder=%!T, name=%!T, val=%!T, enum=%!iT, obj_key=%!T",
+				                     (long) duk_get_top(ctx), (duk_tval *) duk_get_tval(ctx, -5),
+				                     (duk_tval *) duk_get_tval(ctx, -4), (duk_tval *) duk_get_tval(ctx, -3),
+				                     (duk_tval *) duk_get_tval(ctx, -2), (duk_tval *) duk_get_tval(ctx, -1)));
+
+				/* [ ... holder name val enum obj_key ] */
+				duk_dup(ctx, -3);
+				duk_dup(ctx, -2);
+
+				/* [ ... holder name val enum obj_key val obj_key ] */
+				duk__dec_reviver_walk(js_ctx);
+
+				/* [ ... holder name val enum obj_key new_elem ] */
+				if (duk_is_undefined(ctx, -1)) {
+					duk_pop(ctx);
+					duk_del_prop(ctx, -3);
+				} else {
+					/* XXX: duk_def_prop_index_wec() would be more appropriate
+					 * here but it currently makes some assumptions that might
+					 * not hold (e.g. that previous property is not an accessor).
+					 *
+					 * Using duk_put_prop() works incorrectly with '__proto__'
+					 * if the own property with that name has been deleted.  This
+					 * does not happen normally, but a clever reviver can trigger
+					 * that, see complex reviver case in: test-bug-json-parse-__proto__.js.
+					 */
+					duk_put_prop(ctx, -4);
+				}
+			}
+			duk_pop(ctx);  /* pop enum */
+		}
+	}
+
+	/* [ ... holder name val ] */
+
+	duk_dup(ctx, js_ctx->idx_reviver);
+	duk_insert(ctx, -4);  /* -> [ ... reviver holder name val ] */
+	duk_call_method(ctx, 2);  /* -> [ ... res ] */
+
+	DUK_DDD(DUK_DDDPRINT("walk: top=%ld, result=%!T",
+	                     (long) duk_get_top(ctx), (duk_tval *) duk_get_tval(ctx, -1)));
+}
+
+/*
+ *  Stringify implementation.
+ */
+
+#define DUK__EMIT_1(js_ctx,ch)          duk__emit_1((js_ctx), (duk_uint_fast8_t) (ch))
+#define DUK__EMIT_2(js_ctx,ch1,ch2)     duk__emit_2((js_ctx), (((duk_uint_fast16_t)(ch1)) << 8) + (duk_uint_fast16_t)(ch2))
+#define DUK__EMIT_ESC_AUTO(js_ctx,cp)   duk__emit_esc_auto((js_ctx), (cp))
+#define DUK__EMIT_XUTF8(js_ctx,cp)      duk__emit_xutf8((js_ctx), (cp))
+#define DUK__EMIT_HSTR(js_ctx,h)        duk__emit_hstring((js_ctx), (h))
+#if defined(DUK_USE_JX) || defined(DUK_USE_JC)
+#define DUK__EMIT_CSTR(js_ctx,p)        duk__emit_cstring((js_ctx), (p))
+#endif
+#define DUK__EMIT_STRIDX(js_ctx,i)      duk__emit_stridx((js_ctx), (i))
+
+static void duk__emit_1(duk_json_enc_ctx *js_ctx, duk_uint_fast8_t ch) {
+	duk_hbuffer_append_byte(js_ctx->thr, js_ctx->h_buf, (duk_uint8_t) ch);
+}
+
+static void duk__emit_2(duk_json_enc_ctx *js_ctx, duk_uint_fast16_t packed_chars) {
+	duk_uint8_t buf[2];
+	buf[0] = (duk_uint8_t) (packed_chars >> 8);
+	buf[1] = (duk_uint8_t) (packed_chars & 0xff);
+	duk_hbuffer_append_bytes(js_ctx->thr, js_ctx->h_buf, (duk_uint8_t *) buf, 2);
+}
+
+#define DUK__MKESC(nybbles,esc1,esc2)  \
+	(((duk_uint_fast32_t) (nybbles)) << 16) | \
+	(((duk_uint_fast32_t) (esc1)) << 8) | \
+	((duk_uint_fast32_t) (esc2))
+
+static void duk__emit_esc_auto(duk_json_enc_ctx *js_ctx, duk_uint_fast32_t cp) {
+	duk_uint8_t buf[2];
+	duk_uint_fast32_t tmp;
+	duk_small_uint_t dig;
+
+	/* Select appropriate escape format automatically, and set 'tmp' to a
+	 * value encoding both the escape format character and the nybble count:
+	 *
+	 *   (nybble_count << 16) | (escape_char1) | (escape_char2)
+	 */
+
+#ifdef DUK_USE_JX
+	if (DUK_LIKELY(cp < 0x100UL)) {
+		if (DUK_UNLIKELY(js_ctx->flag_ext_custom)) {
+			tmp = DUK__MKESC(2, DUK_ASC_BACKSLASH, DUK_ASC_LC_X);
+		} else {
+			tmp = DUK__MKESC(4, DUK_ASC_BACKSLASH, DUK_ASC_LC_U);
+		}
+	} else
+#endif
+	if (DUK_LIKELY(cp < 0x10000UL)) {
+		tmp = DUK__MKESC(4, DUK_ASC_BACKSLASH, DUK_ASC_LC_U);
+	} else {
+#ifdef DUK_USE_JX
+		if (DUK_LIKELY(js_ctx->flag_ext_custom)) {
+			tmp = DUK__MKESC(8, DUK_ASC_BACKSLASH, DUK_ASC_UC_U);
+		} else
+#endif
+		{
+			/* In compatible mode and standard JSON mode, output
+			 * something useful for non-BMP characters.  This won't
+			 * roundtrip but will still be more or less readable and
+			 * more useful than an error.
+			 */
+			tmp = DUK__MKESC(8, DUK_ASC_UC_U, DUK_ASC_PLUS);
+		}
+	}
+
+	buf[0] = (duk_uint8_t) ((tmp >> 8) & 0xff);
+	buf[1] = (duk_uint8_t) (tmp & 0xff);
+	duk_hbuffer_append_bytes(js_ctx->thr, js_ctx->h_buf, buf, 2);
+
+	tmp = tmp >> 16;
+	while (tmp > 0) {
+		tmp--;
+		dig = (duk_small_uint_t) ((cp >> (4 * tmp)) & 0x0f);
+		duk_hbuffer_append_byte(js_ctx->thr, js_ctx->h_buf, duk_lc_digits[dig]);
+	}
+}
+
+static void duk__emit_xutf8(duk_json_enc_ctx *js_ctx, duk_uint_fast32_t cp) {
+	(void) duk_hbuffer_append_xutf8(js_ctx->thr, js_ctx->h_buf, cp);
+}
+
+static void duk__emit_hstring(duk_json_enc_ctx *js_ctx, duk_hstring *h) {
+	DUK_ASSERT(h != NULL);
+	duk_hbuffer_append_bytes(js_ctx->thr,
+	                         js_ctx->h_buf,
+	                         (duk_uint8_t *) DUK_HSTRING_GET_DATA(h),
+	                         (duk_size_t) DUK_HSTRING_GET_BYTELEN(h));
+}
+
+#if defined(DUK_USE_JX) || defined(DUK_USE_JC)
+static void duk__emit_cstring(duk_json_enc_ctx *js_ctx, const char *p) {
+	DUK_ASSERT(p != NULL);
+	(void) duk_hbuffer_append_cstring(js_ctx->thr, js_ctx->h_buf, p);
+}
+#endif
+
+static void duk__emit_stridx(duk_json_enc_ctx *js_ctx, duk_small_uint_t stridx) {
+	DUK_ASSERT_DISABLE(stridx >= 0);  /* unsigned */
+	DUK_ASSERT(stridx < DUK_HEAP_NUM_STRINGS);
+	duk__emit_hstring(js_ctx, DUK_HTHREAD_GET_STRING(js_ctx->thr, stridx));
+}
+
+/* Check whether key quotes would be needed (custom encoding). */
+static duk_bool_t duk__enc_key_quotes_needed(duk_hstring *h_key) {
+	duk_uint8_t *p, *p_start, *p_end;
+	duk_small_uint_t ch;
+
+	DUK_ASSERT(h_key != NULL);
+	p_start = DUK_HSTRING_GET_DATA(h_key);
+	p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_key);
+	p = p_start;
+
+	DUK_DDD(DUK_DDDPRINT("duk__enc_key_quotes_needed: h_key=%!O, p_start=%p, p_end=%p, p=%p",
+	                     (duk_heaphdr *) h_key, (void *) p_start, (void *) p_end, (void *) p));
+
+	/* Since we only accept ASCII characters, there is no need for
+	 * actual decoding.  A non-ASCII character will be >= 0x80 which
+	 * causes a false return value immediately.
+	 */
+
+	if (p == p_end) {
+		/* Zero length string is not accepted without quotes */
+		return 1;
+	}
+
+	while (p < p_end) {
+		ch = (duk_small_uint_t) (*p);
+
+		/* Accept ASCII IdentifierStart and IdentifierPart if not first char.
+		 * Function selection is a bit uncommon.
+		 */
+		if ((p > p_start ? duk_unicode_is_identifier_part :
+		                   duk_unicode_is_identifier_start) ((duk_codepoint_t) ch)) {
+			p++;
+			continue;
+		}
+
+		/* all non-ASCII characters also come here (first byte >= 0x80) */
+		return 1;
+	}
+
+	return 0;
+}
+
+/* The Quote(value) operation: quote a string.
+ *
+ * Stack policy: [ ] -> [ ].
+ */
+
+static duk_uint8_t duk__quote_esc[14] = {
+	DUK_ASC_NUL, DUK_ASC_NUL, DUK_ASC_NUL, DUK_ASC_NUL,
+	DUK_ASC_NUL, DUK_ASC_NUL, DUK_ASC_NUL, DUK_ASC_NUL,
+	DUK_ASC_LC_B, DUK_ASC_LC_T, DUK_ASC_LC_N, DUK_ASC_NUL,
+	DUK_ASC_LC_F, DUK_ASC_LC_R
+};
+
+static void duk__enc_quote_string(duk_json_enc_ctx *js_ctx, duk_hstring *h_str) {
+	duk_hthread *thr = js_ctx->thr;
+	duk_uint8_t *p, *p_start, *p_end, *p_tmp;
+	duk_ucodepoint_t cp;  /* typed for duk_unicode_decode_xutf8() */
+
+	DUK_DDD(DUK_DDDPRINT("duk__enc_quote_string: h_str=%!O", (duk_heaphdr *) h_str));
+
+	DUK_ASSERT(h_str != NULL);
+	p_start = DUK_HSTRING_GET_DATA(h_str);
+	p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_str);
+	p = p_start;
+
+	DUK__EMIT_1(js_ctx, DUK_ASC_DOUBLEQUOTE);
+
+	while (p < p_end) {
+		cp = *p;
+
+		if (DUK_LIKELY(cp <= 0x7f)) {
+			/* ascii fast path: avoid decoding utf-8 */
+			p++;
+			if (cp == 0x22 || cp == 0x5c) {
+				/* double quote or backslash */
+				DUK__EMIT_2(js_ctx, DUK_ASC_BACKSLASH, cp);
+			} else if (cp < 0x20) {
+				duk_uint_fast8_t esc_char;
+
+				/* This approach is a bit shorter than a straight
+				 * if-else-ladder and also a bit faster.
+				 */
+				if (cp < (sizeof(duk__quote_esc) / sizeof(duk_uint8_t)) &&
+				    (esc_char = duk__quote_esc[cp]) != 0) {
+					DUK__EMIT_2(js_ctx, DUK_ASC_BACKSLASH, esc_char);
+				} else {
+					DUK__EMIT_ESC_AUTO(js_ctx, cp);
+				}
+			} else if (cp == 0x7f && js_ctx->flag_ascii_only) {
+				DUK__EMIT_ESC_AUTO(js_ctx, cp);
+			} else {
+				/* any other printable -> as is */
+				DUK__EMIT_1(js_ctx, cp);
+			}
+		} else {
+			/* slow path decode */
+
+			/* If XUTF-8 decoding fails, treat the offending byte as a codepoint directly
+			 * and go forward one byte.  This is of course very lossy, but allows some kind
+			 * of output to be produced even for internal strings which don't conform to
+			 * XUTF-8.  All standard Ecmascript strings are always CESU-8, so this behavior
+			 * does not violate the Ecmascript specification.  The behavior is applied to
+			 * all modes, including Ecmascript standard JSON.  Because the current XUTF-8
+			 * decoding is not very strict, this behavior only really affects initial bytes
+			 * and truncated codepoints.
+			 *
+			 * XXX: another alternative would be to scan forwards to start of next codepoint
+			 * (or end of input) and emit just one replacement codepoint.
+			 */
+
+			p_tmp = p;
+			if (!duk_unicode_decode_xutf8(thr, &p, p_start, p_end, &cp)) {
+				/* Decode failed. */
+				cp = *p_tmp;
+				p = p_tmp + 1;
+			}
+
+			if (js_ctx->flag_ascii_only) {
+				DUK__EMIT_ESC_AUTO(js_ctx, cp);
+			} else {
+				/* as is */
+				DUK__EMIT_XUTF8(js_ctx, cp);
+			}
+		}
+	}
+
+	DUK__EMIT_1(js_ctx, DUK_ASC_DOUBLEQUOTE);
+}
+
+/* Shared entry handling for object/array serialization: indent/stepback,
+ * loop detection.
+ */
+static void duk__enc_objarr_entry(duk_json_enc_ctx *js_ctx, duk_hstring **h_stepback, duk_hstring **h_indent, duk_idx_t *entry_top) {
+	duk_context *ctx = (duk_context *) js_ctx->thr;
+	duk_hobject *h_target;
+
+	*entry_top = duk_get_top(ctx);
+
+	duk_require_stack(ctx, DUK_JSON_ENC_REQSTACK);
+
+	/* loop check */
+
+	h_target = duk_get_hobject(ctx, -1);  /* object or array */
+	DUK_ASSERT(h_target != NULL);
+	duk_push_sprintf(ctx, DUK_STR_FMT_PTR, (void *) h_target);
+
+	duk_dup_top(ctx);  /* -> [ ... voidp voidp ] */
+	if (duk_has_prop(ctx, js_ctx->idx_loop)) {
+		DUK_ERROR((duk_hthread *) ctx, DUK_ERR_TYPE_ERROR, DUK_STR_CYCLIC_INPUT);
+	}
+	duk_push_true(ctx);  /* -> [ ... voidp true ] */
+	duk_put_prop(ctx, js_ctx->idx_loop);  /* -> [ ... ] */
+
+	/* c recursion check */
+
+	DUK_ASSERT(js_ctx->recursion_depth >= 0);
+	DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit);
+	if (js_ctx->recursion_depth >= js_ctx->recursion_limit) {
+		DUK_ERROR((duk_hthread *) ctx, DUK_ERR_RANGE_ERROR, DUK_STR_JSONENC_RECLIMIT);
+	}
+	js_ctx->recursion_depth++;
+
+	/* figure out indent and stepback */
+
+	*h_indent = NULL;
+	*h_stepback = NULL;
+	if (js_ctx->h_gap != NULL) {
+		DUK_ASSERT(js_ctx->h_indent != NULL);
+
+		*h_stepback = js_ctx->h_indent;
+		duk_push_hstring(ctx, js_ctx->h_indent);
+		duk_push_hstring(ctx, js_ctx->h_gap);
+		duk_concat(ctx, 2);
+		js_ctx->h_indent = duk_get_hstring(ctx, -1);
+		*h_indent = js_ctx->h_indent;
+		DUK_ASSERT(js_ctx->h_indent != NULL);
+
+		/* The new indent string is left at value stack top, and will
+		 * be popped by the shared exit handler.
+	 	 */
+	} else {
+		DUK_ASSERT(js_ctx->h_indent == NULL);
+	}
+
+	DUK_DDD(DUK_DDDPRINT("shared entry finished: top=%ld, loop=%!T",
+	                     (long) duk_get_top(ctx), (duk_tval *) duk_get_tval(ctx, js_ctx->idx_loop)));
+}
+
+/* Shared exit handling for object/array serialization. */
+static void duk__enc_objarr_exit(duk_json_enc_ctx *js_ctx, duk_hstring **h_stepback, duk_hstring **h_indent, duk_idx_t *entry_top) {
+	duk_context *ctx = (duk_context *) js_ctx->thr;
+	duk_hobject *h_target;
+
+	DUK_UNREF(h_indent);
+
+	if (js_ctx->h_gap != NULL) {
+		DUK_ASSERT(js_ctx->h_indent != NULL);
+		DUK_ASSERT(*h_stepback != NULL);
+		DUK_ASSERT(*h_indent != NULL);
+
+		js_ctx->h_indent = *h_stepback;  /* previous js_ctx->h_indent */
+
+		/* Note: we don't need to pop anything because the duk_set_top()
+		 * at the end will take care of it.
+		 */
+	} else {
+		DUK_ASSERT(js_ctx->h_indent == NULL);
+		DUK_ASSERT(*h_stepback == NULL);
+		DUK_ASSERT(*h_indent == NULL);
+	}
+
+	/* c recursion check */
+
+	DUK_ASSERT(js_ctx->recursion_depth > 0);
+	DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit);
+	js_ctx->recursion_depth--;
+
+	/* loop check */
+
+	h_target = duk_get_hobject(ctx, *entry_top - 1);  /* original target at entry_top - 1 */
+	DUK_ASSERT(h_target != NULL);
+	duk_push_sprintf(ctx, DUK_STR_FMT_PTR, (void *) h_target);
+
+	duk_del_prop(ctx, js_ctx->idx_loop);  /* -> [ ... ] */
+
+	/* restore stack top after unbalanced code paths */
+	duk_set_top(ctx, *entry_top);
+
+	DUK_DDD(DUK_DDDPRINT("shared entry finished: top=%ld, loop=%!T",
+	                     (long) duk_get_top(ctx), (duk_tval *) duk_get_tval(ctx, js_ctx->idx_loop)));
+}
+
+/* The JO(value) operation: encode object.
+ *
+ * Stack policy: [ object ] -> [ object ].
+ */
+static void duk__enc_object(duk_json_enc_ctx *js_ctx) {
+	duk_context *ctx = (duk_context *) js_ctx->thr;
+	duk_hstring *h_stepback;
+	duk_hstring *h_indent;
+	duk_hstring *h_key;
+	duk_idx_t entry_top;
+	duk_idx_t idx_obj;
+	duk_idx_t idx_keys;
+	duk_bool_t first;
+	duk_bool_t undef;
+	duk_uarridx_t arr_len, i;
+
+	DUK_DDD(DUK_DDDPRINT("duk__enc_object: obj=%!T", (duk_tval *) duk_get_tval(ctx, -1)));
+
+	duk__enc_objarr_entry(js_ctx, &h_stepback, &h_indent, &entry_top);
+
+	idx_obj = entry_top - 1;
+
+	if (js_ctx->idx_proplist >= 0) {
+		idx_keys = js_ctx->idx_proplist;
+	} else {
+		/* FIXME: would be nice to enumerate an object at specified index */
+		duk_dup(ctx, idx_obj);
+		(void) duk_hobject_get_enumerated_keys(ctx, DUK_ENUM_OWN_PROPERTIES_ONLY /*flags*/);  /* [ ... target ] -> [ ... target keys ] */
+		idx_keys = duk_require_normalize_index(ctx, -1);
+		/* leave stack unbalanced on purpose */
+	}
+
+	DUK_DDD(DUK_DDDPRINT("idx_keys=%ld, h_keys=%!T",
+	                     (long) idx_keys, (duk_tval *) duk_get_tval(ctx, idx_keys)));
+
+	/* Steps 8-10 have been merged to avoid a "partial" variable. */
+
+	DUK__EMIT_1(js_ctx, DUK_ASC_LCURLY);
+
+	/* FIXME: keys is an internal object with all keys to be processed
+	 * in its (gapless) array part.  Because nobody can touch the keys
+	 * object, we could iterate its array part directly (keeping in mind
+	 * that it can be reallocated).
+	 */
+
+	arr_len = (duk_uarridx_t) duk_get_length(ctx, idx_keys);
+	first = 1;
+	for (i = 0; i < arr_len; i++) {
+		duk_get_prop_index(ctx, idx_keys, i);  /* -> [ ... key ] */
+
+		DUK_DDD(DUK_DDDPRINT("object property loop: holder=%!T, key=%!T",
+		                     (duk_tval *) duk_get_tval(ctx, idx_obj),
+		                     (duk_tval *) duk_get_tval(ctx, -1)));
+
+		undef = duk__enc_value1(js_ctx, idx_obj);
+		if (undef) {
+			/* Value would yield 'undefined', so skip key altogether.
+			 * Side effects have already happened.
+			 */
+			continue;
+		}
+
+		/* [ ... key val ] */
+
+		if (first) {
+			first = 0;
+		} else {
+			DUK__EMIT_1(js_ctx, DUK_ASC_COMMA);
+		}
+		if (h_indent != NULL) {
+			DUK__EMIT_1(js_ctx, 0x0a);
+			DUK__EMIT_HSTR(js_ctx, h_indent);
+		}
+
+		h_key = duk_get_hstring(ctx, -2);
+		DUK_ASSERT(h_key != NULL);
+		if (js_ctx->flag_avoid_key_quotes && !duk__enc_key_quotes_needed(h_key)) {
+			/* emit key as is */
+			DUK__EMIT_HSTR(js_ctx, h_key);
+		} else {
+			duk__enc_quote_string(js_ctx, h_key);
+		}
+
+		if (h_indent != NULL) {
+			DUK__EMIT_2(js_ctx, DUK_ASC_COLON, DUK_ASC_SPACE);
+		} else {
+			DUK__EMIT_1(js_ctx, DUK_ASC_COLON);
+		}
+
+		/* [ ... key val ] */
+
+		duk__enc_value2(js_ctx);  /* -> [ ... ] */
+	}
+
+	if (!first) {
+		if (h_stepback != NULL) {
+			DUK_ASSERT(h_indent != NULL);
+			DUK__EMIT_1(js_ctx, 0x0a);
+			DUK__EMIT_HSTR(js_ctx, h_stepback);
+		}
+	}
+	DUK__EMIT_1(js_ctx, DUK_ASC_RCURLY);
+
+	duk__enc_objarr_exit(js_ctx, &h_stepback, &h_indent, &entry_top);
+
+	DUK_ASSERT_TOP(ctx, entry_top);
+}
+
+/* The JA(value) operation: encode array.
+ *
+ * Stack policy: [ array ] -> [ array ].
+ */
+static void duk__enc_array(duk_json_enc_ctx *js_ctx) {
+	duk_context *ctx = (duk_context *) js_ctx->thr;
+	duk_hstring *h_stepback;
+	duk_hstring *h_indent;
+	duk_idx_t entry_top;
+	duk_idx_t idx_arr;
+	duk_bool_t undef;
+	duk_uarridx_t i, arr_len;
+
+	DUK_DDD(DUK_DDDPRINT("duk__enc_array: array=%!T",
+	                     (duk_tval *) duk_get_tval(ctx, -1)));
+
+	duk__enc_objarr_entry(js_ctx, &h_stepback, &h_indent, &entry_top);
+
+	idx_arr = entry_top - 1;
+
+	/* Steps 8-10 have been merged to avoid a "partial" variable. */
+
+	DUK__EMIT_1(js_ctx, DUK_ASC_LBRACKET);
+
+	arr_len = (duk_uarridx_t) duk_get_length(ctx, idx_arr);
+	for (i = 0; i < arr_len; i++) {
+		DUK_DDD(DUK_DDDPRINT("array entry loop: array=%!T, h_indent=%!O, h_stepback=%!O, index=%ld, arr_len=%ld",
+		                     (duk_tval *) duk_get_tval(ctx, idx_arr), (duk_heaphdr *) h_indent,
+		                     (duk_heaphdr *) h_stepback, (long) i, (long) arr_len));
+
+		if (i > 0) {
+			DUK__EMIT_1(js_ctx, DUK_ASC_COMMA);
+		}
+		if (h_indent != NULL) {
+			DUK__EMIT_1(js_ctx, 0x0a);
+			DUK__EMIT_HSTR(js_ctx, h_indent);
+		}
+
+		/* FIXME: duk_push_uint_string() */
+		duk_push_uint(ctx, (duk_uint_t) i);
+		duk_to_string(ctx, -1);  /* -> [ ... key ] */
+		undef = duk__enc_value1(js_ctx, idx_arr);
+
+		if (undef) {
+			DUK__EMIT_STRIDX(js_ctx, DUK_STRIDX_LC_NULL);
+		} else {
+			/* [ ... key val ] */
+			duk__enc_value2(js_ctx);
+		}
+	}
+
+	if (arr_len > 0) {
+		if (h_stepback != NULL) {
+			DUK_ASSERT(h_indent != NULL);
+			DUK__EMIT_1(js_ctx, 0x0a);
+			DUK__EMIT_HSTR(js_ctx, h_stepback);
+		}
+	}
+	DUK__EMIT_1(js_ctx, DUK_ASC_RBRACKET);
+
+	duk__enc_objarr_exit(js_ctx, &h_stepback, &h_indent, &entry_top);
+
+	DUK_ASSERT_TOP(ctx, entry_top);
+}
+
+/* The Str(key, holder) operation: encode value, steps 1-4.
+ *
+ * Returns non-zero if the value between steps 4 and 5 would yield an
+ * 'undefined' final result.  This is useful in JO() because we need to
+ * get the side effects out, but need to know whether or not a key will
+ * be omitted from the serialization.
+ *
+ * Stack policy: [ ... key ] -> [ ... key val ]  if retval == 0.
+ *                           -> [ ... ]          if retval != 0.
+ */
+static duk_bool_t duk__enc_value1(duk_json_enc_ctx *js_ctx, duk_idx_t idx_holder) {
+	duk_context *ctx = (duk_context *) js_ctx->thr;
+	duk_hobject *h;
+	duk_tval *tv;
+	duk_small_int_t c;
+
+	DUK_DDD(DUK_DDDPRINT("duk__enc_value1: idx_holder=%ld, holder=%!T, key=%!T",
+	                     (long) idx_holder, (duk_tval *) duk_get_tval(ctx, idx_holder),
+	                     (duk_tval *) duk_get_tval(ctx, -1)));
+
+	duk_dup_top(ctx);               /* -> [ ... key key ] */
+	duk_get_prop(ctx, idx_holder);  /* -> [ ... key val ] */
+
+	DUK_DDD(DUK_DDDPRINT("value=%!T", (duk_tval *) duk_get_tval(ctx, -1)));
+
+	h = duk_get_hobject(ctx, -1);
+	if (h != NULL) {
+		duk_get_prop_stridx(ctx, -1, DUK_STRIDX_TO_JSON);
+		h = duk_get_hobject(ctx, -1);
+		if (h != NULL && DUK_HOBJECT_IS_CALLABLE(h)) {
+			DUK_DDD(DUK_DDDPRINT("value is object, has callable toJSON() -> call it"));
+			duk_dup(ctx, -2);         /* -> [ ... key val toJSON val ] */
+			duk_dup(ctx, -4);         /* -> [ ... key val toJSON val key ] */
+			duk_call_method(ctx, 1);  /* -> [ ... key val val' ] */
+			duk_remove(ctx, -2);      /* -> [ ... key val' ] */
+		} else {
+			duk_pop(ctx);
+		}
+	}
+
+	/* [ ... key val ] */
+
+	DUK_DDD(DUK_DDDPRINT("value=%!T", (duk_tval *) duk_get_tval(ctx, -1)));
+
+	if (js_ctx->h_replacer) {
+		/* FIXME: here a "slice copy" would be useful */
+		DUK_DDD(DUK_DDDPRINT("replacer is set, call replacer"));
+		duk_push_hobject(ctx, js_ctx->h_replacer);  /* -> [ ... key val replacer ] */
+		duk_dup(ctx, idx_holder);                   /* -> [ ... key val replacer holder ] */
+		duk_dup(ctx, -4);                           /* -> [ ... key val replacer holder key ] */
+		duk_dup(ctx, -4);                           /* -> [ ... key val replacer holder key val ] */
+		duk_call_method(ctx, 2);                    /* -> [ ... key val val' ] */
+		duk_remove(ctx, -2);                        /* -> [ ... key val' ] */
+	}
+
+	/* [ ... key val ] */
+
+	DUK_DDD(DUK_DDDPRINT("value=%!T", (duk_tval *) duk_get_tval(ctx, -1)));
+
+	tv = duk_get_tval(ctx, -1);
+	DUK_ASSERT(tv != NULL);
+	if (DUK_TVAL_IS_OBJECT(tv)) {
+		h = DUK_TVAL_GET_OBJECT(tv);
+		DUK_ASSERT(h != NULL);
+
+		c = (duk_small_int_t) DUK_HOBJECT_GET_CLASS_NUMBER(h);
+		switch ((int) c) {
+		case DUK_HOBJECT_CLASS_NUMBER:
+			DUK_DDD(DUK_DDDPRINT("value is a Number object -> coerce with ToNumber()"));
+			duk_to_number(ctx, -1);
+			break;
+		case DUK_HOBJECT_CLASS_STRING:
+			DUK_DDD(DUK_DDDPRINT("value is a String object -> coerce with ToString()"));
+			duk_to_string(ctx, -1);
+			break;
+#if defined(DUK_USE_JX) || defined(DUK_USE_JC)
+		case DUK_HOBJECT_CLASS_BUFFER:
+		case DUK_HOBJECT_CLASS_POINTER:
+#endif
+		case DUK_HOBJECT_CLASS_BOOLEAN:
+			DUK_DDD(DUK_DDDPRINT("value is a Boolean/Buffer/Pointer object -> get internal value"));
+			duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_VALUE);
+			duk_remove(ctx, -2);
+			break;
+		}
+	}
+
+	/* [ ... key val ] */
+
+	DUK_DDD(DUK_DDDPRINT("value=%!T", (duk_tval *) duk_get_tval(ctx, -1)));
+
+	if (duk_check_type_mask(ctx, -1, js_ctx->mask_for_undefined)) {
+		/* will result in undefined */
+		DUK_DDD(DUK_DDDPRINT("-> will result in undefined (type mask check)"));
+		goto undef;
+	}
+
+	/* functions are detected specially */
+	h = duk_get_hobject(ctx, -1);
+	if (h != NULL && DUK_HOBJECT_IS_CALLABLE(h)) {
+		if (js_ctx->flags & (DUK_JSON_FLAG_EXT_CUSTOM |
+		                     DUK_JSON_FLAG_EXT_COMPATIBLE)) {
+			/* function will be serialized to custom format */
+		} else {
+			/* functions are not serialized, results in undefined */
+			DUK_DDD(DUK_DDDPRINT("-> will result in undefined (function)"));
+			goto undef;
+		}
+	}
+
+	DUK_DDD(DUK_DDDPRINT("-> will not result in undefined"));
+	return 0;
+
+ undef:
+	duk_pop_2(ctx);
+	return 1;
+}
+
+/* The Str(key, holder) operation: encode value, steps 5-10.
+ *
+ * This must not be called unless duk__enc_value1() returns non-zero.
+ * If so, this is guaranteed to produce a non-undefined result.
+ * Non-standard encodings (e.g. for undefined) are only used if
+ * duk__enc_value1() indicates they are accepted; they're not
+ * checked or asserted here again.
+ *
+ * Stack policy: [ ... key val ] -> [ ... ].
+ */
+static void duk__enc_value2(duk_json_enc_ctx *js_ctx) {
+	duk_context *ctx = (duk_context *) js_ctx->thr;
+	duk_tval *tv;
+
+	DUK_DDD(DUK_DDDPRINT("duk__enc_value2: key=%!T, val=%!T",
+	                     (duk_tval *) duk_get_tval(ctx, -2),
+	                     (duk_tval *) duk_get_tval(ctx, -1)));
+
+	/* [ ... key val ] */
+
+	tv = duk_get_tval(ctx, -1);
+	DUK_ASSERT(tv != NULL);
+
+	switch (DUK_TVAL_GET_TAG(tv)) {
+#if defined(DUK_USE_JX) || defined(DUK_USE_JC)
+	/* When JX/JC not in use, duk__enc_value1 will block undefined values. */
+	case DUK_TAG_UNDEFINED: {
+		DUK__EMIT_STRIDX(js_ctx, js_ctx->stridx_custom_undefined);
+		break;
+	}
+#endif
+	case DUK_TAG_NULL: {
+		DUK__EMIT_STRIDX(js_ctx, DUK_STRIDX_LC_NULL);
+		break;
+	}
+	case DUK_TAG_BOOLEAN: {
+		DUK__EMIT_STRIDX(js_ctx, DUK_TVAL_GET_BOOLEAN(tv) ?
+		                 DUK_STRIDX_TRUE : DUK_STRIDX_FALSE);
+		break;
+	}
+#if defined(DUK_USE_JX) || defined(DUK_USE_JC)
+	/* When JX/JC not in use, duk__enc_value1 will block pointer values. */
+	case DUK_TAG_POINTER: {
+		char buf[64];  /* XXX: how to figure correct size? */
+		const char *fmt;
+		void *ptr = DUK_TVAL_GET_POINTER(tv);
+
+		DUK_MEMZERO(buf, sizeof(buf));
+
+		/* The #ifdef clutter here needs to handle the three cases:
+		 * (1) JX+JC, (2) JX only, (3) JC only.
+		 */
+#if defined(DUK_USE_JX) && defined(DUK_USE_JC)
+		if (js_ctx->flag_ext_custom)
+#endif
+#if defined(DUK_USE_JX)
+		{
+			fmt = ptr ? "(%p)" : "(null)";
+		}
+#endif
+#if defined(DUK_USE_JX) && defined(DUK_USE_JC)
+		else
+#endif
+#if defined(DUK_USE_JC)
+		{
+			fmt = ptr ? "{\"_ptr\":\"%p\"}" : "{\"_ptr\":\"null\"}";
+		}
+#endif
+
+		/* When ptr == NULL, the format argument is unused. */
+		DUK_SNPRINTF(buf, sizeof(buf) - 1, fmt, ptr);  /* must not truncate */
+		DUK__EMIT_CSTR(js_ctx, buf);
+		break;
+	}
+#endif  /* DUK_USE_JX || DUK_USE_JC */
+	case DUK_TAG_STRING: {
+		duk_hstring *h = DUK_TVAL_GET_STRING(tv);
+		DUK_ASSERT(h != NULL);
+
+		duk__enc_quote_string(js_ctx, h);
+		break;
+	}
+	case DUK_TAG_OBJECT: {
+		duk_hobject *h = DUK_TVAL_GET_OBJECT(tv);
+		DUK_ASSERT(h != NULL);
+
+#if defined(DUK_USE_JX) || defined(DUK_USE_JC)
+		if (DUK_HOBJECT_IS_CALLABLE(h)) {
+			/* We only get here when doing non-standard JSON encoding */
+			DUK_ASSERT(js_ctx->flag_ext_custom || js_ctx->flag_ext_compatible);
+			DUK__EMIT_STRIDX(js_ctx, js_ctx->stridx_custom_function);
+		} else  /* continues below */
+#endif
+		if (DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_ARRAY) {
+			duk__enc_array(js_ctx);
+		} else {
+			duk__enc_object(js_ctx);
+		}
+		break;
+	}
+#if defined(DUK_USE_JX) || defined(DUK_USE_JC)
+	/* When JX/JC not in use, duk__enc_value1 will block buffer values. */
+	case DUK_TAG_BUFFER: {
+		/* Buffer values are encoded in (lowercase) hex to make the
+		 * binary data readable.  Base64 or similar would be more
+		 * compact but less readable, and the point of JX/JC
+		 * variants is to be as useful to a programmer as possible.
+		 */
+
+		/* The #ifdef clutter here needs to handle the three cases:
+		 * (1) JX+JC, (2) JX only, (3) JC only.
+		 */
+#if defined(DUK_USE_JX) && defined(DUK_USE_JC)
+		if (js_ctx->flag_ext_custom)
+#endif
+#if defined(DUK_USE_JX)
+		{
+			duk_uint8_t *p, *p_end;
+			duk_small_uint_t x;
+			duk_hbuffer *h;
+
+			h = DUK_TVAL_GET_BUFFER(tv);
+			DUK_ASSERT(h != NULL);
+			p = (duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(h);
+			p_end = p + DUK_HBUFFER_GET_SIZE(h);
+			DUK__EMIT_1(js_ctx, DUK_ASC_PIPE);
+			while (p < p_end) {
+				x = *p++;
+				duk_hbuffer_append_byte(js_ctx->thr, js_ctx->h_buf, duk_lc_digits[(x >> 4) & 0x0f]);
+				duk_hbuffer_append_byte(js_ctx->thr, js_ctx->h_buf, duk_lc_digits[x & 0x0f]);
+			}
+			DUK__EMIT_1(js_ctx, DUK_ASC_PIPE);
+		}
+#endif
+#if defined(DUK_USE_JX) && defined(DUK_USE_JC)
+		else
+#endif
+#if defined(DUK_USE_JC)
+		{
+			DUK_ASSERT(js_ctx->flag_ext_compatible);
+			duk_hex_encode(ctx, -1);
+			DUK__EMIT_CSTR(js_ctx, "{\"_buf\":");
+			duk__enc_quote_string(js_ctx, duk_require_hstring(ctx, -1));
+			DUK__EMIT_1(js_ctx, DUK_ASC_RCURLY);
+		}
+#endif
+		break;
+	}
+#endif  /* DUK_USE_JX || DUK_USE_JC */
+	default: {
+		/* number */
+		duk_double_t d;
+		duk_small_int_t c;
+		duk_small_int_t s;
+		duk_small_uint_t stridx;
+		duk_small_uint_t n2s_flags;
+		duk_hstring *h_str;
+
+		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
+		d = DUK_TVAL_GET_NUMBER(tv);
+		c = (duk_small_int_t) DUK_FPCLASSIFY(d);
+		s = (duk_small_int_t) DUK_SIGNBIT(d);
+		DUK_UNREF(s);
+
+		if (DUK_LIKELY(!(c == DUK_FP_INFINITE || c == DUK_FP_NAN))) {
+			DUK_ASSERT(DUK_ISFINITE(d));
+
+#if defined(DUK_USE_JX) || defined(DUK_USE_JC)
+			/* Negative zero needs special handling in JX/JC because
+			 * it would otherwise serialize to '0', not '-0'.
+			 */
+			if (DUK_UNLIKELY(c == DUK_FP_ZERO && s != 0 &&
+			                 (js_ctx->flag_ext_custom || js_ctx->flag_ext_compatible))) {
+				duk_push_hstring_stridx(ctx, DUK_STRIDX_MINUS_ZERO);  /* '-0' */
+			} else
+#endif  /* DUK_USE_JX || DUK_USE_JC */
+			{
+				n2s_flags = 0;
+				/* [ ... number ] -> [ ... string ] */
+				duk_numconv_stringify(ctx, 10 /*radix*/, 0 /*digits*/, n2s_flags);
+			}
+			h_str = duk_to_hstring(ctx, -1);
+			DUK_ASSERT(h_str != NULL);
+			DUK__EMIT_HSTR(js_ctx, h_str);
+			break;
+		}
+
+#if defined(DUK_USE_JX) || defined(DUK_USE_JC)
+		if (!(js_ctx->flags & (DUK_JSON_FLAG_EXT_CUSTOM |
+		                       DUK_JSON_FLAG_EXT_COMPATIBLE))) {
+			stridx = DUK_STRIDX_LC_NULL;
+		} else if (c == DUK_FP_NAN) {
+			stridx = js_ctx->stridx_custom_nan;
+		} else if (s == 0) {
+			stridx = js_ctx->stridx_custom_posinf;
+		} else {
+			stridx = js_ctx->stridx_custom_neginf;
+		}
+#else
+		stridx = DUK_STRIDX_LC_NULL;
+#endif
+		DUK__EMIT_STRIDX(js_ctx, stridx);
+		break;
+	}
+	}
+
+	/* [ ... key val ] -> [ ... ] */
+
+	duk_pop_2(ctx);
+}
+
+/* E5 Section 15.12.3, main algorithm, step 4.b.ii steps 1-4. */
+static duk_bool_t duk__enc_allow_into_proplist(duk_tval *tv) {
+	duk_hobject *h;
+	duk_small_int_t c;
+
+	DUK_ASSERT(tv != NULL);
+	if (DUK_TVAL_IS_STRING(tv) || DUK_TVAL_IS_NUMBER(tv)) {
+		return 1;
+	} else if (DUK_TVAL_IS_OBJECT(tv)) {
+		h = DUK_TVAL_GET_OBJECT(tv);
+		DUK_ASSERT(h != NULL);
+		c = (duk_small_int_t) DUK_HOBJECT_GET_CLASS_NUMBER(h);
+		if (c == DUK_HOBJECT_CLASS_STRING || c == DUK_HOBJECT_CLASS_NUMBER) {
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ *  Top level wrappers
+ */
+
+void duk_bi_json_parse_helper(duk_context *ctx,
+                              duk_idx_t idx_value,
+                              duk_idx_t idx_reviver,
+                              duk_small_uint_t flags) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_json_dec_ctx js_ctx_alloc;
+	duk_json_dec_ctx *js_ctx = &js_ctx_alloc;
+	duk_hstring *h_text;
+#ifdef DUK_USE_ASSERTIONS
+	duk_idx_t entry_top = duk_get_top(ctx);
+#endif
+
+	/* negative top-relative indices not allowed now */
+	DUK_ASSERT(idx_value == DUK_INVALID_INDEX || idx_value >= 0);
+	DUK_ASSERT(idx_reviver == DUK_INVALID_INDEX || idx_reviver >= 0);
+
+	DUK_DDD(DUK_DDDPRINT("JSON parse start: text=%!T, reviver=%!T, flags=0x%08lx, stack_top=%ld",
+	                     (duk_tval *) duk_get_tval(ctx, idx_value),
+	                     (duk_tval *) duk_get_tval(ctx, idx_reviver),
+	                     (unsigned long) flags,
+	                     (long) duk_get_top(ctx)));
+
+	DUK_MEMZERO(&js_ctx_alloc, sizeof(js_ctx_alloc));
+	js_ctx->thr = thr;
+#ifdef DUK_USE_EXPLICIT_NULL_INIT
+	/* nothing now */
+#endif
+	js_ctx->recursion_limit = DUK_JSON_DEC_RECURSION_LIMIT;
+
+	/* Flag handling currently assumes that flags are consistent.  This is OK
+	 * because the call sites are now strictly controlled.
+	 */
+
+	js_ctx->flags = flags;
+#ifdef DUK_USE_JX
+	js_ctx->flag_ext_custom = flags & DUK_JSON_FLAG_EXT_CUSTOM;
+#endif
+#ifdef DUK_USE_JC
+	js_ctx->flag_ext_compatible = flags & DUK_JSON_FLAG_EXT_COMPATIBLE;
+#endif
+
+	h_text = duk_to_hstring(ctx, idx_value);  /* coerce in-place */
+	DUK_ASSERT(h_text != NULL);
+
+	js_ctx->p = (duk_uint8_t *) DUK_HSTRING_GET_DATA(h_text);
+	js_ctx->p_end = ((duk_uint8_t *) DUK_HSTRING_GET_DATA(h_text)) +
+	                DUK_HSTRING_GET_BYTELEN(h_text);
+
+	duk__dec_value(js_ctx);  /* -> [ ... value ] */
+
+	/* Trailing whitespace has been eaten by duk__dec_value(), so if
+	 * we're not at end of input here, it's a SyntaxError.
+	 */
+
+	if (js_ctx->p != js_ctx->p_end) {
+		DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_INVALID_JSON);
+	}
+
+	if (duk_is_callable(ctx, idx_reviver)) {
+		DUK_DDD(DUK_DDDPRINT("applying reviver: %!T",
+		                     (duk_tval *) duk_get_tval(ctx, idx_reviver)));
+
+		js_ctx->idx_reviver = idx_reviver;
+
+		duk_push_object(ctx);
+		duk_dup(ctx, -2);  /* -> [ ... val root val ] */
+		duk_put_prop_stridx(ctx, -2, DUK_STRIDX_EMPTY_STRING);  /* default attrs ok */
+		duk_push_hstring_stridx(ctx, DUK_STRIDX_EMPTY_STRING);  /* -> [ ... val root "" ] */
+
+		DUK_DDD(DUK_DDDPRINT("start reviver walk, root=%!T, name=%!T",
+		                     (duk_tval *) duk_get_tval(ctx, -2),
+		                     (duk_tval *) duk_get_tval(ctx, -1)));
+
+		duk__dec_reviver_walk(js_ctx);  /* [ ... val root "" ] -> [ ... val val' ] */
+		duk_remove(ctx, -2);            /* -> [ ... val' ] */
+	} else {
+		DUK_DDD(DUK_DDDPRINT("reviver does not exist or is not callable: %!T",
+		                     (duk_tval *) duk_get_tval(ctx, idx_reviver)));
+	}
+
+	/* Final result is at stack top. */
+
+	DUK_DDD(DUK_DDDPRINT("JSON parse end: text=%!T, reviver=%!T, flags=0x%08lx, result=%!T, stack_top=%ld",
+	                     (duk_tval *) duk_get_tval(ctx, idx_value),
+	                     (duk_tval *) duk_get_tval(ctx, idx_reviver),
+	                     (unsigned long) flags,
+	                     (duk_tval *) duk_get_tval(ctx, -1),
+	                     (long) duk_get_top(ctx)));
+
+	DUK_ASSERT(duk_get_top(ctx) == entry_top + 1);
+}
+
+void duk_bi_json_stringify_helper(duk_context *ctx,
+                                  duk_idx_t idx_value,
+                                  duk_idx_t idx_replacer,
+                                  duk_idx_t idx_space,
+                                  duk_small_uint_t flags) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_json_enc_ctx js_ctx_alloc;
+	duk_json_enc_ctx *js_ctx = &js_ctx_alloc;
+	duk_hobject *h;
+	duk_bool_t undef;
+	duk_idx_t idx_holder;
+	duk_idx_t entry_top;
+
+	/* negative top-relative indices not allowed now */
+	DUK_ASSERT(idx_value == DUK_INVALID_INDEX || idx_value >= 0);
+	DUK_ASSERT(idx_replacer == DUK_INVALID_INDEX || idx_replacer >= 0);
+	DUK_ASSERT(idx_space == DUK_INVALID_INDEX || idx_space >= 0);
+
+	DUK_DDD(DUK_DDDPRINT("JSON stringify start: value=%!T, replacer=%!T, space=%!T, flags=0x%08lx, stack_top=%ld",
+	                     (duk_tval *) duk_get_tval(ctx, idx_value),
+	                     (duk_tval *) duk_get_tval(ctx, idx_replacer),
+	                     (duk_tval *) duk_get_tval(ctx, idx_space),
+	                     (unsigned long) flags,
+	                     (long) duk_get_top(ctx)));
+
+	entry_top = duk_get_top(ctx);
+
+	/*
+	 *  Context init
+	 */
+
+	DUK_MEMZERO(&js_ctx_alloc, sizeof(js_ctx_alloc));
+	js_ctx->thr = thr;
+#ifdef DUK_USE_EXPLICIT_NULL_INIT
+	js_ctx->h_replacer = NULL;
+	js_ctx->h_gap = NULL;
+	js_ctx->h_indent = NULL;
+#endif
+	js_ctx->idx_proplist = -1;
+	js_ctx->recursion_limit = DUK_JSON_ENC_RECURSION_LIMIT;
+
+	/* Flag handling currently assumes that flags are consistent.  This is OK
+	 * because the call sites are now strictly controlled.
+	 */
+
+	js_ctx->flags = flags;
+	js_ctx->flag_ascii_only = flags & DUK_JSON_FLAG_ASCII_ONLY;
+	js_ctx->flag_avoid_key_quotes = flags & DUK_JSON_FLAG_AVOID_KEY_QUOTES;
+#ifdef DUK_USE_JX
+	js_ctx->flag_ext_custom = flags & DUK_JSON_FLAG_EXT_CUSTOM;
+#endif
+#ifdef DUK_USE_JC
+	js_ctx->flag_ext_compatible = flags & DUK_JSON_FLAG_EXT_COMPATIBLE;
+#endif
+
+	/* The #ifdef clutter here handles the JX/JC enable/disable
+	 * combinations properly.
+	 */
+#if defined(DUK_USE_JX) || defined(DUK_USE_JC)
+#if defined(DUK_USE_JX)
+	if (flags & DUK_JSON_FLAG_EXT_CUSTOM) {
+		js_ctx->stridx_custom_undefined = DUK_STRIDX_LC_UNDEFINED;
+		js_ctx->stridx_custom_nan = DUK_STRIDX_NAN;
+		js_ctx->stridx_custom_neginf = DUK_STRIDX_MINUS_INFINITY;
+		js_ctx->stridx_custom_posinf = DUK_STRIDX_INFINITY;
+		js_ctx->stridx_custom_function =
+		        (flags & DUK_JSON_FLAG_AVOID_KEY_QUOTES) ?
+		                DUK_STRIDX_JSON_EXT_FUNCTION2 :
+		                DUK_STRIDX_JSON_EXT_FUNCTION1;
+	}
+#endif  /* DUK_USE_JX */
+#if defined(DUK_USE_JX) && defined(DUK_USE_JC)
+	else
+#endif  /* DUK_USE_JX && DUK_USE_JC */
+#if defined(DUK_USE_JC)
+	if (js_ctx->flags & DUK_JSON_FLAG_EXT_COMPATIBLE) {
+		js_ctx->stridx_custom_undefined = DUK_STRIDX_JSON_EXT_UNDEFINED;
+		js_ctx->stridx_custom_nan = DUK_STRIDX_JSON_EXT_NAN;
+		js_ctx->stridx_custom_neginf = DUK_STRIDX_JSON_EXT_NEGINF;
+		js_ctx->stridx_custom_posinf = DUK_STRIDX_JSON_EXT_POSINF;
+		js_ctx->stridx_custom_function = DUK_STRIDX_JSON_EXT_FUNCTION1;
+	}
+#endif  /* DUK_USE_JC */
+#endif  /* DUK_USE_JX || DUK_USE_JC */
+
+#if defined(DUK_USE_JX) || defined(DUK_USE_JC)
+	if (js_ctx->flags & (DUK_JSON_FLAG_EXT_CUSTOM |
+	                     DUK_JSON_FLAG_EXT_COMPATIBLE)) {
+		DUK_ASSERT(js_ctx->mask_for_undefined == 0);  /* already zero */
+	}
+	else
+#endif  /* DUK_USE_JX || DUK_USE_JC */
+	{
+		js_ctx->mask_for_undefined = DUK_TYPE_MASK_UNDEFINED |
+		                             DUK_TYPE_MASK_POINTER |
+		                             DUK_TYPE_MASK_BUFFER;
+	}
+
+	(void) duk_push_dynamic_buffer(ctx, 0);
+	js_ctx->h_buf = (duk_hbuffer_dynamic *) duk_get_hbuffer(ctx, -1);
+	DUK_ASSERT(js_ctx->h_buf != NULL);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(js_ctx->h_buf));
+
+	js_ctx->idx_loop = duk_push_object_internal(ctx);
+	DUK_ASSERT(js_ctx->idx_loop >= 0);
+
+	/* [ ... buf loop ] */
+
+	/*
+	 *  Process replacer/proplist (2nd argument to JSON.stringify)
+	 */
+
+	h = duk_get_hobject(ctx, idx_replacer);
+	if (h != NULL) {
+		if (DUK_HOBJECT_IS_CALLABLE(h)) {
+			js_ctx->h_replacer = h;
+		} else if (DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_ARRAY) {
+			/* Here the specification requires correct array index enumeration
+			 * which is a bit tricky for sparse arrays (it is handled by the
+			 * enum setup code).  We now enumerate ancestors too, although the
+			 * specification is not very clear on whether that is required.
+			 */
+
+			duk_uarridx_t plist_idx = 0;
+			duk_small_uint_t enum_flags;
+
+			js_ctx->idx_proplist = duk_push_array(ctx);  /* FIXME: array internal? */
+
+			enum_flags = DUK_ENUM_ARRAY_INDICES_ONLY |
+			             DUK_ENUM_SORT_ARRAY_INDICES;  /* expensive flag */
+			duk_enum(ctx, idx_replacer, enum_flags);
+			while (duk_next(ctx, -1 /*enum_index*/, 1 /*get_value*/)) {
+				/* [ ... proplist enum_obj key val ] */
+				if (duk__enc_allow_into_proplist(duk_get_tval(ctx, -1))) {
+					/* FIXME: duplicates should be eliminated here */
+					DUK_DDD(DUK_DDDPRINT("proplist enum: key=%!T, val=%!T --> accept",
+					                     (duk_tval *) duk_get_tval(ctx, -2),
+					                     (duk_tval *) duk_get_tval(ctx, -1)));
+					duk_to_string(ctx, -1);  /* extra coercion of strings is OK */
+					duk_put_prop_index(ctx, -4, plist_idx);  /* -> [ ... proplist enum_obj key ] */
+					plist_idx++;
+					duk_pop(ctx);
+				} else {
+					DUK_DDD(DUK_DDDPRINT("proplist enum: key=%!T, val=%!T --> reject",
+					                     (duk_tval *) duk_get_tval(ctx, -2),
+					                     (duk_tval *) duk_get_tval(ctx, -1)));
+					duk_pop_2(ctx);
+				}
+                        }
+                        duk_pop(ctx);  /* pop enum */
+
+			/* [ ... proplist ] */
+		}
+	}
+
+	/* [ ... buf loop (proplist) ] */
+
+	/*
+	 *  Process space (3rd argument to JSON.stringify)
+	 */
+
+	h = duk_get_hobject(ctx, idx_space);
+	if (h != NULL) {
+		int c = DUK_HOBJECT_GET_CLASS_NUMBER(h);
+		if (c == DUK_HOBJECT_CLASS_NUMBER) {
+			duk_to_number(ctx, idx_space);
+		} else if (c == DUK_HOBJECT_CLASS_STRING) {
+			duk_to_string(ctx, idx_space);
+		}
+	}
+
+	if (duk_is_number(ctx, idx_space)) {
+		duk_small_int_t nspace;
+		/* spaces[] must be static to allow initializer with old compilers like BCC */
+		static const char spaces[10] = {
+			DUK_ASC_SPACE, DUK_ASC_SPACE, DUK_ASC_SPACE, DUK_ASC_SPACE,
+			DUK_ASC_SPACE, DUK_ASC_SPACE, DUK_ASC_SPACE, DUK_ASC_SPACE,
+			DUK_ASC_SPACE, DUK_ASC_SPACE
+		};  /* FIXME:helper */
+
+		/* ToInteger() coercion; NaN -> 0, infinities are clamped to 0 and 10 */
+		nspace = (duk_small_int_t) duk_to_int_clamped(ctx, idx_space, 0 /*minval*/, 10 /*maxval*/);
+		DUK_ASSERT(nspace >= 0 && nspace <= 10);
+
+		duk_push_lstring(ctx, spaces, (duk_size_t) nspace);
+		js_ctx->h_gap = duk_get_hstring(ctx, -1);
+		DUK_ASSERT(js_ctx->h_gap != NULL);
+	} else if (duk_is_string(ctx, idx_space)) {
+		/* FIXME: substring in-place at idx_place? */
+		duk_dup(ctx, idx_space);
+		duk_substring(ctx, -1, 0, 10);  /* clamp to 10 chars */
+		js_ctx->h_gap = duk_get_hstring(ctx, -1);
+		DUK_ASSERT(js_ctx->h_gap != NULL);
+	} else {
+		/* nop */
+	}
+
+	if (js_ctx->h_gap != NULL) {
+		/* if gap is empty, behave as if not given at all */
+		if (DUK_HSTRING_GET_CHARLEN(js_ctx->h_gap) == 0) {
+			js_ctx->h_gap = NULL;
+		} else {
+			/* set 'indent' only if it will actually increase */
+			js_ctx->h_indent = DUK_HTHREAD_STRING_EMPTY_STRING(thr);
+		}
+	}
+
+	DUK_ASSERT((js_ctx->h_gap == NULL && js_ctx->h_indent == NULL) ||
+	           (js_ctx->h_gap != NULL && js_ctx->h_indent != NULL));
+
+	/* [ ... buf loop (proplist) (gap) ] */
+
+	/*
+	 *  Create wrapper object and serialize
+	 */
+
+	idx_holder = duk_push_object(ctx);
+	duk_dup(ctx, idx_value);
+	duk_put_prop_stridx(ctx, -2, DUK_STRIDX_EMPTY_STRING);
+
+	DUK_DDD(DUK_DDDPRINT("before: flags=0x%08lx, buf=%!O, loop=%!T, replacer=%!O, "
+	                     "proplist=%!T, gap=%!O, indent=%!O, holder=%!T",
+	                     (unsigned long) js_ctx->flags,
+	                     (duk_heaphdr *) js_ctx->h_buf,
+	                     (duk_tval *) duk_get_tval(ctx, js_ctx->idx_loop),
+	                     (duk_heaphdr *) js_ctx->h_replacer,
+	                     (duk_tval *) (js_ctx->idx_proplist >= 0 ? duk_get_tval(ctx, js_ctx->idx_proplist) : NULL),
+	                     (duk_heaphdr *) js_ctx->h_gap,
+	                     (duk_heaphdr *) js_ctx->h_indent,
+	                     (duk_tval *) duk_get_tval(ctx, -1)));
+	
+	/* serialize the wrapper with empty string key */
+
+	duk_push_hstring_stridx(ctx, DUK_STRIDX_EMPTY_STRING);
+
+	/* [ ... buf loop (proplist) (gap) holder "" ] */
+
+	undef = duk__enc_value1(js_ctx, idx_holder);  /* [ ... holder key ] -> [ ... holder key val ] */
+
+	DUK_DDD(DUK_DDDPRINT("after: flags=0x%08lx, buf=%!O, loop=%!T, replacer=%!O, "
+	                     "proplist=%!T, gap=%!O, indent=%!O, holder=%!T",
+	                     (unsigned long) js_ctx->flags,
+	                     (duk_heaphdr *) js_ctx->h_buf,
+	                     (duk_tval *) duk_get_tval(ctx, js_ctx->idx_loop),
+	                     (duk_heaphdr *) js_ctx->h_replacer,
+	                     (duk_tval *) (js_ctx->idx_proplist >= 0 ? duk_get_tval(ctx, js_ctx->idx_proplist) : NULL),
+	                     (duk_heaphdr *) js_ctx->h_gap,
+	                     (duk_heaphdr *) js_ctx->h_indent,
+	                     (duk_tval *) duk_get_tval(ctx, -3)));
+
+	if (undef) {
+		/*
+		 *  Result is undefined
+		 */
+
+		duk_push_undefined(ctx);
+	} else {
+		/*
+		 *  Finish and convert buffer to result string
+		 */
+
+		duk__enc_value2(js_ctx);  /* [ ... key val ] -> [ ... ] */
+		DUK_ASSERT(js_ctx->h_buf != NULL);
+		duk_push_hbuffer(ctx, (duk_hbuffer *) js_ctx->h_buf);
+		duk_to_string(ctx, -1);
+	}
+
+	/* The stack has a variable shape here, so force it to the
+	 * desired one explicitly.
+	 */
+
+	duk_replace(ctx, entry_top);
+	duk_set_top(ctx, entry_top + 1);
+
+	DUK_DDD(DUK_DDDPRINT("JSON stringify end: value=%!T, replacer=%!T, space=%!T, "
+	                     "flags=0x%08lx, result=%!T, stack_top=%ld",
+	                     (duk_tval *) duk_get_tval(ctx, idx_value),
+	                     (duk_tval *) duk_get_tval(ctx, idx_replacer),
+	                     (duk_tval *) duk_get_tval(ctx, idx_space),
+	                     (unsigned long) flags,
+	                     (duk_tval *) duk_get_tval(ctx, -1),
+	                     (long) duk_get_top(ctx)));
+
+	DUK_ASSERT(duk_get_top(ctx) == entry_top + 1);
+}
+
+/*
+ *  Entry points
+ */
+
+duk_ret_t duk_bi_json_object_parse(duk_context *ctx) {
+	duk_bi_json_parse_helper(ctx,
+	                         0 /*idx_value*/,
+	                         1 /*idx_replacer*/,
+	                         0 /*flags*/);
+	return 1;
+}
+
+duk_ret_t duk_bi_json_object_stringify(duk_context *ctx) {
+	duk_bi_json_stringify_helper(ctx,
+	                             0 /*idx_value*/,
+	                             1 /*idx_replacer*/,
+	                             2 /*idx_space*/,
+	                             0 /*flags*/);
+	return 1;
+}
+#line 1 "duk_bi_logger.c"
+/*
+ *  Logging support
+ */
+
+/* include removed: duk_internal.h */
+
+/* 3-letter log level strings */
+static const duk_uint8_t duk__log_level_strings[] = {
+	(duk_uint8_t) DUK_ASC_UC_T, (duk_uint8_t) DUK_ASC_UC_R, (duk_uint8_t) DUK_ASC_UC_C,
+	(duk_uint8_t) DUK_ASC_UC_D, (duk_uint8_t) DUK_ASC_UC_B, (duk_uint8_t) DUK_ASC_UC_G,
+	(duk_uint8_t) DUK_ASC_UC_I, (duk_uint8_t) DUK_ASC_UC_N, (duk_uint8_t) DUK_ASC_UC_F,
+	(duk_uint8_t) DUK_ASC_UC_W, (duk_uint8_t) DUK_ASC_UC_R, (duk_uint8_t) DUK_ASC_UC_N,
+	(duk_uint8_t) DUK_ASC_UC_E, (duk_uint8_t) DUK_ASC_UC_R, (duk_uint8_t) DUK_ASC_UC_R,
+	(duk_uint8_t) DUK_ASC_UC_F, (duk_uint8_t) DUK_ASC_UC_T, (duk_uint8_t) DUK_ASC_UC_L
+};
+
+/* Constructor */
+duk_ret_t duk_bi_logger_constructor(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_int_t nargs;  /* FIXME: type */
+
+	/* Calling as a non-constructor is not meaningful. */
+	if (!duk_is_constructor_call(ctx)) {
+		return DUK_RET_TYPE_ERROR;
+	}
+
+	nargs = duk_get_top(ctx);
+	duk_set_top(ctx, 1);
+
+	duk_push_this(ctx);
+
+	/* [ name this ] */
+
+	if (nargs == 0) {
+		/* Automatic defaulting of logger name from caller.  This would
+		 * work poorly with tail calls, but constructor calls are currently
+		 * never tail calls, so tail calls are not an issue now.
+		 */
+
+		if (thr->callstack_top >= 2) {
+			duk_activation *act_caller = thr->callstack + thr->callstack_top - 2;
+			if (act_caller->func) {
+				/* Stripping the filename might be a good idea
+				 * ("/foo/bar/quux.js" -> logger name "quux"),
+				 * but now used verbatim.
+				 */
+				duk_push_hobject(ctx, act_caller->func);
+				duk_get_prop_stridx(ctx, -1, DUK_STRIDX_FILE_NAME);
+				duk_replace(ctx, 0);
+			}
+		}
+	}
+	/* the stack is unbalanced here on purpose; we only rely on the
+	 * initial two values: [ name this ].
+	 */
+
+	if (duk_is_string(ctx, 0)) {
+		duk_dup(ctx, 0);
+		duk_put_prop_stridx(ctx, 1, DUK_STRIDX_LC_N);
+	} else {
+		/* don't set 'n' at all, inherited value is used as name */
+	}
+
+	duk_compact(ctx, 1);
+
+	return 0;  /* keep default instance */
+}
+
+/* Default function to format objects.  Tries to use toLogString() but falls
+ * back to toString().  Any errors are propagated out without catching.
+ */
+duk_ret_t duk_bi_logger_prototype_fmt(duk_context *ctx) {
+	if (duk_get_prop_stridx(ctx, 0, DUK_STRIDX_TO_LOG_STRING)) {
+		/* [ arg toLogString ] */
+
+		duk_dup(ctx, 0);
+		duk_call_method(ctx, 0);
+
+		/* [ arg result ] */
+		return 1;
+	}
+
+	/* [ arg undefined ] */
+	duk_pop(ctx);
+	duk_to_string(ctx, 0);
+	return 1;
+}
+
+/* Default function to write a formatted log line.  Writes to stderr,
+ * appending a newline to the log line.
+ *
+ * The argument is a buffer whose visible size contains the log message.
+ * This function should avoid coercing the buffer to a string to avoid
+ * string table traffic.
+ */
+duk_ret_t duk_bi_logger_prototype_raw(duk_context *ctx) {
+	const char *data;
+	duk_size_t data_len;
+
+	DUK_UNREF(ctx);
+	DUK_UNREF(data);
+	DUK_UNREF(data_len);
+
+#ifdef DUK_USE_FILE_IO
+	data = (const char *) duk_require_buffer(ctx, 0, &data_len);
+	DUK_FWRITE((const void *) data, 1, data_len, stderr);
+	DUK_FPUTC((int) '\n', stderr);
+	DUK_FFLUSH(stderr);
+#else
+	/* nop */
+#endif
+	return 0;
+}
+
+/* Log frontend shared helper, magic value indicates log level.  Provides
+ * frontend functions: trace(), debug(), info(), warn(), error(), fatal().
+ * This needs to have small footprint, reasonable performance, minimal
+ * memory churn, etc.
+ */
+duk_ret_t duk_bi_logger_prototype_log_shared(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_double_t now;
+	duk_small_int_t entry_lev = duk_get_magic(ctx);
+	duk_small_int_t logger_lev;
+	duk_int_t nargs;
+	duk_int_t i;
+	duk_size_t tot_len;
+	const duk_uint8_t *arg_str;
+	duk_size_t arg_len;
+	duk_uint8_t *buf, *p;
+	const duk_uint8_t *q;
+	duk_uint8_t date_buf[DUK_BI_DATE_ISO8601_BUFSIZE];
+	duk_size_t date_len;
+	duk_small_int_t rc;
+
+	DUK_ASSERT(entry_lev >= 0 && entry_lev <= 5);
+
+	/* XXX: sanitize to printable (and maybe ASCII) */
+	/* XXX: better multiline */
+
+	/*
+	 *  Logger arguments are:
+	 *
+	 *    magic: log level (0-5)
+	 *    this: logger
+	 *    stack: plain log args
+	 *
+	 *  We want to minimize memory churn so a two-pass approach
+	 *  is used: first pass formats arguments and computes final
+	 *  string length, second pass copies strings either into a
+	 *  pre-allocated and reused buffer (short messages) or into a
+	 *  newly allocated fixed buffer.  If the backend function plays
+	 *  nice, it won't coerce the buffer to a string (and thus
+	 *  intern it).
+	 */
+
+	nargs = duk_get_top(ctx);
+
+	/* [ arg1 ... argN this ] */
+
+	/*
+	 *  Log level check
+	 */
+
+	duk_push_this(ctx);
+
+	duk_get_prop_stridx(ctx, -1, DUK_STRIDX_LC_L);
+	logger_lev = (duk_small_int_t) duk_get_int(ctx, -1);
+	if (entry_lev < logger_lev) {
+		return 0;
+	}
+	/* log level could be popped but that's not necessary */
+
+	now = duk_bi_date_get_now(ctx);
+	duk_bi_date_format_timeval(now, date_buf);
+	date_len = DUK_STRLEN((const char *) date_buf);
+
+	duk_get_prop_stridx(ctx, -2, DUK_STRIDX_LC_N);
+	duk_to_string(ctx, -1);
+	DUK_ASSERT(duk_is_string(ctx, -1));
+
+	/* [ arg1 ... argN this loggerLevel loggerName ] */
+
+	/*
+	 *  Pass 1
+	 */
+
+	/* Line format: <time> <entryLev> <loggerName>: <msg> */
+
+	tot_len = 0;
+	tot_len += 3 +  /* separators: space, space, colon */
+	           3 +  /* level string */
+	           date_len +  /* time */
+	           duk_get_length(ctx, -1);  /* loggerName */
+
+	for (i = 0; i < nargs; i++) {
+		/* When formatting an argument to a string, errors may happen from multiple
+		 * causes.  In general we want to catch obvious errors like a toLogString()
+		 * throwing an error, but we don't currently try to catch every possible
+		 * error.  In particular, internal errors (like out of memory or stack) are
+		 * not caught.  Also, we expect Error toString() to not throw an error.
+		 */
+		if (duk_is_object(ctx, i)) {
+			/* duk_pcall_prop() may itself throw an error, but we're content
+			 * in catching the obvious errors (like toLogString() throwing an
+			 * error).
+			 */
+			duk_push_hstring_stridx(ctx, DUK_STRIDX_FMT);
+			duk_dup(ctx, i);
+			/* [ arg1 ... argN this loggerLevel loggerName 'fmt' arg ] */
+			/* call: this.fmt(arg) */
+			rc = duk_pcall_prop(ctx, -5 /*obj_index*/, 1 /*nargs*/);
+			if (rc) {
+				/* Keep the error as the result (coercing it might fail below,
+				 * but we don't catch that now).
+				 */
+				;
+			}
+			duk_replace(ctx, i);
+		}
+		(void) duk_to_lstring(ctx, i, &arg_len);
+		tot_len++;  /* sep (even before first one) */
+		tot_len += arg_len;
+	}
+
+	/*
+	 *  Pass 2
+	 */
+
+	if (tot_len <= DUK_BI_LOGGER_SHORT_MSG_LIMIT) {
+		duk_hbuffer_dynamic *h_buf;
+
+		DUK_DDD(DUK_DDDPRINT("reuse existing small log message buffer, tot_len %ld", (long) tot_len));
+
+		/* We can assert for all buffer properties because user code
+		 * never has access to heap->log_buffer.
+		 */
+
+		DUK_ASSERT(thr != NULL);
+		DUK_ASSERT(thr->heap != NULL);
+		h_buf = thr->heap->log_buffer;
+		DUK_ASSERT(h_buf != NULL);
+		DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC((duk_hbuffer *) h_buf));
+		DUK_ASSERT(DUK_HBUFFER_DYNAMIC_GET_USABLE_SIZE(h_buf) == DUK_BI_LOGGER_SHORT_MSG_LIMIT);
+
+		/* Set buffer 'visible size' to actual message length and
+		 * push it to the stack.
+		 */
+
+		DUK_HBUFFER_SET_SIZE((duk_hbuffer *) h_buf, tot_len);
+		duk_push_hbuffer(ctx, (duk_hbuffer *) h_buf);
+		buf = (duk_uint8_t *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(h_buf);
+	} else {
+		DUK_DDD(DUK_DDDPRINT("use a one-off large log message buffer, tot_len %ld", (long) tot_len));
+		buf = (duk_uint8_t *) duk_push_fixed_buffer(ctx, tot_len);
+	}
+	DUK_ASSERT(buf != NULL);
+	p = buf;
+
+	DUK_MEMCPY((void *) p, (void *) date_buf, date_len);
+	p += date_len;
+	*p++ = (duk_uint8_t) DUK_ASC_SPACE;
+
+	q = duk__log_level_strings + (entry_lev * 3);
+	DUK_MEMCPY((void *) p, (void *) q, (duk_size_t) 3);
+	p += 3;
+
+	*p++ = (duk_uint8_t) DUK_ASC_SPACE;
+
+	arg_str = (const duk_uint8_t *) duk_get_lstring(ctx, -2, &arg_len);
+	DUK_MEMCPY((void *) p, (const void *) arg_str, arg_len);
+	p += arg_len;
+
+	*p++ = (duk_uint8_t) DUK_ASC_COLON;
+
+	for (i = 0; i < nargs; i++) {
+		*p++ = (duk_uint8_t) DUK_ASC_SPACE;
+
+		arg_str = (const duk_uint8_t *) duk_get_lstring(ctx, i, &arg_len);
+		DUK_ASSERT(arg_str != NULL);
+		DUK_MEMCPY((void *) p, (const void *) arg_str, arg_len);
+		p += arg_len;
+	}
+	DUK_ASSERT(buf + tot_len == p);
+
+	/* [ arg1 ... argN this loggerLevel loggerName buffer ] */
+
+	/* Call this.raw(msg); look up through the instance allows user to override
+	 * the raw() function in the instance or in the prototype for maximum
+	 * flexibility.
+	 */
+	duk_push_hstring_stridx(ctx, DUK_STRIDX_RAW);
+	duk_dup(ctx, -2);
+	/* [ arg1 ... argN this loggerLevel loggerName buffer 'raw' buffer ] */
+	duk_call_prop(ctx, -6, 1);  /* this.raw(buffer) */
+
+	return 0;
+}
+#line 1 "duk_bi_math.c"
+/*
+ *  Math built-ins
+ */
+
+/* include removed: duk_internal.h */
+
+#if defined(DUK_USE_MATH_BUILTIN)
+
+/*
+ *  Use static helpers which can work with math.h functions matching
+ *  the following signatures. This is not portable if any of these math
+ *  functions is actually a macro.
+ *
+ *  Typing here is intentionally 'double' wherever values interact with
+ *  the standard library APIs.
+ */
+
+typedef double (*duk__one_arg_func)(double);
+typedef double (*duk__two_arg_func)(double, double);
+
+static duk_ret_t duk__math_minmax(duk_context *ctx, duk_double_t initial, duk__two_arg_func min_max) {
+	duk_idx_t n = duk_get_top(ctx);
+	duk_idx_t i;
+	duk_double_t res = initial;
+	duk_double_t t;
+
+	/*
+	 *  Note: fmax() does not match the E5 semantics.  E5 requires
+	 *  that if -any- input to Math.max() is a NaN, the result is a
+	 *  NaN.  fmax() will return a NaN only if -both- inputs are NaN.
+	 *  Same applies to fmin().
+	 *
+	 *  Note: every input value must be coerced with ToNumber(), even
+	 *  if we know the result will be a NaN anyway: ToNumber() may have
+	 *  side effects for which even order of evaluation matters.
+	 */
+
+	for (i = 0; i < n; i++) {
+		t = duk_to_number(ctx, i);
+		if (DUK_FPCLASSIFY(t) == DUK_FP_NAN || DUK_FPCLASSIFY(res) == DUK_FP_NAN) {
+			/* Note: not normalized, but duk_push_number() will normalize */
+			res = (duk_double_t) DUK_DOUBLE_NAN;
+		} else {
+			res = (duk_double_t) min_max(res, (double) t);
+		}
+	}
+
+	duk_push_number(ctx, res);
+	return 1;
+}
+
+static double duk__fmin_fixed(double x, double y) {
+	/* fmin() with args -0 and +0 is not guaranteed to return
+	 * -0 as Ecmascript requires.
+	 */
+	if (x == 0 && y == 0) {
+		/* XXX: what's the safest way of creating a negative zero? */
+		if (DUK_SIGNBIT(x) != 0 || DUK_SIGNBIT(y) != 0) {
+			return -0.0;
+		} else {
+			return +0.0;
+		}
+	}
+#ifdef DUK_USE_MATH_FMIN
+	return DUK_FMIN(x, y);
+#else
+	return (x < y ? x : y);
+#endif
+}
+
+static double duk__fmax_fixed(double x, double y) {
+	/* fmax() with args -0 and +0 is not guaranteed to return
+	 * +0 as Ecmascript requires.
+	 */
+	if (x == 0 && y == 0) {
+		if (DUK_SIGNBIT(x) == 0 || DUK_SIGNBIT(y) == 0) {
+			return +0.0;
+		} else {
+			return -0.0;
+		}
+	}
+#ifdef DUK_USE_MATH_FMAX
+	return DUK_FMAX(x, y);
+#else
+	return (x > y ? x : y);
+#endif
+}
+
+static double duk__round_fixed(double x) {
+	/* Numbers half-way between integers must be rounded towards +Infinity,
+	 * e.g. -3.5 must be rounded to -3 (not -4).  When rounded to zero, zero
+	 * sign must be set appropriately.  E5.1 Section 15.8.2.15.
+	 *
+	 * Note that ANSI C round() is "round to nearest integer, away from zero",
+	 * which is incorrect for negative values.  Here we make do with floor().
+	 */
+
+	duk_small_int_t c = (duk_small_int_t) DUK_FPCLASSIFY(x);
+	if (c == DUK_FP_NAN || c == DUK_FP_INFINITE || c == DUK_FP_ZERO) {
+		return x;
+	}
+
+	/*
+	 *  x is finite and non-zero
+	 *
+	 *  -1.6 -> floor(-1.1) -> -2
+	 *  -1.5 -> floor(-1.0) -> -1  (towards +Inf)
+	 *  -1.4 -> floor(-0.9) -> -1
+	 *  -0.5 -> -0.0               (special case)
+	 *  -0.1 -> -0.0               (special case)
+	 *  +0.1 -> +0.0               (special case)
+	 *  +0.5 -> floor(+1.0) -> 1   (towards +Inf)
+	 *  +1.4 -> floor(+1.9) -> 1
+	 *  +1.5 -> floor(+2.0) -> 2   (towards +Inf)
+	 *  +1.6 -> floor(+2.1) -> 2
+	 */
+
+	if (x >= -0.5 && x < 0.5) {
+		/* +0.5 is handled by floor, this is on purpose */
+		if (x < 0.0) {
+			return -0.0;
+		} else {
+			return +0.0;
+		}
+	}
+
+	return DUK_FLOOR(x + 0.5);
+}
+
+static double duk__pow_fixed(double x, double y) {
+	/* The ANSI C pow() semantics differ from Ecmascript.
+	 *
+	 * E.g. when x==1 and y is +/- infinite, the Ecmascript required
+	 * result is NaN, while at least Linux pow() returns 1.
+	 */
+
+	duk_small_int_t cx, cy, sx;
+
+	DUK_UNREF(cx);
+	DUK_UNREF(sx);
+	cy = (duk_small_int_t) DUK_FPCLASSIFY(y);
+
+	if (cy == DUK_FP_NAN) {
+		goto ret_nan;
+	}
+	if (DUK_FABS(x) == 1.0 && cy == DUK_FP_INFINITE) {
+		goto ret_nan;
+	}
+#if defined(DUK_USE_POW_NETBSD_WORKAROUND)
+	/* See test-bug-netbsd-math-pow.js: NetBSD 6.0 on x86 (at least) does not
+	 * correctly handle some cases where x=+/-0.  Specific fixes to these
+	 * here.
+	 */
+	cx = (duk_small_int_t) DUK_FPCLASSIFY(x);
+	if (cx == DUK_FP_ZERO && y < 0.0) {
+		sx = (duk_small_int_t) DUK_SIGNBIT(x);
+		if (sx == 0) {
+			/* Math.pow(+0,y) should be Infinity when y<0.  NetBSD pow()
+			 * returns -Infinity instead when y is <0 and finite.  The
+			 * if-clause also catches y == -Infinity (which works even
+			 * without the fix).
+			 */
+			return DUK_DOUBLE_INFINITY;
+		} else {
+			/* Math.pow(-0,y) where y<0 should be:
+			 *   - -Infinity if y<0 and an odd integer
+			 *   - Infinity otherwise
+			 * NetBSD pow() returns -Infinity for all finite y<0.  The
+			 * if-clause also catches y == -Infinity (which works even
+			 * without the fix).
+			 */
+
+			/* fmod() return value has same sign as input (negative) so
+			 * the result here will be in the range ]-2,0], 1 indicates
+			 * odd.  If x is -Infinity, NaN is returned and the odd check
+			 * always concludes "not odd" which results in desired outcome.
+			 */
+			double tmp = DUK_FMOD(y, 2);
+			if (tmp == -1.0) {
+				return -DUK_DOUBLE_INFINITY;
+			} else {
+				/* Not odd, or y == -Infinity */
+				return DUK_DOUBLE_INFINITY;
+			}
+		}
+	}
+#endif
+	return DUK_POW(x, y);
+
+ ret_nan:
+	return DUK_DOUBLE_NAN;
+}
+
+/* Wrappers for calling standard math library methods.  These may be required
+ * on platforms where one or more of the math built-ins are defined as macros
+ * or inline functions and are thus not suitable to be used as function pointers.
+ */
+#if defined(DUK_USE_AVOID_PLATFORM_FUNCPTRS)
+static double duk__fabs(double x) {
+	return fabs(x);
+}
+static double duk__acos(double x) {
+	return acos(x);
+}
+static double duk__asin(double x) {
+	return asin(x);
+}
+static double duk__atan(double x) {
+	return atan(x);
+}
+static double duk__ceil(double x) {
+	return ceil(x);
+}
+static double duk__cos(double x) {
+	return cos(x);
+}
+static double duk__exp(double x) {
+	return exp(x);
+}
+static double duk__floor(double x) {
+	return floor(x);
+}
+static double duk__log(double x) {
+	return log(x);
+}
+static double duk__sin(double x) {
+	return sin(x);
+}
+static double duk__sqrt(double x) {
+	return sqrt(x);
+}
+static double duk__tan(double x) {
+	return tan(x);
+}
+static double duk__atan2(double x, double y) {
+	return atan2(x, y);
+}
+#endif  /* DUK_USE_AVOID_PLATFORM_FUNCPTRS */
+
+/* order must match constants in genbuiltins.py */
+static const duk__one_arg_func duk__one_arg_funcs[] = {
+#if defined(DUK_USE_AVOID_PLATFORM_FUNCPTRS)
+	duk__fabs,
+	duk__acos,
+	duk__asin,
+	duk__atan,
+	duk__ceil,
+	duk__cos,
+	duk__exp,
+	duk__floor,
+	duk__log,
+	duk__round_fixed,
+	duk__sin,
+	duk__sqrt,
+	duk__tan
+#else
+	DUK_FABS,
+	DUK_ACOS,
+	DUK_ASIN,
+	DUK_ATAN,
+	DUK_CEIL,
+	DUK_COS,
+	DUK_EXP,
+	DUK_FLOOR,
+	DUK_LOG,
+	duk__round_fixed,
+	DUK_SIN,
+	DUK_SQRT,
+	DUK_TAN
+#endif
+};
+
+/* order must match constants in genbuiltins.py */
+static const duk__two_arg_func duk__two_arg_funcs[] = {
+#if defined(DUK_USE_AVOID_PLATFORM_FUNCPTRS)
+	duk__atan2,
+	duk__pow_fixed
+#else
+	DUK_ATAN2,
+	duk__pow_fixed
+#endif
+};
+
+duk_ret_t duk_bi_math_object_onearg_shared(duk_context *ctx) {
+	duk_small_int_t fun_idx = duk_get_magic(ctx);
+	duk__one_arg_func fun;
+
+	DUK_ASSERT(fun_idx >= 0);
+	DUK_ASSERT(fun_idx < (duk_small_int_t) (sizeof(duk__one_arg_funcs) / sizeof(duk__one_arg_func)));
+	fun = duk__one_arg_funcs[fun_idx];
+	duk_push_number(ctx, (duk_double_t) fun((double) duk_to_number(ctx, 0)));
+	return 1;
+}
+
+duk_ret_t duk_bi_math_object_twoarg_shared(duk_context *ctx) {
+	duk_small_int_t fun_idx = duk_get_magic(ctx);
+	duk__two_arg_func fun;
+
+	DUK_ASSERT(fun_idx >= 0);
+	DUK_ASSERT(fun_idx < (duk_small_int_t) (sizeof(duk__two_arg_funcs) / sizeof(duk__two_arg_func)));
+	fun = duk__two_arg_funcs[fun_idx];
+	duk_push_number(ctx, (duk_double_t) fun((double) duk_to_number(ctx, 0), (double) duk_to_number(ctx, 1)));
+	return 1;
+}
+
+duk_ret_t duk_bi_math_object_max(duk_context *ctx) {
+	return duk__math_minmax(ctx, -DUK_DOUBLE_INFINITY, duk__fmax_fixed);
+}
+
+duk_ret_t duk_bi_math_object_min(duk_context *ctx) {
+	return duk__math_minmax(ctx, DUK_DOUBLE_INFINITY, duk__fmin_fixed);
+}
+
+duk_ret_t duk_bi_math_object_random(duk_context *ctx) {
+	duk_push_number(ctx, (duk_double_t) duk_util_tinyrandom_get_double((duk_hthread *) ctx));
+	return 1;
+}
+
+#else  /* DUK_USE_MATH_BUILTIN */
+
+/* A stubbed built-in is useful for e.g. compilation torture testing with BCC. */
+
+duk_ret_t duk_bi_math_object_onearg_shared(duk_context *ctx) {
+	DUK_UNREF(ctx);
+	return DUK_RET_UNIMPLEMENTED_ERROR;
+}
+
+duk_ret_t duk_bi_math_object_twoarg_shared(duk_context *ctx) {
+	DUK_UNREF(ctx);
+	return DUK_RET_UNIMPLEMENTED_ERROR;
+}
+
+duk_ret_t duk_bi_math_object_max(duk_context *ctx) {
+	DUK_UNREF(ctx);
+	return DUK_RET_UNIMPLEMENTED_ERROR;
+}
+
+duk_ret_t duk_bi_math_object_min(duk_context *ctx) {
+	DUK_UNREF(ctx);
+	return DUK_RET_UNIMPLEMENTED_ERROR;
+}
+
+duk_ret_t duk_bi_math_object_random(duk_context *ctx) {
+	DUK_UNREF(ctx);
+	return DUK_RET_UNIMPLEMENTED_ERROR;
+}
+
+#endif  /* DUK_USE_MATH_BUILTIN */
+#line 1 "duk_bi_number.c"
+/*
+ *  Number built-ins
+ */
+
+/* FIXME: needs to be refactored when exact parsing / string conversion
+ * primitives are implemented.
+ */
+
+/* include removed: duk_internal.h */
+
+static duk_double_t duk__push_this_number_plain(duk_context *ctx) {
+	duk_hobject *h;
+
+	/* Number built-in accepts a plain number or a Number object (whose
+	 * internal value is operated on).  Other types cause TypeError.
+	 */
+
+	duk_push_this(ctx);
+	if (duk_is_number(ctx, -1)) {
+		DUK_DDD(DUK_DDDPRINT("plain number value: %!T", (duk_tval *) duk_get_tval(ctx, -1)));
+		goto done;
+	}
+	h = duk_get_hobject(ctx, -1);
+	if (!h || 
+	    (DUK_HOBJECT_GET_CLASS_NUMBER(h) != DUK_HOBJECT_CLASS_NUMBER)) {
+		DUK_DDD(DUK_DDDPRINT("unacceptable this value: %!T", (duk_tval *) duk_get_tval(ctx, -1)));
+		DUK_ERROR((duk_hthread *) ctx, DUK_ERR_TYPE_ERROR, "expected a number");
+	}
+	duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_VALUE);
+	DUK_ASSERT(duk_is_number(ctx, -1));
+	DUK_DDD(DUK_DDDPRINT("number object: %!T, internal value: %!T",
+	                     (duk_tval *) duk_get_tval(ctx, -2), (duk_tval *) duk_get_tval(ctx, -1)));
+	duk_remove(ctx, -2);
+
+ done:
+	return duk_get_number(ctx, -1);
+}
+
+duk_ret_t duk_bi_number_constructor(duk_context *ctx) {
+	duk_idx_t nargs;
+	duk_hobject *h_this;
+
+	/*
+	 *  The Number constructor uses ToNumber(arg) for number coercion
+	 *  (coercing an undefined argument to NaN).  However, if the
+	 *  argument is not given at all, +0 must be used instead.  To do
+	 *  this, a vararg function is used.
+	 */
+
+	nargs = duk_get_top(ctx);
+	if (nargs == 0) {
+		duk_push_int(ctx, 0);
+	}
+	duk_to_number(ctx, 0);
+	duk_set_top(ctx, 1);
+	DUK_ASSERT_TOP(ctx, 1);
+
+	if (!duk_is_constructor_call(ctx)) {
+		return 1;
+	}
+
+	/*
+	 *  E5 Section 15.7.2.1 requires that the constructed object
+	 *  must have the original Number.prototype as its internal
+	 *  prototype.  However, since Number.prototype is non-writable
+	 *  and non-configurable, this doesn't have to be enforced here:
+	 *  The default object (bound to 'this') is OK, though we have
+	 *  to change its class.
+	 *
+	 *  Internal value set to ToNumber(arg) or +0; if no arg given,
+	 *  ToNumber(undefined) = NaN, so special treatment is needed
+	 *  (above).  String internal value is immutable.
+	 */
+
+	/* FIXME: helper */
+	duk_push_this(ctx);
+	h_this = duk_get_hobject(ctx, -1);
+	DUK_ASSERT(h_this != NULL);
+	DUK_HOBJECT_SET_CLASS_NUMBER(h_this, DUK_HOBJECT_CLASS_NUMBER);
+
+	DUK_ASSERT(h_this->prototype == ((duk_hthread *) ctx)->builtins[DUK_BIDX_NUMBER_PROTOTYPE]);
+	DUK_ASSERT(DUK_HOBJECT_GET_CLASS_NUMBER(h_this) == DUK_HOBJECT_CLASS_NUMBER);
+	DUK_ASSERT(DUK_HOBJECT_HAS_EXTENSIBLE(h_this));
+
+	duk_dup(ctx, 0);  /* -> [ val obj val ] */
+	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_NONE);
+	return 0;  /* no return value -> don't replace created value */
+}
+
+duk_ret_t duk_bi_number_prototype_value_of(duk_context *ctx) {
+	(void) duk__push_this_number_plain(ctx);
+	return 1;
+}
+
+duk_ret_t duk_bi_number_prototype_to_string(duk_context *ctx) {
+	duk_small_int_t radix;
+	duk_small_uint_t n2s_flags;
+
+	(void) duk__push_this_number_plain(ctx);
+	if (duk_is_undefined(ctx, 0)) {
+		radix = 10;
+	} else {
+		radix = (duk_small_int_t) duk_to_int_check_range(ctx, 0, 2, 36);
+	}
+	DUK_DDD(DUK_DDDPRINT("radix=%ld", (long) radix));
+
+	n2s_flags = 0;
+
+	duk_numconv_stringify(ctx,
+	                      radix /*radix*/,
+	                      0 /*digits*/,
+	                      n2s_flags /*flags*/);
+	return 1;
+}
+
+duk_ret_t duk_bi_number_prototype_to_locale_string(duk_context *ctx) {
+	/* XXX: just use toString() for now; permitted although not recommended.
+	 * nargs==1, so radix is passed to toString().
+	 */
+	return duk_bi_number_prototype_to_string(ctx);
+}
+
+/*
+ *  toFixed(), toExponential(), toPrecision()
+ */
+
+/* FIXME: shared helper for toFixed(), toExponential(), toPrecision()? */
+
+duk_ret_t duk_bi_number_prototype_to_fixed(duk_context *ctx) {
+	duk_small_int_t frac_digits;
+	duk_double_t d;
+	duk_small_int_t c;
+	duk_small_uint_t n2s_flags;
+
+	frac_digits = (duk_small_int_t) duk_to_int_check_range(ctx, 0, 0, 20);
+	d = duk__push_this_number_plain(ctx);
+
+	c = (duk_small_int_t) DUK_FPCLASSIFY(d);
+	if (c == DUK_FP_NAN || c == DUK_FP_INFINITE) {
+		goto use_to_string;
+	}
+
+	if (d >= 1.0e21 || d <= -1.0e21) {
+		goto use_to_string;
+	}
+
+	n2s_flags = DUK_N2S_FLAG_FIXED_FORMAT |
+	            DUK_N2S_FLAG_FRACTION_DIGITS;
+
+	duk_numconv_stringify(ctx,
+	                      10 /*radix*/,
+	                      frac_digits /*digits*/,
+	                      n2s_flags /*flags*/);
+	return 1;
+
+ use_to_string:
+	DUK_ASSERT_TOP(ctx, 2);
+	duk_to_string(ctx, -1);
+	return 1;
+}
+
+duk_ret_t duk_bi_number_prototype_to_exponential(duk_context *ctx) {
+	duk_bool_t frac_undefined;
+	duk_small_int_t frac_digits;
+	duk_double_t d;
+	duk_small_int_t c;
+	duk_small_uint_t n2s_flags;
+
+	d = duk__push_this_number_plain(ctx);
+
+	frac_undefined = duk_is_undefined(ctx, 0);
+	duk_to_int(ctx, 0);  /* for side effects */
+
+	c = (duk_small_int_t) DUK_FPCLASSIFY(d);
+	if (c == DUK_FP_NAN || c == DUK_FP_INFINITE) {
+		goto use_to_string;
+	}
+
+	frac_digits = (duk_small_int_t) duk_to_int_check_range(ctx, 0, 0, 20);
+
+	n2s_flags = DUK_N2S_FLAG_FORCE_EXP |
+	           (frac_undefined ? 0 : DUK_N2S_FLAG_FIXED_FORMAT);
+
+	duk_numconv_stringify(ctx,
+	                      10 /*radix*/,
+	                      frac_digits + 1 /*leading digit + fractions*/,
+	                      n2s_flags /*flags*/);
+	return 1;
+
+ use_to_string:
+	DUK_ASSERT_TOP(ctx, 2);
+	duk_to_string(ctx, -1);
+	return 1;
+}
+
+duk_ret_t duk_bi_number_prototype_to_precision(duk_context *ctx) {
+	/* The specification has quite awkward order of coercion and
+	 * checks for toPrecision().  The operations below are a bit
+	 * reordered, within constraints of observable side effects.
+	 */
+
+	duk_double_t d;
+	duk_small_int_t prec;
+	duk_small_int_t c;
+	duk_small_uint_t n2s_flags;
+
+	DUK_ASSERT_TOP(ctx, 1);
+
+	d = duk__push_this_number_plain(ctx);
+	if (duk_is_undefined(ctx, 0)) {
+		goto use_to_string;
+	}
+	DUK_ASSERT_TOP(ctx, 2);
+
+	duk_to_int(ctx, 0);  /* for side effects */
+
+	c = (duk_small_int_t) DUK_FPCLASSIFY(d);
+	if (c == DUK_FP_NAN || c == DUK_FP_INFINITE) {
+		goto use_to_string;
+	}
+
+	prec = (duk_small_int_t) duk_to_int_check_range(ctx, 0, 1, 21);
+
+	n2s_flags = DUK_N2S_FLAG_FIXED_FORMAT |
+	            DUK_N2S_FLAG_NO_ZERO_PAD;
+
+	duk_numconv_stringify(ctx,
+	                      10 /*radix*/,
+	                      prec /*digits*/,
+	                      n2s_flags /*flags*/);
+	return 1;
+
+ use_to_string:
+	/* Used when precision is undefined; also used for NaN (-> "NaN"),
+	 * and +/- infinity (-> "Infinity", "-Infinity").
+	 */
+
+	DUK_ASSERT_TOP(ctx, 2);
+	duk_to_string(ctx, -1);
+	return 1;
+}
+#line 1 "duk_bi_object.c"
+/*
+ *  Object built-ins
+ */
+
+/* include removed: duk_internal.h */
+
+duk_ret_t duk_bi_object_constructor(duk_context *ctx) {
+	if (!duk_is_constructor_call(ctx) &&
+	    !duk_is_null_or_undefined(ctx, 0)) {
+		duk_to_object(ctx, 0);
+		return 1;
+	}
+
+	if (duk_is_object(ctx, 0)) {
+		return 1;
+	}
+
+	/* Pointer and buffer primitive values are treated like other
+	 * primitives values which have a fully fledged object counterpart:
+	 * promote to an object value.
+	 */
+	if (duk_check_type_mask(ctx, 0, DUK_TYPE_MASK_STRING |
+	                                DUK_TYPE_MASK_BOOLEAN |
+	                                DUK_TYPE_MASK_NUMBER |
+	                                DUK_TYPE_MASK_POINTER |
+	                                DUK_TYPE_MASK_BUFFER)) {
+		duk_to_object(ctx, 0);
+		return 1;
+	}
+
+	duk_push_object_helper(ctx,
+	                       DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                       DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT),
+	                       DUK_BIDX_OBJECT_PROTOTYPE);
+	return 1;
+}
+
+/* Shared helper to implement Object.getPrototypeOf and the ES6
+ * Object.prototype.__proto__ getter.
+ *
+ * https://people.mozilla.org/~jorendorff/es6-draft.html#sec-get-object.prototype.__proto__
+ */
+duk_ret_t duk_bi_object_getprototype_shared(duk_context *ctx) {
+	duk_hobject *h;
+
+	/* magic: 0=getter call, 1=Object.getPrototypeOf */
+	if (duk_get_magic(ctx) == 0) {
+		duk_push_this_coercible_to_object(ctx);
+		duk_insert(ctx, 0);
+	}
+
+	h = duk_require_hobject(ctx, 0);
+	DUK_ASSERT(h != NULL);
+
+	/* XXX: should the API call handle this directly, i.e. attempt
+	 * to duk_push_hobject(ctx, null) would push a null instead?
+	 * (On the other hand 'undefined' would be just as logical, but
+	 * not wanted here.)
+	 */
+
+	if (h->prototype) {
+		duk_push_hobject(ctx, h->prototype);
+	} else {
+		duk_push_null(ctx);
+	}
+	return 1;
+}
+
+/* Shared helper to implement ES6 Object.setPrototypeOf and
+ * Object.prototype.__proto__ setter.
+ *
+ * https://people.mozilla.org/~jorendorff/es6-draft.html#sec-get-object.prototype.__proto__
+ * https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.setprototypeof
+ */
+duk_ret_t duk_bi_object_setprototype_shared(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hobject *h_obj;
+	duk_hobject *h_new_proto;
+	duk_hobject *h_curr;
+	duk_ret_t ret_success = 1;  /* retval for success path */
+
+	/* Preliminaries for __proto__ and setPrototypeOf (E6 19.1.2.18 steps 1-4);
+	 * magic: 0=setter call, 1=Object.setPrototypeOf
+	 */
+	if (duk_get_magic(ctx) == 0) {
+		duk_push_this_check_object_coercible(ctx);
+		duk_insert(ctx, 0);
+		if (!duk_check_type_mask(ctx, 1, DUK_TYPE_MASK_NULL | DUK_TYPE_MASK_OBJECT)) {
+			return 0;
+		}
+
+		/* __proto__ setter returns 'undefined' on success unlike the
+		 * setPrototypeOf() call which returns the target object.
+		 */
+		ret_success = 0;
+	} else {
+		duk_require_object_coercible(ctx, 0);
+		duk_require_type_mask(ctx, 1, DUK_TYPE_MASK_NULL | DUK_TYPE_MASK_OBJECT);
+	}
+	h_obj = duk_get_hobject(ctx, 0);
+	if (!h_obj) {
+		goto skip;
+	}
+	h_new_proto = duk_get_hobject(ctx, 1);
+	DUK_ASSERT(h_obj != NULL);
+	/* h_new_proto may be NULL */
+
+	/* [[SetPrototypeOf]] standard behavior, E6 9.1.2 */
+	/* NOTE: steps 7-8 seem to be a cut-paste bug in the E6 draft */
+	/* TODO: implement Proxy object support here */
+
+	if (h_new_proto == h_obj->prototype) {
+		goto skip;
+	}
+	if (!DUK_HOBJECT_HAS_EXTENSIBLE(h_obj)) {
+		goto fail_nonextensible;
+	}
+	for (h_curr = h_new_proto; h_curr != NULL; h_curr = h_curr->prototype) {
+		/* Loop prevention */
+		if (h_curr == h_obj) {
+			goto fail_loop;
+		}
+	}
+	DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, h_obj, h_new_proto);
+	/* fall thru */
+
+ skip:
+	duk_set_top(ctx, 1);
+	return ret_success;
+
+ fail_nonextensible:
+ fail_loop:
+	return DUK_RET_TYPE_ERROR;
+}
+
+duk_ret_t duk_bi_object_constructor_get_own_property_descriptor(duk_context *ctx) {
+	/* XXX: no need for indirect call */
+	return duk_hobject_object_get_own_property_descriptor(ctx);
+}
+
+duk_ret_t duk_bi_object_constructor_create(duk_context *ctx) {
+	duk_tval *tv;
+	duk_hobject *proto = NULL;
+
+	DUK_ASSERT_TOP(ctx, 2);
+
+	tv = duk_get_tval(ctx, 0);
+	DUK_ASSERT(tv != NULL);
+	if (DUK_TVAL_IS_NULL(tv)) {
+		;
+	} else if (DUK_TVAL_IS_OBJECT(tv)) {
+		proto = DUK_TVAL_GET_OBJECT(tv);
+		DUK_ASSERT(proto != NULL);
+	} else {
+		return DUK_RET_TYPE_ERROR;
+	}
+
+	(void) duk_push_object_helper_proto(ctx,
+	                                    DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                                    DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT),
+	                                    proto);
+
+	if (!duk_is_undefined(ctx, 1)) {
+		/* [ O Properties obj ] */
+
+		/* Use original function.  No need to get it explicitly,
+		 * just call the helper.
+		 */
+
+		duk_replace(ctx, 0);
+
+		/* [ obj Properties ] */
+
+		return duk_hobject_object_define_properties(ctx);
+	}
+
+	/* [ O Properties obj ] */
+
+	return 1;
+}
+
+duk_ret_t duk_bi_object_constructor_define_property(duk_context *ctx) {
+	/* XXX: no need for indirect call */
+	return duk_hobject_object_define_property(ctx);
+}
+
+duk_ret_t duk_bi_object_constructor_define_properties(duk_context *ctx) {
+	/* XXX: no need for indirect call */
+	return duk_hobject_object_define_properties(ctx);
+}
+
+duk_ret_t duk_bi_object_constructor_seal_freeze_shared(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hobject *h;
+	duk_bool_t is_freeze;
+
+	h = duk_require_hobject(ctx, 0);
+	DUK_ASSERT(h != NULL);
+
+	is_freeze = (duk_bool_t) duk_get_magic(ctx);
+	duk_hobject_object_seal_freeze_helper(thr, h, is_freeze);
+
+	/* Sealed and frozen objects cannot gain any more properties,
+	 * so this is a good time to compact them.
+	 */
+	duk_hobject_compact_props(thr, h);
+
+	return 1;
+}
+
+duk_ret_t duk_bi_object_constructor_prevent_extensions(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hobject *h;
+
+	h = duk_require_hobject(ctx, 0);
+	DUK_ASSERT(h != NULL);
+
+	DUK_HOBJECT_CLEAR_EXTENSIBLE(h);
+
+	/* A non-extensible object cannot gain any more properties,
+	 * so this is a good time to compact.
+	 */
+	duk_hobject_compact_props(thr, h);
+	
+	return 1;
+}
+
+duk_ret_t duk_bi_object_constructor_is_sealed_frozen_shared(duk_context *ctx) {
+	duk_hobject *h;
+	duk_bool_t is_frozen;
+	duk_bool_t rc;
+
+	h = duk_require_hobject(ctx, 0);
+	DUK_ASSERT(h != NULL);
+
+	is_frozen = duk_get_magic(ctx);
+	rc = duk_hobject_object_is_sealed_frozen_helper(h, is_frozen /*is_frozen*/);
+	duk_push_boolean(ctx, rc);
+	return 1;
+}
+
+duk_ret_t duk_bi_object_constructor_is_extensible(duk_context *ctx) {
+	duk_hobject *h;
+
+	h = duk_require_hobject(ctx, 0);
+	DUK_ASSERT(h != NULL);
+
+	duk_push_boolean(ctx, DUK_HOBJECT_HAS_EXTENSIBLE(h));
+	return 1;
+}
+
+/* Shared helper for Object.getOwnPropertyNames() and Object.keys().
+ * Magic: 0=getOwnPropertyNames, 1=Object.keys.
+ */
+duk_ret_t duk_bi_object_constructor_keys_shared(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hobject *obj;
+#if defined(DUK_USE_ES6_PROXY)
+	duk_hobject *h_proxy_target;
+	duk_hobject *h_proxy_handler;
+	duk_hobject *h_trap_result;
+	duk_uarridx_t i, len, idx;
+#endif
+	duk_small_uint_t enum_flags;
+
+	DUK_ASSERT_TOP(ctx, 1);
+
+	obj = duk_require_hobject(ctx, 0);
+	DUK_ASSERT(obj != NULL);
+	DUK_UNREF(obj);
+
+#if defined(DUK_USE_ES6_PROXY)
+	if (DUK_LIKELY(!duk_hobject_proxy_check(thr,
+	                                        obj,
+	                                        &h_proxy_target,
+	                                        &h_proxy_handler))) {
+		goto skip_proxy;
+	}
+
+	duk_push_hobject(ctx, h_proxy_handler);
+	if (!duk_get_prop_stridx(ctx, -1, DUK_STRIDX_OWN_KEYS)) {
+		/* Careful with reachability here: don't pop 'obj' before pushing
+		 * proxy target.
+		 */
+		DUK_DDD(DUK_DDDPRINT("no ownKeys trap, get keys of target instead"));
+		duk_pop_2(ctx);
+		duk_push_hobject(ctx, h_proxy_target);
+		duk_replace(ctx, 0);
+		DUK_ASSERT_TOP(ctx, 1);
+		goto skip_proxy;
+	}
+
+	/* [ obj handler trap ] */
+	duk_insert(ctx, -2);
+	duk_push_hobject(ctx, h_proxy_target);  /* -> [ obj trap handler target ] */
+	duk_call_method(ctx, 1 /*nargs*/);      /* -> [ obj trap_result ] */
+	h_trap_result = duk_require_hobject(ctx, -1);
+	DUK_UNREF(h_trap_result);
+
+	len = (duk_uarridx_t) duk_get_length(ctx, -1);
+	idx = 0;
+	duk_push_array(ctx);
+	for (i = 0; i < len; i++) {
+		/* [ obj trap_result res_arr ] */
+		if (duk_get_prop_index(ctx, -2, i) && duk_is_string(ctx, -1)) {
+			/* XXX: for Object.keys() we should check enumerability of key */
+			/* [ obj trap_result res_arr propname ] */
+			duk_put_prop_index(ctx, -2, idx);
+			idx++;
+		} else {
+			duk_pop(ctx);
+		}
+	}
+
+	/* XXX: for Object.keys() the [[OwnPropertyKeys]] result (trap result)
+	 * should be filtered so that only enumerable keys remain.  Enumerability
+	 * should be checked with [[GetOwnProperty]] on the original object
+	 * (i.e., the proxy in this case).  If the proxy has a getOwnPropertyDescriptor
+	 * trap, it should be triggered for every property.  If the proxy doesn't have
+	 * the trap, enumerability should be checked against the target object instead.
+	 * We don't do any of this now, so Object.keys() and Object.getOwnPropertyNames()
+	 * return the same result now for proxy traps.  We still do clean up the trap
+	 * result, so that Object.keys() and Object.getOwnPropertyNames() will return a
+	 * clean array of strings without gaps.
+	 */
+	return 1;
+
+ skip_proxy:
+#endif  /* DUK_USE_ES6_PROXY */
+
+	DUK_ASSERT_TOP(ctx, 1);
+
+	if (duk_get_magic(ctx)) {
+		/* Object.keys */
+		enum_flags = DUK_ENUM_OWN_PROPERTIES_ONLY |
+		             DUK_ENUM_NO_PROXY_BEHAVIOR;
+	} else {
+		/* Object.getOwnPropertyNames */
+		enum_flags = DUK_ENUM_INCLUDE_NONENUMERABLE |
+		             DUK_ENUM_OWN_PROPERTIES_ONLY |
+		             DUK_ENUM_NO_PROXY_BEHAVIOR;
+	}
+
+	return duk_hobject_get_enumerated_keys(ctx, enum_flags);
+}
+
+duk_ret_t duk_bi_object_prototype_to_string(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+
+	duk_push_this(ctx);
+	duk_push_string(ctx, "[object ");
+
+	if (duk_is_undefined(ctx, -2)) {
+		duk_push_hstring_stridx(ctx, DUK_STRIDX_UC_UNDEFINED);
+	} else if (duk_is_null(ctx, -2)) {
+		duk_push_hstring_stridx(ctx, DUK_STRIDX_UC_NULL);
+	} else {
+		duk_hobject *h_this;
+		duk_hstring *h_classname;
+
+		duk_to_object(ctx, -2);
+		h_this = duk_get_hobject(ctx, -2);
+		DUK_ASSERT(h_this != NULL);
+
+		h_classname = DUK_HOBJECT_GET_CLASS_STRING(thr->heap, h_this);
+		DUK_ASSERT(h_classname != NULL);
+
+		duk_push_hstring(ctx, h_classname);
+	}
+
+	duk_push_string(ctx, "]");
+	duk_concat(ctx, 3);
+	return 1;
+}
+
+duk_ret_t duk_bi_object_prototype_to_locale_string(duk_context *ctx) {
+	DUK_ASSERT_TOP(ctx, 0);
+	(void) duk_push_this_coercible_to_object(ctx);
+	duk_get_prop_stridx(ctx, 0, DUK_STRIDX_TO_STRING);
+	if (!duk_is_callable(ctx, 1)) {
+		return DUK_RET_TYPE_ERROR;
+	}
+	duk_dup(ctx, 0);  /* -> [ O toString O ] */
+	duk_call_method(ctx, 0);  /* XXX: call method tailcall? */
+	return 1;
+}
+
+duk_ret_t duk_bi_object_prototype_value_of(duk_context *ctx) {
+	(void) duk_push_this_coercible_to_object(ctx);
+	return 1;
+}
+
+duk_ret_t duk_bi_object_prototype_is_prototype_of(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hobject *h_v;
+	duk_hobject *h_obj;
+
+	DUK_ASSERT_TOP(ctx, 1);
+
+	h_v = duk_get_hobject(ctx, 0);
+	if (!h_v) {
+		duk_push_false(ctx);  /* XXX: tail call: return duk_push_false(ctx) */
+		return 1;
+	}
+
+	h_obj = duk_push_this_coercible_to_object(ctx);
+	DUK_ASSERT(h_obj != NULL);
+
+	/* E5.1 Section 15.2.4.6, step 3.a, lookup proto once before compare */
+	duk_push_boolean(ctx, duk_hobject_prototype_chain_contains(thr, h_v->prototype, h_obj));
+	return 1;
+}
+
+duk_ret_t duk_bi_object_prototype_has_own_property(duk_context *ctx) {
+	return duk_hobject_object_ownprop_helper(ctx, 0 /*required_desc_flags*/);
+}
+
+duk_ret_t duk_bi_object_prototype_property_is_enumerable(duk_context *ctx) {
+	return duk_hobject_object_ownprop_helper(ctx, DUK_PROPDESC_FLAG_ENUMERABLE /*required_desc_flags*/);
+}
+#line 1 "duk_bi_pointer.c"
+/*
+ *  Pointer built-ins
+ */
+
+/* include removed: duk_internal.h */
+
+/*
+ *  Constructor
+ */
+
+duk_ret_t duk_bi_pointer_constructor(duk_context *ctx) {
+	/* FIXME: this behavior is quite useless now; it would be nice to be able
+	 * to create pointer values from e.g. numbers or strings.  Numbers are
+	 * problematic on 64-bit platforms though.  Hex encoded strings?
+	 */
+	if (duk_get_top(ctx) == 0) {
+		duk_push_pointer(ctx, NULL);
+	} else {
+		duk_to_pointer(ctx, 0);
+	}
+	DUK_ASSERT(duk_is_pointer(ctx, 0));
+	duk_set_top(ctx, 1);
+
+	if (duk_is_constructor_call(ctx)) {
+		duk_push_object_helper(ctx,
+		                       DUK_HOBJECT_FLAG_EXTENSIBLE |
+		                       DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_POINTER),
+		                       DUK_BIDX_POINTER_PROTOTYPE);
+
+		/* Pointer object internal value is immutable */
+		duk_dup(ctx, 0);
+		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_NONE);
+	}
+	/* Note: unbalanced stack on purpose */
+
+	return 1;
+}
+
+/*
+ *  toString(), valueOf()
+ */
+
+duk_ret_t duk_bi_pointer_prototype_tostring_shared(duk_context *ctx) {
+	duk_tval *tv;
+	duk_small_int_t to_string = duk_get_magic(ctx);
+
+	duk_push_this(ctx);
+	tv = duk_require_tval(ctx, -1);
+	DUK_ASSERT(tv != NULL);
+
+	if (DUK_TVAL_IS_POINTER(tv)) {
+		/* nop */
+	} else if (DUK_TVAL_IS_OBJECT(tv)) {
+		duk_hobject *h = DUK_TVAL_GET_OBJECT(tv);
+		DUK_ASSERT(h != NULL);
+
+		/* Must be a "pointer object", i.e. class "Pointer" */
+		if (DUK_HOBJECT_GET_CLASS_NUMBER(h) != DUK_HOBJECT_CLASS_POINTER) {
+			goto type_error;
+		}
+
+		duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_VALUE);
+	} else {
+		goto type_error;
+	}
+
+	if (to_string) {
+		duk_to_string(ctx, -1);
+	}
+	return 1;
+
+ type_error:
+	return DUK_RET_TYPE_ERROR;
+}
+#line 1 "duk_bi_proxy.c"
+/*
+ *  Proxy built-in (ES6 draft)
+ */
+
+/* include removed: duk_internal.h */
+
+#if defined(DUK_USE_ES6_PROXY)
+duk_ret_t duk_bi_proxy_constructor(duk_context *ctx) {
+	duk_hobject *h_target;
+	duk_hobject *h_handler;
+
+	if (!duk_is_constructor_call(ctx)) {
+		return DUK_RET_TYPE_ERROR;
+	}
+
+	/* Reject a proxy object as the target because it would need
+	 * special handler in property lookups.  (ES6 has no such restriction)
+	 */
+	h_target = duk_require_hobject(ctx, 0);
+	DUK_ASSERT(h_target != NULL);
+	if (DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(h_target)) {
+		return DUK_RET_TYPE_ERROR;
+	}
+
+	/* Reject a proxy object as the handler because it would cause
+	 * potentially unbounded recursion.  (ES6 has no such restriction)
+	 */
+	h_handler = duk_require_hobject(ctx, 1);
+	DUK_ASSERT(h_handler != NULL);
+	if (DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(h_handler)) {
+		return DUK_RET_TYPE_ERROR;
+	}
+
+	/* XXX: the returned value is exotic in ES6 (draft), but we use a
+	 * simple object here with no prototype.  Without a prototype,
+	 * [[DefaultValue]] coercion fails which is abit confusing.
+	 * No callable check/handling in the current Proxy subset.
+	 */
+	(void) duk_push_object_helper_proto(ctx,
+	                                    DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                                    DUK_HOBJECT_FLAG_EXOTIC_PROXYOBJ |
+	                                    DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT),
+	                                    NULL);
+	DUK_ASSERT_TOP(ctx, 3);
+
+	/* Proxy target */
+	duk_dup(ctx, 0);
+	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_TARGET, DUK_PROPDESC_FLAGS_WC);
+
+	/* Proxy handler */
+	duk_dup(ctx, 1);
+	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_HANDLER, DUK_PROPDESC_FLAGS_WC);
+
+	return 1;  /* replacement handler */
+}
+#else  /* DUK_USE_ES6_PROXY */
+duk_ret_t duk_bi_proxy_constructor(duk_context *ctx) {
+	DUK_UNREF(ctx);
+	return DUK_RET_UNSUPPORTED_ERROR;
+}
+#endif  /* DUK_USE_ES6_PROXY */
+#line 1 "duk_bi_regexp.c"
+/*
+ *  RegExp built-ins
+ */
+
+/* include removed: duk_internal.h */
+
+#ifdef DUK_USE_REGEXP_SUPPORT
+
+static void duk__get_this_regexp(duk_context *ctx) {
+	duk_hobject *h;
+
+	duk_push_this(ctx);
+	h = duk_require_hobject_with_class(ctx, -1, DUK_HOBJECT_CLASS_REGEXP);
+	DUK_ASSERT(h != NULL);
+	DUK_UNREF(h);
+	duk_insert(ctx, 0);  /* prepend regexp to valstack 0 index */
+}
+
+/* FIXME: much to improve (code size) */
+duk_ret_t duk_bi_regexp_constructor(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hobject *h_pattern;
+
+	DUK_ASSERT_TOP(ctx, 2);
+	h_pattern = duk_get_hobject(ctx, 0);
+
+	if (!duk_is_constructor_call(ctx) &&
+	    h_pattern != NULL &&
+	    DUK_HOBJECT_GET_CLASS_NUMBER(h_pattern) == DUK_HOBJECT_CLASS_REGEXP &&
+	    duk_is_undefined(ctx, 1)) {
+		/* Called as a function, pattern has [[Class]] "RegExp" and
+		 * flags is undefined -> return object as is.
+		 */
+		duk_dup(ctx, 0);
+		return 1;
+	}
+
+	/* Else functionality is identical for function call and constructor
+	 * call.
+	 */
+
+	if (h_pattern != NULL &&
+	    DUK_HOBJECT_GET_CLASS_NUMBER(h_pattern) == DUK_HOBJECT_CLASS_REGEXP) {
+		if (duk_is_undefined(ctx, 1)) {
+			duk_bool_t flag_g, flag_i, flag_m;
+			duk_get_prop_stridx(ctx, 0, DUK_STRIDX_SOURCE);
+			flag_g = duk_get_prop_stridx_boolean(ctx, 0, DUK_STRIDX_GLOBAL, NULL);
+			flag_i = duk_get_prop_stridx_boolean(ctx, 0, DUK_STRIDX_IGNORE_CASE, NULL);
+			flag_m = duk_get_prop_stridx_boolean(ctx, 0, DUK_STRIDX_MULTILINE, NULL);
+
+			duk_push_sprintf(ctx, "%s%s%s",
+			                 (const char *) (flag_g ? "g" : ""),
+			                 (const char *) (flag_i ? "i" : ""),
+			                 (const char *) (flag_m ? "m" : ""));
+
+			/* [ ... pattern flags ] */
+		} else {
+			return DUK_RET_TYPE_ERROR;
+		}
+	} else {
+		if (duk_is_undefined(ctx, 0)) {
+			duk_push_string(ctx, "");
+		} else {
+			duk_dup(ctx, 0);
+			duk_to_string(ctx, -1);
+		}
+		if (duk_is_undefined(ctx, 1)) {
+			duk_push_string(ctx, "");
+		} else {
+			duk_dup(ctx, 1);
+			duk_to_string(ctx, -1);
+		}
+
+		/* [ ... pattern flags ] */
+	}
+
+	DUK_DDD(DUK_DDDPRINT("RegExp constructor/function call, pattern=%!T, flags=%!T",
+	                     (duk_tval *) duk_get_tval(ctx, -2), (duk_tval *) duk_get_tval(ctx, -1)));
+
+	/* [ ... pattern flags ] */
+
+	duk_regexp_compile(thr);
+
+	/* [ ... bytecode escaped_source ] */
+
+	duk_regexp_create_instance(thr);
+
+	/* [ ... RegExp ] */
+
+	return 1;
+}
+
+duk_ret_t duk_bi_regexp_prototype_exec(duk_context *ctx) {
+	duk__get_this_regexp(ctx);
+
+	/* [ regexp input ] */
+
+	duk_regexp_match((duk_hthread *) ctx);
+
+	/* [ result ] */
+
+	return 1;
+}
+
+duk_ret_t duk_bi_regexp_prototype_test(duk_context *ctx) {
+	duk__get_this_regexp(ctx);
+
+	/* [ regexp input ] */
+
+	/* result object is created and discarded; wasteful but saves code space */
+	duk_regexp_match((duk_hthread *) ctx);
+
+	/* [ result ] */
+
+	duk_push_boolean(ctx, (duk_is_null(ctx, -1) ? 0 : 1));
+
+	return 1;
+}
+
+duk_ret_t duk_bi_regexp_prototype_to_string(duk_context *ctx) {
+	duk_hstring *h_bc;
+	duk_small_int_t re_flags;
+
+#if 0
+	/* A little tricky string approach to provide the flags string.
+	 * This depends on the specific flag values in duk_regexp.h,
+	 * which needs to be asserted for.  In practice this doesn't
+	 * produce more compact code than the easier approach in use.
+	 */
+
+	const char *flag_strings = "gim\0gi\0gm\0g\0";
+	duk_uint8_t flag_offsets[8] = {
+		(duk_uint8_t) 3,   /* flags: ""    */
+		(duk_uint8_t) 10,  /* flags: "g"   */
+		(duk_uint8_t) 5,   /* flags: "i"   */
+		(duk_uint8_t) 4,   /* flags: "gi"  */
+		(duk_uint8_t) 2,   /* flags: "m"   */
+		(duk_uint8_t) 7,   /* flags: "gm"  */
+		(duk_uint8_t) 1,   /* flags: "im"  */
+		(duk_uint8_t) 0,   /* flags: "gim" */
+	};
+	DUK_ASSERT(DUK_RE_FLAG_GLOBAL == 1);
+	DUK_ASSERT(DUK_RE_FLAG_IGNORE_CASE == 2);
+	DUK_ASSERT(DUK_RE_FLAG_MULTILINE == 4);
+#endif
+
+	duk__get_this_regexp(ctx);
+
+	/* [ regexp ] */
+
+	duk_get_prop_stridx(ctx, 0, DUK_STRIDX_SOURCE);
+	duk_get_prop_stridx(ctx, 0, DUK_STRIDX_INT_BYTECODE);
+	h_bc = duk_get_hstring(ctx, -1);
+	DUK_ASSERT(h_bc != NULL);
+	DUK_ASSERT(DUK_HSTRING_GET_BYTELEN(h_bc) >= 1);
+	DUK_ASSERT(DUK_HSTRING_GET_CHARLEN(h_bc) >= 1);
+	DUK_ASSERT(DUK_HSTRING_GET_DATA(h_bc)[0] < 0x80);
+	re_flags = (duk_small_int_t) DUK_HSTRING_GET_DATA(h_bc)[0];
+
+	/* [ regexp source bytecode ] */
+
+#if 1
+	/* This is a cleaner approach and also produces smaller code than
+	 * the other alternative.  Use duk_require_string() for format
+	 * safety (although the source property should always exist).
+	 */
+	duk_push_sprintf(ctx, "/%s/%s%s%s",
+	                 (const char *) duk_require_string(ctx, -2),  /* require to be safe */
+	                 (re_flags & DUK_RE_FLAG_GLOBAL) ? "g" : "",
+	                 (re_flags & DUK_RE_FLAG_IGNORE_CASE) ? "i" : "",
+	                 (re_flags & DUK_RE_FLAG_MULTILINE) ? "m" : "");
+#else
+	/* This should not be necessary because no-one should tamper with the
+	 * regexp bytecode, but is prudent to avoid potential segfaults if that
+	 * were to happen for some reason.
+	 */
+	re_flags &= 0x07;
+	DUK_ASSERT(re_flags >= 0 && re_flags <= 7);  /* three flags */
+	duk_push_sprintf(ctx, "/%s/%s",
+	                 (const char *) duk_require_string(ctx, -2),
+	                 (const char *) (flag_strings + flag_offsets[re_flags]));
+#endif
+
+	return 1;
+}
+
+#else  /* DUK_USE_REGEXP_SUPPORT */
+
+duk_ret_t duk_bi_regexp_constructor(duk_context *ctx) {
+	DUK_UNREF(ctx);
+	return DUK_RET_UNSUPPORTED_ERROR;
+}
+
+duk_ret_t duk_bi_regexp_prototype_exec(duk_context *ctx) {
+	DUK_UNREF(ctx);
+	return DUK_RET_UNSUPPORTED_ERROR;
+}
+
+duk_ret_t duk_bi_regexp_prototype_test(duk_context *ctx) {
+	DUK_UNREF(ctx);
+	return DUK_RET_UNSUPPORTED_ERROR;
+}
+
+duk_ret_t duk_bi_regexp_prototype_to_string(duk_context *ctx) {
+	DUK_UNREF(ctx);
+	return DUK_RET_UNSUPPORTED_ERROR;
+}
+
+#endif  /* DUK_USE_REGEXP_SUPPORT */
+#line 1 "duk_bi_string.c"
+/*
+ *  String built-ins
+ */
+
+/* XXX: There are several limitations in the current implementation for
+ * strings with >= 0x80000000UL characters.  In some cases one would need
+ * to be able to represent the range [-0xffffffff,0xffffffff] and so on.
+ * Generally character and byte length are assumed to fit into signed 32
+ * bits (< 0x80000000UL).  Places with issues are not marked explicitly
+ * below in all cases, look for signed type usage (duk_int_t etc) for
+ * offsets/lengths.
+ */
+
+/* include removed: duk_internal.h */
+
+/*
+ *  Constructor
+ */
+
+duk_ret_t duk_bi_string_constructor(duk_context *ctx) {
+	/* String constructor needs to distinguish between an argument not given at all
+	 * vs. given as 'undefined'.  We're a vararg function to handle this properly.
+	 */
+
+	if (duk_get_top(ctx) == 0) {
+		duk_push_hstring_stridx(ctx, DUK_STRIDX_EMPTY_STRING);
+	} else {
+		duk_to_string(ctx, 0);
+	}
+	DUK_ASSERT(duk_is_string(ctx, 0));
+	duk_set_top(ctx, 1);
+
+	if (duk_is_constructor_call(ctx)) {
+		duk_push_object_helper(ctx,
+		                       DUK_HOBJECT_FLAG_EXTENSIBLE |
+		                       DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ |
+		                       DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_STRING),
+		                       DUK_BIDX_STRING_PROTOTYPE);
+
+		/* String object internal value is immutable */
+		duk_dup(ctx, 0);
+		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_NONE);
+	}
+	/* Note: unbalanced stack on purpose */
+
+	return 1;
+}
+
+duk_ret_t duk_bi_string_constructor_from_char_code(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hbuffer_dynamic *h;
+	duk_idx_t i, n;
+	duk_ucodepoint_t cp;
+
+	/* XXX: It would be nice to build the string directly but ToUint16()
+	 * coercion is needed so a generic helper would not be very
+	 * helpful (perhaps coerce the value stack first here and then
+	 * build a string from a duk_tval number sequence in one go?).
+	 */
+
+	n = duk_get_top(ctx);
+	duk_push_dynamic_buffer(ctx, 0);  /* XXX: initial spare size estimate from 'n' */
+	h = (duk_hbuffer_dynamic *) duk_get_hbuffer(ctx, -1);
+
+	for (i = 0; i < n; i++) {
+		cp = duk_to_uint16(ctx, i);
+		duk_hbuffer_append_cesu8(thr, h, cp);
+	}
+
+	duk_to_string(ctx, -1);
+	return 1;
+}
+
+/*
+ *  toString(), valueOf()
+ */
+
+duk_ret_t duk_bi_string_prototype_to_string(duk_context *ctx) {
+	duk_tval *tv;
+
+	duk_push_this(ctx);
+	tv = duk_require_tval(ctx, -1);
+	DUK_ASSERT(tv != NULL);
+
+	if (DUK_TVAL_IS_STRING(tv)) {
+		/* return as is */
+		return 1;
+	} else if (DUK_TVAL_IS_OBJECT(tv)) {
+		duk_hobject *h = DUK_TVAL_GET_OBJECT(tv);
+		DUK_ASSERT(h != NULL);
+
+		/* Must be a "string object", i.e. class "String" */
+		if (DUK_HOBJECT_GET_CLASS_NUMBER(h) != DUK_HOBJECT_CLASS_STRING) {
+			goto type_error;
+		}
+
+		duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_VALUE);
+		DUK_ASSERT(duk_is_string(ctx, -1));
+
+		return 1;
+	} else {
+		goto type_error;
+	}
+
+	/* never here, but fall through */
+
+ type_error:
+	return DUK_RET_TYPE_ERROR;
+}
+
+/*
+ *  Character and charcode access
+ */
+
+duk_ret_t duk_bi_string_prototype_char_at(duk_context *ctx) {
+	duk_int_t pos;
+
+	/* XXX: faster implementation */
+
+	(void) duk_push_this_coercible_to_string(ctx);
+	pos = duk_to_int(ctx, 0);
+	duk_substring(ctx, -1, pos, pos + 1);
+	return 1;
+}
+
+duk_ret_t duk_bi_string_prototype_char_code_at(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_int_t pos;
+	duk_hstring *h;
+	duk_bool_t clamped;
+
+	/* XXX: faster implementation */
+
+	DUK_DDD(DUK_DDDPRINT("arg=%!T", (duk_tval *) duk_get_tval(ctx, 0)));
+
+	h = duk_push_this_coercible_to_string(ctx);
+	DUK_ASSERT(h != NULL);
+
+	pos = duk_to_int_clamped_raw(ctx,
+	                             0 /*index*/,
+	                             0 /*min(incl)*/,
+	                             DUK_HSTRING_GET_CHARLEN(h) - 1 /*max(incl)*/,
+	                             &clamped /*out_clamped*/);
+	if (clamped) {
+		duk_push_number(ctx, DUK_DOUBLE_NAN);
+		return 1;
+	}
+
+	duk_push_u32(ctx, (duk_uint32_t) duk_hstring_char_code_at_raw(thr, h, pos));
+	return 1;
+}
+
+/*
+ *  substring(), substr(), slice()
+ */
+
+/* XXX: any chance of merging these three similar but still slightly
+ * different algorithms so that footprint would be reduced?
+ */
+
+duk_ret_t duk_bi_string_prototype_substring(duk_context *ctx) {
+	duk_hstring *h;
+	duk_int_t start_pos, end_pos;
+	duk_int_t len;
+
+	h = duk_push_this_coercible_to_string(ctx);
+	DUK_ASSERT(h != NULL);
+	len = (duk_int_t) DUK_HSTRING_GET_CHARLEN(h);
+
+	/* [ start end str ] */
+
+	start_pos = duk_to_int_clamped(ctx, 0, 0, len);
+	if (duk_is_undefined(ctx, 1)) {
+		end_pos = len;
+	} else {
+		end_pos = duk_to_int_clamped(ctx, 1, 0, len);
+	}
+	DUK_ASSERT(start_pos >= 0 && start_pos <= len);
+	DUK_ASSERT(end_pos >= 0 && end_pos <= len);
+
+	if (start_pos > end_pos) {
+		duk_int_t tmp = start_pos;
+		start_pos = end_pos;
+		end_pos = tmp;
+	}
+
+	DUK_ASSERT(end_pos >= start_pos);
+
+	duk_substring(ctx, -1, (duk_size_t) start_pos, (duk_size_t) end_pos);
+	return 1;
+}
+
+#ifdef DUK_USE_SECTION_B
+duk_ret_t duk_bi_string_prototype_substr(duk_context *ctx) {
+	duk_hstring *h;
+	duk_int_t start_pos, end_pos;
+	duk_int_t len;
+
+	/* Unlike non-obsolete String calls, substr() algorithm in E5.1
+	 * specification will happily coerce undefined and null to strings
+	 * ("undefined" and "null").
+	 */
+	duk_push_this(ctx);
+	h = duk_to_hstring(ctx, -1);
+	DUK_ASSERT(h != NULL);
+	len = (duk_int_t) DUK_HSTRING_GET_CHARLEN(h);
+
+	/* [ start length str ] */
+
+	/* The implementation for computing of start_pos and end_pos differs
+	 * from the standard algorithm, but is intended to result in the exactly
+	 * same behavior.  This is not always obvious.
+	 */
+
+	/* combines steps 2 and 5; -len ensures max() not needed for step 5 */
+	start_pos = duk_to_int_clamped(ctx, 0, -len, len);
+	if (start_pos < 0) {
+		start_pos = len + start_pos;
+	}
+	DUK_ASSERT(start_pos >= 0 && start_pos <= len);
+
+	/* combines steps 3, 6; step 7 is not needed */
+	if (duk_is_undefined(ctx, 1)) {
+		end_pos = len;
+	} else {
+		DUK_ASSERT(start_pos <= len);
+		end_pos = start_pos + duk_to_int_clamped(ctx, 1, 0, len - start_pos);
+	}
+	DUK_ASSERT(start_pos >= 0 && start_pos <= len);
+	DUK_ASSERT(end_pos >= 0 && end_pos <= len);
+	DUK_ASSERT(end_pos >= start_pos);
+
+	duk_substring(ctx, -1, (duk_size_t) start_pos, (duk_size_t) end_pos);
+	return 1;
+}
+#else  /* DUK_USE_SECTION_B */
+duk_ret_t duk_bi_string_prototype_substr(duk_context *ctx) {
+	DUK_UNREF(ctx);
+	return DUK_RET_UNSUPPORTED_ERROR;
+}
+#endif  /* DUK_USE_SECTION_B */
+
+duk_ret_t duk_bi_string_prototype_slice(duk_context *ctx) {
+	duk_hstring *h;
+	duk_int_t start_pos, end_pos;
+	duk_int_t len;
+
+	h = duk_push_this_coercible_to_string(ctx);
+	DUK_ASSERT(h != NULL);
+	len = (duk_int_t) DUK_HSTRING_GET_CHARLEN(h);
+
+	/* [ start end str ] */
+
+	start_pos = duk_to_int_clamped(ctx, 0, -len, len);
+	if (start_pos < 0) {
+		start_pos = len + start_pos;
+	}
+	if (duk_is_undefined(ctx, 1)) {
+		end_pos = len;
+	} else {
+		end_pos = duk_to_int_clamped(ctx, 1, -len, len);
+		if (end_pos < 0) {
+			end_pos = len + end_pos;
+		}
+	}
+	DUK_ASSERT(start_pos >= 0 && start_pos <= len);
+	DUK_ASSERT(end_pos >= 0 && end_pos <= len);
+
+	if (end_pos < start_pos) {
+		end_pos = start_pos;
+	}
+
+	DUK_ASSERT(end_pos >= start_pos);
+
+	duk_substring(ctx, -1, (duk_size_t) start_pos, (duk_size_t) end_pos);
+	return 1;
+}
+
+/*
+ *  Case conversion
+ */
+
+duk_ret_t duk_bi_string_prototype_caseconv_shared(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_small_int_t uppercase = duk_get_magic(ctx);
+
+	(void) duk_push_this_coercible_to_string(ctx);
+	duk_unicode_case_convert_string(thr, (duk_bool_t) uppercase);
+	return 1;
+}
+
+/*
+ *  indexOf() and lastIndexOf()
+ */
+
+duk_ret_t duk_bi_string_prototype_indexof_shared(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hstring *h_this;
+	duk_hstring *h_search;
+	duk_int_t clen_this;
+	duk_int_t cpos;
+	duk_int_t bpos;
+	duk_uint8_t *p_start, *p_end, *p;
+	duk_uint8_t *q_start;
+	duk_int_t q_blen;
+	duk_uint8_t firstbyte;
+	duk_uint8_t t;
+	duk_small_int_t is_lastindexof = duk_get_magic(ctx);  /* 0=indexOf, 1=lastIndexOf */
+
+	h_this = duk_push_this_coercible_to_string(ctx);
+	DUK_ASSERT(h_this != NULL);
+	clen_this = (duk_int_t) DUK_HSTRING_GET_CHARLEN(h_this);
+
+	h_search = duk_to_hstring(ctx, 0);
+	DUK_ASSERT(h_search != NULL);
+	q_start = DUK_HSTRING_GET_DATA(h_search);
+	q_blen = (duk_int_t) DUK_HSTRING_GET_BYTELEN(h_search);
+
+	duk_to_number(ctx, 1);
+	if (duk_is_nan(ctx, 1) && is_lastindexof) {
+		/* indexOf: NaN should cause pos to be zero.
+		 * lastIndexOf: NaN should cause pos to be +Infinity
+	 	 * (and later be clamped to len).
+		 */
+		cpos = clen_this;
+	} else {
+		cpos = duk_to_int_clamped(ctx, 1, 0, clen_this);
+	}
+
+	/* Empty searchstring always matches; cpos must be clamped here.
+	 * (If q_blen were < 0 due to clamped coercion, it would also be
+	 * caught here.)
+	 */
+	if (q_blen <= 0) {
+		duk_push_int(ctx, cpos);
+		return 1;
+	}
+	DUK_ASSERT(q_blen > 0);
+
+	bpos = (duk_int_t) duk_heap_strcache_offset_char2byte(thr, h_this, (duk_uint32_t) cpos);
+
+	p_start = DUK_HSTRING_GET_DATA(h_this);
+	p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_this);
+	p = p_start + bpos;
+
+	/* This loop is optimized for size.  For speed, there should be
+	 * two separate loops, and we should ensure that memcmp() can be
+	 * used without an extra "will searchstring fit" check.  Doing
+	 * the preconditioning for 'p' and 'p_end' is easy but cpos
+	 * must be updated if 'p' is wound back (backward scanning).
+	 */
+
+	firstbyte = q_start[0];  /* leading byte of match string */
+	while (p <= p_end && p >= p_start) {
+		t = *p;
+
+		/* For Ecmascript strings, this check can only match for
+		 * initial UTF-8 bytes (not continuation bytes).  For other
+		 * strings all bets are off.
+		 */
+
+		if ((t == firstbyte) && ((duk_size_t) (p_end - p) >= (duk_size_t) q_blen)) {
+			DUK_ASSERT(q_blen > 0);  /* no issues with memcmp() zero size, even if broken */
+			if (DUK_MEMCMP(p, q_start, (duk_size_t) q_blen) == 0) {
+				duk_push_int(ctx, cpos);
+				return 1;
+			}
+		}
+
+		/* track cpos while scanning */
+		if (is_lastindexof) {
+			/* when going backwards, we decrement cpos 'early';
+			 * 'p' may point to a continuation byte of the char
+			 * at offset 'cpos', but that's OK because we'll
+			 * backtrack all the way to the initial byte.
+			 */
+			if ((t & 0xc0) != 0x80) {
+				cpos--;
+			}
+			p--;
+		} else {
+			if ((t & 0xc0) != 0x80) {
+				cpos++;
+			}
+			p++;
+		}
+	}
+
+	/* Not found.  Empty string case is handled specially above. */
+	duk_push_int(ctx, -1);
+	return 1;
+}
+
+/*
+ *  replace()
+ */
+
+/* XXX: the current implementation works but is quite clunky; it compiles
+ * to almost 1,4kB of x86 code so it needs to be simplified (better approach,
+ * shared helpers, etc).  Some ideas for refactoring:
+ *
+ * - a primitive to convert a string into a regexp matcher (reduces matching
+ *   code at the cost of making matching much slower)
+ * - use replace() as a basic helper for match() and split(), which are both
+ *   much simpler
+ * - API call to get_prop and to_boolean
+ */
+
+duk_ret_t duk_bi_string_prototype_replace(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hstring *h_input;
+	duk_hstring *h_repl;
+	duk_hstring *h_match;
+	duk_hstring *h_search;
+	duk_hobject *h_re;
+	duk_hbuffer_dynamic *h_buf;
+#ifdef DUK_USE_REGEXP_SUPPORT
+	duk_bool_t is_regexp;
+	duk_bool_t is_global;
+#endif
+	duk_bool_t is_repl_func;
+	duk_uint32_t match_start_coff, match_start_boff;
+#ifdef DUK_USE_REGEXP_SUPPORT
+	duk_int_t match_caps;
+#endif
+	duk_uint32_t prev_match_end_boff;
+	duk_uint8_t *r_start, *r_end, *r;   /* repl string scan */
+
+	DUK_ASSERT_TOP(ctx, 2);
+	h_input = duk_push_this_coercible_to_string(ctx);
+	DUK_ASSERT(h_input != NULL);
+	duk_push_dynamic_buffer(ctx, 0);
+	h_buf = (duk_hbuffer_dynamic *) duk_get_hbuffer(ctx, -1);
+	DUK_ASSERT(h_buf != NULL);
+	DUK_ASSERT_TOP(ctx, 4);
+
+	/* stack[0] = search value
+	 * stack[1] = replace value
+	 * stack[2] = input string
+	 * stack[3] = result buffer
+	 */
+
+	h_re = duk_get_hobject_with_class(ctx, 0, DUK_HOBJECT_CLASS_REGEXP);
+	if (h_re) {
+#ifdef DUK_USE_REGEXP_SUPPORT
+		is_regexp = 1;
+		is_global = duk_get_prop_stridx_boolean(ctx, 0, DUK_STRIDX_GLOBAL, NULL);
+
+		if (is_global) {
+			/* start match from beginning */
+			duk_push_int(ctx, 0);
+			duk_put_prop_stridx(ctx, 0, DUK_STRIDX_LAST_INDEX);
+		}
+#else  /* DUK_USE_REGEXP_SUPPORT */
+		return DUK_RET_UNSUPPORTED_ERROR;
+#endif  /* DUK_USE_REGEXP_SUPPORT */
+	} else {
+		duk_to_string(ctx, 0);
+#ifdef DUK_USE_REGEXP_SUPPORT
+		is_regexp = 0;
+		is_global = 0;
+#endif
+	}
+
+	if (duk_is_function(ctx, 1)) {
+		is_repl_func = 1;
+		r_start = NULL;
+		r_end = NULL;
+	} else {
+		is_repl_func = 0;
+		h_repl = duk_to_hstring(ctx, 1);
+		DUK_ASSERT(h_repl != NULL);
+		r_start = DUK_HSTRING_GET_DATA(h_repl);
+		r_end = r_start + DUK_HSTRING_GET_BYTELEN(h_repl);
+	}
+
+	prev_match_end_boff = 0;
+
+	for (;;) {
+		/*
+		 *  If matching with a regexp:
+		 *    - non-global RegExp: lastIndex not touched on a match, zeroed
+		 *      on a non-match
+		 *    - global RegExp: on match, lastIndex will be updated by regexp
+		 *      executor to point to next char after the matching part (so that
+		 *      characters in the matching part are not matched again)
+		 *
+		 *  If matching with a string:
+		 *    - always non-global match, find first occurrence
+		 *
+		 *  We need:
+		 *    - The character offset of start-of-match for the replacer function
+		 *    - The byte offsets for start-of-match and end-of-match to implement
+		 *      the replacement values $&, $`, and $', and to copy non-matching
+		 *      input string portions (including header and trailer) verbatim.
+		 *
+		 *  NOTE: the E5.1 specification is a bit vague how the RegExp should
+		 *  behave in the replacement process; e.g. is matching done first for
+		 *  all matches (in the global RegExp case) before any replacer calls
+		 *  are made?  See: test-bi-string-proto-replace.js for discussion.
+		 */
+
+		DUK_ASSERT_TOP(ctx, 4);
+
+#ifdef DUK_USE_REGEXP_SUPPORT
+		if (is_regexp) {
+			duk_dup(ctx, 0);
+			duk_dup(ctx, 2);
+			duk_regexp_match(thr);  /* [ ... regexp input ] -> [ res_obj ] */
+			if (!duk_is_object(ctx, -1)) {
+				duk_pop(ctx);
+				break;
+			}
+
+			duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INDEX);
+			DUK_ASSERT(duk_is_number(ctx, -1));
+			match_start_coff = duk_get_int(ctx, -1);
+			duk_pop(ctx);
+
+			duk_get_prop_index(ctx, -1, 0);
+			DUK_ASSERT(duk_is_string(ctx, -1));
+			h_match = duk_get_hstring(ctx, -1);
+			DUK_ASSERT(h_match != NULL);
+			duk_pop(ctx);  /* h_match is borrowed, remains reachable through match_obj */
+
+			if (DUK_HSTRING_GET_BYTELEN(h_match) == 0) {
+				/* This should be equivalent to match() algorithm step 8.f.iii.2:
+				 * detect an empty match and allow it, but don't allow it twice.
+				 */
+				duk_uint32_t last_index;
+
+				duk_get_prop_stridx(ctx, 0, DUK_STRIDX_LAST_INDEX);
+				last_index = (duk_uint32_t) duk_get_uint(ctx, -1);
+				DUK_DDD(DUK_DDDPRINT("empty match, bump lastIndex: %ld -> %ld",
+				                     (long) last_index, (long) (last_index + 1)));
+				duk_pop(ctx);
+				duk_push_int(ctx, last_index + 1);
+				duk_put_prop_stridx(ctx, 0, DUK_STRIDX_LAST_INDEX);
+			}
+
+			DUK_ASSERT(duk_get_length(ctx, -1) <= DUK_INT_MAX);  /* string limits */
+			match_caps = (duk_int_t) duk_get_length(ctx, -1);
+		} else {
+#else  /* DUK_USE_REGEXP_SUPPORT */
+		{  /* unconditionally */
+#endif  /* DUK_USE_REGEXP_SUPPORT */
+			duk_uint8_t *p_start, *p_end, *p;   /* input string scan */
+			duk_uint8_t *q_start;               /* match string */
+			duk_size_t q_blen;
+
+#ifdef DUK_USE_REGEXP_SUPPORT
+			DUK_ASSERT(!is_global);  /* single match always */
+#endif
+
+			p_start = DUK_HSTRING_GET_DATA(h_input);
+			p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_input);
+			p = p_start;
+
+			h_search = duk_get_hstring(ctx, 0);
+			DUK_ASSERT(h_search != NULL);
+			q_start = DUK_HSTRING_GET_DATA(h_search);
+			q_blen = (duk_size_t) DUK_HSTRING_GET_BYTELEN(h_search);
+
+			p_end -= q_blen;  /* ensure full memcmp() fits in while */
+
+			match_start_coff = 0;
+
+			while (p <= p_end) {
+				DUK_ASSERT(p + q_blen <= DUK_HSTRING_GET_DATA(h_input) + DUK_HSTRING_GET_BYTELEN(h_input));
+				if (DUK_MEMCMP((void *) p, (void *) q_start, (size_t) q_blen) == 0) {
+					duk_dup(ctx, 0);
+					h_match = duk_get_hstring(ctx, -1);
+					DUK_ASSERT(h_match != NULL);
+#ifdef DUK_USE_REGEXP_SUPPORT
+					match_caps = 0;
+#endif
+					goto found;
+				}
+
+				/* track utf-8 non-continuation bytes */
+				if ((p[0] & 0xc0) != 0x80) {
+					match_start_coff++;
+				}
+				p++;
+			}
+
+			/* not found */
+			break;
+		}
+	 found:
+
+		/* stack[0] = search value
+		 * stack[1] = replace value
+		 * stack[2] = input string
+		 * stack[3] = result buffer
+		 * stack[4] = regexp match OR match string
+		 */
+
+		match_start_boff = duk_heap_strcache_offset_char2byte(thr, h_input, match_start_coff);
+
+		duk_hbuffer_append_bytes(thr,
+		                         h_buf,
+		                         DUK_HSTRING_GET_DATA(h_input) + prev_match_end_boff,
+		                         (duk_size_t) (match_start_boff - prev_match_end_boff));
+
+		prev_match_end_boff = match_start_boff + DUK_HSTRING_GET_BYTELEN(h_match);
+
+		if (is_repl_func) {
+			duk_idx_t idx_args;
+			duk_hstring *h_repl;
+
+			/* regexp res_obj is at index 4 */
+
+			duk_dup(ctx, 1);
+			idx_args = duk_get_top(ctx);
+
+#ifdef DUK_USE_REGEXP_SUPPORT
+			if (is_regexp) {
+				duk_int_t idx;
+				duk_require_stack(ctx, match_caps + 2);
+				for (idx = 0; idx < match_caps; idx++) {
+					/* match followed by capture(s) */
+					duk_get_prop_index(ctx, 4, idx);
+				}
+			} else {
+#else  /* DUK_USE_REGEXP_SUPPORT */
+			{  /* unconditionally */
+#endif  /* DUK_USE_REGEXP_SUPPORT */
+				/* match == search string, by definition */
+				duk_dup(ctx, 0);
+			}
+			duk_push_int(ctx, match_start_coff);
+			duk_dup(ctx, 2);
+
+			/* [ ... replacer match [captures] match_char_offset input ] */
+
+			duk_call(ctx, duk_get_top(ctx) - idx_args);
+			h_repl = duk_to_hstring(ctx, -1);  /* -> [ ... repl_value ] */
+			DUK_ASSERT(h_repl != NULL);
+			duk_hbuffer_append_hstring(thr, h_buf, h_repl);
+			duk_pop(ctx);  /* repl_value */
+		} else {
+			r = r_start;
+
+			while (r < r_end) {
+				duk_int_t ch1;
+				duk_int_t ch2;
+#ifdef DUK_USE_REGEXP_SUPPORT
+				duk_int_t ch3;
+#endif
+				duk_size_t left;
+
+				ch1 = *r++;
+				if (ch1 != DUK_ASC_DOLLAR) {
+					goto repl_write;
+				}
+				left = r_end - r;
+
+				if (left <= 0) {
+					goto repl_write;
+				}
+
+				ch2 = r[0];
+				switch ((int) ch2) {
+				case DUK_ASC_DOLLAR: {
+					ch1 = (1 << 8) + DUK_ASC_DOLLAR;
+					goto repl_write;
+				}
+				case DUK_ASC_AMP: {
+					duk_hbuffer_append_hstring(thr, h_buf, h_match);
+					r++;
+					continue;
+				}
+				case DUK_ASC_GRAVE: {
+					duk_hbuffer_append_bytes(thr,
+					                         h_buf,
+					                         DUK_HSTRING_GET_DATA(h_input),
+					                         match_start_boff);
+					r++;
+					continue;
+				}
+				case DUK_ASC_SINGLEQUOTE: {
+					duk_uint32_t match_end_boff;
+
+					/* Use match charlen instead of bytelen, just in case the input and
+					 * match codepoint encodings would have different lengths.
+					 */
+					match_end_boff = duk_heap_strcache_offset_char2byte(thr,
+					                                                    h_input,
+					                                                    match_start_coff + DUK_HSTRING_GET_CHARLEN(h_match));
+
+					duk_hbuffer_append_bytes(thr,
+					                         h_buf,
+					                         DUK_HSTRING_GET_DATA(h_input) + match_end_boff,
+					                         DUK_HSTRING_GET_BYTELEN(h_input) - match_end_boff);
+					r++;
+					continue;
+				}
+				default: {
+#ifdef DUK_USE_REGEXP_SUPPORT
+					duk_int_t capnum, captmp, capadv;
+					/* XXX: optional check, match_caps is zero if no regexp,
+					 * so dollar will be interpreted literally anyway.
+					 */
+
+					if (!is_regexp) {
+						goto repl_write;
+					}
+
+					if (!(ch2 >= DUK_ASC_0 && ch2 <= DUK_ASC_9)) {
+						goto repl_write;
+					}
+					capnum = ch2 - DUK_ASC_0;
+					capadv = 1;
+
+					if (left >= 2) {
+						ch3 = r[1];
+						if (ch3 >= DUK_ASC_0 && ch3 <= DUK_ASC_9) {
+							captmp = capnum * 10 + (ch3 - DUK_ASC_0);
+							if (captmp < match_caps) {
+								capnum = captmp;
+								capadv = 2;
+							}
+						}
+					}
+
+					if (capnum > 0 && capnum < match_caps) {
+						DUK_ASSERT(is_regexp != 0);  /* match_caps == 0 without regexps */
+
+						/* regexp res_obj is at offset 4 */
+						duk_get_prop_index(ctx, 4, (duk_uarridx_t) capnum);
+						if (duk_is_string(ctx, -1)) {
+							DUK_ASSERT(duk_get_hstring(ctx, -1) != NULL);
+							duk_hbuffer_append_hstring(thr, h_buf, duk_get_hstring(ctx, -1));
+						} else {
+							/* undefined -> skip (replaced with empty) */
+						}
+						duk_pop(ctx);
+						r += capadv;
+						continue;
+					} else {
+						goto repl_write;
+					}
+#else  /* DUK_USE_REGEXP_SUPPORT */
+					goto repl_write;  /* unconditionally */
+#endif  /* DUK_USE_REGEXP_SUPPORT */
+				}  /* default case */
+				}  /* switch (ch2) */
+
+			 repl_write:
+				/* ch1 = (r_increment << 8) + byte */
+				duk_hbuffer_append_byte(thr, h_buf, (duk_uint8_t) (ch1 & 0xff));
+				r += ch1 >> 8;
+			}  /* while repl */
+		}  /* if (is_repl_func) */
+
+		duk_pop(ctx);  /* pop regexp res_obj or match string */
+
+#ifdef DUK_USE_REGEXP_SUPPORT
+		if (!is_global) {
+#else
+		{  /* unconditionally; is_global==0 */
+#endif
+			break;
+		}
+	}
+
+	/* trailer */
+	duk_hbuffer_append_bytes(thr,
+	                         h_buf,
+	                         DUK_HSTRING_GET_DATA(h_input) + prev_match_end_boff,
+	                         (duk_size_t) (DUK_HSTRING_GET_BYTELEN(h_input) - prev_match_end_boff));
+
+	DUK_ASSERT_TOP(ctx, 4);
+	duk_to_string(ctx, -1);
+	return 1;
+}
+
+/*
+ *  split()
+ */
+
+/* XXX: very messy now, but works; clean up, remove unused variables (nomimally
+ * used so compiler doesn't complain).
+ */
+
+duk_ret_t duk_bi_string_prototype_split(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hstring *h_input;
+	duk_hstring *h_sep;
+	duk_uint32_t limit;
+	duk_uint32_t arr_idx;
+#ifdef DUK_USE_REGEXP_SUPPORT
+	duk_bool_t is_regexp;
+#endif
+	duk_bool_t matched;  /* set to 1 if any match exists (needed for empty input special case) */
+	duk_uint32_t prev_match_end_coff, prev_match_end_boff;
+	duk_uint32_t match_start_boff, match_start_coff;
+	duk_uint32_t match_end_boff, match_end_coff;
+
+	DUK_UNREF(thr);
+
+	h_input = duk_push_this_coercible_to_string(ctx);
+	DUK_ASSERT(h_input != NULL);
+
+	duk_push_array(ctx);
+
+	if (duk_is_undefined(ctx, 1)) {
+		limit = 0xffffffffUL;
+	} else {
+		limit = duk_to_uint32(ctx, 1);
+	}
+
+	if (limit == 0) {
+		return 1;
+	}
+
+	/* If the separator is a RegExp, make a "clone" of it.  The specification
+	 * algorithm calls [[Match]] directly for specific indices; we emulate this
+	 * by tweaking lastIndex and using a "force global" variant of duk_regexp_match()
+	 * which will use global-style matching even when the RegExp itself is non-global.
+	 */
+
+	if (duk_is_undefined(ctx, 0)) {
+		/* The spec algorithm first does "R = ToString(separator)" before checking
+		 * whether separator is undefined.  Since this is side effect free, we can
+		 * skip the ToString() here.
+		 */
+		duk_dup(ctx, 2);
+		duk_put_prop_index(ctx, 3, 0);
+		return 1;
+	} else if (duk_get_hobject_with_class(ctx, 0, DUK_HOBJECT_CLASS_REGEXP) != NULL) {
+#ifdef DUK_USE_REGEXP_SUPPORT
+		duk_push_hobject_bidx(ctx, DUK_BIDX_REGEXP_CONSTRUCTOR);
+		duk_dup(ctx, 0);
+		duk_new(ctx, 1);  /* [ ... RegExp val ] -> [ ... res ] */
+		duk_replace(ctx, 0);
+		/* lastIndex is initialized to zero by new RegExp() */
+		is_regexp = 1;
+#else
+		return DUK_RET_UNSUPPORTED_ERROR;
+#endif
+	} else {
+		duk_to_string(ctx, 0);
+#ifdef DUK_USE_REGEXP_SUPPORT
+		is_regexp = 0;
+#endif
+	}
+
+	/* stack[0] = separator (string or regexp)
+	 * stack[1] = limit
+	 * stack[2] = input string
+	 * stack[3] = result array
+	 */
+
+	prev_match_end_boff = 0;
+	prev_match_end_coff = 0;
+	arr_idx = 0;
+	matched = 0;
+
+	for (;;) {
+		/*
+		 *  The specification uses RegExp [[Match]] to attempt match at specific
+		 *  offsets.  We don't have such a primitive, so we use an actual RegExp
+		 *  and tweak lastIndex.  Since the RegExp may be non-global, we use a
+		 *  special variant which forces global-like behavior for matching.
+		 */
+
+		DUK_ASSERT_TOP(ctx, 4);
+
+#ifdef DUK_USE_REGEXP_SUPPORT
+		if (is_regexp) {
+			duk_dup(ctx, 0);
+			duk_dup(ctx, 2);
+			duk_regexp_match_force_global(thr);  /* [ ... regexp input ] -> [ res_obj ] */
+			if (!duk_is_object(ctx, -1)) {
+				duk_pop(ctx);
+				break;
+			}
+			matched = 1;
+
+			duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INDEX);
+			DUK_ASSERT(duk_is_number(ctx, -1));
+			match_start_coff = duk_get_int(ctx, -1);
+			match_start_boff = duk_heap_strcache_offset_char2byte(thr, h_input, match_start_coff);
+			duk_pop(ctx);
+
+			if (match_start_coff == DUK_HSTRING_GET_CHARLEN(h_input)) {
+				/* don't allow an empty match at the end of the string */
+				duk_pop(ctx);
+				break;
+			}
+
+			duk_get_prop_stridx(ctx, 0, DUK_STRIDX_LAST_INDEX);
+			DUK_ASSERT(duk_is_number(ctx, -1));
+			match_end_coff = duk_get_int(ctx, -1);
+			match_end_boff = duk_heap_strcache_offset_char2byte(thr, h_input, match_end_coff);
+			duk_pop(ctx);
+
+			/* empty match -> bump and continue */
+			if (prev_match_end_boff == match_end_boff) {
+				duk_push_int(ctx, match_end_coff + 1);
+				duk_put_prop_stridx(ctx, 0, DUK_STRIDX_LAST_INDEX);
+				duk_pop(ctx);
+				continue;
+			}
+		} else {
+#else  /* DUK_USE_REGEXP_SUPPORT */
+		{  /* unconditionally */
+#endif  /* DUK_USE_REGEXP_SUPPORT */
+			duk_uint8_t *p_start, *p_end, *p;   /* input string scan */
+			duk_uint8_t *q_start;               /* match string */
+			duk_size_t q_blen, q_clen;
+
+			p_start = DUK_HSTRING_GET_DATA(h_input);
+			p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_input);
+			p = p_start + prev_match_end_boff;
+
+			h_sep = duk_get_hstring(ctx, 0);
+			DUK_ASSERT(h_sep != NULL);
+			q_start = DUK_HSTRING_GET_DATA(h_sep);
+			q_blen = (duk_size_t) DUK_HSTRING_GET_BYTELEN(h_sep);
+			q_clen = (duk_size_t) DUK_HSTRING_GET_CHARLEN(h_sep);
+
+			p_end -= q_blen;  /* ensure full memcmp() fits in while */
+
+			match_start_coff = prev_match_end_coff;
+
+			if (q_blen == 0) {
+				/* Handle empty separator case: it will always match, and always
+				 * triggers the check in step 13.c.iii initially.  Note that we
+				 * must skip to either end of string or start of first codepoint,
+				 * skipping over any continuation bytes!
+				 *
+				 * Don't allow an empty string to match at the end of the input.
+				 */
+
+				matched = 1;  /* empty separator can always match */
+
+				match_start_coff++;
+				p++;
+				while (p < p_end) {
+					if ((p[0] & 0xc0) != 0x80) {
+						goto found;
+					}
+					p++;
+				}
+				goto not_found;
+			}
+
+			DUK_ASSERT(q_blen > 0 && q_clen > 0);
+			while (p <= p_end) {
+				DUK_ASSERT(p + q_blen <= DUK_HSTRING_GET_DATA(h_input) + DUK_HSTRING_GET_BYTELEN(h_input));
+				DUK_ASSERT(q_blen > 0);  /* no issues with empty memcmp() */
+				if (DUK_MEMCMP((void *) p, (void *) q_start, (duk_size_t) q_blen) == 0) {
+					/* never an empty match, so step 13.c.iii can't be triggered */
+					goto found;
+				}
+
+				/* track utf-8 non-continuation bytes */
+				if ((p[0] & 0xc0) != 0x80) {
+					match_start_coff++;
+				}
+				p++;
+			}
+
+		 not_found:
+			/* not found */
+			break;
+
+		 found:
+			matched = 1;
+			match_start_boff = (duk_uint32_t) (p - p_start);
+			match_end_coff = match_start_coff + q_clen;
+			match_end_boff = match_start_boff + q_blen;
+
+			/* empty match (may happen with empty separator) -> bump and continue */
+			if (prev_match_end_boff == match_end_boff) {
+				prev_match_end_boff++;
+				prev_match_end_coff++;
+				continue;
+			}
+		}  /* if (is_regexp) */
+
+		/* stack[0] = separator (string or regexp)
+		 * stack[1] = limit
+		 * stack[2] = input string
+		 * stack[3] = result array
+		 * stack[4] = regexp res_obj (if is_regexp)
+		 */
+
+		DUK_DDD(DUK_DDDPRINT("split; match_start b=%ld,c=%ld, match_end b=%ld,c=%ld, prev_end b=%ld,c=%ld",
+		                     (long) match_start_boff, (long) match_start_coff,
+		                     (long) match_end_boff, (long) match_end_coff,
+		                     (long) prev_match_end_boff, (long) prev_match_end_coff));
+
+		duk_push_lstring(ctx,
+		                 (const char *) (DUK_HSTRING_GET_DATA(h_input) + prev_match_end_boff),
+		                 (duk_size_t) (match_start_boff - prev_match_end_boff));
+		duk_put_prop_index(ctx, 3, arr_idx);
+		arr_idx++;
+		if (arr_idx >= limit) {
+			goto hit_limit;
+		}
+
+#ifdef DUK_USE_REGEXP_SUPPORT
+		if (is_regexp) {
+			duk_size_t i, len;
+
+			len = duk_get_length(ctx, 4);
+			for (i = 1; i < len; i++) {
+				DUK_ASSERT(i <= DUK_UARRIDX_MAX);  /* cannot have >4G captures */
+				duk_get_prop_index(ctx, 4, (duk_uarridx_t) i);
+				duk_put_prop_index(ctx, 3, arr_idx);
+				arr_idx++;
+				if (arr_idx >= limit) {
+					goto hit_limit;
+				}
+			}
+
+			duk_pop(ctx);
+			/* lastIndex already set up for next match */
+		} else {
+#else  /* DUK_USE_REGEXP_SUPPORT */
+		{  /* unconditionally */
+#endif  /* DUK_USE_REGEXP_SUPPORT */
+			/* no action */
+		}
+
+		prev_match_end_boff = match_end_boff;
+		prev_match_end_coff = match_end_coff;
+		continue;
+	}  /* for */
+
+	/* Combined step 11 (empty string special case) and 14-15. */
+
+	DUK_DDD(DUK_DDDPRINT("split trailer; prev_end b=%ld,c=%ld",
+	                     (long) prev_match_end_boff, (long) prev_match_end_coff));
+
+	if (DUK_HSTRING_GET_CHARLEN(h_input) > 0 || !matched) {
+		/* Add trailer if:
+		 *   a) non-empty input
+		 *   b) empty input and no (zero size) match found (step 11)
+		 */
+
+		duk_push_lstring(ctx,
+		                 (const char *) DUK_HSTRING_GET_DATA(h_input) + prev_match_end_boff,
+		                 (duk_size_t) (DUK_HSTRING_GET_BYTELEN(h_input) - prev_match_end_boff));
+		duk_put_prop_index(ctx, 3, arr_idx);
+		/* No arr_idx update or limit check */
+	}
+
+	return 1;
+
+ hit_limit:
+#ifdef DUK_USE_REGEXP_SUPPORT
+	if (is_regexp) {
+		duk_pop(ctx);
+	}
+#endif
+
+	return 1;
+}
+
+/*
+ *  Various
+ */
+
+#ifdef DUK_USE_REGEXP_SUPPORT
+static void duk__to_regexp_helper(duk_context *ctx, duk_idx_t index, duk_bool_t force_new) {
+	duk_hobject *h;
+
+	/* Shared helper for match() steps 3-4, search() steps 3-4. */
+
+	DUK_ASSERT(index >= 0);
+
+	if (force_new) {
+		goto do_new;
+	}
+
+	h = duk_get_hobject_with_class(ctx, index, DUK_HOBJECT_CLASS_REGEXP);
+	if (!h) {
+		goto do_new;
+	}
+	return;
+
+ do_new:
+	duk_push_hobject_bidx(ctx, DUK_BIDX_REGEXP_CONSTRUCTOR);
+	duk_dup(ctx, index);
+	duk_new(ctx, 1);  /* [ ... RegExp val ] -> [ ... res ] */
+	duk_replace(ctx, index);
+}
+#endif  /* DUK_USE_REGEXP_SUPPORT */
+
+#ifdef DUK_USE_REGEXP_SUPPORT
+duk_ret_t duk_bi_string_prototype_search(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+
+	/* Easiest way to implement the search required by the specification
+	 * is to do a RegExp test() with lastIndex forced to zero.  To avoid
+	 * side effects on the argument, "clone" the RegExp if a RegExp was
+	 * given as input.
+	 *
+	 * The global flag of the RegExp should be ignored; setting lastIndex
+	 * to zero (which happens when "cloning" the RegExp) should have an
+	 * equivalent effect.
+	 */
+
+	DUK_ASSERT_TOP(ctx, 1);
+	(void) duk_push_this_coercible_to_string(ctx);  /* at index 1 */
+	duk__to_regexp_helper(ctx, 0 /*index*/, 1 /*force_new*/);
+
+	/* stack[0] = regexp
+	 * stack[1] = string
+	 */
+
+	/* Avoid using RegExp.prototype methods, as they're writable and
+	 * configurable and may have been changed.
+	 */
+
+	duk_dup(ctx, 0);
+	duk_dup(ctx, 1);  /* [ ... re_obj input ] */
+	duk_regexp_match(thr);  /* -> [ ... res_obj ] */
+
+	if (!duk_is_object(ctx, -1)) {
+		duk_push_int(ctx, -1);
+		return 1;
+	}
+
+	duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INDEX);
+	DUK_ASSERT(duk_is_number(ctx, -1));
+	return 1;
+}
+#else  /* DUK_USE_REGEXP_SUPPORT */
+duk_ret_t duk_bi_string_prototype_search(duk_context *ctx) {
+	DUK_UNREF(ctx);
+	return DUK_RET_UNSUPPORTED_ERROR;
+}
+#endif  /* DUK_USE_REGEXP_SUPPORT */
+
+#ifdef DUK_USE_REGEXP_SUPPORT
+duk_ret_t duk_bi_string_prototype_match(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_bool_t global;
+	duk_int_t prev_last_index;
+	duk_int_t this_index;
+	duk_int_t arr_idx;
+
+	DUK_ASSERT_TOP(ctx, 1);
+	(void) duk_push_this_coercible_to_string(ctx);
+	duk__to_regexp_helper(ctx, 0 /*index*/, 0 /*force_new*/);
+	global = duk_get_prop_stridx_boolean(ctx, 0, DUK_STRIDX_GLOBAL, NULL);
+	DUK_ASSERT_TOP(ctx, 2);
+
+	/* stack[0] = regexp
+	 * stack[1] = string
+	 */
+
+	if (!global) {
+		duk_regexp_match(thr);  /* -> [ res_obj ] */
+		return 1;  /* return 'res_obj' */
+	}
+
+	/* Global case is more complex. */
+
+	/* [ regexp string ] */
+
+	duk_push_int(ctx, 0);
+	duk_put_prop_stridx(ctx, 0, DUK_STRIDX_LAST_INDEX);
+	duk_push_array(ctx);
+
+	/* [ regexp string res_arr ] */
+
+	prev_last_index = 0;
+	arr_idx = 0;
+
+	for (;;) {
+		DUK_ASSERT_TOP(ctx, 3);
+
+		duk_dup(ctx, 0);
+		duk_dup(ctx, 1);
+		duk_regexp_match(thr);  /* -> [ ... regexp string ] -> [ ... res_obj ] */
+
+		if (!duk_is_object(ctx, -1)) {
+			duk_pop(ctx);
+			break;
+		}
+
+		duk_get_prop_stridx(ctx, 0, DUK_STRIDX_LAST_INDEX);
+		DUK_ASSERT(duk_is_number(ctx, -1));
+		this_index = duk_get_int(ctx, -1);
+		duk_pop(ctx);
+
+		if (this_index == prev_last_index) {
+			this_index++;
+			duk_push_int(ctx, this_index);
+			duk_put_prop_stridx(ctx, 0, DUK_STRIDX_LAST_INDEX);
+		}
+		prev_last_index = this_index;
+
+		duk_get_prop_index(ctx, -1, 0);  /* match string */
+		duk_put_prop_index(ctx, 2, arr_idx);
+		arr_idx++;
+		duk_pop(ctx);  /* res_obj */
+	}
+
+	if (arr_idx == 0) {
+		duk_push_null(ctx);
+	}
+
+	return 1;  /* return 'res_arr' or 'null' */
+}
+#else  /* DUK_USE_REGEXP_SUPPORT */
+duk_ret_t duk_bi_string_prototype_match(duk_context *ctx) {
+	DUK_UNREF(ctx);
+	return DUK_RET_UNSUPPORTED_ERROR;
+}
+#endif  /* DUK_USE_REGEXP_SUPPORT */
+
+duk_ret_t duk_bi_string_prototype_concat(duk_context *ctx) {
+	/* duk_concat() coerces arguments with ToString() in correct order */
+	(void) duk_push_this_coercible_to_string(ctx);
+	duk_insert(ctx, 0);  /* this is relatively expensive */
+	duk_concat(ctx, duk_get_top(ctx));
+	return 1;
+}
+
+duk_ret_t duk_bi_string_prototype_trim(duk_context *ctx) {
+	DUK_ASSERT_TOP(ctx, 0);
+	(void) duk_push_this_coercible_to_string(ctx);
+	duk_trim(ctx, 0);
+	DUK_ASSERT_TOP(ctx, 1);
+	return 1;
+}
+
+duk_ret_t duk_bi_string_prototype_locale_compare(duk_context *ctx) {
+	duk_hstring *h1;
+	duk_hstring *h2;
+	duk_size_t h1_len, h2_len, prefix_len;
+	duk_small_int_t ret = 0;
+	duk_small_int_t rc;
+
+	/* The current implementation of localeCompare() is simply a codepoint
+	 * by codepoint comparison, implemented with a simple string compare
+	 * because UTF-8 should preserve codepoint ordering (assuming valid
+	 * shortest UTF-8 encoding).
+	 *
+	 * The specification requires that the return value must be related
+	 * to the sort order: e.g. negative means that 'this' comes before
+	 * 'that' in sort order.  We assume an ascending sort order.
+	 */
+
+	/* XXX: could share code with duk_js_ops.c, duk_js_compare_helper */
+
+	h1 = duk_push_this_coercible_to_string(ctx);
+	DUK_ASSERT(h1 != NULL);
+
+	h2 = duk_to_hstring(ctx, 0);
+	DUK_ASSERT(h2 != NULL);
+
+	h1_len = (duk_size_t) DUK_HSTRING_GET_BYTELEN(h1);
+	h2_len = (duk_size_t) DUK_HSTRING_GET_BYTELEN(h2);
+	prefix_len = (h1_len <= h2_len ? h1_len : h2_len);
+
+	/* Zero size compare not an issue with DUK_MEMCMP. */
+	rc = (duk_small_int_t) DUK_MEMCMP((const char *) DUK_HSTRING_GET_DATA(h1),
+	                                  (const char *) DUK_HSTRING_GET_DATA(h2),
+	                                  prefix_len);
+
+	if (rc < 0) {
+		ret = -1;
+		goto done;
+	} else if (rc > 0) {
+		ret = 1;
+		goto done;
+	}
+
+	/* prefix matches, lengths matter now */
+	if (h1_len > h2_len) {
+		ret = 1;
+		goto done;
+	} else if (h1_len == h2_len) {
+		DUK_ASSERT(ret == 0);
+		goto done;
+	}
+	ret = -1;
+	goto done;
+
+ done:
+	duk_push_int(ctx, (duk_int_t) ret);
+	return 1;
+}
+#line 1 "duk_bi_thread.c"
+/*
+ *  Thread builtins
+ */
+
+/* include removed: duk_internal.h */
+
+/*
+ *  Constructor
+ */
+
+duk_ret_t duk_bi_thread_constructor(duk_context *ctx) {
+	duk_hthread *new_thr;
+	duk_hobject *func;
+
+	if (!duk_is_callable(ctx, 0)) {
+		return DUK_RET_TYPE_ERROR;
+	}
+	func = duk_get_hobject(ctx, 0);
+	DUK_ASSERT(func != NULL);
+
+	duk_push_thread(ctx);
+	new_thr = (duk_hthread *) duk_get_hobject(ctx, -1);
+	DUK_ASSERT(new_thr != NULL);
+	new_thr->state = DUK_HTHREAD_STATE_INACTIVE;
+
+	/* push initial function call to new thread stack; this is
+	 * picked up by resume().
+	 */
+	duk_push_hobject((duk_context *) new_thr, func);
+
+	return 1;  /* return thread */
+}
+
+/*
+ *  Resume a thread.
+ *
+ *  The thread must be in resumable state, either (a) new thread which hasn't
+ *  yet started, or (b) a thread which has previously yielded.  This method
+ *  must be called from an Ecmascript function.
+ *
+ *  Args:
+ *    - thread
+ *    - value
+ *    - isError (defaults to false)
+ *
+ *  Note: yield and resume handling is currently asymmetric.
+ */
+
+duk_ret_t duk_bi_thread_resume(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hthread *thr_resume;
+	duk_tval tv_tmp;
+	duk_tval *tv;
+	duk_hobject *func;
+	duk_small_int_t is_error;
+
+	DUK_DDD(DUK_DDDPRINT("Duktape.Thread.resume(): thread=%!T, value=%!T, is_error=%!T",
+	                     (duk_tval *) duk_get_tval(ctx, 0),
+	                     (duk_tval *) duk_get_tval(ctx, 1),
+	                     (duk_tval *) duk_get_tval(ctx, 2)));
+
+	DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING);
+	DUK_ASSERT(thr->heap->curr_thread == thr);
+
+	thr_resume = duk_require_hthread(ctx, 0);
+	is_error = (duk_small_int_t) duk_to_boolean(ctx, 2);
+	duk_set_top(ctx, 2);
+
+	/* [ thread value ] */
+
+	/*
+	 *  Thread state and calling context checks
+	 */
+
+	if (thr->callstack_top < 2) {
+		DUK_DD(DUK_DDPRINT("resume state invalid: callstack should contain at least 2 entries (caller and Duktape.Thread.resume)"));
+		goto state_error;
+	}
+	DUK_ASSERT((thr->callstack + thr->callstack_top - 1)->func != NULL);  /* us */
+	DUK_ASSERT(DUK_HOBJECT_IS_NATIVEFUNCTION((thr->callstack + thr->callstack_top - 1)->func));
+	DUK_ASSERT((thr->callstack + thr->callstack_top - 2)->func != NULL);  /* caller */
+
+	if (!DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->callstack + thr->callstack_top - 2)->func)) {
+		DUK_DD(DUK_DDPRINT("resume state invalid: caller must be Ecmascript code"));
+		goto state_error;
+	}
+
+	/* Note: there is no requirement that: 'thr->callstack_preventcount == 1'
+	 * like for yield.
+	 */
+
+	if (thr_resume->state != DUK_HTHREAD_STATE_INACTIVE &&
+	    thr_resume->state != DUK_HTHREAD_STATE_YIELDED) {
+		DUK_DD(DUK_DDPRINT("resume state invalid: target thread must be INACTIVE or YIELDED"));
+		goto state_error;
+	}
+
+	DUK_ASSERT(thr_resume->state == DUK_HTHREAD_STATE_INACTIVE ||
+	           thr_resume->state == DUK_HTHREAD_STATE_YIELDED);
+
+	/* Further state-dependent pre-checks */
+
+	if (thr_resume->state == DUK_HTHREAD_STATE_YIELDED) {
+		/* no pre-checks now, assume a previous yield() has left things in
+		 * tip-top shape (longjmp handler will assert for these).
+		 */
+	} else {
+		DUK_ASSERT(thr_resume->state == DUK_HTHREAD_STATE_INACTIVE);
+
+		if ((thr_resume->callstack_top != 0) ||
+		    (thr_resume->valstack_top - thr_resume->valstack != 1)) {
+			goto state_invalid_initial;
+		}
+		tv = &thr_resume->valstack_top[-1];
+		DUK_ASSERT(tv >= thr_resume->valstack && tv < thr_resume->valstack_top);
+		if (!DUK_TVAL_IS_OBJECT(tv)) {
+			goto state_invalid_initial;
+		}
+		func = DUK_TVAL_GET_OBJECT(tv);
+		DUK_ASSERT(func != NULL);
+		if (!DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) {
+			/* Note: cannot be a bound function either right now,
+			 * this would be easy to relax though.
+			 */
+			goto state_invalid_initial;
+		}
+
+	}
+
+	/*
+	 *  The error object has been augmented with a traceback and other
+	 *  info from its creation point -- usually another thread.  The
+	 *  error handler is called here right before throwing, but it also
+	 *  runs in the resumer's thread.  It might be nice to get a traceback
+	 *  from the resumee but this is not the case now.
+	 */
+
+#if defined(DUK_USE_AUGMENT_ERROR_THROW)
+	if (is_error) {
+		DUK_ASSERT_TOP(ctx, 2);  /* value (error) is at stack top */
+		duk_err_augment_error_throw(thr);  /* in resumer's context */
+	}
+#endif
+
+#ifdef DUK_USE_DEBUG
+	if (is_error) {
+		DUK_DDD(DUK_DDDPRINT("RESUME ERROR: thread=%!T, value=%!T",
+		                     (duk_tval *) duk_get_tval(ctx, 0),
+		                     (duk_tval *) duk_get_tval(ctx, 1)));
+	} else if (thr_resume->state == DUK_HTHREAD_STATE_YIELDED) {
+		DUK_DDD(DUK_DDDPRINT("RESUME NORMAL: thread=%!T, value=%!T",
+		                     (duk_tval *) duk_get_tval(ctx, 0),
+		                     (duk_tval *) duk_get_tval(ctx, 1)));
+	} else {
+		DUK_DDD(DUK_DDDPRINT("RESUME INITIAL: thread=%!T, value=%!T",
+		                     (duk_tval *) duk_get_tval(ctx, 0),
+		                     (duk_tval *) duk_get_tval(ctx, 1)));
+	}
+#endif
+
+	thr->heap->lj.type = DUK_LJ_TYPE_RESUME;
+
+	/* lj value2: thread */
+	DUK_ASSERT(thr->valstack_bottom < thr->valstack_top);
+	DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value2);
+	DUK_TVAL_SET_TVAL(&thr->heap->lj.value2, &thr->valstack_bottom[0]);
+	DUK_TVAL_INCREF(thr, &thr->heap->lj.value2);
+	DUK_TVAL_DECREF(thr, &tv_tmp);
+
+	/* lj value1: value */
+	DUK_ASSERT(thr->valstack_bottom + 1 < thr->valstack_top);
+	DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value1);
+	DUK_TVAL_SET_TVAL(&thr->heap->lj.value1, &thr->valstack_bottom[1]);
+	DUK_TVAL_INCREF(thr, &thr->heap->lj.value1);
+	DUK_TVAL_DECREF(thr, &tv_tmp);
+
+	thr->heap->lj.iserror = is_error;
+
+	DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL);  /* call is from executor, so we know we have a jmpbuf */
+	duk_err_longjmp(thr);  /* execution resumes in bytecode executor */
+	return 0;  /* never here */
+
+ state_invalid_initial:
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid initial thread state/stack");
+	return 0;  /* never here */
+
+ state_error:
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid state for resume");
+	return 0;  /* never here */
+}
+
+/*
+ *  Yield the current thread.
+ *
+ *  The thread must be in yieldable state: it must have a resumer, and there
+ *  must not be any yield-preventing calls (native calls and constructor calls,
+ *  currently) in the thread's call stack (otherwise a resume would not be
+ *  possible later).  This method must be called from an Ecmascript function.
+ *
+ *  Args:
+ *    - value
+ *    - isError (defaults to false)
+ *
+ *  Note: yield and resume handling is currently asymmetric.
+ */
+
+duk_ret_t duk_bi_thread_yield(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_tval tv_tmp;
+	duk_small_int_t is_error;
+
+	DUK_DDD(DUK_DDDPRINT("Duktape.Thread.yield(): value=%!T, is_error=%!T",
+	                     (duk_tval *) duk_get_tval(ctx, 0),
+	                     (duk_tval *) duk_get_tval(ctx, 1)));
+
+	DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING);
+	DUK_ASSERT(thr->heap->curr_thread == thr);
+
+	is_error = (duk_small_int_t) duk_to_boolean(ctx, 1);
+	duk_set_top(ctx, 1);
+
+	/* [ value ] */
+
+	/*
+	 *  Thread state and calling context checks
+	 */
+
+	if (!thr->resumer) {
+		DUK_DD(DUK_DDPRINT("yield state invalid: current thread must have a resumer"));
+		goto state_error;
+	}
+	DUK_ASSERT(thr->resumer->state == DUK_HTHREAD_STATE_RESUMED);
+
+	if (thr->callstack_top < 2) {
+		DUK_DD(DUK_DDPRINT("yield state invalid: callstack should contain at least 2 entries (caller and Duktape.Thread.yield)"));
+		goto state_error;
+	}
+	DUK_ASSERT((thr->callstack + thr->callstack_top - 1)->func != NULL);  /* us */
+	DUK_ASSERT(DUK_HOBJECT_IS_NATIVEFUNCTION((thr->callstack + thr->callstack_top - 1)->func));
+	DUK_ASSERT((thr->callstack + thr->callstack_top - 2)->func != NULL);  /* caller */
+
+	if (!DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->callstack + thr->callstack_top - 2)->func)) {
+		DUK_DD(DUK_DDPRINT("yield state invalid: caller must be Ecmascript code"));
+		goto state_error;
+	}
+
+	DUK_ASSERT(thr->callstack_preventcount >= 1);  /* should never be zero, because we (Duktape.Thread.yield) are on the stack */
+	if (thr->callstack_preventcount != 1) {
+		/* Note: the only yield-preventing call is Duktape.Thread.yield(), hence check for 1, not 0 */
+		DUK_DD(DUK_DDPRINT("yield state invalid: there must be no yield-preventing calls in current thread callstack (preventcount is %ld)",
+		                   (long) thr->callstack_preventcount));
+		goto state_error;
+	}
+
+	/*
+	 *  The error object has been augmented with a traceback and other
+	 *  info from its creation point -- usually the current thread.
+	 *  The error handler, however, is called right before throwing
+	 *  and runs in the yielder's thread.
+	 */
+
+#if defined(DUK_USE_AUGMENT_ERROR_THROW)
+	if (is_error) {
+		DUK_ASSERT_TOP(ctx, 1);  /* value (error) is at stack top */
+		duk_err_augment_error_throw(thr);  /* in yielder's context */
+	}
+#endif
+
+#ifdef DUK_USE_DEBUG
+	if (is_error) {
+		DUK_DDD(DUK_DDDPRINT("YIELD ERROR: value=%!T",
+		                     (duk_tval *) duk_get_tval(ctx, 0)));
+	} else {
+		DUK_DDD(DUK_DDDPRINT("YIELD NORMAL: value=%!T",
+		                     (duk_tval *) duk_get_tval(ctx, 0)));
+	}
+#endif
+
+	/*
+	 *  Process yield
+	 *
+	 *  After longjmp(), processing continues in bytecode executor longjmp
+	 *  handler, which will e.g. update thr->resumer to NULL.
+	 */
+
+	thr->heap->lj.type = DUK_LJ_TYPE_YIELD;
+
+	/* lj value1: value */
+	DUK_ASSERT(thr->valstack_bottom < thr->valstack_top);
+	DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value1);
+	DUK_TVAL_SET_TVAL(&thr->heap->lj.value1, &thr->valstack_bottom[0]);
+	DUK_TVAL_INCREF(thr, &thr->heap->lj.value1);
+	DUK_TVAL_DECREF(thr, &tv_tmp);
+
+	thr->heap->lj.iserror = is_error;
+
+	DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL);  /* call is from executor, so we know we have a jmpbuf */
+	duk_err_longjmp(thr);  /* execution resumes in bytecode executor */
+	return 0;  /* never here */
+
+ state_error:
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid state for yield");
+	return 0;  /* never here */
+}
+
+duk_ret_t duk_bi_thread_current(duk_context *ctx) {
+	duk_push_current_thread(ctx);
+	return 1;
+}
+
+#line 1 "duk_bi_thrower.c"
+/*
+ *  Type error thrower, E5 Section 13.2.3.
+ */
+
+/* include removed: duk_internal.h */
+
+int duk_bi_type_error_thrower(duk_context *ctx) {
+	DUK_UNREF(ctx);
+	return DUK_RET_TYPE_ERROR;
+}
+#line 1 "duk_builtins.c"
+/*
+ *  Automatically generated by genbuiltins.py, do not edit!
+ */
+
+/* include removed: duk_internal.h */
+
+#if defined(DUK_USE_DOUBLE_LE)
+const duk_uint8_t duk_strings_data[] = {
+55,86,227,24,145,55,102,120,144,3,63,94,228,54,100,137,186,26,20,164,137,
+186,50,11,164,109,77,215,5,61,35,106,3,25,110,8,22,158,130,38,163,8,217,
+200,158,76,156,210,117,128,153,203,210,70,46,137,187,18,27,164,187,201,209,
+130,100,55,91,70,4,145,63,66,231,44,128,105,187,41,197,13,49,122,8,196,24,
+71,75,70,138,104,115,77,215,5,36,20,201,214,209,107,79,104,209,144,168,105,
+6,207,251,209,104,209,125,212,227,66,127,235,191,239,232,180,90,52,95,69,
+247,83,141,9,255,174,255,191,162,211,80,210,253,23,221,78,52,39,254,183,
+254,254,139,72,105,126,139,238,167,26,19,255,91,255,127,69,166,129,191,69,
+247,83,141,9,255,175,255,191,162,213,26,50,23,232,190,234,113,161,63,245,
+115,119,86,227,118,83,138,26,98,9,110,48,86,22,148,160,152,22,82,70,46,137,
+44,8,180,163,32,104,98,206,32,17,7,16,88,101,100,206,42,70,36,108,205,18,
+74,140,33,196,230,60,2,152,146,33,38,230,8,36,79,182,251,65,156,151,24,200,
+33,145,162,25,80,209,24,67,0,166,68,52,174,61,73,25,33,205,25,27,84,177,
+195,234,220,1,144,105,99,135,217,16,17,17,208,72,199,179,60,93,100,146,49,
+232,162,64,76,135,19,152,244,44,136,220,72,96,130,68,62,230,120,144,3,70,
+206,6,141,100,138,182,84,52,11,70,73,19,236,64,90,200,66,109,128,121,118,8,
+154,69,220,206,137,35,111,23,217,45,13,33,247,39,82,34,33,247,80,68,141,
+169,246,178,92,141,169,247,80,69,128,122,54,87,69,128,92,147,176,226,100,
+19,134,66,237,164,188,207,185,130,38,36,205,186,129,116,33,222,228,54,100,
+137,131,66,148,145,76,226,1,16,96,152,20,180,52,157,109,24,18,69,5,66,201,
+214,129,132,19,196,89,76,2,83,187,51,197,214,236,240,242,116,145,139,162,
+126,142,138,152,30,65,32,103,137,33,68,68,137,214,159,23,77,218,211,164,73,
+245,241,116,221,60,60,157,113,78,235,55,170,38,36,146,54,140,36,65,50,56,
+100,89,38,78,190,46,121,35,60,12,224,145,122,248,186,248,48,128,181,144,
+140,234,27,80,45,3,250,14,140,19,33,127,111,235,190,187,235,191,116,95,21,
+241,33,250,0,253,96,190,183,236,5,245,127,96,31,176,79,214,245,13,42,26,
+137,225,63,87,212,52,168,106,39,132,117,13,42,26,137,224,206,10,72,41,154,
+83,138,26,99,54,140,9,34,112,185,203,32,26,154,52,100,42,26,65,166,83,138,
+26,100,23,3,152,26,73,66,51,28,144,210,197,212,104,205,16,52,110,96,222,
+235,13,136,104,216,11,141,110,49,74,183,58,35,37,222,49,58,68,17,16,178,
+130,96,111,217,16,19,3,72,9,33,164,0,157,33,128,50,47,165,8,207,236,143,
+233,66,51,251,29,125,144,188,244,27,203,113,190,199,236,8,95,45,198,251,34,
+15,203,111,217,19,203,111,216,253,128,122,67,176,146,144,12,66,52,12,33,80,
+215,210,101,67,70,75,147,234,62,255,238,190,165,43,4,167,212,52,100,186,89,
+69,205,11,67,72,164,25,174,137,58,32,72,134,147,169,17,16,147,36,166,66,92,
+130,92,221,227,50,114,244,226,134,152,242,36,251,130,2,39,49,39,220,16,17,
+52,221,228,201,205,92,221,228,73,210,244,226,134,153,115,119,169,49,75,211,
+138,26,103,72,147,245,38,34,250,139,95,112,64,69,114,36,250,90,45,125,193,
+1,21,200,147,245,38,38,121,205,153,209,34,79,172,115,102,117,72,147,245,38,
+33,248,19,33,228,73,244,96,153,23,34,79,212,152,143,231,82,50,68,137,62,
+157,212,140,155,34,79,212,152,153,228,54,147,36,149,34,79,172,67,105,50,73,
+178,36,253,73,138,94,32,156,209,201,82,36,251,34,9,205,28,165,34,79,212,
+152,153,228,45,104,145,4,230,142,79,145,39,214,33,107,68,136,39,52,114,60,
+137,62,205,12,36,76,73,246,104,97,50,230,147,174,41,100,73,200,196,159,169,
+49,51,200,90,209,34,9,205,28,158,98,79,172,66,214,137,16,78,104,228,211,18,
+126,164,197,47,16,78,104,228,163,18,125,145,4,230,142,77,49,39,234,76,76,
+242,27,73,146,74,49,39,214,33,180,153,36,179,18,126,164,196,127,58,145,146,
+12,73,244,238,164,100,147,18,126,164,196,63,3,6,49,39,209,131,10,49,39,234,
+76,67,240,38,67,152,147,232,193,50,44,196,159,169,49,51,206,108,206,131,18,
+125,99,155,51,184,196,159,169,49,23,212,90,251,130,2,43,49,39,210,209,107,
+238,8,8,156,196,159,102,134,18,83,119,86,225,1,100,236,208,194,118,83,138,
+26,105,77,221,91,132,5,147,163,4,201,217,78,40,105,153,55,118,104,97,59,41,
+197,13,51,38,238,140,19,39,101,56,161,166,27,93,97,250,147,17,94,8,200,139,
+155,186,248,144,72,146,28,214,205,221,37,222,230,145,179,64,22,121,187,165,
+69,200,56,249,238,146,38,138,147,72,105,80,212,79,17,243,72,96,77,21,38,
+144,210,161,168,158,7,214,131,173,79,152,134,234,160,93,8,159,48,23,213,64,
+186,16,210,160,101,56,146,113,67,35,55,117,110,16,22,78,209,239,36,122,32,
+72,139,155,187,71,188,145,232,129,34,70,110,234,220,32,44,157,91,172,72,
+244,64,145,23,55,117,110,177,35,209,2,68,76,168,25,78,40,105,139,39,173,19,
+52,136,8,136,231,137,30,176,8,133,96,38,35,181,110,16,22,78,137,204,120,34,
+69,8,224,143,68,225,147,160,153,132,112,71,160,155,11,23,51,162,56,35,209,
+56,100,92,72,58,8,157,138,12,121,154,36,29,4,67,21,11,153,34,54,1,231,43,
+163,210,0,71,36,156,194,20,149,36,112,181,130,83,234,26,50,95,174,41,208,
+209,146,253,113,79,70,200,232,44,205,39,173,2,33,36,232,204,178,90,4,66,
+200,232,44,207,18,84,145,145,8,250,72,227,123,158,68,185,13,48,156,209,2,
+103,68,186,8,196,24,72,34,106,48,141,156,140,32,45,100,137,2,134,140,130,2,
+214,80,61,235,197,31,23,60,145,158,58,137,116,141,163,9,16,5,100,106,37,
+215,197,211,116,240,242,117,197,113,193,46,186,205,235,226,231,146,51,193,
+234,5,208,157,113,93,55,117,110,16,22,78,202,113,67,76,137,187,178,156,80,
+211,44,78,108,167,26,10,110,136,228,73,140,196,154,136,218,48,145,0,86,70,
+9,205,42,13,72,128,43,34,45,20,76,1,89,11,80,46,132,34,137,137,49,18,233,
+47,50,54,72,10,200,132,75,165,139,178,70,161,18,236,136,5,144,113,124,73,
+82,54,125,37,230,70,201,14,108,140,88,144,153,33,36,64,46,3,33,80,210,117,
+241,115,201,25,160,146,112,200,84,52,157,124,92,242,70,120,48,162,64,76,
+147,49,39,215,89,189,124,92,242,70,120,235,64,194,75,6,36,250,235,55,175,
+139,158,72,207,29,25,36,40,161,243,116,92,98,79,175,139,166,233,225,228,
+235,138,151,197,211,116,240,242,25,100,105,166,114,129,100,140,202,248,161,
+179,69,26,73,8,15,33,137,33,1,228,145,26,39,12,158,164,80,47,57,143,115,72,
+217,164,141,19,134,79,82,40,144,200,39,12,158,164,80,47,57,143,115,72,217,
+164,100,19,134,79,82,40,66,37,210,161,168,153,10,137,117,160,235,84,240,70,
+68,233,86,224,77,15,4,100,78,161,179,33,42,5,163,212,138,9,232,197,209,79,
+103,135,147,164,140,93,21,246,88,108,193,126,146,49,116,93,216,144,164,137,
+26,34,116,145,139,162,174,196,13,49,58,72,197,209,79,73,80,47,164,140,93,
+18,134,36,2,136,32,72,133,16,38,35,160,78,108,208,218,17,3,32,104,99,18,39,
+25,10,10,46,102,25,22,73,144,67,113,8,185,16,229,67,64,181,224,101,116,80,
+90,52,83,67,154,36,20,72,106,144,217,76,13,17,28,81,180,150,52,73,58,69,
+166,149,145,49,28,147,58,36,89,158,46,176,231,28,26,120,121,28,81,212,17,
+37,92,129,150,199,66,200,75,34,103,40,150,9,72,162,115,101,50,17,180,97,
+137,119,186,51,57,47,50,52,114,50,24,247,70,101,149,30,72,145,180,90,201,
+56,208,133,40,23,34,41,12,122,200,194,54,114,74,27,50,68,160,17,6,178,76,
+239,0,148,3,16,239,138,42,9,145,47,139,166,65,76,131,51,232,21,160,70,148,
+193,52,8,184,65,22,48,
+};
+
+/* to convert a heap stridx to a token number, subtract
+ * DUK_STRIDX_START_RESERVED and add DUK_TOK_START_RESERVED.
+ */
+
+/* native functions: 129 */
+const duk_c_function duk_bi_native_functions[] = {
+	duk_bi_array_constructor,
+	duk_bi_array_constructor_is_array,
+	duk_bi_array_prototype_concat,
+	duk_bi_array_prototype_indexof_shared,
+	duk_bi_array_prototype_iter_shared,
+	duk_bi_array_prototype_join_shared,
+	duk_bi_array_prototype_pop,
+	duk_bi_array_prototype_push,
+	duk_bi_array_prototype_reduce_shared,
+	duk_bi_array_prototype_reverse,
+	duk_bi_array_prototype_shift,
+	duk_bi_array_prototype_slice,
+	duk_bi_array_prototype_sort,
+	duk_bi_array_prototype_splice,
+	duk_bi_array_prototype_to_string,
+	duk_bi_array_prototype_unshift,
+	duk_bi_boolean_constructor,
+	duk_bi_boolean_prototype_tostring_shared,
+	duk_bi_buffer_constructor,
+	duk_bi_buffer_prototype_tostring_shared,
+	duk_bi_date_constructor,
+	duk_bi_date_constructor_now,
+	duk_bi_date_constructor_parse,
+	duk_bi_date_constructor_utc,
+	duk_bi_date_prototype_get_shared,
+	duk_bi_date_prototype_get_timezone_offset,
+	duk_bi_date_prototype_set_shared,
+	duk_bi_date_prototype_set_time,
+	duk_bi_date_prototype_to_json,
+	duk_bi_date_prototype_tostring_shared,
+	duk_bi_date_prototype_value_of,
+	duk_bi_duktape_object_act,
+	duk_bi_duktape_object_compact,
+	duk_bi_duktape_object_dec,
+	duk_bi_duktape_object_enc,
+	duk_bi_duktape_object_fin,
+	duk_bi_duktape_object_gc,
+	duk_bi_duktape_object_info,
+	duk_bi_error_constructor_shared,
+	duk_bi_error_prototype_filename_getter,
+	duk_bi_error_prototype_linenumber_getter,
+	duk_bi_error_prototype_nop_setter,
+	duk_bi_error_prototype_stack_getter,
+	duk_bi_error_prototype_to_string,
+	duk_bi_function_constructor,
+	duk_bi_function_prototype,
+	duk_bi_function_prototype_apply,
+	duk_bi_function_prototype_bind,
+	duk_bi_function_prototype_call,
+	duk_bi_function_prototype_to_string,
+	duk_bi_global_object_alert,
+	duk_bi_global_object_decode_uri,
+	duk_bi_global_object_decode_uri_component,
+	duk_bi_global_object_encode_uri,
+	duk_bi_global_object_encode_uri_component,
+	duk_bi_global_object_escape,
+	duk_bi_global_object_eval,
+	duk_bi_global_object_is_finite,
+	duk_bi_global_object_is_nan,
+	duk_bi_global_object_parse_float,
+	duk_bi_global_object_parse_int,
+	duk_bi_global_object_print,
+	duk_bi_global_object_require,
+	duk_bi_global_object_unescape,
+	duk_bi_json_object_parse,
+	duk_bi_json_object_stringify,
+	duk_bi_logger_constructor,
+	duk_bi_logger_prototype_fmt,
+	duk_bi_logger_prototype_log_shared,
+	duk_bi_logger_prototype_raw,
+	duk_bi_math_object_max,
+	duk_bi_math_object_min,
+	duk_bi_math_object_onearg_shared,
+	duk_bi_math_object_random,
+	duk_bi_math_object_twoarg_shared,
+	duk_bi_number_constructor,
+	duk_bi_number_prototype_to_exponential,
+	duk_bi_number_prototype_to_fixed,
+	duk_bi_number_prototype_to_locale_string,
+	duk_bi_number_prototype_to_precision,
+	duk_bi_number_prototype_to_string,
+	duk_bi_number_prototype_value_of,
+	duk_bi_object_constructor,
+	duk_bi_object_constructor_create,
+	duk_bi_object_constructor_define_properties,
+	duk_bi_object_constructor_define_property,
+	duk_bi_object_constructor_get_own_property_descriptor,
+	duk_bi_object_constructor_is_extensible,
+	duk_bi_object_constructor_is_sealed_frozen_shared,
+	duk_bi_object_constructor_keys_shared,
+	duk_bi_object_constructor_prevent_extensions,
+	duk_bi_object_constructor_seal_freeze_shared,
+	duk_bi_object_getprototype_shared,
+	duk_bi_object_prototype_has_own_property,
+	duk_bi_object_prototype_is_prototype_of,
+	duk_bi_object_prototype_property_is_enumerable,
+	duk_bi_object_prototype_to_locale_string,
+	duk_bi_object_prototype_to_string,
+	duk_bi_object_prototype_value_of,
+	duk_bi_object_setprototype_shared,
+	duk_bi_pointer_constructor,
+	duk_bi_pointer_prototype_tostring_shared,
+	duk_bi_proxy_constructor,
+	duk_bi_regexp_constructor,
+	duk_bi_regexp_prototype_exec,
+	duk_bi_regexp_prototype_test,
+	duk_bi_regexp_prototype_to_string,
+	duk_bi_string_constructor,
+	duk_bi_string_constructor_from_char_code,
+	duk_bi_string_prototype_caseconv_shared,
+	duk_bi_string_prototype_char_at,
+	duk_bi_string_prototype_char_code_at,
+	duk_bi_string_prototype_concat,
+	duk_bi_string_prototype_indexof_shared,
+	duk_bi_string_prototype_locale_compare,
+	duk_bi_string_prototype_match,
+	duk_bi_string_prototype_replace,
+	duk_bi_string_prototype_search,
+	duk_bi_string_prototype_slice,
+	duk_bi_string_prototype_split,
+	duk_bi_string_prototype_substr,
+	duk_bi_string_prototype_substring,
+	duk_bi_string_prototype_to_string,
+	duk_bi_string_prototype_trim,
+	duk_bi_thread_constructor,
+	duk_bi_thread_current,
+	duk_bi_thread_resume,
+	duk_bi_thread_yield,
+	duk_bi_type_error_thrower,
+};
+
+const duk_uint8_t duk_builtins_data[] = {
+105,195,74,144,77,40,105,44,9,124,104,45,3,3,72,0,71,225,65,165,172,33,243,
+6,145,0,122,24,210,150,14,249,35,120,160,55,226,13,76,224,196,177,164,152,
+22,192,4,202,52,147,72,152,0,169,70,146,105,11,0,23,40,210,77,32,96,3,37,
+26,73,163,236,0,108,163,73,52,121,128,14,148,105,38,142,176,1,242,144,56,
+209,0,84,6,166,98,242,80,210,248,1,250,67,72,144,15,232,13,44,128,47,162,
+52,161,0,62,80,160,255,253,102,76,0,0,0,0,0,0,15,135,243,84,0,0,0,0,0,0,15,
+7,243,124,64,153,132,18,49,2,38,48,64,200,7,153,64,227,48,26,103,3,13,0,89,
+165,34,53,36,38,180,128,216,143,155,81,227,114,58,111,2,142,0,73,194,94,56,
+202,167,33,209,195,130,70,207,17,26,59,36,100,232,145,131,146,69,204,201,
+22,52,36,84,212,145,67,98,68,205,201,18,63,36,68,244,122,32,100,60,87,62,
+39,255,254,9,46,24,0,10,31,224,29,13,92,40,0,9,101,141,32,0,48,197,100,66,
+214,73,10,83,68,37,85,144,133,68,65,214,201,6,91,40,0,12,21,104,144,69,130,
+64,214,10,0,3,2,87,36,5,100,160,0,63,254,16,37,135,92,99,25,242,194,7,195,
+0,30,236,64,123,46,145,234,188,71,162,249,5,23,240,0,15,241,0,17,242,98,7,
+153,114,30,70,7,207,18,243,225,71,252,0,93,192,36,15,241,128,85,242,28,7,
+192,40,64,0,93,160,71,206,192,82,58,193,128,234,7,62,116,132,129,208,20,7,
+56,90,28,193,132,114,134,175,57,3,207,156,96,103,224,0,46,32,51,255,255,
+247,8,33,163,128,16,212,0,5,190,8,106,0,4,222,4,53,0,3,110,130,26,128,2,55,
+2,15,192,0,91,97,7,255,255,226,147,248,0,182,155,15,131,252,128,73,144,128,
+202,62,94,128,247,122,3,101,184,141,134,242,59,92,15,156,110,38,142,39,19,
+80,0,22,187,145,26,206,100,106,186,33,168,234,71,59,178,26,110,232,105,60,
+161,162,218,6,131,104,25,237,161,0,1,103,54,132,0,5,154,246,6,99,194,5,47,
+240,0,15,242,129,38,70,9,242,34,16,0,23,184,136,5,55,241,89,112,63,255,255,
+255,255,255,251,223,217,80,0,64,0,0,0,0,0,0,25,48,0,0,0,0,0,0,62,31,216,
+240,0,0,0,0,0,0,60,31,217,16,0,0,0,0,0,0,60,63,192,15,243,1,38,64,0,0,0,0,
+0,0,0,0,25,242,160,71,194,113,30,234,32,99,38,145,138,152,70,34,121,5,63,
+240,3,97,139,17,132,47,252,193,21,0,127,156,9,50,64,0,0,0,0,0,7,195,253,
+175,145,208,128,52,48,7,66,0,80,191,29,8,2,67,224,116,32,29,11,225,208,128,
+84,47,71,66,1,144,155,29,8,3,2,104,116,32,140,73,145,194,61,199,128,188,30,
+2,236,96,32,1,11,161,128,46,70,2,32,16,184,24,8,128,2,220,96,36,1,139,97,
+128,144,2,45,70,2,224,16,180,24,11,128,2,204,96,38,1,11,33,128,152,0,44,70,
+2,128,16,176,24,10,0,2,188,96,42,1,10,225,128,168,0,43,70,2,192,16,172,24,
+11,0,2,172,100,10,161,178,42,70,138,34,16,168,26,40,136,2,156,105,124,132,
+33,76,52,190,66,0,165,26,127,49,8,82,13,63,152,128,40,198,167,208,66,20,67,
+83,232,32,10,17,162,136,4,40,6,138,32,0,159,26,95,32,8,79,13,47,144,0,39,
+70,159,204,2,83,131,79,230,0,41,129,128,128,12,37,198,138,100,2,42,63,128,
+0,127,160,49,38,8,24,0,64,82,112,145,224,120,201,40,201,24,201,9,0,0,0,0,0,
+0,0,0,1,165,90,8,148,105,35,229,168,2,167,248,0,7,250,66,163,8,22,139,32,
+49,77,202,138,74,46,78,82,80,114,130,144,95,37,96,21,95,192,1,63,212,9,24,
+82,36,89,1,128,21,127,192,1,63,214,9,24,82,20,89,1,128,21,159,192,1,63,216,
+9,24,82,4,89,1,128,21,191,192,1,63,218,9,24,81,244,89,1,128,21,223,192,1,
+63,220,9,24,81,228,89,1,128,21,255,192,1,63,222,9,24,81,212,89,1,128,15,
+255,200,69,64,105,87,20,139,10,191,5,64,68,192,22,85,181,187,177,107,2,64,
+68,64,239,57,250,254,66,46,230,63,67,192,254,130,43,101,71,21,247,63,67,64,
+14,229,38,21,123,203,219,63,66,192,24,45,68,84,251,33,9,64,66,64,205,59,
+127,102,158,160,230,63,65,192,205,59,127,102,158,160,246,63,73,4,144,68,10,
+65,64,0,80,9,5,0,2,63,164,20,0,12,252,148,131,234,65,64,1,15,137,5,0,5,61,
+164,20,0,24,244,144,80,0,115,202,65,64,2,15,8,203,199,116,117,227,178,82,
+64,0,78,169,32,58,36,20,0,36,230,144,80,0,163,146,65,64,2,206,41,5,0,12,15,
+255,192,9,134,128,131,130,11,5,255,240,0,23,255,192,0,63,255,20,105,0,0,0,
+0,0,0,194,69,0,6,115,128,142,144,9,202,0,58,199,25,146,144,100,62,65,137,
+33,6,4,99,194,242,33,225,113,8,240,164,128,130,205,248,1,211,223,208,40,
+126,96,157,244,1,255,40,0,11,71,224,0,31,243,128,19,228,76,32,0,47,113,48,
+11,87,224,0,31,244,128,19,229,148,32,0,47,118,80,11,103,224,0,31,245,132,
+30,144,0,0,0,0,0,0,0,128,31,146,9,135,118,253,196,9,144,200,37,69,32,145,
+16,120,70,136,62,0,2,51,68,31,0,2,17,34,15,128,1,136,81,7,192,1,4,8,131,
+224,0,169,255,248,72,200,33,113,55,245,197,179,44,94,92,183,242,69,193,23,
+203,203,150,254,72,52,238,65,151,151,45,252,144,104,195,187,38,205,59,179,
+128,
+};
+#ifdef DUK_USE_BUILTIN_INITJS
+const duk_uint8_t duk_initjs_data[] = {
+40,102,117,110,99,116,105,111,110,40,100,44,97,41,123,102,117,110,99,116,
+105,111,110,32,98,40,97,44,98,44,99,41,123,79,98,106,101,99,116,46,100,101,
+102,105,110,101,80,114,111,112,101,114,116,121,40,97,44,98,44,123,118,97,
+108,117,101,58,99,44,119,114,105,116,97,98,108,101,58,33,48,44,101,110,117,
+109,101,114,97,98,108,101,58,33,49,44,99,111,110,102,105,103,117,114,97,98,
+108,101,58,33,48,125,41,125,98,40,97,46,76,111,103,103,101,114,44,34,99,
+108,111,103,34,44,110,101,119,32,97,46,76,111,103,103,101,114,40,34,67,34,
+41,41,59,98,40,97,44,34,109,111,100,76,111,97,100,101,100,34,44,123,125,41,
+125,41,40,116,104,105,115,44,68,117,107,116,97,112,101,41,59,10,0,
+};
+#endif  /* DUK_USE_BUILTIN_INITJS */
+#elif defined(DUK_USE_DOUBLE_BE)
+const duk_uint8_t duk_strings_data[] = {
+55,86,227,24,145,55,102,120,144,3,63,94,228,54,100,137,186,26,20,164,137,
+186,50,11,164,109,77,215,5,61,35,106,3,25,110,8,22,158,130,38,163,8,217,
+200,158,76,156,210,117,128,153,203,210,70,46,137,187,18,27,164,187,201,209,
+130,100,55,91,70,4,145,63,66,231,44,128,105,187,41,197,13,49,122,8,196,24,
+71,75,70,138,104,115,77,215,5,36,20,201,214,209,107,79,104,209,144,168,105,
+6,207,251,209,104,209,125,212,227,66,127,235,191,239,232,180,90,52,95,69,
+247,83,141,9,255,174,255,191,162,211,80,210,253,23,221,78,52,39,254,183,
+254,254,139,72,105,126,139,238,167,26,19,255,91,255,127,69,166,129,191,69,
+247,83,141,9,255,175,255,191,162,213,26,50,23,232,190,234,113,161,63,245,
+115,119,86,227,118,83,138,26,98,9,110,48,86,22,148,160,152,22,82,70,46,137,
+44,8,180,163,32,104,98,206,32,17,7,16,88,101,100,206,42,70,36,108,205,18,
+74,140,33,196,230,60,2,152,146,33,38,230,8,36,79,182,251,65,156,151,24,200,
+33,145,162,25,80,209,24,67,0,166,68,52,174,61,73,25,33,205,25,27,84,177,
+195,234,220,1,144,105,99,135,217,16,17,17,208,72,199,179,60,93,100,146,49,
+232,162,64,76,135,19,152,244,44,136,220,72,96,130,68,62,230,120,144,3,70,
+206,6,141,100,138,182,84,52,11,70,73,19,236,64,90,200,66,109,128,121,118,8,
+154,69,220,206,137,35,111,23,217,45,13,33,247,39,82,34,33,247,80,68,141,
+169,246,178,92,141,169,247,80,69,128,122,54,87,69,128,92,147,176,226,100,
+19,134,66,237,164,188,207,185,130,38,36,205,186,129,116,33,222,228,54,100,
+137,131,66,148,145,76,226,1,16,96,152,20,180,52,157,109,24,18,69,5,66,201,
+214,129,132,19,196,89,76,2,83,187,51,197,214,236,240,242,116,145,139,162,
+126,142,138,152,30,65,32,103,137,33,68,68,137,214,159,23,77,218,211,164,73,
+245,241,116,221,60,60,157,113,78,235,55,170,38,36,146,54,140,36,65,50,56,
+100,89,38,78,190,46,121,35,60,12,224,145,122,248,186,248,48,128,181,144,
+140,234,27,80,45,3,250,14,140,19,33,127,111,235,190,187,235,191,116,95,21,
+241,33,250,0,253,96,190,183,236,5,245,127,96,31,176,79,214,245,13,42,26,
+137,225,63,87,212,52,168,106,39,132,117,13,42,26,137,224,206,10,72,41,154,
+83,138,26,99,54,140,9,34,112,185,203,32,26,154,52,100,42,26,65,166,83,138,
+26,100,23,3,152,26,73,66,51,28,144,210,197,212,104,205,16,52,110,96,222,
+235,13,136,104,216,11,141,110,49,74,183,58,35,37,222,49,58,68,17,16,178,
+130,96,111,217,16,19,3,72,9,33,164,0,157,33,128,50,47,165,8,207,236,143,
+233,66,51,251,29,125,144,188,244,27,203,113,190,199,236,8,95,45,198,251,34,
+15,203,111,217,19,203,111,216,253,128,122,67,176,146,144,12,66,52,12,33,80,
+215,210,101,67,70,75,147,234,62,255,238,190,165,43,4,167,212,52,100,186,89,
+69,205,11,67,72,164,25,174,137,58,32,72,134,147,169,17,16,147,36,166,66,92,
+130,92,221,227,50,114,244,226,134,152,242,36,251,130,2,39,49,39,220,16,17,
+52,221,228,201,205,92,221,228,73,210,244,226,134,153,115,119,169,49,75,211,
+138,26,103,72,147,245,38,34,250,139,95,112,64,69,114,36,250,90,45,125,193,
+1,21,200,147,245,38,38,121,205,153,209,34,79,172,115,102,117,72,147,245,38,
+33,248,19,33,228,73,244,96,153,23,34,79,212,152,143,231,82,50,68,137,62,
+157,212,140,155,34,79,212,152,153,228,54,147,36,149,34,79,172,67,105,50,73,
+178,36,253,73,138,94,32,156,209,201,82,36,251,34,9,205,28,165,34,79,212,
+152,153,228,45,104,145,4,230,142,79,145,39,214,33,107,68,136,39,52,114,60,
+137,62,205,12,36,76,73,246,104,97,50,230,147,174,41,100,73,200,196,159,169,
+49,51,200,90,209,34,9,205,28,158,98,79,172,66,214,137,16,78,104,228,211,18,
+126,164,197,47,16,78,104,228,163,18,125,145,4,230,142,77,49,39,234,76,76,
+242,27,73,146,74,49,39,214,33,180,153,36,179,18,126,164,196,127,58,145,146,
+12,73,244,238,164,100,147,18,126,164,196,63,3,6,49,39,209,131,10,49,39,234,
+76,67,240,38,67,152,147,232,193,50,44,196,159,169,49,51,206,108,206,131,18,
+125,99,155,51,184,196,159,169,49,23,212,90,251,130,2,43,49,39,210,209,107,
+238,8,8,156,196,159,102,134,18,83,119,86,225,1,100,236,208,194,118,83,138,
+26,105,77,221,91,132,5,147,163,4,201,217,78,40,105,153,55,118,104,97,59,41,
+197,13,51,38,238,140,19,39,101,56,161,166,27,93,97,250,147,17,94,8,200,139,
+155,186,248,144,72,146,28,214,205,221,37,222,230,145,179,64,22,121,187,165,
+69,200,56,249,238,146,38,138,147,72,105,80,212,79,17,243,72,96,77,21,38,
+144,210,161,168,158,7,214,131,173,79,152,134,234,160,93,8,159,48,23,213,64,
+186,16,210,160,101,56,146,113,67,35,55,117,110,16,22,78,209,239,36,122,32,
+72,139,155,187,71,188,145,232,129,34,70,110,234,220,32,44,157,91,172,72,
+244,64,145,23,55,117,110,177,35,209,2,68,76,168,25,78,40,105,139,39,173,19,
+52,136,8,136,231,137,30,176,8,133,96,38,35,181,110,16,22,78,137,204,120,34,
+69,8,224,143,68,225,147,160,153,132,112,71,160,155,11,23,51,162,56,35,209,
+56,100,92,72,58,8,157,138,12,121,154,36,29,4,67,21,11,153,34,54,1,231,43,
+163,210,0,71,36,156,194,20,149,36,112,181,130,83,234,26,50,95,174,41,208,
+209,146,253,113,79,70,200,232,44,205,39,173,2,33,36,232,204,178,90,4,66,
+200,232,44,207,18,84,145,145,8,250,72,227,123,158,68,185,13,48,156,209,2,
+103,68,186,8,196,24,72,34,106,48,141,156,140,32,45,100,137,2,134,140,130,2,
+214,80,61,235,197,31,23,60,145,158,58,137,116,141,163,9,16,5,100,106,37,
+215,197,211,116,240,242,117,197,113,193,46,186,205,235,226,231,146,51,193,
+234,5,208,157,113,93,55,117,110,16,22,78,202,113,67,76,137,187,178,156,80,
+211,44,78,108,167,26,10,110,136,228,73,140,196,154,136,218,48,145,0,86,70,
+9,205,42,13,72,128,43,34,45,20,76,1,89,11,80,46,132,34,137,137,49,18,233,
+47,50,54,72,10,200,132,75,165,139,178,70,161,18,236,136,5,144,113,124,73,
+82,54,125,37,230,70,201,14,108,140,88,144,153,33,36,64,46,3,33,80,210,117,
+241,115,201,25,160,146,112,200,84,52,157,124,92,242,70,120,48,162,64,76,
+147,49,39,215,89,189,124,92,242,70,120,235,64,194,75,6,36,250,235,55,175,
+139,158,72,207,29,25,36,40,161,243,116,92,98,79,175,139,166,233,225,228,
+235,138,151,197,211,116,240,242,25,100,105,166,114,129,100,140,202,248,161,
+179,69,26,73,8,15,33,137,33,1,228,145,26,39,12,158,164,80,47,57,143,115,72,
+217,164,141,19,134,79,82,40,144,200,39,12,158,164,80,47,57,143,115,72,217,
+164,100,19,134,79,82,40,66,37,210,161,168,153,10,137,117,160,235,84,240,70,
+68,233,86,224,77,15,4,100,78,161,179,33,42,5,163,212,138,9,232,197,209,79,
+103,135,147,164,140,93,21,246,88,108,193,126,146,49,116,93,216,144,164,137,
+26,34,116,145,139,162,174,196,13,49,58,72,197,209,79,73,80,47,164,140,93,
+18,134,36,2,136,32,72,133,16,38,35,160,78,108,208,218,17,3,32,104,99,18,39,
+25,10,10,46,102,25,22,73,144,67,113,8,185,16,229,67,64,181,224,101,116,80,
+90,52,83,67,154,36,20,72,106,144,217,76,13,17,28,81,180,150,52,73,58,69,
+166,149,145,49,28,147,58,36,89,158,46,176,231,28,26,120,121,28,81,212,17,
+37,92,129,150,199,66,200,75,34,103,40,150,9,72,162,115,101,50,17,180,97,
+137,119,186,51,57,47,50,52,114,50,24,247,70,101,149,30,72,145,180,90,201,
+56,208,133,40,23,34,41,12,122,200,194,54,114,74,27,50,68,160,17,6,178,76,
+239,0,148,3,16,239,138,42,9,145,47,139,166,65,76,131,51,232,21,160,70,148,
+193,52,8,184,65,22,48,
+};
+
+/* to convert a heap stridx to a token number, subtract
+ * DUK_STRIDX_START_RESERVED and add DUK_TOK_START_RESERVED.
+ */
+
+/* native functions: 129 */
+const duk_c_function duk_bi_native_functions[] = {
+	duk_bi_array_constructor,
+	duk_bi_array_constructor_is_array,
+	duk_bi_array_prototype_concat,
+	duk_bi_array_prototype_indexof_shared,
+	duk_bi_array_prototype_iter_shared,
+	duk_bi_array_prototype_join_shared,
+	duk_bi_array_prototype_pop,
+	duk_bi_array_prototype_push,
+	duk_bi_array_prototype_reduce_shared,
+	duk_bi_array_prototype_reverse,
+	duk_bi_array_prototype_shift,
+	duk_bi_array_prototype_slice,
+	duk_bi_array_prototype_sort,
+	duk_bi_array_prototype_splice,
+	duk_bi_array_prototype_to_string,
+	duk_bi_array_prototype_unshift,
+	duk_bi_boolean_constructor,
+	duk_bi_boolean_prototype_tostring_shared,
+	duk_bi_buffer_constructor,
+	duk_bi_buffer_prototype_tostring_shared,
+	duk_bi_date_constructor,
+	duk_bi_date_constructor_now,
+	duk_bi_date_constructor_parse,
+	duk_bi_date_constructor_utc,
+	duk_bi_date_prototype_get_shared,
+	duk_bi_date_prototype_get_timezone_offset,
+	duk_bi_date_prototype_set_shared,
+	duk_bi_date_prototype_set_time,
+	duk_bi_date_prototype_to_json,
+	duk_bi_date_prototype_tostring_shared,
+	duk_bi_date_prototype_value_of,
+	duk_bi_duktape_object_act,
+	duk_bi_duktape_object_compact,
+	duk_bi_duktape_object_dec,
+	duk_bi_duktape_object_enc,
+	duk_bi_duktape_object_fin,
+	duk_bi_duktape_object_gc,
+	duk_bi_duktape_object_info,
+	duk_bi_error_constructor_shared,
+	duk_bi_error_prototype_filename_getter,
+	duk_bi_error_prototype_linenumber_getter,
+	duk_bi_error_prototype_nop_setter,
+	duk_bi_error_prototype_stack_getter,
+	duk_bi_error_prototype_to_string,
+	duk_bi_function_constructor,
+	duk_bi_function_prototype,
+	duk_bi_function_prototype_apply,
+	duk_bi_function_prototype_bind,
+	duk_bi_function_prototype_call,
+	duk_bi_function_prototype_to_string,
+	duk_bi_global_object_alert,
+	duk_bi_global_object_decode_uri,
+	duk_bi_global_object_decode_uri_component,
+	duk_bi_global_object_encode_uri,
+	duk_bi_global_object_encode_uri_component,
+	duk_bi_global_object_escape,
+	duk_bi_global_object_eval,
+	duk_bi_global_object_is_finite,
+	duk_bi_global_object_is_nan,
+	duk_bi_global_object_parse_float,
+	duk_bi_global_object_parse_int,
+	duk_bi_global_object_print,
+	duk_bi_global_object_require,
+	duk_bi_global_object_unescape,
+	duk_bi_json_object_parse,
+	duk_bi_json_object_stringify,
+	duk_bi_logger_constructor,
+	duk_bi_logger_prototype_fmt,
+	duk_bi_logger_prototype_log_shared,
+	duk_bi_logger_prototype_raw,
+	duk_bi_math_object_max,
+	duk_bi_math_object_min,
+	duk_bi_math_object_onearg_shared,
+	duk_bi_math_object_random,
+	duk_bi_math_object_twoarg_shared,
+	duk_bi_number_constructor,
+	duk_bi_number_prototype_to_exponential,
+	duk_bi_number_prototype_to_fixed,
+	duk_bi_number_prototype_to_locale_string,
+	duk_bi_number_prototype_to_precision,
+	duk_bi_number_prototype_to_string,
+	duk_bi_number_prototype_value_of,
+	duk_bi_object_constructor,
+	duk_bi_object_constructor_create,
+	duk_bi_object_constructor_define_properties,
+	duk_bi_object_constructor_define_property,
+	duk_bi_object_constructor_get_own_property_descriptor,
+	duk_bi_object_constructor_is_extensible,
+	duk_bi_object_constructor_is_sealed_frozen_shared,
+	duk_bi_object_constructor_keys_shared,
+	duk_bi_object_constructor_prevent_extensions,
+	duk_bi_object_constructor_seal_freeze_shared,
+	duk_bi_object_getprototype_shared,
+	duk_bi_object_prototype_has_own_property,
+	duk_bi_object_prototype_is_prototype_of,
+	duk_bi_object_prototype_property_is_enumerable,
+	duk_bi_object_prototype_to_locale_string,
+	duk_bi_object_prototype_to_string,
+	duk_bi_object_prototype_value_of,
+	duk_bi_object_setprototype_shared,
+	duk_bi_pointer_constructor,
+	duk_bi_pointer_prototype_tostring_shared,
+	duk_bi_proxy_constructor,
+	duk_bi_regexp_constructor,
+	duk_bi_regexp_prototype_exec,
+	duk_bi_regexp_prototype_test,
+	duk_bi_regexp_prototype_to_string,
+	duk_bi_string_constructor,
+	duk_bi_string_constructor_from_char_code,
+	duk_bi_string_prototype_caseconv_shared,
+	duk_bi_string_prototype_char_at,
+	duk_bi_string_prototype_char_code_at,
+	duk_bi_string_prototype_concat,
+	duk_bi_string_prototype_indexof_shared,
+	duk_bi_string_prototype_locale_compare,
+	duk_bi_string_prototype_match,
+	duk_bi_string_prototype_replace,
+	duk_bi_string_prototype_search,
+	duk_bi_string_prototype_slice,
+	duk_bi_string_prototype_split,
+	duk_bi_string_prototype_substr,
+	duk_bi_string_prototype_substring,
+	duk_bi_string_prototype_to_string,
+	duk_bi_string_prototype_trim,
+	duk_bi_thread_constructor,
+	duk_bi_thread_current,
+	duk_bi_thread_resume,
+	duk_bi_thread_yield,
+	duk_bi_type_error_thrower,
+};
+
+const duk_uint8_t duk_builtins_data[] = {
+105,195,74,144,77,40,105,44,9,124,104,45,3,3,72,0,71,225,65,165,172,33,243,
+6,145,0,122,24,210,150,14,249,35,120,160,55,226,13,76,224,196,177,164,152,
+22,192,4,202,52,147,72,152,0,169,70,146,105,11,0,23,40,210,77,32,96,3,37,
+26,73,163,236,0,108,163,73,52,121,128,14,148,105,38,142,176,1,242,144,56,
+209,0,84,6,166,98,242,80,210,248,1,250,67,72,144,15,232,13,44,128,47,162,
+52,161,0,62,80,160,255,253,102,76,7,255,128,0,0,0,0,0,3,84,7,255,0,0,0,0,0,
+0,3,124,64,153,132,18,49,2,38,48,64,200,7,153,64,227,48,26,103,3,13,0,89,
+165,34,53,36,38,180,128,216,143,155,81,227,114,58,111,2,142,0,73,194,94,56,
+202,167,33,209,195,130,70,207,17,26,59,36,100,232,145,131,146,69,204,201,
+22,52,36,84,212,145,67,98,68,205,201,18,63,36,68,244,122,32,100,60,87,62,
+39,255,254,9,46,24,0,10,31,224,29,13,92,40,0,9,101,141,32,0,48,197,100,66,
+214,73,10,83,68,37,85,144,133,68,65,214,201,6,91,40,0,12,21,104,144,69,130,
+64,214,10,0,3,2,87,36,5,100,160,0,63,254,16,37,135,92,99,25,242,194,7,195,
+0,30,236,64,123,46,145,234,188,71,162,249,5,23,240,0,15,241,0,17,242,98,7,
+153,114,30,70,7,207,18,243,225,71,252,0,93,192,36,15,241,128,85,242,28,7,
+192,40,64,0,93,160,71,206,192,82,58,193,128,234,7,62,116,132,129,208,20,7,
+56,90,28,193,132,114,134,175,57,3,207,156,96,103,224,0,46,32,51,255,255,
+247,8,33,163,128,16,212,0,5,190,8,106,0,4,222,4,53,0,3,110,130,26,128,2,55,
+2,15,192,0,91,97,7,255,255,226,147,248,0,182,155,15,131,252,128,73,144,128,
+202,62,94,128,247,122,3,101,184,141,134,242,59,92,15,156,110,38,142,39,19,
+80,0,22,187,145,26,206,100,106,186,33,168,234,71,59,178,26,110,232,105,60,
+161,162,218,6,131,104,25,237,161,0,1,103,54,132,0,5,154,246,6,99,194,5,47,
+240,0,15,242,129,38,70,9,242,34,16,0,23,184,136,5,55,241,89,112,31,251,255,
+255,255,255,255,255,217,80,0,0,0,0,0,0,0,0,89,48,31,254,0,0,0,0,0,0,24,240,
+31,252,0,0,0,0,0,0,25,16,63,252,0,0,0,0,0,0,0,15,243,1,38,64,0,0,0,0,0,0,0,
+0,25,242,160,71,194,113,30,234,32,99,38,145,138,152,70,34,121,5,63,240,3,
+97,139,17,132,47,252,193,21,0,127,156,9,50,67,255,192,0,0,0,0,0,5,175,145,
+208,128,52,48,7,66,0,80,191,29,8,2,67,224,116,32,29,11,225,208,128,84,47,
+71,66,1,144,155,29,8,3,2,104,116,32,140,73,145,194,61,199,128,188,30,2,236,
+96,32,1,11,161,128,46,70,2,32,16,184,24,8,128,2,220,96,36,1,139,97,128,144,
+2,45,70,2,224,16,180,24,11,128,2,204,96,38,1,11,33,128,152,0,44,70,2,128,
+16,176,24,10,0,2,188,96,42,1,10,225,128,168,0,43,70,2,192,16,172,24,11,0,2,
+172,100,10,161,178,42,70,138,34,16,168,26,40,136,2,156,105,124,132,33,76,
+52,190,66,0,165,26,127,49,8,82,13,63,152,128,40,198,167,208,66,20,67,83,
+232,32,10,17,162,136,4,40,6,138,32,0,159,26,95,32,8,79,13,47,144,0,39,70,
+159,204,2,83,131,79,230,0,41,129,128,128,12,37,198,138,100,2,42,63,128,0,
+127,160,49,38,8,24,0,64,82,112,145,224,120,201,40,201,24,201,9,0,0,0,0,0,0,
+0,0,1,165,90,8,148,105,35,229,168,2,167,248,0,7,250,66,163,8,22,139,32,49,
+77,202,138,74,46,78,82,80,114,130,144,95,37,96,21,95,192,1,63,212,9,24,82,
+36,89,1,128,21,127,192,1,63,214,9,24,82,20,89,1,128,21,159,192,1,63,216,9,
+24,82,4,89,1,128,21,191,192,1,63,218,9,24,81,244,89,1,128,21,223,192,1,63,
+220,9,24,81,228,89,1,128,21,255,192,1,63,222,9,24,81,212,89,1,128,15,255,
+200,69,64,64,5,191,10,139,20,87,105,68,192,64,2,107,177,187,181,85,22,68,
+64,63,230,46,66,254,250,57,239,67,192,63,247,21,71,101,43,130,254,67,64,63,
+219,203,123,21,38,229,14,66,192,64,9,33,251,84,68,45,24,66,64,63,230,160,
+158,102,127,59,205,65,192,63,246,160,158,102,127,59,205,73,4,144,68,10,65,
+64,0,80,9,5,0,2,63,164,20,0,12,252,148,131,234,65,64,1,15,137,5,0,5,61,164,
+20,0,24,244,144,80,0,115,202,65,64,2,15,8,203,199,116,117,227,178,82,64,0,
+78,169,32,58,36,20,0,36,230,144,80,0,163,146,65,64,2,206,41,5,0,12,15,255,
+192,9,134,128,131,130,11,5,255,240,0,23,255,192,0,63,255,20,105,1,2,68,192,
+0,0,0,0,0,6,115,128,142,144,9,202,0,58,199,25,146,144,100,62,65,137,33,6,4,
+99,194,242,33,225,113,8,240,164,128,130,205,248,1,211,223,208,40,126,96,
+157,244,1,255,40,0,11,71,224,0,31,243,128,19,228,76,32,0,47,113,48,11,87,
+224,0,31,244,128,19,229,148,32,0,47,118,80,11,103,224,0,31,245,132,30,144,
+128,0,0,0,0,0,0,0,31,146,9,135,118,253,196,9,144,200,37,69,32,145,16,120,
+70,136,62,0,2,51,68,31,0,2,17,34,15,128,1,136,81,7,192,1,4,8,131,224,0,169,
+255,248,72,200,33,113,55,245,197,179,44,94,92,183,242,69,193,23,203,203,
+150,254,72,52,238,65,151,151,45,252,144,104,195,187,38,205,59,179,128,
+};
+#ifdef DUK_USE_BUILTIN_INITJS
+const duk_uint8_t duk_initjs_data[] = {
+40,102,117,110,99,116,105,111,110,40,100,44,97,41,123,102,117,110,99,116,
+105,111,110,32,98,40,97,44,98,44,99,41,123,79,98,106,101,99,116,46,100,101,
+102,105,110,101,80,114,111,112,101,114,116,121,40,97,44,98,44,123,118,97,
+108,117,101,58,99,44,119,114,105,116,97,98,108,101,58,33,48,44,101,110,117,
+109,101,114,97,98,108,101,58,33,49,44,99,111,110,102,105,103,117,114,97,98,
+108,101,58,33,48,125,41,125,98,40,97,46,76,111,103,103,101,114,44,34,99,
+108,111,103,34,44,110,101,119,32,97,46,76,111,103,103,101,114,40,34,67,34,
+41,41,59,98,40,97,44,34,109,111,100,76,111,97,100,101,100,34,44,123,125,41,
+125,41,40,116,104,105,115,44,68,117,107,116,97,112,101,41,59,10,0,
+};
+#endif  /* DUK_USE_BUILTIN_INITJS */
+#elif defined(DUK_USE_DOUBLE_ME)
+const duk_uint8_t duk_strings_data[] = {
+55,86,227,24,145,55,102,120,144,3,63,94,228,54,100,137,186,26,20,164,137,
+186,50,11,164,109,77,215,5,61,35,106,3,25,110,8,22,158,130,38,163,8,217,
+200,158,76,156,210,117,128,153,203,210,70,46,137,187,18,27,164,187,201,209,
+130,100,55,91,70,4,145,63,66,231,44,128,105,187,41,197,13,49,122,8,196,24,
+71,75,70,138,104,115,77,215,5,36,20,201,214,209,107,79,104,209,144,168,105,
+6,207,251,209,104,209,125,212,227,66,127,235,191,239,232,180,90,52,95,69,
+247,83,141,9,255,174,255,191,162,211,80,210,253,23,221,78,52,39,254,183,
+254,254,139,72,105,126,139,238,167,26,19,255,91,255,127,69,166,129,191,69,
+247,83,141,9,255,175,255,191,162,213,26,50,23,232,190,234,113,161,63,245,
+115,119,86,227,118,83,138,26,98,9,110,48,86,22,148,160,152,22,82,70,46,137,
+44,8,180,163,32,104,98,206,32,17,7,16,88,101,100,206,42,70,36,108,205,18,
+74,140,33,196,230,60,2,152,146,33,38,230,8,36,79,182,251,65,156,151,24,200,
+33,145,162,25,80,209,24,67,0,166,68,52,174,61,73,25,33,205,25,27,84,177,
+195,234,220,1,144,105,99,135,217,16,17,17,208,72,199,179,60,93,100,146,49,
+232,162,64,76,135,19,152,244,44,136,220,72,96,130,68,62,230,120,144,3,70,
+206,6,141,100,138,182,84,52,11,70,73,19,236,64,90,200,66,109,128,121,118,8,
+154,69,220,206,137,35,111,23,217,45,13,33,247,39,82,34,33,247,80,68,141,
+169,246,178,92,141,169,247,80,69,128,122,54,87,69,128,92,147,176,226,100,
+19,134,66,237,164,188,207,185,130,38,36,205,186,129,116,33,222,228,54,100,
+137,131,66,148,145,76,226,1,16,96,152,20,180,52,157,109,24,18,69,5,66,201,
+214,129,132,19,196,89,76,2,83,187,51,197,214,236,240,242,116,145,139,162,
+126,142,138,152,30,65,32,103,137,33,68,68,137,214,159,23,77,218,211,164,73,
+245,241,116,221,60,60,157,113,78,235,55,170,38,36,146,54,140,36,65,50,56,
+100,89,38,78,190,46,121,35,60,12,224,145,122,248,186,248,48,128,181,144,
+140,234,27,80,45,3,250,14,140,19,33,127,111,235,190,187,235,191,116,95,21,
+241,33,250,0,253,96,190,183,236,5,245,127,96,31,176,79,214,245,13,42,26,
+137,225,63,87,212,52,168,106,39,132,117,13,42,26,137,224,206,10,72,41,154,
+83,138,26,99,54,140,9,34,112,185,203,32,26,154,52,100,42,26,65,166,83,138,
+26,100,23,3,152,26,73,66,51,28,144,210,197,212,104,205,16,52,110,96,222,
+235,13,136,104,216,11,141,110,49,74,183,58,35,37,222,49,58,68,17,16,178,
+130,96,111,217,16,19,3,72,9,33,164,0,157,33,128,50,47,165,8,207,236,143,
+233,66,51,251,29,125,144,188,244,27,203,113,190,199,236,8,95,45,198,251,34,
+15,203,111,217,19,203,111,216,253,128,122,67,176,146,144,12,66,52,12,33,80,
+215,210,101,67,70,75,147,234,62,255,238,190,165,43,4,167,212,52,100,186,89,
+69,205,11,67,72,164,25,174,137,58,32,72,134,147,169,17,16,147,36,166,66,92,
+130,92,221,227,50,114,244,226,134,152,242,36,251,130,2,39,49,39,220,16,17,
+52,221,228,201,205,92,221,228,73,210,244,226,134,153,115,119,169,49,75,211,
+138,26,103,72,147,245,38,34,250,139,95,112,64,69,114,36,250,90,45,125,193,
+1,21,200,147,245,38,38,121,205,153,209,34,79,172,115,102,117,72,147,245,38,
+33,248,19,33,228,73,244,96,153,23,34,79,212,152,143,231,82,50,68,137,62,
+157,212,140,155,34,79,212,152,153,228,54,147,36,149,34,79,172,67,105,50,73,
+178,36,253,73,138,94,32,156,209,201,82,36,251,34,9,205,28,165,34,79,212,
+152,153,228,45,104,145,4,230,142,79,145,39,214,33,107,68,136,39,52,114,60,
+137,62,205,12,36,76,73,246,104,97,50,230,147,174,41,100,73,200,196,159,169,
+49,51,200,90,209,34,9,205,28,158,98,79,172,66,214,137,16,78,104,228,211,18,
+126,164,197,47,16,78,104,228,163,18,125,145,4,230,142,77,49,39,234,76,76,
+242,27,73,146,74,49,39,214,33,180,153,36,179,18,126,164,196,127,58,145,146,
+12,73,244,238,164,100,147,18,126,164,196,63,3,6,49,39,209,131,10,49,39,234,
+76,67,240,38,67,152,147,232,193,50,44,196,159,169,49,51,206,108,206,131,18,
+125,99,155,51,184,196,159,169,49,23,212,90,251,130,2,43,49,39,210,209,107,
+238,8,8,156,196,159,102,134,18,83,119,86,225,1,100,236,208,194,118,83,138,
+26,105,77,221,91,132,5,147,163,4,201,217,78,40,105,153,55,118,104,97,59,41,
+197,13,51,38,238,140,19,39,101,56,161,166,27,93,97,250,147,17,94,8,200,139,
+155,186,248,144,72,146,28,214,205,221,37,222,230,145,179,64,22,121,187,165,
+69,200,56,249,238,146,38,138,147,72,105,80,212,79,17,243,72,96,77,21,38,
+144,210,161,168,158,7,214,131,173,79,152,134,234,160,93,8,159,48,23,213,64,
+186,16,210,160,101,56,146,113,67,35,55,117,110,16,22,78,209,239,36,122,32,
+72,139,155,187,71,188,145,232,129,34,70,110,234,220,32,44,157,91,172,72,
+244,64,145,23,55,117,110,177,35,209,2,68,76,168,25,78,40,105,139,39,173,19,
+52,136,8,136,231,137,30,176,8,133,96,38,35,181,110,16,22,78,137,204,120,34,
+69,8,224,143,68,225,147,160,153,132,112,71,160,155,11,23,51,162,56,35,209,
+56,100,92,72,58,8,157,138,12,121,154,36,29,4,67,21,11,153,34,54,1,231,43,
+163,210,0,71,36,156,194,20,149,36,112,181,130,83,234,26,50,95,174,41,208,
+209,146,253,113,79,70,200,232,44,205,39,173,2,33,36,232,204,178,90,4,66,
+200,232,44,207,18,84,145,145,8,250,72,227,123,158,68,185,13,48,156,209,2,
+103,68,186,8,196,24,72,34,106,48,141,156,140,32,45,100,137,2,134,140,130,2,
+214,80,61,235,197,31,23,60,145,158,58,137,116,141,163,9,16,5,100,106,37,
+215,197,211,116,240,242,117,197,113,193,46,186,205,235,226,231,146,51,193,
+234,5,208,157,113,93,55,117,110,16,22,78,202,113,67,76,137,187,178,156,80,
+211,44,78,108,167,26,10,110,136,228,73,140,196,154,136,218,48,145,0,86,70,
+9,205,42,13,72,128,43,34,45,20,76,1,89,11,80,46,132,34,137,137,49,18,233,
+47,50,54,72,10,200,132,75,165,139,178,70,161,18,236,136,5,144,113,124,73,
+82,54,125,37,230,70,201,14,108,140,88,144,153,33,36,64,46,3,33,80,210,117,
+241,115,201,25,160,146,112,200,84,52,157,124,92,242,70,120,48,162,64,76,
+147,49,39,215,89,189,124,92,242,70,120,235,64,194,75,6,36,250,235,55,175,
+139,158,72,207,29,25,36,40,161,243,116,92,98,79,175,139,166,233,225,228,
+235,138,151,197,211,116,240,242,25,100,105,166,114,129,100,140,202,248,161,
+179,69,26,73,8,15,33,137,33,1,228,145,26,39,12,158,164,80,47,57,143,115,72,
+217,164,141,19,134,79,82,40,144,200,39,12,158,164,80,47,57,143,115,72,217,
+164,100,19,134,79,82,40,66,37,210,161,168,153,10,137,117,160,235,84,240,70,
+68,233,86,224,77,15,4,100,78,161,179,33,42,5,163,212,138,9,232,197,209,79,
+103,135,147,164,140,93,21,246,88,108,193,126,146,49,116,93,216,144,164,137,
+26,34,116,145,139,162,174,196,13,49,58,72,197,209,79,73,80,47,164,140,93,
+18,134,36,2,136,32,72,133,16,38,35,160,78,108,208,218,17,3,32,104,99,18,39,
+25,10,10,46,102,25,22,73,144,67,113,8,185,16,229,67,64,181,224,101,116,80,
+90,52,83,67,154,36,20,72,106,144,217,76,13,17,28,81,180,150,52,73,58,69,
+166,149,145,49,28,147,58,36,89,158,46,176,231,28,26,120,121,28,81,212,17,
+37,92,129,150,199,66,200,75,34,103,40,150,9,72,162,115,101,50,17,180,97,
+137,119,186,51,57,47,50,52,114,50,24,247,70,101,149,30,72,145,180,90,201,
+56,208,133,40,23,34,41,12,122,200,194,54,114,74,27,50,68,160,17,6,178,76,
+239,0,148,3,16,239,138,42,9,145,47,139,166,65,76,131,51,232,21,160,70,148,
+193,52,8,184,65,22,48,
+};
+
+/* to convert a heap stridx to a token number, subtract
+ * DUK_STRIDX_START_RESERVED and add DUK_TOK_START_RESERVED.
+ */
+
+/* native functions: 129 */
+const duk_c_function duk_bi_native_functions[] = {
+	duk_bi_array_constructor,
+	duk_bi_array_constructor_is_array,
+	duk_bi_array_prototype_concat,
+	duk_bi_array_prototype_indexof_shared,
+	duk_bi_array_prototype_iter_shared,
+	duk_bi_array_prototype_join_shared,
+	duk_bi_array_prototype_pop,
+	duk_bi_array_prototype_push,
+	duk_bi_array_prototype_reduce_shared,
+	duk_bi_array_prototype_reverse,
+	duk_bi_array_prototype_shift,
+	duk_bi_array_prototype_slice,
+	duk_bi_array_prototype_sort,
+	duk_bi_array_prototype_splice,
+	duk_bi_array_prototype_to_string,
+	duk_bi_array_prototype_unshift,
+	duk_bi_boolean_constructor,
+	duk_bi_boolean_prototype_tostring_shared,
+	duk_bi_buffer_constructor,
+	duk_bi_buffer_prototype_tostring_shared,
+	duk_bi_date_constructor,
+	duk_bi_date_constructor_now,
+	duk_bi_date_constructor_parse,
+	duk_bi_date_constructor_utc,
+	duk_bi_date_prototype_get_shared,
+	duk_bi_date_prototype_get_timezone_offset,
+	duk_bi_date_prototype_set_shared,
+	duk_bi_date_prototype_set_time,
+	duk_bi_date_prototype_to_json,
+	duk_bi_date_prototype_tostring_shared,
+	duk_bi_date_prototype_value_of,
+	duk_bi_duktape_object_act,
+	duk_bi_duktape_object_compact,
+	duk_bi_duktape_object_dec,
+	duk_bi_duktape_object_enc,
+	duk_bi_duktape_object_fin,
+	duk_bi_duktape_object_gc,
+	duk_bi_duktape_object_info,
+	duk_bi_error_constructor_shared,
+	duk_bi_error_prototype_filename_getter,
+	duk_bi_error_prototype_linenumber_getter,
+	duk_bi_error_prototype_nop_setter,
+	duk_bi_error_prototype_stack_getter,
+	duk_bi_error_prototype_to_string,
+	duk_bi_function_constructor,
+	duk_bi_function_prototype,
+	duk_bi_function_prototype_apply,
+	duk_bi_function_prototype_bind,
+	duk_bi_function_prototype_call,
+	duk_bi_function_prototype_to_string,
+	duk_bi_global_object_alert,
+	duk_bi_global_object_decode_uri,
+	duk_bi_global_object_decode_uri_component,
+	duk_bi_global_object_encode_uri,
+	duk_bi_global_object_encode_uri_component,
+	duk_bi_global_object_escape,
+	duk_bi_global_object_eval,
+	duk_bi_global_object_is_finite,
+	duk_bi_global_object_is_nan,
+	duk_bi_global_object_parse_float,
+	duk_bi_global_object_parse_int,
+	duk_bi_global_object_print,
+	duk_bi_global_object_require,
+	duk_bi_global_object_unescape,
+	duk_bi_json_object_parse,
+	duk_bi_json_object_stringify,
+	duk_bi_logger_constructor,
+	duk_bi_logger_prototype_fmt,
+	duk_bi_logger_prototype_log_shared,
+	duk_bi_logger_prototype_raw,
+	duk_bi_math_object_max,
+	duk_bi_math_object_min,
+	duk_bi_math_object_onearg_shared,
+	duk_bi_math_object_random,
+	duk_bi_math_object_twoarg_shared,
+	duk_bi_number_constructor,
+	duk_bi_number_prototype_to_exponential,
+	duk_bi_number_prototype_to_fixed,
+	duk_bi_number_prototype_to_locale_string,
+	duk_bi_number_prototype_to_precision,
+	duk_bi_number_prototype_to_string,
+	duk_bi_number_prototype_value_of,
+	duk_bi_object_constructor,
+	duk_bi_object_constructor_create,
+	duk_bi_object_constructor_define_properties,
+	duk_bi_object_constructor_define_property,
+	duk_bi_object_constructor_get_own_property_descriptor,
+	duk_bi_object_constructor_is_extensible,
+	duk_bi_object_constructor_is_sealed_frozen_shared,
+	duk_bi_object_constructor_keys_shared,
+	duk_bi_object_constructor_prevent_extensions,
+	duk_bi_object_constructor_seal_freeze_shared,
+	duk_bi_object_getprototype_shared,
+	duk_bi_object_prototype_has_own_property,
+	duk_bi_object_prototype_is_prototype_of,
+	duk_bi_object_prototype_property_is_enumerable,
+	duk_bi_object_prototype_to_locale_string,
+	duk_bi_object_prototype_to_string,
+	duk_bi_object_prototype_value_of,
+	duk_bi_object_setprototype_shared,
+	duk_bi_pointer_constructor,
+	duk_bi_pointer_prototype_tostring_shared,
+	duk_bi_proxy_constructor,
+	duk_bi_regexp_constructor,
+	duk_bi_regexp_prototype_exec,
+	duk_bi_regexp_prototype_test,
+	duk_bi_regexp_prototype_to_string,
+	duk_bi_string_constructor,
+	duk_bi_string_constructor_from_char_code,
+	duk_bi_string_prototype_caseconv_shared,
+	duk_bi_string_prototype_char_at,
+	duk_bi_string_prototype_char_code_at,
+	duk_bi_string_prototype_concat,
+	duk_bi_string_prototype_indexof_shared,
+	duk_bi_string_prototype_locale_compare,
+	duk_bi_string_prototype_match,
+	duk_bi_string_prototype_replace,
+	duk_bi_string_prototype_search,
+	duk_bi_string_prototype_slice,
+	duk_bi_string_prototype_split,
+	duk_bi_string_prototype_substr,
+	duk_bi_string_prototype_substring,
+	duk_bi_string_prototype_to_string,
+	duk_bi_string_prototype_trim,
+	duk_bi_thread_constructor,
+	duk_bi_thread_current,
+	duk_bi_thread_resume,
+	duk_bi_thread_yield,
+	duk_bi_type_error_thrower,
+};
+
+const duk_uint8_t duk_builtins_data[] = {
+105,195,74,144,77,40,105,44,9,124,104,45,3,3,72,0,71,225,65,165,172,33,243,
+6,145,0,122,24,210,150,14,249,35,120,160,55,226,13,76,224,196,177,164,152,
+22,192,4,202,52,147,72,152,0,169,70,146,105,11,0,23,40,210,77,32,96,3,37,
+26,73,163,236,0,108,163,73,52,121,128,14,148,105,38,142,176,1,242,144,56,
+209,0,84,6,166,98,242,80,210,248,1,250,67,72,144,15,232,13,44,128,47,162,
+52,161,0,62,80,160,255,253,102,76,0,0,15,135,240,0,0,0,3,84,0,0,15,7,240,0,
+0,0,3,124,64,153,132,18,49,2,38,48,64,200,7,153,64,227,48,26,103,3,13,0,89,
+165,34,53,36,38,180,128,216,143,155,81,227,114,58,111,2,142,0,73,194,94,56,
+202,167,33,209,195,130,70,207,17,26,59,36,100,232,145,131,146,69,204,201,
+22,52,36,84,212,145,67,98,68,205,201,18,63,36,68,244,122,32,100,60,87,62,
+39,255,254,9,46,24,0,10,31,224,29,13,92,40,0,9,101,141,32,0,48,197,100,66,
+214,73,10,83,68,37,85,144,133,68,65,214,201,6,91,40,0,12,21,104,144,69,130,
+64,214,10,0,3,2,87,36,5,100,160,0,63,254,16,37,135,92,99,25,242,194,7,195,
+0,30,236,64,123,46,145,234,188,71,162,249,5,23,240,0,15,241,0,17,242,98,7,
+153,114,30,70,7,207,18,243,225,71,252,0,93,192,36,15,241,128,85,242,28,7,
+192,40,64,0,93,160,71,206,192,82,58,193,128,234,7,62,116,132,129,208,20,7,
+56,90,28,193,132,114,134,175,57,3,207,156,96,103,224,0,46,32,51,255,255,
+247,8,33,163,128,16,212,0,5,190,8,106,0,4,222,4,53,0,3,110,130,26,128,2,55,
+2,15,192,0,91,97,7,255,255,226,147,248,0,182,155,15,131,252,128,73,144,128,
+202,62,94,128,247,122,3,101,184,141,134,242,59,92,15,156,110,38,142,39,19,
+80,0,22,187,145,26,206,100,106,186,33,168,234,71,59,178,26,110,232,105,60,
+161,162,218,6,131,104,25,237,161,0,1,103,54,132,0,5,154,246,6,99,194,5,47,
+240,0,15,242,129,38,70,9,242,34,16,0,23,184,136,5,55,241,89,112,63,255,251,
+223,255,255,255,255,217,80,0,0,0,0,0,64,0,0,25,48,0,0,62,31,192,0,0,0,24,
+240,0,0,60,31,192,0,0,0,25,16,0,0,60,63,192,0,0,0,0,15,243,1,38,64,0,0,0,0,
+0,0,0,0,25,242,160,71,194,113,30,234,32,99,38,145,138,152,70,34,121,5,63,
+240,3,97,139,17,132,47,252,193,21,0,127,156,9,50,64,0,7,195,248,0,0,0,5,
+175,145,208,128,52,48,7,66,0,80,191,29,8,2,67,224,116,32,29,11,225,208,128,
+84,47,71,66,1,144,155,29,8,3,2,104,116,32,140,73,145,194,61,199,128,188,30,
+2,236,96,32,1,11,161,128,46,70,2,32,16,184,24,8,128,2,220,96,36,1,139,97,
+128,144,2,45,70,2,224,16,180,24,11,128,2,204,96,38,1,11,33,128,152,0,44,70,
+2,128,16,176,24,10,0,2,188,96,42,1,10,225,128,168,0,43,70,2,192,16,172,24,
+11,0,2,172,100,10,161,178,42,70,138,34,16,168,26,40,136,2,156,105,124,132,
+33,76,52,190,66,0,165,26,127,49,8,82,13,63,152,128,40,198,167,208,66,20,67,
+83,232,32,10,17,162,136,4,40,6,138,32,0,159,26,95,32,8,79,13,47,144,0,39,
+70,159,204,2,83,131,79,230,0,41,129,128,128,12,37,198,138,100,2,42,63,128,
+0,127,160,49,38,8,24,0,64,82,112,145,224,120,201,40,201,24,201,9,0,0,0,0,0,
+0,0,0,1,165,90,8,148,105,35,229,168,2,167,248,0,7,250,66,163,8,22,139,32,
+49,77,202,138,74,46,78,82,80,114,130,144,95,37,96,21,95,192,1,63,212,9,24,
+82,36,89,1,128,21,127,192,1,63,214,9,24,82,20,89,1,128,21,159,192,1,63,216,
+9,24,82,4,89,1,128,21,191,192,1,63,218,9,24,81,244,89,1,128,21,223,192,1,
+63,220,9,24,81,228,89,1,128,21,255,192,1,63,222,9,24,81,212,89,1,128,15,
+255,200,69,64,10,191,5,64,105,87,20,139,68,192,177,107,2,64,22,85,181,187,
+68,64,66,46,230,63,239,57,250,254,67,192,71,21,247,63,254,130,43,101,67,64,
+123,203,219,63,14,229,38,21,66,192,251,33,9,64,24,45,68,84,66,64,158,160,
+230,63,205,59,127,102,65,192,158,160,246,63,205,59,127,102,73,4,144,68,10,
+65,64,0,80,9,5,0,2,63,164,20,0,12,252,148,131,234,65,64,1,15,137,5,0,5,61,
+164,20,0,24,244,144,80,0,115,202,65,64,2,15,8,203,199,116,117,227,178,82,
+64,0,78,169,32,58,36,20,0,36,230,144,80,0,163,146,65,64,2,206,41,5,0,12,15,
+255,192,9,134,128,131,130,11,5,255,240,0,23,255,192,0,63,255,20,105,0,0,
+194,69,0,0,0,0,0,6,115,128,142,144,9,202,0,58,199,25,146,144,100,62,65,137,
+33,6,4,99,194,242,33,225,113,8,240,164,128,130,205,248,1,211,223,208,40,
+126,96,157,244,1,255,40,0,11,71,224,0,31,243,128,19,228,76,32,0,47,113,48,
+11,87,224,0,31,244,128,19,229,148,32,0,47,118,80,11,103,224,0,31,245,132,
+30,144,0,0,0,128,0,0,0,0,31,146,9,135,118,253,196,9,144,200,37,69,32,145,
+16,120,70,136,62,0,2,51,68,31,0,2,17,34,15,128,1,136,81,7,192,1,4,8,131,
+224,0,169,255,248,72,200,33,113,55,245,197,179,44,94,92,183,242,69,193,23,
+203,203,150,254,72,52,238,65,151,151,45,252,144,104,195,187,38,205,59,179,
+128,
+};
+#ifdef DUK_USE_BUILTIN_INITJS
+const duk_uint8_t duk_initjs_data[] = {
+40,102,117,110,99,116,105,111,110,40,100,44,97,41,123,102,117,110,99,116,
+105,111,110,32,98,40,97,44,98,44,99,41,123,79,98,106,101,99,116,46,100,101,
+102,105,110,101,80,114,111,112,101,114,116,121,40,97,44,98,44,123,118,97,
+108,117,101,58,99,44,119,114,105,116,97,98,108,101,58,33,48,44,101,110,117,
+109,101,114,97,98,108,101,58,33,49,44,99,111,110,102,105,103,117,114,97,98,
+108,101,58,33,48,125,41,125,98,40,97,46,76,111,103,103,101,114,44,34,99,
+108,111,103,34,44,110,101,119,32,97,46,76,111,103,103,101,114,40,34,67,34,
+41,41,59,98,40,97,44,34,109,111,100,76,111,97,100,101,100,34,44,123,125,41,
+125,41,40,116,104,105,115,44,68,117,107,116,97,112,101,41,59,10,0,
+};
+#endif  /* DUK_USE_BUILTIN_INITJS */
+#else
+#error invalid endianness defines
+#endif
+#line 1 "duk_debug_fixedbuffer.c"
+/*
+ *  Fixed buffer helper useful for debugging, requires no allocation
+ *  which is critical for debugging.
+ */
+
+/* include removed: duk_internal.h */
+
+#ifdef DUK_USE_DEBUG
+
+void duk_fb_put_bytes(duk_fixedbuffer *fb, duk_uint8_t *buffer, duk_size_t length) {
+	duk_size_t avail;
+	duk_size_t copylen;
+
+	avail = (fb->offset >= fb->length ? (duk_size_t) 0 : (duk_size_t) (fb->length - fb->offset));
+	if (length > avail) {
+		copylen = avail;
+		fb->truncated = 1;
+	} else {
+		copylen = length;
+	}
+	DUK_MEMCPY(fb->buffer + fb->offset, buffer, copylen);
+	fb->offset += copylen;
+}
+
+void duk_fb_put_byte(duk_fixedbuffer *fb, duk_uint8_t x) {
+	duk_fb_put_bytes(fb, &x, 1);
+}
+
+void duk_fb_put_cstring(duk_fixedbuffer *fb, const char *x) {
+	duk_fb_put_bytes(fb, (duk_uint8_t *) x, (duk_size_t) DUK_STRLEN(x));
+}
+
+void duk_fb_sprintf(duk_fixedbuffer *fb, const char *fmt, ...) {
+	duk_size_t avail;
+	va_list ap;
+
+	va_start(ap, fmt);
+	avail = (fb->offset >= fb->length ? (duk_size_t) 0 : (duk_size_t) (fb->length - fb->offset));
+	if (avail > 0) {
+		duk_int_t res = (duk_int_t) DUK_VSNPRINTF((char *) (fb->buffer + fb->offset), avail, fmt, ap);
+		if (res < 0) {
+			/* error */
+		} else if ((duk_size_t) res >= avail) {
+			/* (maybe) truncated */
+			fb->offset += avail;
+			if ((duk_size_t) res > avail) {
+				/* actual chars dropped (not just NUL term) */
+				fb->truncated = 1;
+			}
+		} else {
+			/* normal */
+			fb->offset += res;
+		}
+	}
+	va_end(ap);
+}
+
+duk_bool_t duk_fb_is_full(duk_fixedbuffer *fb) {
+	return (fb->offset >= fb->length);
+}
+
+#endif  /* DUK_USE_DEBUG */
+#line 1 "duk_debug_heap.c"
+/*
+ *  Debug dumping of duk_heap.
+ */
+
+/* include removed: duk_internal.h */
+
+#ifdef DUK_USE_DEBUG
+
+static void duk__sanitize_snippet(char *buf, duk_size_t buf_size, duk_hstring *str) {
+	duk_size_t i;
+	duk_size_t nchars;
+	duk_size_t maxchars;
+	duk_uint8_t *data;
+
+	DUK_MEMZERO(buf, buf_size);
+
+	maxchars = (duk_size_t) (buf_size - 1);
+	data = DUK_HSTRING_GET_DATA(str);
+	nchars = ((duk_size_t) str->blen < maxchars ? (duk_size_t) str->blen : maxchars);
+	for (i = 0; i < nchars; i++) {
+		duk_small_int_t c = (duk_small_int_t) data[i];
+		if (c < 0x20 || c > 0x7e) {
+			c = '.';
+		}
+		buf[i] = (char) c;
+	}
+}
+
+static const char *duk__get_heap_type_string(duk_heaphdr *hdr) {
+	switch (DUK_HEAPHDR_GET_TYPE(hdr)) {
+	case DUK_HTYPE_STRING:
+		return "string";
+	case DUK_HTYPE_OBJECT:
+		return "object";
+	case DUK_HTYPE_BUFFER:
+		return "buffer";
+	default:
+		return "???";
+	}
+}
+
+static void duk__dump_indented(duk_heaphdr *obj, int index) {
+#ifdef DUK_USE_REFERENCE_COUNTING
+	DUK_D(DUK_DPRINT("  [%ld]: %p %s (flags: 0x%08lx, ref: %ld) -> %!O",
+	                 (long) index,
+	                 (void *) obj,
+	                 (const char *) duk__get_heap_type_string(obj),
+	                 (unsigned long) DUK_HEAPHDR_GET_FLAGS(obj),
+	                 (long) DUK_HEAPHDR_GET_REFCOUNT(obj),
+	                 (duk_heaphdr *) obj));
+#else
+	DUK_D(DUK_DPRINT("  [%ld]: %p %s (flags: 0x%08lx) -> %!O",
+	                 (long) index,
+	                 (void *) obj,
+	                 (const char *) duk__get_heap_type_string(obj),
+	                 (unsigned long) DUK_HEAPHDR_GET_FLAGS(obj),
+	                 (duk_heaphdr *) obj));
+#endif
+}
+
+static void duk__dump_heaphdr_list(duk_heap *heap, duk_heaphdr *root, const char *name) {
+	duk_int_t count;
+	duk_heaphdr *curr;
+
+	DUK_UNREF(heap);
+
+	count = 0;
+	curr = root;
+	while (curr) {
+		count++;
+		curr = DUK_HEAPHDR_GET_NEXT(curr);
+	}
+
+	DUK_D(DUK_DPRINT("%s, %ld objects", (const char *) name, (long) count));
+
+	count = 0;
+	curr = root;
+	while (curr) {
+		count++;
+		duk__dump_indented(curr, count);
+		curr = DUK_HEAPHDR_GET_NEXT(curr);
+	}
+}
+
+static void duk__dump_stringtable(duk_heap *heap) {
+	duk_uint_fast32_t i;
+	char buf[64+1];
+
+	DUK_D(DUK_DPRINT("stringtable %p, used %ld, size %ld, load %ld%%",
+	                 (void *) heap->st,
+	                 (long) heap->st_used,
+	                 (long) heap->st_size,
+	                 (long) (((double) heap->st_used) / ((double) heap->st_size) * 100.0)));
+
+	for (i = 0; i < (duk_uint_fast32_t) heap->st_size; i++) {
+		duk_hstring *e = heap->st[i];
+
+		if (!e) {
+			DUK_D(DUK_DPRINT("  [%ld]: NULL", (long) i));
+		} else if (e == DUK_STRTAB_DELETED_MARKER(heap)) {
+			DUK_D(DUK_DPRINT("  [%ld]: DELETED", (long) i));
+		} else {
+			duk__sanitize_snippet(buf, sizeof(buf), e);
+
+#ifdef DUK_USE_REFERENCE_COUNTING
+			DUK_D(DUK_DPRINT("  [%ld]: %p (flags: 0x%08lx, ref: %ld) '%s', strhash=0x%08lx, blen=%ld, clen=%ld, "
+			                 "arridx=%ld, internal=%ld, reserved_word=%ld, strict_reserved_word=%ld, eval_or_arguments=%ld",
+			                 (long) i,
+			                 (void *) e,
+			                 (unsigned long) DUK_HEAPHDR_GET_FLAGS((duk_heaphdr *) e),
+			                 (long) DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) e),
+			                 (const char *) buf,
+			                 (unsigned long) e->hash,
+			                 (long) e->blen,
+			                 (long) e->clen,
+			                 (long) (DUK_HSTRING_HAS_ARRIDX(e) ? 1 : 0),
+			                 (long) (DUK_HSTRING_HAS_INTERNAL(e) ? 1 : 0),
+			                 (long) (DUK_HSTRING_HAS_RESERVED_WORD(e) ? 1 : 0),
+			                 (long) (DUK_HSTRING_HAS_STRICT_RESERVED_WORD(e) ? 1 : 0),
+			                 (long) (DUK_HSTRING_HAS_EVAL_OR_ARGUMENTS(e) ? 1 : 0)));
+#else
+			DUK_D(DUK_DPRINT("  [%ld]: %p (flags: 0x%08lx) '%s', strhash=0x%08lx, blen=%ld, clen=%ld, "
+			                 "arridx=%ld, internal=%ld, reserved_word=%ld, strict_reserved_word=%ld, eval_or_arguments=%ld",
+			                 (long) i,
+			                 (void *) e,
+			                 (unsigned long) DUK_HEAPHDR_GET_FLAGS((duk_heaphdr *) e),
+			                 (const char *) buf,
+			                 (long) e->hash,
+			                 (long) e->blen,
+			                 (long) e->clen,
+			                 (long) (DUK_HSTRING_HAS_ARRIDX(e) ? 1 : 0),
+			                 (long) (DUK_HSTRING_HAS_INTERNAL(e) ? 1 : 0),
+			                 (long) (DUK_HSTRING_HAS_RESERVED_WORD(e) ? 1 : 0),
+			                 (long) (DUK_HSTRING_HAS_STRICT_RESERVED_WORD(e) ? 1 : 0),
+			                 (long) (DUK_HSTRING_HAS_EVAL_OR_ARGUMENTS(e) ? 1 : 0)));
+#endif
+		}
+	}
+}
+
+static void duk__dump_strcache(duk_heap *heap) {
+	duk_uint_fast32_t i;
+	char buf[64+1];
+
+	DUK_D(DUK_DPRINT("stringcache"));
+
+	for (i = 0; i < (duk_uint_fast32_t) DUK_HEAP_STRCACHE_SIZE; i++) {
+		duk_strcache *c = &heap->strcache[i];
+		if (!c->h) {
+			DUK_D(DUK_DPRINT("  [%ld]: bidx=%ld, cidx=%ld, str=NULL",
+			                 (long) i, (long) c->bidx, (long) c->cidx));
+		} else {
+			duk__sanitize_snippet(buf, sizeof(buf), c->h);
+			DUK_D(DUK_DPRINT("  [%ld]: bidx=%ld cidx=%ld str=%s",
+			                 (long) i, (long) c->bidx, (long) c->cidx, (const char *) buf));
+		}
+	} 
+}
+
+void duk_debug_dump_heap(duk_heap *heap) {
+	char buf[64+1];
+
+	DUK_D(DUK_DPRINT("=== heap %p ===", (void *) heap));
+	DUK_D(DUK_DPRINT("  flags: 0x%08lx", (unsigned long) heap->flags));
+
+	/* Note: there is no standard formatter for function pointers */
+#ifdef DUK_USE_GCC_PRAGMAS
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-pedantic"
+#endif
+	duk_debug_format_funcptr(buf, sizeof(buf), (unsigned char *) &heap->alloc_func, sizeof(heap->alloc_func));
+	DUK_D(DUK_DPRINT("  alloc_func: %s", (const char *) buf));
+	duk_debug_format_funcptr(buf, sizeof(buf), (unsigned char *) &heap->realloc_func, sizeof(heap->realloc_func));
+	DUK_D(DUK_DPRINT("  realloc_func: %s", (const char *) buf));
+	duk_debug_format_funcptr(buf, sizeof(buf), (unsigned char *) &heap->free_func, sizeof(heap->free_func));
+	DUK_D(DUK_DPRINT("  free_func: %s", (const char *) buf));
+	duk_debug_format_funcptr(buf, sizeof(buf), (unsigned char *) &heap->fatal_func, sizeof(heap->fatal_func));
+	DUK_D(DUK_DPRINT("  fatal_func: %s", (const char *) buf));
+#ifdef DUK_USE_GCC_PRAGMAS
+#pragma GCC diagnostic pop
+#endif
+
+	DUK_D(DUK_DPRINT("  alloc_udata: %p", (void *) heap->alloc_udata));
+
+#ifdef DUK_USE_MARK_AND_SWEEP
+#ifdef DUK_USE_VOLUNTARY_GC
+	DUK_D(DUK_DPRINT("  mark-and-sweep trig counter: %ld", (long) heap->mark_and_sweep_trigger_counter));
+#endif
+	DUK_D(DUK_DPRINT("  mark-and-sweep rec depth: %ld", (long) heap->mark_and_sweep_recursion_depth));
+	DUK_D(DUK_DPRINT("  mark-and-sweep base flags: 0x%08lx", (unsigned long) heap->mark_and_sweep_base_flags));
+#endif
+
+	DUK_D(DUK_DPRINT("  lj.jmpbuf_ptr: %p", (void *) heap->lj.jmpbuf_ptr));
+	DUK_D(DUK_DPRINT("  lj.type: %ld", (long) heap->lj.type));
+	DUK_D(DUK_DPRINT("  lj.value1: %!T", (duk_tval *) &heap->lj.value1));
+	DUK_D(DUK_DPRINT("  lj.value2: %!T", (duk_tval *) &heap->lj.value2));
+	DUK_D(DUK_DPRINT("  lj.iserror: %ld", (long) heap->lj.iserror));
+
+	DUK_D(DUK_DPRINT("  handling_error: %ld", (long) heap->handling_error));
+
+	DUK_D(DUK_DPRINT("  heap_thread: %!@O", (duk_heaphdr *) heap->heap_thread));
+	DUK_D(DUK_DPRINT("  curr_thread: %!@O", (duk_heaphdr *) heap->curr_thread));
+	DUK_D(DUK_DPRINT("  heap_object: %!@O", (duk_heaphdr *) heap->heap_object));
+
+	DUK_D(DUK_DPRINT("  call_recursion_depth: %ld", (long) heap->call_recursion_depth));
+	DUK_D(DUK_DPRINT("  call_recursion_limit: %ld", (long) heap->call_recursion_limit));
+
+	DUK_D(DUK_DPRINT("  hash_seed: 0x%08lx", (unsigned long) heap->hash_seed));
+	DUK_D(DUK_DPRINT("  rnd_state: 0x%08lx", (unsigned long) heap->rnd_state));
+
+	duk__dump_strcache(heap);
+
+	duk__dump_heaphdr_list(heap, heap->heap_allocated, "heap allocated");
+
+#ifdef DUK_USE_REFERENCE_COUNTING
+	duk__dump_heaphdr_list(heap, heap->refzero_list, "refcounting refzero list");
+#endif
+
+#ifdef DUK_USE_MARK_AND_SWEEP
+	duk__dump_heaphdr_list(heap, heap->finalize_list, "mark-and-sweep finalize list");
+#endif
+
+	duk__dump_stringtable(heap);
+
+	/* heap->strs: not worth dumping */
+}
+
+#endif  /* DUK_USE_DEBUG */
+#line 1 "duk_debug_hobject.c"
+/*
+ *  Debug dumping of duk_hobject.
+ */
+
+/* include removed: duk_internal.h */
+
+#ifdef DUK_USE_DEBUG
+
+/* must match duk_hobject.h */
+static const char *duk__class_names[32] = {
+	"unused",
+	"Arguments",
+	"Array",
+	"Boolean",
+	"Date",
+	"Error",
+	"Function",
+	"JSON",
+	"Math",
+	"Number",
+	"Object",
+	"RegExp",
+	"String",
+	"global",
+	"ObjEnv",
+	"DecEnv",
+	"Buffer",
+	"Pointer",
+	"unused",
+	"unused",
+	"unused",
+	"unused",
+	"unused",
+	"unused",
+	"unused",
+	"unused",
+	"unused",
+	"unused",
+	"unused",
+	"unused",
+	"unused",
+	"unused",
+};
+
+/* for thread dumping */
+static char duk__get_act_summary_char(duk_activation *act) {
+	if (act->func) {
+		if (DUK_HOBJECT_IS_COMPILEDFUNCTION(act->func)) {
+			return 'c';
+		} else if (DUK_HOBJECT_IS_NATIVEFUNCTION(act->func)) {
+			return 'n';
+		} else {
+			/* should not happen */
+			return '?';
+		}
+	} else {
+		/* should not happen */
+		return '?';
+	}
+}
+
+/* for thread dumping */
+static char duk__get_tval_summary_char(duk_tval *tv) {
+	switch (DUK_TVAL_GET_TAG(tv)) {
+	case DUK_TAG_UNDEFINED:
+		if (DUK_TVAL_IS_UNDEFINED_UNUSED(tv)) {
+			return '.';
+		}
+		return 'u';
+	case DUK_TAG_NULL:
+		return 'n';
+	case DUK_TAG_BOOLEAN:
+		return 'b';
+	case DUK_TAG_STRING:
+		return 's';
+	case DUK_TAG_OBJECT: {
+		duk_hobject *h = DUK_TVAL_GET_OBJECT(tv);
+
+		if (DUK_HOBJECT_IS_ARRAY(h)) {
+			return 'A';
+		} else if (DUK_HOBJECT_IS_COMPILEDFUNCTION(h)) {
+			return 'C';
+		} else if (DUK_HOBJECT_IS_NATIVEFUNCTION(h)) {
+			return 'N';
+		} else if (DUK_HOBJECT_IS_THREAD(h)) {
+			return 'T';
+		}
+		return 'O';
+	}
+	case DUK_TAG_BUFFER: {
+		return 'B';
+	}
+	case DUK_TAG_POINTER: {
+		return 'P';
+	}
+	default:
+		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
+		return 'd';
+	}
+
+	DUK_UNREACHABLE();
+}
+
+/* for thread dumping */
+static char duk__get_cat_summary_char(duk_catcher *catcher) {
+	switch (DUK_CAT_GET_TYPE(catcher)) {
+	case DUK_CAT_TYPE_TCF:
+		if (DUK_CAT_HAS_CATCH_ENABLED(catcher)) {
+			if (DUK_CAT_HAS_FINALLY_ENABLED(catcher)) {
+				return 'C';  /* catch and finally active */
+			} else {
+				return 'c';  /* only catch active */
+			}
+		} else {
+			if (DUK_CAT_HAS_FINALLY_ENABLED(catcher)) {
+				return 'f';  /* only finally active */
+			} else {
+				return 'w';  /* neither active (usually 'with') */
+			}
+		}
+	case DUK_CAT_TYPE_LABEL:
+		return 'l';
+	case DUK_CAT_TYPE_UNKNOWN:
+	default:
+		return '?';
+	}
+
+	DUK_UNREACHABLE();
+}
+
+void duk_debug_dump_hobject(duk_hobject *obj) {
+	duk_uint_fast32_t i;
+	const char *str_empty = "";
+	const char *str_excl = "!";
+
+	DUK_UNREF(str_empty);
+	DUK_UNREF(str_excl);
+
+	DUK_D(DUK_DPRINT("=== hobject %p ===", (void *) obj));
+	if (!obj) {
+		return;
+	}
+
+	DUK_D(DUK_DPRINT("  %sextensible", (const char *) (DUK_HOBJECT_HAS_EXTENSIBLE(obj) ? str_empty : str_excl)));
+	DUK_D(DUK_DPRINT("  %sconstructable", (const char *) (DUK_HOBJECT_HAS_CONSTRUCTABLE(obj) ? str_empty : str_excl)));
+	DUK_D(DUK_DPRINT("  %sbound", (const char *) (DUK_HOBJECT_HAS_BOUND(obj) ? str_empty : str_excl)));
+	DUK_D(DUK_DPRINT("  %scompiledfunction", (const char *) (DUK_HOBJECT_HAS_COMPILEDFUNCTION(obj) ? str_empty : str_excl)));
+	DUK_D(DUK_DPRINT("  %snativefunction", (const char *) (DUK_HOBJECT_HAS_NATIVEFUNCTION(obj) ? str_empty : str_excl)));
+	DUK_D(DUK_DPRINT("  %sthread", (const char *) (DUK_HOBJECT_HAS_THREAD(obj) ? str_empty : str_excl)));
+	DUK_D(DUK_DPRINT("  %sarray_part", (const char *) (DUK_HOBJECT_HAS_ARRAY_PART(obj) ? str_empty : str_excl)));
+	DUK_D(DUK_DPRINT("  %sstrict", (const char *) (DUK_HOBJECT_HAS_STRICT(obj) ? str_empty : str_excl)));
+	DUK_D(DUK_DPRINT("  %snewenv", (const char *) (DUK_HOBJECT_HAS_NEWENV(obj) ? str_empty : str_excl)));
+	DUK_D(DUK_DPRINT("  %snamebinding", (const char *) (DUK_HOBJECT_HAS_NAMEBINDING(obj) ? str_empty : str_excl)));
+	DUK_D(DUK_DPRINT("  %screateargs", (const char *) (DUK_HOBJECT_HAS_CREATEARGS(obj) ? str_empty : str_excl)));
+	DUK_D(DUK_DPRINT("  %senvrecclosed", (const char *) (DUK_HOBJECT_HAS_ENVRECCLOSED(obj) ? str_empty : str_excl)));
+	DUK_D(DUK_DPRINT("  %sexotic_array", (const char *) (DUK_HOBJECT_HAS_EXOTIC_ARRAY(obj) ? str_empty : str_excl)));
+	DUK_D(DUK_DPRINT("  %sexotic_stringobj", (const char *) (DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(obj) ? str_empty : str_excl)));
+	DUK_D(DUK_DPRINT("  %sexotic_arguments", (const char *) (DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj) ? str_empty : str_excl)));
+	DUK_D(DUK_DPRINT("  %sexotic_dukfunc", (const char *) (DUK_HOBJECT_HAS_EXOTIC_DUKFUNC(obj) ? str_empty : str_excl)));
+	DUK_D(DUK_DPRINT("  %sexotic_bufferobj", (const char *) (DUK_HOBJECT_HAS_EXOTIC_BUFFEROBJ(obj) ? str_empty : str_excl)));
+	DUK_D(DUK_DPRINT("  %sexotic_proxyobj", (const char *) (DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(obj) ? str_empty : str_excl)));
+
+	DUK_D(DUK_DPRINT("  class: number %ld -> %s",
+	                 (long) DUK_HOBJECT_GET_CLASS_NUMBER(obj),
+	                 (const char *) (duk__class_names[(DUK_HOBJECT_GET_CLASS_NUMBER(obj)) & ((1 << DUK_HOBJECT_FLAG_CLASS_BITS) - 1)])));
+
+	DUK_D(DUK_DPRINT("  prototype: %p -> %!O",
+	                 (void *) obj->prototype,
+	                 (duk_heaphdr *) obj->prototype));
+
+	DUK_D(DUK_DPRINT("  props: p=%p, e_size=%ld, e_used=%ld, a_size=%ld, h_size=%ld",
+	                 (void *) obj->p,
+	                 (long) obj->e_size,
+	                 (long) obj->e_used,
+	                 (long) obj->a_size,
+	                 (long) obj->h_size));
+
+	/*
+	 *  Object (struct layout) specific dumping.  Inline code here
+	 *  instead of helpers, to ensure debug line prefix is identical.
+	 */
+
+	if (DUK_HOBJECT_IS_COMPILEDFUNCTION(obj)) {
+		duk_hcompiledfunction *h = (duk_hcompiledfunction *) obj;
+
+		DUK_D(DUK_DPRINT("  hcompiledfunction"));
+		DUK_D(DUK_DPRINT("  data: %!O", (duk_heaphdr *) h->data));
+		DUK_D(DUK_DPRINT("  nregs: %ld", (long) h->nregs));
+		DUK_D(DUK_DPRINT("  nargs: %ld", (long) h->nargs));
+
+		if (h->data && DUK_HBUFFER_HAS_DYNAMIC(h->data) && DUK_HBUFFER_GET_DATA_PTR(h->data)) {
+			DUK_D(DUK_DPRINT("  consts: %p (%ld, %ld bytes)",
+			                 (void *) DUK_HCOMPILEDFUNCTION_GET_CONSTS_BASE(h),
+			                 (long) DUK_HCOMPILEDFUNCTION_GET_CONSTS_COUNT(h),
+			                 (long) DUK_HCOMPILEDFUNCTION_GET_CONSTS_SIZE(h)));
+			DUK_D(DUK_DPRINT("  funcs: %p (%ld, %ld bytes)",
+			                 (void *) DUK_HCOMPILEDFUNCTION_GET_FUNCS_BASE(h),
+			                 (long) DUK_HCOMPILEDFUNCTION_GET_FUNCS_COUNT(h),
+			                 (long) DUK_HCOMPILEDFUNCTION_GET_FUNCS_SIZE(h)));
+			DUK_D(DUK_DPRINT("  bytecode: %p (%ld, %ld bytes)",
+			                 (void *) DUK_HCOMPILEDFUNCTION_GET_CODE_BASE(h),
+			                 (long) DUK_HCOMPILEDFUNCTION_GET_CODE_COUNT(h),
+			                 (long) DUK_HCOMPILEDFUNCTION_GET_CODE_SIZE(h)));
+		} else {
+			DUK_D(DUK_DPRINT("  consts: ???"));
+			DUK_D(DUK_DPRINT("  funcs: ???"));
+			DUK_D(DUK_DPRINT("  bytecode: ???"));
+		}
+	} else if (DUK_HOBJECT_IS_NATIVEFUNCTION(obj)) {
+		duk_hnativefunction *h = (duk_hnativefunction *) obj;
+		DUK_UNREF(h);
+
+		DUK_D(DUK_DPRINT("  hnativefunction"));
+		/* XXX: h->func, cannot print function pointers portably */
+		DUK_D(DUK_DPRINT("  nargs: %ld", (long) h->nargs));
+	} else if (DUK_HOBJECT_IS_THREAD(obj)) {
+		duk_hthread *thr = (duk_hthread *) obj;
+		duk_tval *p;
+
+		DUK_D(DUK_DPRINT("  hthread"));
+		DUK_D(DUK_DPRINT("  strict: %ld", (long) thr->strict));
+		DUK_D(DUK_DPRINT("  state: %ld", (long) thr->state));
+
+		DUK_D(DUK_DPRINT("  valstack_max: %ld, callstack_max: %ld, catchstack_max: %ld",
+		                 (long) thr->valstack_max, (long) thr->callstack_max, (long) thr->catchstack_max));
+
+		DUK_D(DUK_DPRINT("  callstack: ptr %p, size %ld, top %ld, preventcount %ld, used size %ld entries (%ld bytes), alloc size %ld entries (%ld bytes)",
+		                 (void *) thr->callstack,
+		                 (long) thr->callstack_size,
+		                 (long) thr->callstack_top,
+		                 (long) thr->callstack_preventcount,
+		                 (long) thr->callstack_top,
+		                 (long) (thr->callstack_top * sizeof(duk_activation)),
+		                 (long) thr->callstack_size,
+		                 (long) (thr->callstack_size * sizeof(duk_activation))));
+
+		DUK_DEBUG_SUMMARY_INIT();
+		DUK_DEBUG_SUMMARY_CHAR('[');
+		for (i = 0; i <= (duk_uint_fast32_t) thr->callstack_size; i++) {
+			if (i == thr->callstack_top) {
+				DUK_DEBUG_SUMMARY_CHAR('|');
+			}
+			if (!thr->callstack) {
+				DUK_DEBUG_SUMMARY_CHAR('@');
+			} else if (i < thr->callstack_size) {
+				if (i < thr->callstack_top) {
+					/* tailcalling is nice to see immediately; other flags (e.g. strict)
+					 * not that important.
+					 */
+					if (thr->callstack[i].flags & DUK_ACT_FLAG_TAILCALLED) {
+						DUK_DEBUG_SUMMARY_CHAR('/');
+					}
+					DUK_DEBUG_SUMMARY_CHAR(duk__get_act_summary_char(thr->callstack + i));
+				} else {
+					DUK_DEBUG_SUMMARY_CHAR('.');
+				}
+			}
+		}
+		DUK_DEBUG_SUMMARY_CHAR(']');
+		DUK_DEBUG_SUMMARY_FINISH();
+
+		DUK_D(DUK_DPRINT("  valstack: ptr %p, end %p (%ld), bottom %p (%ld), top %p (%ld), used size %ld entries (%ld bytes), alloc size %ld entries (%ld bytes)",
+		                 (void *) thr->valstack,
+		                 (void *) thr->valstack_end,
+		                 (long) (thr->valstack_end - thr->valstack),
+		                 (void *) thr->valstack_bottom,
+		                 (long) (thr->valstack_bottom - thr->valstack),
+		                 (void *) thr->valstack_top,
+		                 (long) (thr->valstack_top - thr->valstack),
+		                 (long) (thr->valstack_top - thr->valstack),
+		                 (long) (thr->valstack_top - thr->valstack) * sizeof(duk_tval),
+		                 (long) (thr->valstack_end - thr->valstack),
+		                 (long) (thr->valstack_end - thr->valstack) * sizeof(duk_tval)));
+
+		DUK_DEBUG_SUMMARY_INIT();
+		DUK_DEBUG_SUMMARY_CHAR('[');
+		p = thr->valstack;
+		while (p <= thr->valstack_end) {
+			i = (duk_uint_fast32_t) (p - thr->valstack);
+			if (thr->callstack &&
+			    thr->callstack_top > 0 &&
+			    i == (duk_size_t) (thr->callstack + thr->callstack_top - 1)->idx_bottom) {
+				DUK_DEBUG_SUMMARY_CHAR('>');
+			}
+			if (p == thr->valstack_top) {
+				DUK_DEBUG_SUMMARY_CHAR('|');
+			}
+			if (p < thr->valstack_end) {
+				if (p < thr->valstack_top) {
+					DUK_DEBUG_SUMMARY_CHAR(duk__get_tval_summary_char(p));
+				} else {
+					/* XXX: safe printer for these?  would be nice, because
+					 * we could visualize whether the values are in proper
+					 * state.
+					 */
+					DUK_DEBUG_SUMMARY_CHAR('.');
+				}
+			}
+			p++;
+		}
+		DUK_DEBUG_SUMMARY_CHAR(']');
+		DUK_DEBUG_SUMMARY_FINISH();
+
+		DUK_D(DUK_DPRINT("  catchstack: ptr %p, size %ld, top %ld, used size %ld entries (%ld bytes), alloc size %ld entries (%ld bytes)",
+		                 (void *) thr->catchstack,
+		                 (long) thr->catchstack_size,
+		                 (long) thr->catchstack_top,
+		                 (long) thr->catchstack_top,
+		                 (long) (thr->catchstack_top * sizeof(duk_catcher)),
+		                 (long) thr->catchstack_size,
+		                 (long) (thr->catchstack_size * sizeof(duk_catcher))));
+
+		DUK_DEBUG_SUMMARY_INIT();
+		DUK_DEBUG_SUMMARY_CHAR('[');
+		for (i = 0; i <= (duk_uint_fast32_t) thr->catchstack_size; i++) {
+			if (i == thr->catchstack_top) {
+				DUK_DEBUG_SUMMARY_CHAR('|');
+			}
+			if (!thr->catchstack) {
+				DUK_DEBUG_SUMMARY_CHAR('@');
+			} else if (i < thr->catchstack_size) {
+				if (i < thr->catchstack_top) {
+					DUK_DEBUG_SUMMARY_CHAR(duk__get_cat_summary_char(thr->catchstack + i));
+				} else {
+					DUK_DEBUG_SUMMARY_CHAR('.');
+				}
+			}
+		}
+		DUK_DEBUG_SUMMARY_CHAR(']');
+		DUK_DEBUG_SUMMARY_FINISH();
+
+		DUK_D(DUK_DPRINT("  resumer: ptr %p", (void *) thr->resumer));
+
+#if 0  /* worth dumping? */
+		for (i = 0; i < DUK_NUM_BUILTINS; i++) {
+			DUK_D(DUK_DPRINT("  builtins[%ld] -> %!@O", (long) i, (duk_heaphdr *) thr->builtins[i]));
+		}
+#endif
+	}
+
+	if (obj->p) {
+		DUK_D(DUK_DPRINT("  props alloc size: %ld",
+		                 (long) DUK_HOBJECT_P_COMPUTE_SIZE(obj->e_size, obj->a_size, obj->h_size)));
+	} else {
+		DUK_D(DUK_DPRINT("  props alloc size: n/a"));
+	}
+
+	DUK_D(DUK_DPRINT("  prop entries:"));
+	for (i = 0; i < (duk_uint_fast32_t) obj->e_size; i++) {
+		duk_hstring *k;
+		duk_propvalue *v;
+
+		k = DUK_HOBJECT_E_GET_KEY(obj, i);
+		v = DUK_HOBJECT_E_GET_VALUE_PTR(obj, i);
+		DUK_UNREF(v);
+
+		if (i >= obj->e_used) {
+			DUK_D(DUK_DPRINT("    [%ld]: UNUSED", (long) i));
+			continue;
+		}
+
+		if (!k) {
+			DUK_D(DUK_DPRINT("    [%ld]: NULL", (long) i));
+			continue;
+		}
+
+		if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, i)) {
+			DUK_D(DUK_DPRINT("    [%ld]: [w=%ld e=%ld c=%ld a=%ld] %!O -> get:%p set:%p; get %!O; set %!O",
+			                 (long) i,
+			                 (long) (DUK_HOBJECT_E_SLOT_IS_WRITABLE(obj, i) ? 1 : 0),
+			                 (long) (DUK_HOBJECT_E_SLOT_IS_ENUMERABLE(obj, i) ? 1 : 0),
+			                 (long) (DUK_HOBJECT_E_SLOT_IS_CONFIGURABLE(obj, i) ? 1 : 0),
+			                 (long) (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, i) ? 1 : 0),
+			                 (duk_heaphdr *) k,
+			                 (void *) v->a.get,
+			                 (void *) v->a.set,
+			                 (duk_heaphdr *) v->a.get,
+			                 (duk_heaphdr *) v->a.set));
+		} else {
+			DUK_D(DUK_DPRINT("    [%ld]: [w=%ld e=%ld c=%ld a=%ld] %!O -> %!T",
+			                 (long) i,
+			                 (long) (DUK_HOBJECT_E_SLOT_IS_WRITABLE(obj, i) ? 1 : 0),
+			                 (long) (DUK_HOBJECT_E_SLOT_IS_ENUMERABLE(obj, i) ? 1 : 0),
+			                 (long) (DUK_HOBJECT_E_SLOT_IS_CONFIGURABLE(obj, i) ? 1 : 0),
+			                 (long) (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, i) ? 1 : 0),
+			                 (duk_heaphdr *) k,
+			                 (duk_tval *) &v->v));
+		}
+	}
+
+	DUK_D(DUK_DPRINT("  array entries:"));
+	for (i = 0; i < (duk_uint_fast32_t) obj->a_size; i++) {
+		DUK_D(DUK_DPRINT("    [%ld]: [w=%ld e=%ld c=%ld a=%ld] %ld -> %!T",
+		                 (long) i,
+		                 (long) 1,  /* implicit attributes */
+		                 (long) 1,
+		                 (long) 1,
+		                 (long) 0,
+		                 (long) i,
+		                 (duk_tval *) DUK_HOBJECT_A_GET_VALUE_PTR(obj, i)));
+	}
+
+	DUK_D(DUK_DPRINT("  hash entries:"));
+	for (i = 0; i < (duk_uint_fast32_t) obj->h_size; i++) {
+		duk_uint32_t t = DUK_HOBJECT_H_GET_INDEX(obj, i);
+		if (t == DUK_HOBJECT_HASHIDX_UNUSED) {
+			DUK_D(DUK_DPRINT("    [%ld]: unused", (long) i));
+		} else if (t == DUK_HOBJECT_HASHIDX_DELETED) {
+			DUK_D(DUK_DPRINT("    [%ld]: deleted", (long) i));
+		} else {
+			DUK_D(DUK_DPRINT("    [%ld]: %ld", (long) i, (long) t));
+		}
+	}
+}
+
+void duk_debug_dump_callstack(duk_hthread *thr) {
+	duk_uint_fast32_t i;
+
+	DUK_D(DUK_DPRINT("=== hthread %p callstack: %ld entries ===",
+	                 (void *) thr,
+	                 (thr == NULL ? (long) 0 : (long) thr->callstack_top)));
+	if (!thr) {
+		return;
+	}
+
+	for (i = 0; i < (duk_uint_fast32_t) thr->callstack_top; i++) {
+		duk_activation *act = thr->callstack + i;
+		duk_tval *this_binding = NULL;
+
+		this_binding = thr->valstack + act->idx_bottom - 1;
+		if (this_binding < thr->valstack || this_binding >= thr->valstack_top) {
+			this_binding = NULL;
+		}
+
+		DUK_D(DUK_DPRINT("  [%ld] -> flags=0x%08lx, func=%!O, var_env=%!iO, lex_env=%!iO, "
+		                 "pc=%ld, idx_bottom=%ld, idx_retval=%ld, this_binding=%!T",
+		                 (long) i,
+		                 (unsigned long) act->flags,
+		                 (duk_heaphdr *) act->func,
+		                 (duk_heaphdr *) act->var_env,
+		                 (duk_heaphdr *) act->lex_env,
+		                 (long) act->pc,
+		                 (long) act->idx_bottom,
+		                 (long) act->idx_retval,
+		                 (duk_tval *) this_binding));
+	}
+}
+
+void duk_debug_dump_activation(duk_hthread *thr, duk_activation *act) {
+	if (!act) {
+		DUK_D(DUK_DPRINT("duk_activation: NULL"));
+	} else {
+		duk_tval *this_binding = NULL;
+
+		this_binding = thr->valstack + act->idx_bottom - 1;
+		if (this_binding < thr->valstack || this_binding >= thr->valstack_top) {
+			this_binding = NULL;
+		}
+
+		DUK_D(DUK_DPRINT("duk_activation: %p -> flags=0x%08lx, func=%!O, var_env=%!O, "
+		                 "lex_env=%!O, pc=%ld, idx_bottom=%ld, idx_retval=%ld, this_binding=%!T",
+		                 (void *) act,
+		                 (unsigned long) act->flags,
+		                 (duk_heaphdr *) act->func,
+		                 (duk_heaphdr *) act->var_env,
+		                 (duk_heaphdr *) act->lex_env,
+		                 (long) act->pc,
+		                 (long) act->idx_bottom,
+		                 (long) act->idx_retval,
+		                 (duk_tval *) this_binding));
+	}
+}
+
+#endif  /* DUK_USE_DEBUG */
+#line 1 "duk_debug_macros.c"
+/*
+ *  Debugging macro calls.
+ */
+
+/* include removed: duk_internal.h */
+
+#ifdef DUK_USE_DEBUG
+
+/*
+ *  Debugging enabled
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+/* for one-char summaries (usable for e.g. valstack) */
+char duk_debug_summary_buf[DUK_DEBUG_SUMMARY_BUF_SIZE];
+duk_int_t duk_debug_summary_idx;
+
+#define DUK__DEBUG_BUFSIZE  DUK_USE_DEBUG_BUFSIZE
+static char duk__debug_buf[DUK__DEBUG_BUFSIZE];
+
+static const char *duk__get_level_string(duk_small_int_t level) {
+	switch ((int) level) {
+	case DUK_LEVEL_DEBUG:
+		return "D";
+	case DUK_LEVEL_DDEBUG:
+		return "DD";
+	case DUK_LEVEL_DDDEBUG:
+		return "DDD";
+	}
+	return "???";
+}
+
+#ifdef DUK_USE_DPRINT_COLORS
+
+/* http://en.wikipedia.org/wiki/ANSI_escape_code */
+#define DUK__TERM_REVERSE  "\x1b[7m"
+#define DUK__TERM_BRIGHT   "\x1b[1m"
+#define DUK__TERM_RESET    "\x1b[0m"
+#define DUK__TERM_BLUE     "\x1b[34m"
+#define DUK__TERM_RED      "\x1b[31m"
+
+static const char *duk__get_term_1(duk_small_int_t level) {
+	DUK_UNREF(level);
+	return (const char *) DUK__TERM_RED;
+}
+
+static const char *duk__get_term_2(duk_small_int_t level) {
+	switch ((int) level) {
+	case DUK_LEVEL_DEBUG:
+		return (const char *) (DUK__TERM_RESET DUK__TERM_BRIGHT);
+	case DUK_LEVEL_DDEBUG:
+		return (const char *) (DUK__TERM_RESET);
+	case DUK_LEVEL_DDDEBUG:
+		return (const char *) (DUK__TERM_RESET DUK__TERM_BLUE);
+	}
+	return (const char *) DUK__TERM_RESET;
+}
+
+static const char *duk__get_term_3(duk_small_int_t level) {
+	DUK_UNREF(level);
+	return (const char *) DUK__TERM_RESET;
+}
+
+#else
+
+static const char *duk__get_term_1(duk_small_int_t level) {
+	DUK_UNREF(level);
+	return (const char *) "";
+}
+
+static const char *duk__get_term_2(duk_small_int_t level) {
+	DUK_UNREF(level);
+	return (const char *) "";
+}
+
+static const char *duk__get_term_3(duk_small_int_t level) {
+	DUK_UNREF(level);
+	return (const char *) "";
+}
+
+#endif  /* DUK_USE_DPRINT_COLORS */
+
+#ifdef DUK_USE_VARIADIC_MACROS
+
+void duk_debug_log(duk_small_int_t level, const char *file, duk_int_t line, const char *func, char *fmt, ...) {
+	va_list ap;
+
+	va_start(ap, fmt);
+
+	DUK_MEMZERO((void *) duk__debug_buf, (size_t) DUK__DEBUG_BUFSIZE);
+	duk_debug_vsnprintf(duk__debug_buf, DUK__DEBUG_BUFSIZE - 1, fmt, ap);
+
+#ifdef DUK_USE_DPRINT_RDTSC
+	DUK_FPRINTF(DUK_STDERR, "%s[%s] <%llu> %s:%ld (%s):%s %s%s\n",
+	            (const char *) duk__get_term_1(level),
+	            (const char *) duk__get_level_string(level),
+	            (unsigned long long) duk_rdtsc(),  /* match the inline asm in duk_features.h */
+	            (const char *) file,
+	            (long) line,
+	            (const char *) func,
+	            (const char *) duk__get_term_2(level),
+	            (const char *) duk__debug_buf,
+	            (const char *) duk__get_term_3(level));
+#else
+	DUK_FPRINTF(DUK_STDERR, "%s[%s] %s:%ld (%s):%s %s%s\n",
+	            (const char *) duk__get_term_1(level),
+	            (const char *) duk__get_level_string(level),
+	            (const char *) file,
+	            (long) line,
+	            (const char *) func,
+	            (const char *) duk__get_term_2(level),
+	            (const char *) duk__debug_buf,
+	            (const char *) duk__get_term_3(level));
+#endif
+	DUK_FFLUSH(DUK_STDERR);
+
+	va_end(ap);
+}
+
+#else  /* DUK_USE_VARIADIC_MACROS */
+
+char duk_debug_file_stash[DUK_DEBUG_STASH_SIZE];
+char duk_debug_line_stash[DUK_DEBUG_STASH_SIZE];
+char duk_debug_func_stash[DUK_DEBUG_STASH_SIZE];
+duk_small_int_t duk_debug_level_stash;
+
+void duk_debug_log(char *fmt, ...) {
+	va_list ap;
+	duk_small_int_t level = duk_debug_level_stash;
+
+	va_start(ap, fmt);
+
+	DUK_MEMZERO((void *) duk__debug_buf, (size_t) DUK__DEBUG_BUFSIZE);
+	duk_debug_vsnprintf(duk__debug_buf, DUK__DEBUG_BUFSIZE - 1, fmt, ap);
+
+#ifdef DUK_USE_DPRINT_RDTSC
+	DUK_FPRINTF(DUK_STDERR, "%s[%s] <%llu> %s:%s (%s):%s %s%s\n",
+	            (const char *) duk__get_term_1(level),
+	            (const char *) duk__get_level_string(duk_debug_level_stash),
+	            (unsigned long long) duk_rdtsc(),  /* match duk_features.h */
+	            (const char *) duk_debug_file_stash,
+	            (const char *) duk_debug_line_stash,
+	            (const char *) duk_debug_func_stash,
+	            (const char *) duk__get_term_2(level),
+	            (const char *) duk__debug_buf,
+	            (const char *) duk__get_term_3(level));
+#else
+	DUK_FPRINTF(DUK_STDERR, "%s[%s] %s:%s (%s):%s %s%s\n",
+	            (const char *) duk__get_term_1(level),
+	            (const char *) duk__get_level_string(duk_debug_level_stash),
+	            (const char *) duk_debug_file_stash,
+	            (const char *) duk_debug_line_stash,
+	            (const char *) duk_debug_func_stash,
+	            (const char *) duk__get_term_2(level),
+	            (const char *) duk__debug_buf,
+	            (const char *) duk__get_term_3(level));
+#endif
+	DUK_FFLUSH(DUK_STDERR);
+
+	va_end(ap);
+}
+
+#endif  /* DUK_USE_VARIADIC_MACROS */
+
+#else  /* DUK_USE_DEBUG */
+
+/*
+ *  Debugging disabled
+ */
+
+#endif  /* DUK_USE_DEBUG */
+#line 1 "duk_debug_vsnprintf.c"
+/*
+ *  Custom formatter for debug printing, allowing Duktape specific data
+ *  structures (such as tagged values and heap objects) to be printed with
+ *  a nice format string.  Because debug printing should not affect execution
+ *  state, formatting here must be independent of execution (see implications
+ *  below) and must not allocate memory.
+ *
+ *  Custom format tags begin with a '%!' to safely distinguish them from
+ *  standard format tags.  The following conversions are supported:
+ *
+ *     %!T    tagged value (duk_tval *)
+ *     %!O    heap object (duk_heaphdr *)
+ *     %!I    decoded bytecode instruction
+ *     %!C    bytecode instruction opcode name (arg is long)
+ *
+ *  Everything is serialized in a JSON-like manner.  The default depth is one
+ *  level, internal prototype is not followed, and internal properties are not
+ *  serialized.  The following modifiers change this behavior:
+ *
+ *     @      print pointers
+ *     #      print binary representations (where applicable)
+ *     d      deep traversal of own properties (not prototype)
+ *     p      follow prototype chain (useless without 'd')
+ *     i      include internal properties (other than prototype)
+ *     x      hexdump buffers
+ *     h      heavy formatting
+ *
+ *  For instance, the following serializes objects recursively, but does not
+ *  follow the prototype chain nor print internal properties: "%!dO".
+ *
+ *  Notes:
+ *
+ *    * Standard snprintf return value semantics seem to vary.  This
+ *      implementation returns the number of bytes it actually wrote
+ *      (excluding the null terminator).  If retval == buffer size,
+ *      output was truncated (except for corner cases).
+ *
+ *    * Output format is intentionally different from Ecmascript
+ *      formatting requirements, as formatting here serves debugging
+ *      of internals.
+ *
+ *    * Depth checking (and updating) is done in each type printer
+ *      separately, to allow them to call each other freely.
+ *
+ *    * Some pathological structures might take ages to print (e.g.
+ *      self recursion with 100 properties pointing to the object
+ *      itself).  To guard against these, each printer also checks
+ *      whether the output buffer is full; if so, early exit.
+ *
+ *    * Reference loops are detected using a loop stack.
+ */
+
+/* include removed: duk_internal.h */
+
+#ifdef DUK_USE_DEBUG
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+/* list of conversion specifiers that terminate a format tag;
+ * this is unfortunately guesswork.
+ */
+#define DUK__ALLOWED_STANDARD_SPECIFIERS  "diouxXeEfFgGaAcsCSpnm"
+
+/* maximum length of standard format tag that we support */
+#define DUK__MAX_FORMAT_TAG_LENGTH  32
+
+/* heapobj recursion depth when deep printing is selected */
+#define DUK__DEEP_DEPTH_LIMIT  8
+
+/* maximum recursion depth for loop detection stacks */
+#define DUK__LOOP_STACK_DEPTH  256
+
+/* must match bytecode defines now; build autogenerate? */
+static const char *duk__bc_optab[] = {
+	"LDREG",    "STREG",    "LDCONST",  "LDINT",    "LDINTX",   "MPUTOBJ",  "MPUTOBJI", "MPUTARR",  "MPUTARRI", "NEW",
+	"NEWI",     "REGEXP", 	"CSREG",    "CSREGI",   "GETVAR",   "PUTVAR",   "DECLVAR",  "DELVAR",   "CSVAR",    "CSVARI",
+	"CLOSURE",  "GETPROP", 	"PUTPROP",  "DELPROP",  "CSPROP",   "CSPROPI",  "ADD",      "SUB",      "MUL",      "DIV",
+	"MOD",      "BAND",     "BOR",      "BXOR",     "BASL",     "BLSR", 	"BASR",     "BNOT", 	"LNOT",     "EQ",
+	"NEQ",      "SEQ",      "SNEQ",     "GT",       "GE",       "LT",       "LE",       "IF", 	"INSTOF",   "IN",
+	"JUMP",     "RETURN",   "CALL",     "CALLI",    "LABEL",    "ENDLABEL", "BREAK",    "CONTINUE", "TRYCATCH", "UNUSED59",
+	"UNUSED60", "EXTRA",    "DEBUG",    "INVALID",
+};
+
+static const char *duk__bc_extraoptab[] = {
+	"NOP", "LDTHIS", "LDUNDEF", "LDNULL", "LDTRUE", "LDFALSE", "NEWOBJ", "NEWARR", "SETALEN", "TYPEOF",
+	"TYPEOFID", "TONUM", "INITENUM", "NEXTENUM", "INITSET", "INITSETI", "INITGET", "INITGETI", "ENDTRY", "ENDCATCH",
+	"ENDFIN", "THROW", "INVLHS", "UNM", "UNP", "INC", "DEC", "XXX", "XXX", "XXX",
+	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+
+	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+
+	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+
+	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+
+	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+
+	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+};
+
+typedef struct duk__dprint_state duk__dprint_state;
+struct duk__dprint_state {
+	duk_fixedbuffer *fb;
+
+	/* loop_stack_index could be perhaps be replaced by 'depth', but it's nice
+	 * to not couple these two mechanisms unnecessarily.
+	 */
+	duk_hobject *loop_stack[DUK__LOOP_STACK_DEPTH];
+	duk_int_t loop_stack_index;
+	duk_int_t loop_stack_limit;
+
+	duk_int_t depth;
+	duk_int_t depth_limit;
+
+	duk_bool_t pointer;
+	duk_bool_t heavy;
+	duk_bool_t binary;
+	duk_bool_t follow_proto;
+	duk_bool_t internal;
+	duk_bool_t hexdump;
+};
+
+/* helpers */
+static void duk__print_hstring(duk__dprint_state *st, duk_hstring *k, duk_bool_t quotes);
+static void duk__print_hobject(duk__dprint_state *st, duk_hobject *h);
+static void duk__print_hbuffer(duk__dprint_state *st, duk_hbuffer *h);
+static void duk__print_tval(duk__dprint_state *st, duk_tval *tv);
+static void duk__print_instr(duk__dprint_state *st, duk_instr_t ins);
+static void duk__print_heaphdr(duk__dprint_state *st, duk_heaphdr *h);
+static void duk__print_shared_heaphdr(duk__dprint_state *st, duk_heaphdr *h);
+static void duk__print_shared_heaphdr_string(duk__dprint_state *st, duk_heaphdr_string *h);
+
+static void duk__print_shared_heaphdr(duk__dprint_state *st, duk_heaphdr *h) {
+	duk_fixedbuffer *fb = st->fb;
+
+	if (st->heavy) {
+		duk_fb_sprintf(fb, "(%p)", (void *) h);
+	}
+
+	if (!h) {
+		return;
+	}
+
+	if (st->binary) {
+		duk_size_t i;
+		duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_LBRACKET);
+		for (i = 0; i < (duk_size_t) sizeof(*h); i++) {
+			duk_fb_sprintf(fb, "%02lx", (unsigned long) ((duk_uint8_t *)h)[i]);
+		}
+		duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_RBRACKET);
+	}
+
+#ifdef DUK_USE_REFERENCE_COUNTING  /* currently implicitly also DUK_USE_DOUBLE_LINKED_HEAP */
+	if (st->heavy) {
+		duk_fb_sprintf(fb, "[h_next=%p,h_prev=%p,h_refcount=%lu,h_flags=%08lx,type=%ld,"
+		               "reachable=%ld,temproot=%ld,finalizable=%ld,finalized=%ld]",
+		               (void *) DUK_HEAPHDR_GET_NEXT(h),
+		               (void *) DUK_HEAPHDR_GET_PREV(h),
+		               (unsigned long) DUK_HEAPHDR_GET_REFCOUNT(h),
+		               (unsigned long) DUK_HEAPHDR_GET_FLAGS(h),
+		               (long) DUK_HEAPHDR_GET_TYPE(h),
+		               (long) (DUK_HEAPHDR_HAS_REACHABLE(h) ? 1 : 0),
+		               (long) (DUK_HEAPHDR_HAS_TEMPROOT(h) ? 1 : 0),
+		               (long) (DUK_HEAPHDR_HAS_FINALIZABLE(h) ? 1 : 0),
+		               (long) (DUK_HEAPHDR_HAS_FINALIZED(h) ? 1 : 0));
+	}
+#else
+	if (st->heavy) {
+		duk_fb_sprintf(fb, "[h_next=%p,h_flags=%08lx,type=%ld,reachable=%ld,temproot=%ld,finalizable=%ld,finalized=%ld]",
+		               (void *) DUK_HEAPHDR_GET_NEXT(h),
+		               (unsigned long) DUK_HEAPHDR_GET_FLAGS(h),
+		               (long) DUK_HEAPHDR_GET_TYPE(h),
+		               (long) (DUK_HEAPHDR_HAS_REACHABLE(h) ? 1 : 0),
+		               (long) (DUK_HEAPHDR_HAS_TEMPROOT(h) ? 1 : 0),
+		               (long) (DUK_HEAPHDR_HAS_FINALIZABLE(h) ? 1 : 0),
+		               (long) (DUK_HEAPHDR_HAS_FINALIZED(h) ? 1 : 0));
+	}
+#endif
+}
+
+static void duk__print_shared_heaphdr_string(duk__dprint_state *st, duk_heaphdr_string *h) {
+	duk_fixedbuffer *fb = st->fb;
+
+	if (st->heavy) {
+		duk_fb_sprintf(fb, "(%p)", (void *) h);
+	}
+
+	if (!h) {
+		return;
+	}
+
+	if (st->binary) {
+		duk_size_t i;
+		duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_LBRACKET);
+		for (i = 0; i < (duk_size_t) sizeof(*h); i++) {
+			duk_fb_sprintf(fb, "%02lx", (unsigned long) ((duk_uint8_t *)h)[i]);
+		}
+		duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_RBRACKET);
+	}
+
+#ifdef DUK_USE_REFERENCE_COUNTING
+	if (st->heavy) {
+		duk_fb_sprintf(fb, "[h_refcount=%lu,h_flags=%08lx,type=%ld,reachable=%ld,temproot=%ld,finalizable=%ld,finalized=%ld]",
+		               (unsigned long) DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h),
+		               (unsigned long) DUK_HEAPHDR_GET_FLAGS((duk_heaphdr *) h),
+		               (long) DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) h),
+		               (long) (DUK_HEAPHDR_HAS_REACHABLE((duk_heaphdr *) h) ? 1 : 0),
+		               (long) (DUK_HEAPHDR_HAS_TEMPROOT((duk_heaphdr *) h) ? 1 : 0),
+		               (long) (DUK_HEAPHDR_HAS_FINALIZABLE((duk_heaphdr *) h) ? 1 : 0),
+		               (long) (DUK_HEAPHDR_HAS_FINALIZED((duk_heaphdr *) h) ? 1 : 0));
+	}
+#else
+	if (st->heavy) {
+		duk_fb_sprintf(fb, "[h_flags=%08lx,type=%ld,reachable=%ld,temproot=%ld,finalizable=%ld,finalized=%ld]",
+		               (unsigned long) DUK_HEAPHDR_GET_FLAGS((duk_heaphdr *) h),
+		               (long) DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) h),
+		               (long) (DUK_HEAPHDR_HAS_REACHABLE((duk_heaphdr *) h) ? 1 : 0),
+		               (long) (DUK_HEAPHDR_HAS_TEMPROOT((duk_heaphdr *) h) ? 1 : 0),
+		               (long) (DUK_HEAPHDR_HAS_FINALIZABLE((duk_heaphdr *) h) ? 1 : 0),
+		               (long) (DUK_HEAPHDR_HAS_FINALIZED((duk_heaphdr *) h) ? 1 : 0));
+	}
+#endif
+}
+
+static void duk__print_hstring(duk__dprint_state *st, duk_hstring *h, duk_bool_t quotes) {
+	duk_fixedbuffer *fb = st->fb;
+	duk_uint8_t *p;
+	duk_uint8_t *p_end;
+
+	/* terminal type: no depth check */
+
+	if (duk_fb_is_full(fb)) {
+		return;
+	}
+
+	duk__print_shared_heaphdr_string(st, &h->hdr);
+
+	if (!h) {
+		duk_fb_put_cstring(fb, "NULL");
+		return;
+	}
+
+	p = DUK_HSTRING_GET_DATA(h);
+	p_end = p + DUK_HSTRING_GET_BYTELEN(h);
+
+	if (p_end > p && p[0] == DUK_ASC_UNDERSCORE) {
+		/* if property key begins with underscore, encode it with
+		 * forced quotes (e.g. "_foo") to distinguish it from encoded
+		 * internal properties (e.g. \xffbar -> _bar).
+		 */
+		quotes = 1;
+	}
+
+	if (quotes) {
+		duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_DOUBLEQUOTE);
+	}
+	while (p < p_end) {
+		duk_uint8_t ch = *p++;
+
+		/* two special escapes: '\' and '"', other printables as is */
+		if (ch == '\\') {
+			duk_fb_sprintf(fb, "\\\\");
+		} else if (ch == '"') {
+			duk_fb_sprintf(fb, "\\\"");
+		} else if (ch >= 0x20 && ch <= 0x7e) {
+			duk_fb_put_byte(fb, ch);
+		} else if (ch == 0xff && !quotes) {
+			/* encode \xffbar as _bar if no quotes are applied, this is for
+			 * readable internal keys.
+			 */
+			duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_UNDERSCORE);
+		} else {
+			duk_fb_sprintf(fb, "\\x%02lx", (unsigned long) ch);
+		}
+	}
+	if (quotes) {
+		duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_DOUBLEQUOTE);
+	}
+#ifdef DUK_USE_REFERENCE_COUNTING
+	/* XXX: limit to quoted strings only, to save keys from being cluttered? */
+	duk_fb_sprintf(fb, "/%lu", (unsigned long) DUK_HEAPHDR_GET_REFCOUNT(&h->hdr));
+#endif
+}
+
+#ifdef DUK__COMMA
+#undef DUK__COMMA
+#endif
+#define DUK__COMMA()  do { \
+		if (first) { \
+			first = 0; \
+		} else { \
+			duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_COMMA); \
+		} \
+	} while (0)
+
+static void duk__print_hobject(duk__dprint_state *st, duk_hobject *h) {
+	duk_fixedbuffer *fb = st->fb;
+	duk_uint_fast32_t i;
+	duk_tval *tv;
+	duk_hstring *key;
+	duk_bool_t first = 1;
+	char *brace1 = "{";
+	char *brace2 = "}";
+	duk_bool_t pushed_loopstack = 0;
+
+	if (duk_fb_is_full(fb)) {
+		return;
+	}
+
+	duk__print_shared_heaphdr(st, &h->hdr);
+
+	if (h && DUK_HOBJECT_HAS_ARRAY_PART(h)) {
+		brace1 = "[";
+		brace2 = "]";
+	}
+
+	if (!h) {
+		duk_fb_put_cstring(fb, "NULL");
+		goto finished;
+	}
+
+	if (st->depth >= st->depth_limit) {
+		if (DUK_HOBJECT_IS_COMPILEDFUNCTION(h)) {
+			duk_fb_sprintf(fb, "%sobject/compiledfunction %p%s", (const char *) brace1, (void *) h, (const char *) brace2);
+		} else if (DUK_HOBJECT_IS_NATIVEFUNCTION(h)) {
+			duk_fb_sprintf(fb, "%sobject/nativefunction %p%s", (const char *) brace1, (void *) h, (const char *) brace2);
+		} else if (DUK_HOBJECT_IS_THREAD(h)) {
+			duk_fb_sprintf(fb, "%sobject/thread %p%s", (const char *) brace1, (void *) h, (const char *) brace2);
+		} else {
+			duk_fb_sprintf(fb, "%sobject %p%s", (const char *) brace1, (void *) h, (const char *) brace2);  /* may be NULL */
+		}
+		return;
+	}
+
+	for (i = 0; i < (duk_uint_fast32_t) st->loop_stack_index; i++) {
+		if (st->loop_stack[i] == h) {
+			duk_fb_sprintf(fb, "%sLOOP:%p%s", (const char *) brace1, (void *) h, (const char *) brace2);
+			return;
+		}
+	}
+
+	/* after this, return paths should 'goto finished' for decrement */
+	st->depth++;
+
+	if (st->loop_stack_index >= st->loop_stack_limit) {
+		duk_fb_sprintf(fb, "%sOUT-OF-LOOP-STACK%s", (const char *) brace1, (const char *) brace2);
+		goto finished;
+	}
+	st->loop_stack[st->loop_stack_index++] = h;
+	pushed_loopstack = 1;
+
+	/*
+	 *  Notation: double underscore used for internal properties which are not
+	 *  stored in the property allocation (e.g. '__valstack').
+	 */
+
+	duk_fb_put_cstring(fb, brace1);
+
+	if (h->p) {
+		duk_uint32_t a_limit;
+
+		a_limit = h->a_size;
+		if (st->internal) {
+			/* dump all allocated entries, unused entries print as 'unused',
+			 * note that these may extend beyond current 'length' and look
+			 * a bit funny.
+			 */
+		} else {
+			/* leave out trailing 'unused' elements */
+			while (a_limit > 0) {
+				tv = DUK_HOBJECT_A_GET_VALUE_PTR(h, a_limit - 1);
+				if (!DUK_TVAL_IS_UNDEFINED_UNUSED(tv)) {
+					break;
+				}
+				a_limit--;
+			}
+		}
+
+		for (i = 0; i < a_limit; i++) {
+			tv = DUK_HOBJECT_A_GET_VALUE_PTR(h, i);
+			DUK__COMMA();
+			duk__print_tval(st, tv);
+		}
+		for (i = 0; i < h->e_used; i++) {
+			key = DUK_HOBJECT_E_GET_KEY(h, i);
+			if (!key) {
+				continue;
+			}
+			if (!st->internal &&
+			    DUK_HSTRING_GET_BYTELEN(key) > 0 &&
+			    DUK_HSTRING_GET_DATA(key)[0] == 0xff) {
+				/* XXX: use DUK_HSTRING_FLAG_INTERNAL? */
+				continue;
+			}
+			DUK__COMMA();
+			duk__print_hstring(st, key, 0);
+			duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_COLON);
+			if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(h, i)) {
+				duk_fb_sprintf(fb, "[get:%p,set:%p]",
+				               (void *) DUK_HOBJECT_E_GET_VALUE(h, i).a.get,
+				               (void *) DUK_HOBJECT_E_GET_VALUE(h, i).a.set);
+			} else {
+				tv = &DUK_HOBJECT_E_GET_VALUE(h, i).v;
+				duk__print_tval(st, tv);
+			}
+			if (st->heavy) {
+				duk_fb_sprintf(fb, "<%02lx>", (unsigned long) DUK_HOBJECT_E_GET_FLAGS(h, i));
+			}
+		}
+	}
+	if (st->internal) {
+		if (DUK_HOBJECT_HAS_EXTENSIBLE(h)) {
+			DUK__COMMA(); duk_fb_sprintf(fb, "__extensible:true");
+		} else {
+			;
+		}
+		if (DUK_HOBJECT_HAS_CONSTRUCTABLE(h)) {
+			DUK__COMMA(); duk_fb_sprintf(fb, "__constructable:true");
+		} else {
+			;
+		}
+		if (DUK_HOBJECT_HAS_BOUND(h)) {
+			DUK__COMMA(); duk_fb_sprintf(fb, "__bound:true");
+		} else {
+			;
+		}
+		if (DUK_HOBJECT_HAS_COMPILEDFUNCTION(h)) {
+			DUK__COMMA(); duk_fb_sprintf(fb, "__compiledfunction:true");
+		} else {
+			;
+		}
+		if (DUK_HOBJECT_HAS_NATIVEFUNCTION(h)) {
+			DUK__COMMA(); duk_fb_sprintf(fb, "__nativefunction:true");
+		} else {
+			;
+		}
+		if (DUK_HOBJECT_HAS_THREAD(h)) {
+			DUK__COMMA(); duk_fb_sprintf(fb, "__thread:true");
+		} else {
+			;
+		}
+		if (DUK_HOBJECT_HAS_ARRAY_PART(h)) {
+			DUK__COMMA(); duk_fb_sprintf(fb, "__array_part:true");
+		} else {
+			;
+		}
+		if (DUK_HOBJECT_HAS_STRICT(h)) {
+			DUK__COMMA(); duk_fb_sprintf(fb, "__strict:true");
+		} else {
+			;
+		}
+		if (DUK_HOBJECT_HAS_NEWENV(h)) {
+			DUK__COMMA(); duk_fb_sprintf(fb, "__newenv:true");
+		} else {
+			;
+		}
+		if (DUK_HOBJECT_HAS_NAMEBINDING(h)) {
+			DUK__COMMA(); duk_fb_sprintf(fb, "__namebinding:true");
+		} else {
+			;
+		}
+		if (DUK_HOBJECT_HAS_CREATEARGS(h)) {
+			DUK__COMMA(); duk_fb_sprintf(fb, "__createargs:true");
+		} else {
+			;
+		}
+		if (DUK_HOBJECT_HAS_ENVRECCLOSED(h)) {
+			DUK__COMMA(); duk_fb_sprintf(fb, "__envrecclosed:true");
+		} else {
+			;
+		}
+		if (DUK_HOBJECT_HAS_EXOTIC_ARRAY(h)) {
+			DUK__COMMA(); duk_fb_sprintf(fb, "__special_array:true");
+		} else {
+			;
+		}
+		if (DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(h)) {
+			DUK__COMMA(); duk_fb_sprintf(fb, "__special_stringobj:true");
+		} else {
+			;
+		}
+		if (DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(h)) {
+			DUK__COMMA(); duk_fb_sprintf(fb, "__special_arguments:true");
+		} else {
+			;
+		}
+		if (DUK_HOBJECT_HAS_EXOTIC_DUKFUNC(h)) {
+			DUK__COMMA(); duk_fb_sprintf(fb, "__special_dukfunc:true");
+		} else {
+			;
+		}
+		if (DUK_HOBJECT_HAS_EXOTIC_BUFFEROBJ(h)) {
+			DUK__COMMA(); duk_fb_sprintf(fb, "__special_bufferobj:true");
+		} else {
+			;
+		}
+		if (DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(h)) {
+			DUK__COMMA(); duk_fb_sprintf(fb, "__special_proxyobj:true");
+		} else {
+			;
+		}
+	}
+	if (st->internal && DUK_HOBJECT_IS_COMPILEDFUNCTION(h)) {
+		duk_hcompiledfunction *f = (duk_hcompiledfunction *) h;
+		DUK__COMMA(); duk_fb_put_cstring(fb, "__data:"); duk__print_hbuffer(st, f->data);
+		DUK__COMMA(); duk_fb_sprintf(fb, "__nregs:%ld", (long) f->nregs);
+		DUK__COMMA(); duk_fb_sprintf(fb, "__nargs:%ld", (long) f->nargs);
+	} else if (st->internal && DUK_HOBJECT_IS_NATIVEFUNCTION(h)) {
+		duk_hnativefunction *f = (duk_hnativefunction *) h;
+#if 0  /* FIXME: no portable way to print function pointers */
+		DUK__COMMA(); duk_fb_sprintf(fb, "__func:%p", (void *) f->func);
+#endif
+		DUK__COMMA(); duk_fb_sprintf(fb, "__nargs:%ld", (long) f->nargs);
+
+	} else if (st->internal && DUK_HOBJECT_IS_THREAD(h)) {
+		duk_hthread *t = (duk_hthread *) h;
+		DUK__COMMA(); duk_fb_sprintf(fb, "__strict:%ld", (long) t->strict);
+		DUK__COMMA(); duk_fb_sprintf(fb, "__state:%ld", (long) t->state);
+		DUK__COMMA(); duk_fb_sprintf(fb, "__unused1:%ld", (long) t->unused1);
+		DUK__COMMA(); duk_fb_sprintf(fb, "__unused2:%ld", (long) t->unused2);
+		DUK__COMMA(); duk_fb_sprintf(fb, "__valstack_max:%ld", (long) t->valstack_max);
+		DUK__COMMA(); duk_fb_sprintf(fb, "__callstack_max:%ld", (long) t->callstack_max);
+		DUK__COMMA(); duk_fb_sprintf(fb, "__catchstack_max:%ld", (long) t->catchstack_max);
+		DUK__COMMA(); duk_fb_sprintf(fb, "__valstack:%p", (void *) t->valstack);
+		DUK__COMMA(); duk_fb_sprintf(fb, "__valstack_end:%p/%ld", (void *) t->valstack_end, (long) (t->valstack_end - t->valstack));
+		DUK__COMMA(); duk_fb_sprintf(fb, "__valstack_bottom:%p/%ld", (void *) t->valstack_bottom, (long) (t->valstack_bottom - t->valstack));
+		DUK__COMMA(); duk_fb_sprintf(fb, "__valstack_top:%p/%ld", (void *) t->valstack_top, (long) (t->valstack_top - t->valstack));
+		DUK__COMMA(); duk_fb_sprintf(fb, "__catchstack:%p", (void *) t->catchstack);
+		DUK__COMMA(); duk_fb_sprintf(fb, "__catchstack_size:%ld", (long) t->catchstack_size);
+		DUK__COMMA(); duk_fb_sprintf(fb, "__catchstack_top:%ld", (long) t->catchstack_top);
+		DUK__COMMA(); duk_fb_sprintf(fb, "__resumer:"); duk__print_hobject(st, (duk_hobject *) t->resumer);
+		/* XXX: print built-ins array? */
+
+	}
+#ifdef DUK_USE_REFERENCE_COUNTING
+	if (st->internal) {
+		DUK__COMMA(); duk_fb_sprintf(fb, "__refcount:%lu", (unsigned long) DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h));
+	}
+#endif
+	if (st->internal) {
+		DUK__COMMA(); duk_fb_sprintf(fb, "__class:%ld", (long) DUK_HOBJECT_GET_CLASS_NUMBER(h));
+	}
+
+	/* prototype should be last, for readability */
+	if (st->follow_proto && h->prototype) {
+		DUK__COMMA(); duk_fb_put_cstring(fb, "__prototype:"); duk__print_hobject(st, h->prototype);
+	}
+
+	duk_fb_put_cstring(fb, brace2);
+
+	if (st->heavy && h->h_size > 0) {
+		duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_LANGLE);
+		for (i = 0; i < h->h_size; i++) {
+			duk_uint_t h_idx = DUK_HOBJECT_H_GET_INDEX(h, i);
+			if (i > 0) {
+				duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_COMMA);
+			}
+			if (h_idx == DUK_HOBJECT_HASHIDX_UNUSED) {
+				duk_fb_sprintf(fb, "u");
+			} else if (h_idx == DUK_HOBJECT_HASHIDX_DELETED) {
+				duk_fb_sprintf(fb, "d");
+			} else {
+				duk_fb_sprintf(fb, "%ld", (long) h_idx);
+			}
+		}
+		duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_RANGLE);
+	}
+
+ finished:
+	st->depth--;
+	if (pushed_loopstack) {
+		st->loop_stack_index--;
+		st->loop_stack[st->loop_stack_index] = NULL;
+	}
+}
+
+#undef DUK__COMMA
+
+static void duk__print_hbuffer(duk__dprint_state *st, duk_hbuffer *h) {
+	duk_fixedbuffer *fb = st->fb;
+	duk_size_t i, n;
+	duk_uint8_t *p;
+
+	if (duk_fb_is_full(fb)) {
+		return;
+	}
+
+	/* terminal type: no depth check */
+
+	if (!h) {
+		duk_fb_put_cstring(fb, "NULL");
+		return;
+	}
+
+	if (DUK_HBUFFER_HAS_DYNAMIC(h)) {
+		duk_hbuffer_dynamic *g = (duk_hbuffer_dynamic *) h;
+		duk_fb_sprintf(fb, "buffer:dynamic:%p:%ld:%ld",
+		               (void *) g->curr_alloc, (long) g->size, (long) g->usable_size);
+	} else {
+		duk_fb_sprintf(fb, "buffer:fixed:%ld", (long) DUK_HBUFFER_GET_SIZE(h));
+	}
+
+#ifdef DUK_USE_REFERENCE_COUNTING
+	duk_fb_sprintf(fb, "/%lu", (unsigned long) DUK_HEAPHDR_GET_REFCOUNT(&h->hdr));
+#endif
+
+	if (st->hexdump) {
+		duk_fb_sprintf(fb, "=[");
+		n = DUK_HBUFFER_GET_SIZE(h);
+		p = (duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(h);
+		for (i = 0; i < n; i++) {
+			duk_fb_sprintf(fb, "%02lx", (unsigned long) p[i]);
+		}
+		duk_fb_sprintf(fb, "]");
+	}
+}
+
+static void duk__print_heaphdr(duk__dprint_state *st, duk_heaphdr *h) {
+	duk_fixedbuffer *fb = st->fb;
+
+	if (duk_fb_is_full(fb)) {
+		return;
+	}
+
+	if (!h) {
+		duk_fb_put_cstring(fb, "NULL");
+		return;
+	}
+
+	switch (DUK_HEAPHDR_GET_TYPE(h)) {
+	case DUK_HTYPE_STRING:
+		duk__print_hstring(st, (duk_hstring *) h, 1);
+		break;
+	case DUK_HTYPE_OBJECT:
+		duk__print_hobject(st, (duk_hobject *) h);
+		break;
+	case DUK_HTYPE_BUFFER:
+		duk__print_hbuffer(st, (duk_hbuffer *) h);
+		break;
+	default:
+		duk_fb_sprintf(fb, "[unknown htype %ld]", (long) DUK_HEAPHDR_GET_TYPE(h));
+		break;
+	}
+}
+
+static void duk__print_tval(duk__dprint_state *st, duk_tval *tv) {
+	duk_fixedbuffer *fb = st->fb;
+
+	if (duk_fb_is_full(fb)) {
+		return;
+	}
+
+	/* depth check is done when printing an actual type */
+
+	if (st->heavy) {
+		duk_fb_sprintf(fb, "(%p)", (void *) tv);
+	}
+
+	if (!tv) {
+		duk_fb_put_cstring(fb, "NULL");
+		return;
+	}
+
+	if (st->binary) {
+		duk_size_t i;
+		duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_LBRACKET);
+		for (i = 0; i < (duk_size_t) sizeof(*tv); i++) {
+			duk_fb_sprintf(fb, "%02lx", (unsigned long) ((duk_uint8_t *)tv)[i]);
+		}
+		duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_RBRACKET);
+	}
+
+	if (st->heavy) {
+		duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_LANGLE);
+	}
+	switch (DUK_TVAL_GET_TAG(tv)) {
+	case DUK_TAG_UNDEFINED: {
+		if (DUK_TVAL_IS_UNDEFINED_UNUSED(tv)) {
+			duk_fb_put_cstring(fb, "unused");
+		} else {
+			duk_fb_put_cstring(fb, "undefined");
+		}
+		break;
+	}
+	case DUK_TAG_NULL: {
+		duk_fb_put_cstring(fb, "null");
+		break;
+	}
+	case DUK_TAG_BOOLEAN: {
+		duk_fb_put_cstring(fb, DUK_TVAL_GET_BOOLEAN(tv) ? "true" : "false");
+		break;
+	}
+	case DUK_TAG_STRING: {
+		/* Note: string is a terminal heap object, so no depth check here */
+		duk__print_hstring(st, DUK_TVAL_GET_STRING(tv), 1);
+		break;
+	}
+	case DUK_TAG_OBJECT: {
+		duk__print_hobject(st, DUK_TVAL_GET_OBJECT(tv));
+		break;
+	}
+	case DUK_TAG_BUFFER: {
+		duk__print_hbuffer(st, DUK_TVAL_GET_BUFFER(tv));
+		break;
+	}
+	case DUK_TAG_POINTER: {
+		duk_fb_sprintf(fb, "pointer:%p", (void *) DUK_TVAL_GET_POINTER(tv));
+		break;
+	}
+	default: {
+		/* IEEE double is approximately 16 decimal digits; print a couple extra */
+		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
+		duk_fb_sprintf(fb, "%.18g", (double) DUK_TVAL_GET_NUMBER(tv));
+		break;
+	}
+	}
+	if (st->heavy) {
+		duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_RANGLE);
+	}
+}
+
+static void duk__print_instr(duk__dprint_state *st, duk_instr_t ins) {
+	duk_fixedbuffer *fb = st->fb;
+	duk_small_int_t op;
+	const char *op_name;
+	const char *extraop_name;
+
+	op = (duk_small_int_t) DUK_DEC_OP(ins);
+	op_name = duk__bc_optab[op];
+
+	/* XXX: option to fix opcode length so it lines up nicely */
+
+	if (op == DUK_OP_EXTRA) {
+		extraop_name = duk__bc_extraoptab[DUK_DEC_A(ins)];
+
+		duk_fb_sprintf(fb, "%s %ld, %ld",
+		               (const char *) extraop_name, (long) DUK_DEC_B(ins), (long) DUK_DEC_C(ins));
+	} else if (op == DUK_OP_JUMP) {
+		duk_int_t diff1 = DUK_DEC_ABC(ins) - DUK_BC_JUMP_BIAS;  /* from next pc */
+		duk_int_t diff2 = diff1 + 1;                            /* from curr pc */
+
+		duk_fb_sprintf(fb, "%s %ld (to pc%c%ld)",
+		               (const char *) op_name, (long) diff1,
+		               (int) (diff2 >= 0 ? '+' : '-'),  /* char format: use int */
+		               (long) (diff2 >= 0 ? diff2 : -diff2));
+	} else {
+		duk_fb_sprintf(fb, "%s %ld, %ld, %ld",
+		               (const char *) op_name, (long) DUK_DEC_A(ins),
+		               (long) DUK_DEC_B(ins), (long) DUK_DEC_C(ins));
+	}
+}
+
+static void duk__print_opcode(duk__dprint_state *st, duk_small_int_t opcode) {
+	duk_fixedbuffer *fb = st->fb;
+
+	if (opcode < DUK_BC_OP_MIN || opcode > DUK_BC_OP_MAX) {
+		duk_fb_sprintf(fb, "?(%ld)", (long) opcode);
+	} else {
+		duk_fb_sprintf(fb, "%s", (const char *) duk__bc_optab[opcode]);
+	}
+}
+
+duk_int_t duk_debug_vsnprintf(char *str, duk_size_t size, const char *format, va_list ap) {
+	duk_fixedbuffer fb;
+	const char *p = format;
+	const char *p_end = p + DUK_STRLEN(format);
+	duk_int_t retval;
+	
+	DUK_MEMZERO(&fb, sizeof(fb));
+	fb.buffer = (duk_uint8_t *) str;
+	fb.length = size;
+	fb.offset = 0;
+	fb.truncated = 0;
+
+	while (p < p_end) {
+		char ch = *p++;
+		const char *p_begfmt = NULL;
+		duk_bool_t got_exclamation = 0;
+		duk_bool_t got_long = 0;  /* %lf, %ld etc */
+		duk__dprint_state st;
+
+		if (ch != DUK_ASC_PERCENT) {
+			duk_fb_put_byte(&fb, (duk_uint8_t) ch);
+			continue;
+		}
+
+		/*
+		 *  Format tag parsing.  Since we don't understand all the
+		 *  possible format tags allowed, we just scan for a terminating
+		 *  specifier and keep track of relevant modifiers that we do
+		 *  understand.  See man 3 printf.
+		 */
+
+		DUK_MEMZERO(&st, sizeof(st));
+		st.fb = &fb;
+		st.depth = 0;
+		st.depth_limit = 1;
+		st.loop_stack_index = 0;
+		st.loop_stack_limit = DUK__LOOP_STACK_DEPTH;
+
+		p_begfmt = p - 1;
+		while (p < p_end) {
+			ch = *p++;
+
+			if (ch == DUK_ASC_STAR) {
+				/* unsupported: would consume multiple args */
+				goto error;
+			} else if (ch == DUK_ASC_PERCENT) {
+				duk_fb_put_byte(&fb, (duk_uint8_t) DUK_ASC_PERCENT);
+				break;
+			} else if (ch == DUK_ASC_EXCLAMATION) {
+				got_exclamation = 1;
+			} else if (!got_exclamation && ch == DUK_ASC_LC_L) {
+				got_long = 1;
+			} else if (got_exclamation && ch == DUK_ASC_LC_D) {
+				st.depth_limit = DUK__DEEP_DEPTH_LIMIT;
+			} else if (got_exclamation && ch == DUK_ASC_LC_P) {
+				st.follow_proto = 1;
+			} else if (got_exclamation && ch == DUK_ASC_LC_I) {
+				st.internal = 1;
+			} else if (got_exclamation && ch == DUK_ASC_LC_X) {
+				st.hexdump = 1;
+			} else if (got_exclamation && ch == DUK_ASC_LC_H) {
+				st.heavy = 1;
+			} else if (got_exclamation && ch == DUK_ASC_ATSIGN) {
+				st.pointer = 1;
+			} else if (got_exclamation && ch == DUK_ASC_HASH) {
+				st.binary = 1;
+			} else if (got_exclamation && ch == DUK_ASC_UC_T) {
+				duk_tval *t = va_arg(ap, duk_tval *);
+				if (st.pointer && !st.heavy) {
+					duk_fb_sprintf(&fb, "(%p)", (void *) t);
+				}
+				duk__print_tval(&st, t);
+				break;
+			} else if (got_exclamation && ch == DUK_ASC_UC_O) {
+				duk_heaphdr *t = va_arg(ap, duk_heaphdr *);
+				if (st.pointer && !st.heavy) {
+					duk_fb_sprintf(&fb, "(%p)", (void *) t);
+				}
+				duk__print_heaphdr(&st, t);
+				break;
+			} else if (got_exclamation && ch == DUK_ASC_UC_I) {
+				duk_instr_t t = va_arg(ap, duk_instr_t);
+				duk__print_instr(&st, t);
+				break;
+			} else if (got_exclamation && ch == DUK_ASC_UC_C) {
+				long t = va_arg(ap, long);
+				duk__print_opcode(&st, (duk_small_int_t) t);
+				break;
+			} else if (!got_exclamation && strchr(DUK__ALLOWED_STANDARD_SPECIFIERS, (int) ch)) {
+				char fmtbuf[DUK__MAX_FORMAT_TAG_LENGTH];
+				duk_size_t fmtlen;
+
+				DUK_ASSERT(p >= p_begfmt);
+				fmtlen = (duk_size_t) (p - p_begfmt);
+				if (fmtlen >= sizeof(fmtbuf)) {
+					/* format is too large, abort */
+					goto error;
+				}
+				DUK_MEMZERO(fmtbuf, sizeof(fmtbuf));
+				DUK_MEMCPY(fmtbuf, p_begfmt, fmtlen);
+
+				/* assume exactly 1 arg, which is why '*' is forbidden; arg size still
+				 * depends on type though.
+				 */
+
+				if (ch == DUK_ASC_LC_F || ch == DUK_ASC_LC_G || ch == DUK_ASC_LC_E) {
+					/* %f and %lf both consume a 'long' */
+					double arg = va_arg(ap, double);
+					duk_fb_sprintf(&fb, fmtbuf, arg);
+				} else if (ch == DUK_ASC_LC_D && got_long) {
+					/* %ld */
+					long arg = va_arg(ap, long);
+					duk_fb_sprintf(&fb, fmtbuf, arg);
+				} else if (ch == DUK_ASC_LC_D) {
+					/* %d; only 16 bits are guaranteed */
+					int arg = va_arg(ap, int);
+					duk_fb_sprintf(&fb, fmtbuf, arg);
+				} else if (ch == DUK_ASC_LC_U && got_long) {
+					/* %lu */
+					unsigned long arg = va_arg(ap, unsigned long);
+					duk_fb_sprintf(&fb, fmtbuf, arg);
+				} else if (ch == DUK_ASC_LC_U) {
+					/* %u; only 16 bits are guaranteed */
+					unsigned int arg = va_arg(ap, unsigned int);
+					duk_fb_sprintf(&fb, fmtbuf, arg);
+				} else if (ch == DUK_ASC_LC_X && got_long) {
+					/* %lx */
+					unsigned long arg = va_arg(ap, unsigned long);
+					duk_fb_sprintf(&fb, fmtbuf, arg);
+				} else if (ch == DUK_ASC_LC_X) {
+					/* %x; only 16 bits are guaranteed */
+					unsigned int arg = va_arg(ap, unsigned int);
+					duk_fb_sprintf(&fb, fmtbuf, arg);
+				} else if (ch == DUK_ASC_LC_S) {
+					/* %s */
+					const char *arg = va_arg(ap, const char *);
+					if (arg == NULL) {
+						/* '%s' and NULL is not portable, so special case
+						 * it for debug printing.
+						 */
+						duk_fb_sprintf(&fb, "NULL");
+					} else {
+						duk_fb_sprintf(&fb, fmtbuf, arg);
+					}
+				} else if (ch == DUK_ASC_LC_P) {
+					/* %p */
+					void *arg = va_arg(ap, void *);
+					if (arg == NULL) {
+						/* '%p' and NULL is portable, but special case it
+						 * anyway to get a standard NULL marker in logs.
+						 */
+						duk_fb_sprintf(&fb, "NULL");
+					} else {
+						duk_fb_sprintf(&fb, fmtbuf, arg);
+					}
+				} else if (ch == DUK_ASC_LC_C) {
+					/* '%c', passed concretely as int */
+					int arg = va_arg(ap, int);
+					duk_fb_sprintf(&fb, fmtbuf, arg);
+				} else {
+					/* Should not happen. */
+					duk_fb_sprintf(&fb, "INVALID-FORMAT(%s)", (const char *) fmtbuf);
+				}
+				break;
+			} else {
+				/* ignore */
+			}
+		}
+	}
+	goto done;
+
+ error:
+	duk_fb_put_cstring(&fb, "FMTERR");
+	/* fall through */
+
+ done:
+	retval = (duk_int_t) fb.offset;
+	duk_fb_put_byte(&fb, (duk_uint8_t) 0);
+
+	/* return total chars written excluding terminator */
+	return retval;
+}
+
+duk_int_t duk_debug_snprintf(char *str, duk_size_t size, const char *format, ...) {
+	duk_int_t retval;
+	va_list ap;
+	va_start(ap, format);
+	retval = duk_debug_vsnprintf(str, size, format, ap);
+	va_end(ap);
+	return retval;
+}
+
+/* Formatting function pointers is tricky: there is no standard pointer for
+ * function pointers and the size of a function pointer may depend on the
+ * specific pointer type.  This helper formats a function pointer based on
+ * its memory layout to get something useful on most platforms.
+ */
+void duk_debug_format_funcptr(char *buf, duk_size_t buf_size, duk_uint8_t *fptr, duk_size_t fptr_size) {
+	duk_size_t i;
+	duk_uint8_t *p = (duk_uint8_t *) buf;
+	duk_uint8_t *p_end = (duk_uint8_t *) (buf + buf_size - 1);
+
+	DUK_MEMZERO(buf, buf_size);
+
+	for (i = 0; i < fptr_size; i++) {
+		duk_int_t left = (duk_int_t) (p_end - p);
+		duk_uint8_t ch;
+		if (left <= 0) {
+			break;
+		}
+
+		/* Quite approximate but should be useful for little and big endian. */
+#ifdef DUK_USE_INTEGER_BE
+		ch = fptr[i];
+#else
+		ch = fptr[fptr_size - 1 - i];
+#endif
+		p += DUK_SNPRINTF((char *) p, left, "%02lx", (unsigned long) ch);
+	}	
+}
+
+#endif  /* DUK_USE_DEBUG */
+#line 1 "duk_error_augment.c"
+/*
+ *  Augmenting errors at their creation site and their throw site.
+ *
+ *  When errors are created, traceback data is added by built-in code
+ *  and a user error handler (if defined) can process or replace the
+ *  error.  Similarly, when errors are thrown, a user error handler
+ *  (if defined) can process or replace the error.
+ *
+ *  Augmentation and other processing at error creation time is nice
+ *  because an error is only created once, but it may be thrown and
+ *  rethrown multiple times.  User error handler registered for processing
+ *  an error at its throw site must be careful to handle rethrowing in
+ *  a useful manner.
+ *
+ *  Error augmentation may throw an internal error (e.g. alloc error).
+ *
+ *  Ecmascript allows throwing any values, so all values cannot be
+ *  augmented.  Currently, the built-in augmentation at error creation
+ *  only augments error values which are Error instances (= have the
+ *  built-in Error.prototype in their prototype chain) and are also
+ *  extensible.  User error handlers have no limitations in this respect.
+ */
+
+/* include removed: duk_internal.h */
+
+/*
+ *  Helper for calling a user error handler.
+ *
+ *  'thr' must be the currently active thread; the error handler is called
+ *  in its context.  The valstack of 'thr' must have the error value on
+ *  top, and will be replaced by another error value based on the return
+ *  value of the error handler.
+ *
+ *  The helper calls duk_handle_call() recursively in protected mode.
+ *  Before that call happens, no longjmps should happen; as a consequence,
+ *  we must assume that the valstack contains enough temporary space for
+ *  arguments and such.
+ *
+ *  While the error handler runs, any errors thrown will not trigger a
+ *  recursive error handler call (this is implemented using a heap level
+ *  flag which will "follow" through any coroutines resumed inside the
+ *  error handler).  If the error handler is not callable or throws an
+ *  error, the resulting error replaces the original error (for Duktape
+ *  internal errors, duk_error_throw.c further substitutes this error with
+ *  a DoubleError which is not ideal).  This would be easy to change and
+ *  even signal to the caller.
+ *
+ *  The user error handler is stored in 'Duktape.errCreate' or
+ *  'Duktape.errThrow' depending on whether we're augmenting the error at
+ *  creation or throw time.  There are several alternatives to this approach,
+ *  see doc/error-objects.txt for discussion.
+ *
+ *  Note: since further longjmp()s may occur while calling the error handler
+ *  (for many reasons, e.g. a labeled 'break' inside the handler), the
+ *  caller can make no assumptions on the thr->heap->lj state after the
+ *  call (this affects especially duk_error_throw.c).  This is not an issue
+ *  as long as the caller writes to the lj state only after the error handler
+ *  finishes.
+ */
+
+#if defined(DUK_USE_ERRTHROW) || defined(DUK_USE_ERRCREATE)
+static void duk__err_augment_user(duk_hthread *thr, duk_small_uint_t stridx_cb) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_tval *tv_hnd;
+	duk_small_uint_t call_flags;
+	duk_int_t rc;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->heap != NULL);
+	DUK_ASSERT_DISABLE(stridx_cb >= 0);  /* unsigned */
+	DUK_ASSERT(stridx_cb < DUK_HEAP_NUM_STRINGS);
+
+	if (DUK_HEAP_HAS_ERRHANDLER_RUNNING(thr->heap)) {
+		DUK_DD(DUK_DDPRINT("recursive call to error handler, ignore"));
+		return;
+	}
+
+	/*
+	 *  Check whether or not we have an error handler.
+	 *
+	 *  We must be careful of not triggering an error when looking up the
+	 *  property.  For instance, if the property is a getter, we don't want
+	 *  to call it, only plain values are allowed.  The value, if it exists,
+	 *  is not checked.  If the value is not a function, a TypeError happens
+	 *  when it is called and that error replaces the original one.
+	 */
+
+	DUK_ASSERT_VALSTACK_SPACE(thr, 4);  /* 3 entries actually needed below */
+
+	/* [ ... errval ] */
+
+	if (thr->builtins[DUK_BIDX_DUKTAPE] == NULL) {
+		/* When creating built-ins, some of the built-ins may not be set
+		 * and we want to tolerate that when throwing errors.
+		 */
+		DUK_DD(DUK_DDPRINT("error occurred when DUK_BIDX_DUKTAPE is NULL, ignoring"));
+		return;
+	}
+	tv_hnd = duk_hobject_find_existing_entry_tval_ptr(thr->builtins[DUK_BIDX_DUKTAPE],
+	                                                  thr->strs[stridx_cb]);
+	if (tv_hnd == NULL) {
+		DUK_DD(DUK_DDPRINT("error handler does not exist or is not a plain value: %!T",
+		                   (duk_tval *) tv_hnd));
+		return;
+	}
+	DUK_DDD(DUK_DDDPRINT("error handler dump (callability not checked): %!T",
+	                     (duk_tval *) tv_hnd));
+	duk_push_tval(ctx, tv_hnd);
+
+	/* [ ... errval errhandler ] */
+
+	duk_insert(ctx, -2);  /* -> [ ... errhandler errval ] */
+	duk_push_undefined(ctx);
+	duk_insert(ctx, -2);  /* -> [ ... errhandler undefined(= this) errval ] */
+
+	/* [ ... errhandler undefined errval ] */
+
+	/*
+	 *  DUK_CALL_FLAG_IGNORE_RECLIMIT causes duk_handle_call() to ignore C
+	 *  recursion depth limit (and won't increase it either).  This is
+	 *  dangerous, but useful because it allows the error handler to run
+	 *  even if the original error is caused by C recursion depth limit.
+	 *
+	 *  The heap level DUK_HEAP_FLAG_ERRHANDLER_RUNNING is set for the
+	 *  duration of the error handler and cleared afterwards.  This flag
+	 *  prevents the error handler from running recursively.  The flag is
+	 *  heap level so that the flag properly controls even coroutines
+	 *  launched by an error handler.  Since the flag is heap level, it is
+	 *  critical to restore it correctly.
+	 *
+	 *  We ignore errors now: a success return and an error value both
+	 *  replace the original error value.  (This would be easy to change.)
+	 */
+
+	DUK_ASSERT(!DUK_HEAP_HAS_ERRHANDLER_RUNNING(thr->heap));  /* since no recursive error handler calls */
+	DUK_HEAP_SET_ERRHANDLER_RUNNING(thr->heap);
+
+	call_flags = DUK_CALL_FLAG_PROTECTED |
+	             DUK_CALL_FLAG_IGNORE_RECLIMIT;  /* protected, ignore reclimit, not constructor */
+
+	rc = duk_handle_call(thr,
+	                     1,            /* num args */
+	                     call_flags);  /* call_flags */
+	DUK_UNREF(rc);  /* no need to check now: both success and error are OK */
+
+	DUK_ASSERT(DUK_HEAP_HAS_ERRHANDLER_RUNNING(thr->heap));
+	DUK_HEAP_CLEAR_ERRHANDLER_RUNNING(thr->heap);
+
+	/* [ ... errval ] */
+}
+#endif  /* DUK_USE_ERRTHROW || DUK_USE_ERRHANDLE */
+
+/*
+ *  Add tracedata to an error on the stack top.
+ */
+
+#ifdef DUK_USE_TRACEBACKS
+static void duk__add_traceback(duk_hthread *thr, duk_hthread *thr_callstack, const char *filename, duk_int_t line, duk_bool_t noblame_fileline) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_small_uint_t depth;
+	duk_int_t i, i_min;
+	duk_uarridx_t arr_idx;
+	duk_double_t d;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr_callstack != NULL);
+	DUK_ASSERT(ctx != NULL);
+
+	/* [ ... error ] */
+
+	/*
+	 *  The traceback format is pretty arcane in an attempt to keep it compact
+	 *  and cheap to create.  It may change arbitrarily from version to version.
+	 *  It should be decoded/accessed through version specific accessors only.
+	 *
+	 *  See doc/error-objects.txt.
+	 */
+
+	DUK_DDD(DUK_DDDPRINT("adding traceback to object: %!T",
+	                     (duk_tval *) duk_get_tval(ctx, -1)));
+
+	duk_push_array(ctx);  /* XXX: specify array size, as we know it */
+	arr_idx = 0;
+
+	/* filename/line from C macros (__FILE__, __LINE__) are added as an
+	 * entry with a special format: (string, number).  The number contains
+	 * the line and flags.
+	 */
+
+	/* XXX: optimize: allocate an array part to the necessary size (upwards
+	 * estimate) and fill in the values directly into the array part; finally
+	 * update 'length'.
+	 */
+
+	/* XXX: using duk_put_prop_index() would cause obscure error cases when Array.prototype
+	 * has write-protected array index named properties.  This was seen as DoubleErrors
+	 * in e.g. some test262 test cases.  Using duk_def_prop_index() is better but heavier.
+	 * The best fix is to fill in the tracedata directly into the array part.
+	 */
+
+	/* [ ... error arr ] */
+
+	if (filename) {
+		duk_push_string(ctx, filename);
+		duk_def_prop_index_wec(ctx, -2, arr_idx);
+		arr_idx++;
+
+		d = (noblame_fileline ? ((duk_double_t) DUK_TB_FLAG_NOBLAME_FILELINE) * DUK_DOUBLE_2TO32 : 0.0) +
+		    (duk_double_t) line;
+		duk_push_number(ctx, d);
+		duk_def_prop_index_wec(ctx, -2, arr_idx);
+		arr_idx++;
+	}
+
+	/* traceback depth doesn't take into account the filename/line
+	 * special handling above (intentional)
+	 */
+	depth = DUK_USE_TRACEBACK_DEPTH;
+	i_min = (thr_callstack->callstack_top > (duk_size_t) depth ? (duk_int_t) (thr_callstack->callstack_top - depth) : 0);
+	DUK_ASSERT(i_min >= 0);
+
+	/* [ ... error arr ] */
+
+	DUK_ASSERT(thr_callstack->callstack_top <= DUK_INT_MAX);  /* callstack limits */
+	for (i = (duk_int_t) (thr_callstack->callstack_top - 1); i >= i_min; i--) {
+		duk_uint32_t pc;
+
+		/*
+		 *  Note: each API operation potentially resizes the callstack,
+		 *  so be careful to re-lookup after every operation.  Currently
+		 *  these is no issue because we don't store a temporary 'act'
+		 *  pointer at all.  (This would be a non-issue if we operated
+		 *  directly on the array part.)
+		 */
+
+		/* [... arr] */
+
+		DUK_ASSERT(thr_callstack->callstack[i].func != NULL);
+		DUK_ASSERT_DISABLE(thr_callstack->callstack[i].pc >= 0);  /* unsigned */
+
+		/* add function */
+		duk_push_hobject(ctx, thr_callstack->callstack[i].func);  /* -> [... arr func] */
+		duk_def_prop_index_wec(ctx, -2, arr_idx);
+		arr_idx++;
+
+		/* add a number containing: pc, activation flags */
+
+		/* Add a number containing: pc, activation flag
+		 *
+		 * PC points to next instruction, find offending PC.  Note that
+		 * PC == 0 for native code.
+		 */
+		pc = thr_callstack->callstack[i].pc;
+		if (pc > 0) {
+			pc--;
+		}
+		DUK_ASSERT_DISABLE(pc >= 0);  /* unsigned */
+		DUK_ASSERT((duk_double_t) pc < DUK_DOUBLE_2TO32);  /* assume PC is at most 32 bits and non-negative */
+		d = ((duk_double_t) thr_callstack->callstack[i].flags) * DUK_DOUBLE_2TO32 + (duk_double_t) pc;
+		duk_push_number(ctx, d);  /* -> [... arr num] */
+		duk_def_prop_index_wec(ctx, -2, arr_idx);
+		arr_idx++;
+	}
+
+	/* XXX: set with duk_hobject_set_length() when tracedata is filled directly */
+	duk_push_uint(ctx, (duk_uint_t) arr_idx);
+	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_WC);
+
+	/* [ ... error arr ] */
+
+	duk_def_prop_stridx_wec(ctx, -2, DUK_STRIDX_TRACEDATA);  /* -> [ ... error ] */
+}
+#endif  /* DUK_USE_TRACEBACKS */
+
+#if defined(DUK_USE_AUGMENT_ERROR_CREATE)
+static void duk__err_augment_builtin_throw(duk_hthread *thr, duk_hthread *thr_callstack, const char *filename, duk_int_t line, duk_small_int_t noblame_fileline, duk_hobject *obj) {
+	duk_context *ctx = (duk_context *) thr;
+#ifdef DUK_USE_ASSERTIONS
+	duk_int_t entry_top;
+#endif
+
+#ifdef DUK_USE_ASSERTIONS
+	entry_top = duk_get_top(ctx);
+#endif
+	DUK_ASSERT(obj != NULL);
+
+	DUK_UNREF(obj);  /* unreferenced w/o tracebacks */
+	DUK_UNREF(ctx);  /* unreferenced w/ tracebacks */
+
+#ifdef DUK_USE_TRACEBACKS
+	/*
+	 *  If tracebacks are enabled, the 'tracedata' property is the only
+	 *  thing we need: 'fileName' and 'lineNumber' are virtual properties
+	 *  which use 'tracedata'.
+	 */
+
+	if (duk_hobject_hasprop_raw(thr, obj, DUK_HTHREAD_STRING_TRACEDATA(thr))) {
+		DUK_DDD(DUK_DDDPRINT("error value already has a 'tracedata' property, not modifying it"));
+	} else {
+		duk__add_traceback(thr, thr_callstack, filename, line, noblame_fileline);
+	}
+#else
+	/*
+	 *  If tracebacks are disabled, 'fileName' and 'lineNumber' are added
+	 *  as plain own properties.  Since Error.prototype has accessors of
+	 *  the same name, we need to define own properties directly (cannot
+	 *  just use e.g. duk_put_prop_stridx).  Existing properties are not
+	 *  overwritten in case they already exist.
+	 */
+
+	if (filename && !noblame_fileline) {
+		/* XXX: file/line is disabled in minimal builds, so disable this too
+		 * when appropriate.
+		 */
+		duk_push_string(ctx, filename);
+		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAG_NO_OVERWRITE);
+		duk_push_int(ctx, line);
+		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_LINE_NUMBER, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAG_NO_OVERWRITE);
+	} else if (thr_callstack->callstack_top > 0) {
+		duk_activation *act;
+		duk_hobject *func;
+
+		act = thr_callstack->callstack + thr_callstack->callstack_top - 1;
+		DUK_ASSERT(act >= thr_callstack->callstack && act < thr_callstack->callstack + thr_callstack->callstack_size);
+		func = act->func;
+		if (func) {
+			duk_uint32_t pc;
+			duk_uint32_t line;
+
+			/* PC points to next instruction, find offending PC.  Note that
+			 * PC == 0 for native code.
+			 */
+			pc = act->pc;
+			if (pc > 0) {
+				pc--;
+			}
+			DUK_ASSERT_DISABLE(pc >= 0);  /* unsigned */
+			DUK_ASSERT((duk_double_t) pc < DUK_DOUBLE_2TO32);  /* assume PC is at most 32 bits and non-negative */
+			act = NULL;  /* invalidated by pushes, so get out of the way */
+
+			duk_push_hobject(ctx, func);
+
+			/* [ ... error func ] */
+
+			duk_get_prop_stridx(ctx, -1, DUK_STRIDX_FILE_NAME);
+			duk_def_prop_stridx(ctx, -3, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAG_NO_OVERWRITE);
+			if (DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) {
+#if 0
+				duk_push_number(ctx, pc);
+				duk_def_prop_stridx(ctx, -3, DUK_STRIDX_PC, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAGS_NO_OVERWRITE);
+#endif
+				line = duk_hobject_pc2line_query(ctx, -1, (duk_uint_fast32_t) pc);
+				if (line > 0) {
+					duk_push_u32(ctx, (duk_uint32_t) line); /* -> [ ... error func line ] */
+					duk_def_prop_stridx(ctx, -3, DUK_STRIDX_LINE_NUMBER, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAG_NO_OVERWRITE);
+				}
+			} else {
+				/* Native function, no relevant lineNumber. */
+			}
+
+			duk_pop(ctx);
+		}
+	}
+#endif  /* DUK_USE_TRACEBACKS */
+
+#ifdef DUK_USE_ASSERTIONS
+	DUK_ASSERT(duk_get_top(ctx) == entry_top);
+#endif
+}
+#endif  /* DUK_USE_AUGMENT_ERROR_CREATE */
+
+/*
+ *  Augment an error at creation time with tracedata/fileName/lineNumber
+ *  and allow a user error handler (if defined) to process/replace the error.
+ *  The error to be augmented is at the stack top.
+ *
+ *  thr: thread containing the error value
+ *  thr_callstack: thread which should be used for generating callstack etc.
+ *  filename: C __FILE__ related to the error
+ *  line: C __LINE__ related to the error
+ *  noblame_fileline: if true, don't fileName/line as error source, otherwise use traceback
+ *                    (needed because user code filename/line are reported but internal ones
+ *                    are not)
+ *
+ *  FIXME: rename noblame_fileline to flags field; combine it to some existing
+ *  field (there are only a few call sites so this may not be worth it).
+ */
+
+#if defined(DUK_USE_AUGMENT_ERROR_CREATE)
+void duk_err_augment_error_create(duk_hthread *thr, duk_hthread *thr_callstack, const char *filename, duk_int_t line, duk_bool_t noblame_fileline) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_hobject *obj;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr_callstack != NULL);
+	DUK_ASSERT(ctx != NULL);
+
+	/* [ ... error ] */
+
+	/*
+	 *  Criteria for augmenting:
+	 *
+	 *   - augmentation enabled in build (naturally)
+	 *   - error value internal prototype chain contains the built-in
+	 *     Error prototype object (i.e. 'val instanceof Error')
+	 *
+	 *  Additional criteria for built-in augmenting:
+	 *
+	 *   - error value is an extensible object
+	 */
+
+	obj = duk_get_hobject(ctx, -1);
+	if (!obj) {
+		DUK_DDD(DUK_DDDPRINT("value is not an object, skip both built-in and user augment"));
+		return;
+	}
+	if (!duk_hobject_prototype_chain_contains(thr, obj, thr->builtins[DUK_BIDX_ERROR_PROTOTYPE])) {
+		DUK_DDD(DUK_DDDPRINT("value is not an error instance, skip both built-in and user augment"));
+		return;
+	}
+
+	if (DUK_HOBJECT_HAS_EXTENSIBLE(obj)) {
+		DUK_DDD(DUK_DDDPRINT("error meets criteria, built-in augment"));
+		duk__err_augment_builtin_throw(thr, thr_callstack, filename, line, noblame_fileline, obj);
+	} else {
+		DUK_DDD(DUK_DDDPRINT("error does not meet criteria, no built-in augment"));
+	}
+
+	/* [ ... error ] */
+
+#if defined(DUK_USE_ERRCREATE)
+	duk__err_augment_user(thr, DUK_STRIDX_ERR_CREATE);
+#endif
+}
+#endif  /* DUK_USE_AUGMENT_ERROR_CREATE */
+
+/*
+ *  Augment an error at throw time; allow a user error handler (if defined)
+ *  to process/replace the error.  The error to be augmented is at the
+ *  stack top.
+ */
+
+#if defined(DUK_USE_AUGMENT_ERROR_THROW)
+void duk_err_augment_error_throw(duk_hthread *thr) {
+#if defined(DUK_USE_ERRTHROW)
+	duk__err_augment_user(thr, DUK_STRIDX_ERR_THROW);
+#endif  /* DUK_USE_ERRTHROW */
+}
+#endif  /* DUK_USE_AUGMENT_ERROR_THROW */
+#line 1 "duk_error_longjmp.c"
+/*
+ *  Do a longjmp call, calling the fatal error handler if no
+ *  catchpoint exists.
+ */
+
+/* include removed: duk_internal.h */
+
+void duk_err_longjmp(duk_hthread *thr) {
+	DUK_ASSERT(thr != NULL);
+
+	if (!thr->heap->lj.jmpbuf_ptr) {
+		/*
+		 *  If we don't have a jmpbuf_ptr, there is little we can do
+		 *  except panic.  The caller's expectation is that we never
+		 *  return.
+		 */
+
+		duk_fatal((duk_context *) thr, DUK_ERR_UNCAUGHT_ERROR, "uncaught error");
+		DUK_UNREACHABLE();
+	}
+
+	DUK_LONGJMP(thr->heap->lj.jmpbuf_ptr->jb, DUK_LONGJMP_DUMMY_VALUE);
+	DUK_UNREACHABLE();
+}
+#line 1 "duk_error_macros.c"
+/*
+ *  Error, fatal, and panic handling.
+ */
+
+/* include removed: duk_internal.h */
+
+#define DUK__ERRFMT_BUFSIZE  256  /* size for formatting buffers */
+
+#ifdef DUK_USE_VERBOSE_ERRORS
+
+#ifdef DUK_USE_VARIADIC_MACROS
+void duk_err_handle_error(const char *filename, duk_int_t line, duk_hthread *thr, duk_errcode_t code, const char *fmt, ...) {
+	va_list ap;
+	char msg[DUK__ERRFMT_BUFSIZE];
+	va_start(ap, fmt);
+	(void) DUK_VSNPRINTF(msg, sizeof(msg), fmt, ap);
+	msg[sizeof(msg) - 1] = (char) 0;
+	duk_err_create_and_throw(thr, code, msg, filename, line);
+	va_end(ap);  /* dead code, but ensures portability (see Linux man page notes) */
+}
+#else  /* DUK_USE_VARIADIC_MACROS */
+const char *duk_err_file_stash = NULL;
+duk_int_t duk_err_line_stash = 0;
+
+DUK_NORETURN(static void duk__handle_error(const char *filename, duk_int_t line, duk_hthread *thr, duk_errcode_t code, const char *fmt, va_list ap));
+
+static void duk__handle_error(const char *filename, duk_int_t line, duk_hthread *thr, duk_errcode_t code, const char *fmt, va_list ap) {
+	char msg[DUK__ERRFMT_BUFSIZE];
+	(void) DUK_VSNPRINTF(msg, sizeof(msg), fmt, ap);
+	msg[sizeof(msg) - 1] = (char) 0;
+	duk_err_create_and_throw(thr, code, msg, filename, line);
+}
+
+void duk_err_handle_error(const char *filename, duk_int_t line, duk_hthread *thr, duk_errcode_t code, const char *fmt, ...) {
+	va_list ap;
+	va_start(ap, fmt);
+	duk__handle_error(filename, line, thr, code, fmt, ap);
+	va_end(ap);  /* dead code */
+}
+
+void duk_err_handle_error_stash(duk_hthread *thr, duk_errcode_t code, const char *fmt, ...) {
+	va_list ap;
+	va_start(ap, fmt);
+	duk__handle_error(duk_err_file_stash, duk_err_line_stash, thr, code, fmt, ap);
+	va_end(ap);  /* dead code */
+}
+#endif  /* DUK_USE_VARIADIC_MACROS */
+
+#else  /* DUK_USE_VERBOSE_ERRORS */
+
+#ifdef DUK_USE_VARIADIC_MACROS
+void duk_err_handle_error(duk_hthread *thr, duk_errcode_t code) {
+	duk_err_create_and_throw(thr, code);
+}
+
+#else  /* DUK_USE_VARIADIC_MACROS */
+void duk_err_handle_error_nonverbose1(duk_hthread *thr, duk_errcode_t code, const char *fmt, ...) {
+	duk_err_create_and_throw(thr, code);
+}
+
+void duk_err_handle_error_nonverbose2(const char *filename, duk_int_t line, duk_hthread *thr, duk_errcode_t code, const char *fmt, ...) {
+	duk_err_create_and_throw(thr, code);
+}
+#endif  /* DUK_USE_VARIADIC_MACROS */
+
+#endif  /* DUK_USE_VERBOSE_ERRORS */
+
+/*
+ *  Default fatal error handler
+ */
+
+void duk_default_fatal_handler(duk_context *ctx, duk_errcode_t code, const char *msg) {
+	DUK_UNREF(ctx);
+#ifdef DUK_USE_FILE_IO
+	DUK_FPRINTF(DUK_STDERR, "FATAL %ld: %s\n", (long) code, (const char *) (msg ? msg : "null"));
+	DUK_FFLUSH(DUK_STDERR);
+#else
+	/* omit print */
+#endif
+	DUK_D(DUK_DPRINT("default fatal handler called, code %ld -> calling DUK_PANIC()", (long) code));
+	DUK_PANIC(code, msg);
+	DUK_UNREACHABLE();
+}
+
+/*
+ *  Default panic handler
+ */
+
+#if !defined(DUK_USE_PANIC_HANDLER)
+void duk_default_panic_handler(duk_errcode_t code, const char *msg) {
+#ifdef DUK_USE_FILE_IO
+	DUK_FPRINTF(DUK_STDERR, "PANIC %ld: %s ("
+#if defined(DUK_USE_PANIC_ABORT)
+	            "calling abort"
+#elif defined(DUK_USE_PANIC_EXIT)
+	            "calling exit"
+#elif defined(DUK_USE_PANIC_SEGFAULT)
+	            "segfaulting on purpose"
+#else
+#error no DUK_USE_PANIC_xxx macro defined
+#endif	
+	            ")\n", (long) code, (const char *) (msg ? msg : "null"));
+	DUK_FFLUSH(DUK_STDERR);
+#else
+	/* omit print */
+	DUK_UNREF(code);
+	DUK_UNREF(msg);
+#endif
+
+#if defined(DUK_USE_PANIC_ABORT)
+	DUK_ABORT();
+#elif defined(DUK_USE_PANIC_EXIT)
+	DUK_EXIT(-1);
+#elif defined(DUK_USE_PANIC_SEGFAULT)
+	/* exit() afterwards to satisfy "noreturn" */
+	DUK_CAUSE_SEGFAULT();  /* SCANBUILD: "Dereference of null pointer", normal */
+	DUK_EXIT(-1);
+#else
+#error no DUK_USE_PANIC_xxx macro defined
+#endif
+
+	DUK_UNREACHABLE();
+}
+#endif  /* !DUK_USE_PANIC_HANDLER */
+
+#undef DUK__ERRFMT_BUFSIZE
+#line 1 "duk_error_misc.c"
+/*
+ *  Error helpers
+ */
+
+/* include removed: duk_internal.h */
+
+/*
+ *  Get prototype object for an integer error code.
+ */
+
+duk_hobject *duk_error_prototype_from_code(duk_hthread *thr, duk_errcode_t code) {
+	switch (code) {
+	case DUK_ERR_EVAL_ERROR:
+		return thr->builtins[DUK_BIDX_EVAL_ERROR_PROTOTYPE];
+	case DUK_ERR_RANGE_ERROR:
+		return thr->builtins[DUK_BIDX_RANGE_ERROR_PROTOTYPE];
+	case DUK_ERR_REFERENCE_ERROR:
+		return thr->builtins[DUK_BIDX_REFERENCE_ERROR_PROTOTYPE];
+	case DUK_ERR_SYNTAX_ERROR:
+		return thr->builtins[DUK_BIDX_SYNTAX_ERROR_PROTOTYPE];
+	case DUK_ERR_TYPE_ERROR:
+		return thr->builtins[DUK_BIDX_TYPE_ERROR_PROTOTYPE];
+	case DUK_ERR_URI_ERROR:
+		return thr->builtins[DUK_BIDX_URI_ERROR_PROTOTYPE];
+
+	/* XXX: more specific error classes? */
+	case DUK_ERR_UNIMPLEMENTED_ERROR:
+	case DUK_ERR_INTERNAL_ERROR:
+	case DUK_ERR_ALLOC_ERROR:
+	case DUK_ERR_ASSERTION_ERROR:
+	case DUK_ERR_API_ERROR:
+	case DUK_ERR_ERROR:
+	default:
+		return thr->builtins[DUK_BIDX_ERROR_PROTOTYPE];
+	}
+}
+
+/*
+ *  Exposed helper for setting up heap longjmp state.
+ */
+
+void duk_err_setup_heap_ljstate(duk_hthread *thr, duk_small_int_t lj_type) {
+	duk_tval tv_tmp;
+
+	thr->heap->lj.type = lj_type;
+
+	DUK_ASSERT(thr->valstack_top > thr->valstack);
+	DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value1);
+	DUK_TVAL_SET_TVAL(&thr->heap->lj.value1, thr->valstack_top - 1);
+	DUK_TVAL_INCREF(thr, &thr->heap->lj.value1);
+	DUK_TVAL_DECREF(thr, &tv_tmp);
+
+	duk_pop((duk_context *) thr);
+}
+#line 1 "duk_error_throw.c"
+/*
+ *  Create and throw an Ecmascript error object based on a code and a message.
+ *
+ *  Used when we throw errors internally.  Ecmascript generated error objects
+ *  are created by Ecmascript code, and the throwing is handled by the bytecode
+ *  executor.
+ */
+
+/* include removed: duk_internal.h */
+
+/*
+ *  Create and throw an error (originating from Duktape internally)
+ *
+ *  Push an error object on top of the stack, possibly throw augmenting
+ *  the error, and finally longjmp.
+ *
+ *  If an error occurs while we're dealing with the current error, we might
+ *  enter an infinite recursion loop.  This is prevented by detecting a
+ *  "double fault" through the heap->handling_error flag; the recursion
+ *  then stops at the second level.
+ */
+
+#ifdef DUK_USE_VERBOSE_ERRORS
+void duk_err_create_and_throw(duk_hthread *thr, duk_errcode_t code, const char *msg, const char *filename, duk_int_t line) {
+#else
+void duk_err_create_and_throw(duk_hthread *thr, duk_errcode_t code) {
+#endif
+	duk_context *ctx = (duk_context *) thr;
+	duk_bool_t double_error = thr->heap->handling_error;
+
+#ifdef DUK_USE_VERBOSE_ERRORS
+	DUK_DD(DUK_DDPRINT("duk_err_create_and_throw(): code=%ld, msg=%s, filename=%s, line=%ld",
+	                   (long) code, (const char *) msg,
+	                   (const char *) filename, (long) line));
+#else
+	DUK_DD(DUK_DDPRINT("duk_err_create_and_throw(): code=%ld", (long) code));
+#endif
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(ctx != NULL);
+
+	thr->heap->handling_error = 1;
+
+	/*
+	 *  Create and push an error object onto the top of stack.
+	 *  If a "double error" occurs, use a fixed error instance
+	 *  to avoid further trouble.
+	 */
+
+	/* FIXME: if attempt to push beyond allocated valstack, this double fault
+	 * handling fails miserably.  We should really write the double error
+	 * directly to thr->heap->lj.value1 and avoid valstack use entirely.
+	 */
+
+	if (double_error) {
+		if (thr->builtins[DUK_BIDX_DOUBLE_ERROR]) {
+			DUK_D(DUK_DPRINT("double fault detected -> push built-in fixed 'double error' instance"));
+			duk_push_hobject_bidx(ctx, DUK_BIDX_DOUBLE_ERROR);
+		} else {
+			DUK_D(DUK_DPRINT("double fault detected; there is no built-in fixed 'double error' instance "
+			                 "-> push the error code as a number"));
+			duk_push_int(ctx, (duk_int_t) code);
+		}
+	} else {
+		/* Error object is augmented at its creation here. */
+		duk_require_stack(ctx, 1);
+		/* XXX: unnecessary '%s' formatting here, but cannot use
+		 * 'msg' as a format string directly.
+		 */
+#ifdef DUK_USE_VERBOSE_ERRORS
+		duk_push_error_object_raw(ctx,
+		                          code | DUK_ERRCODE_FLAG_NOBLAME_FILELINE,
+		                          filename,
+		                          line,
+		                          "%s",
+		                          (const char *) msg);
+#else
+		duk_push_error_object_raw(ctx,
+		                          code | DUK_ERRCODE_FLAG_NOBLAME_FILELINE,
+		                          NULL,
+		                          0,
+		                          NULL);
+#endif
+	}
+
+	/*
+	 *  Augment error (throw time), unless alloc/double error
+	 */
+
+	if (double_error || code == DUK_ERR_ALLOC_ERROR) {
+		DUK_D(DUK_DPRINT("alloc or double error: skip throw augmenting to avoid further trouble"));
+	} else {
+#if defined(DUK_USE_AUGMENT_ERROR_THROW)
+		DUK_DDD(DUK_DDDPRINT("THROW ERROR (INTERNAL): %!iT (before throw augment)",
+		                     (duk_tval *) duk_get_tval(ctx, -1)));
+		duk_err_augment_error_throw(thr);
+#endif
+	}
+
+	/*
+	 *  Finally, longjmp
+	 */
+
+	thr->heap->handling_error = 0;
+
+	duk_err_setup_heap_ljstate(thr, DUK_LJ_TYPE_THROW);
+
+	DUK_DDD(DUK_DDDPRINT("THROW ERROR (INTERNAL): %!iT, %!iT (after throw augment)",
+	                     (duk_tval *) &thr->heap->lj.value1, (duk_tval *) &thr->heap->lj.value2));
+
+	duk_err_longjmp(thr);
+	DUK_UNREACHABLE();
+}
+
+/*
+ *  Helper for C function call negative return values.
+ */
+
+void duk_error_throw_from_negative_rc(duk_hthread *thr, duk_ret_t rc) {
+	duk_context *ctx = (duk_context *) thr;
+	const char *msg;
+	duk_errcode_t code;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(rc < 0);
+
+	/* FIXME: this generates quite large code - perhaps select the error
+	 * class based on the code and then just use the error 'name'?
+	 */
+
+	code = -rc;
+
+	switch (rc) {
+	case DUK_RET_UNIMPLEMENTED_ERROR:  msg = "unimplemented"; break;
+	case DUK_RET_UNSUPPORTED_ERROR:    msg = "unsupported"; break;
+	case DUK_RET_INTERNAL_ERROR:       msg = "internal"; break;
+	case DUK_RET_ALLOC_ERROR:          msg = "alloc"; break;
+	case DUK_RET_ASSERTION_ERROR:      msg = "assertion"; break;
+	case DUK_RET_API_ERROR:            msg = "api"; break;
+	case DUK_RET_UNCAUGHT_ERROR:       msg = "uncaught"; break;
+	case DUK_RET_ERROR:                msg = "error"; break;
+	case DUK_RET_EVAL_ERROR:           msg = "eval"; break;
+	case DUK_RET_RANGE_ERROR:          msg = "range"; break;
+	case DUK_RET_REFERENCE_ERROR:      msg = "reference"; break;
+	case DUK_RET_SYNTAX_ERROR:         msg = "syntax"; break;
+	case DUK_RET_TYPE_ERROR:           msg = "type"; break;
+	case DUK_RET_URI_ERROR:            msg = "uri"; break;
+	default:                           msg = "unknown"; break;
+	}
+
+	DUK_ASSERT(msg != NULL);
+
+	/*
+	 *  The __FILE__ and __LINE__ information is intentionally not used in the
+	 *  creation of the error object, as it isn't useful in the tracedata.  The
+	 *  tracedata still contains the function which returned the negative return
+	 *  code, and having the file/line of this function isn't very useful.
+	 */
+
+	duk_error_raw(ctx, code, NULL, 0, "%s error (rc %ld)", (const char *) msg, (long) rc);
+	DUK_UNREACHABLE();
+}
+#line 1 "duk_hbuffer_alloc.c"
+/*
+ *  duk_hbuffer allocation and freeing.
+ */
+
+/* include removed: duk_internal.h */
+
+duk_hbuffer *duk_hbuffer_alloc(duk_heap *heap, duk_size_t size, duk_bool_t dynamic) {
+	duk_hbuffer *res = NULL;
+	duk_size_t alloc_size;
+
+	DUK_DDD(DUK_DDDPRINT("allocate hbuffer"));
+
+	if (dynamic) {
+		alloc_size = sizeof(duk_hbuffer_dynamic);
+	} else {
+		alloc_size = sizeof(duk_hbuffer_fixed) + size;
+	}
+
+#ifdef DUK_USE_ZERO_BUFFER_DATA
+	/* zero everything */
+	res = (duk_hbuffer *) DUK_ALLOC_ZEROED(heap, alloc_size);
+#else
+	res = (duk_hbuffer *) DUK_ALLOC(heap, alloc_size);
+#endif
+	if (!res) {
+		goto error;
+	}
+
+#ifndef DUK_USE_ZERO_BUFFER_DATA
+	/* if no buffer zeroing, zero the header anyway */
+	DUK_MEMZERO((void *) res, dynamic ? sizeof(duk_hbuffer_dynamic) : sizeof(duk_hbuffer_fixed));
+#endif
+
+	if (dynamic) {
+		duk_hbuffer_dynamic *h = (duk_hbuffer_dynamic *) res;
+		void *ptr;
+		if (size > 0) {
+			DUK_DDD(DUK_DDDPRINT("dynamic buffer with nonzero size, alloc actual buffer"));
+#ifdef DUK_USE_ZERO_BUFFER_DATA
+			ptr = DUK_ALLOC_ZEROED(heap, size);
+#else
+			ptr = DUK_ALLOC(heap, size);
+#endif
+			if (!ptr) {
+				/* Because size > 0, NULL check is correct */
+				goto error;
+			}
+
+			h->curr_alloc = ptr;
+			h->usable_size = size;  /* snug */
+		} else {
+#ifdef DUK_USE_EXPLICIT_NULL_INIT
+			h->curr_alloc = NULL;
+#endif
+			DUK_ASSERT(h->usable_size == 0);
+		}
+	}
+
+	res->size = size;
+
+	DUK_HEAPHDR_SET_TYPE(&res->hdr, DUK_HTYPE_BUFFER);
+	if (dynamic) {
+		DUK_HBUFFER_SET_DYNAMIC(res);
+	}
+        DUK_HEAP_INSERT_INTO_HEAP_ALLOCATED(heap, &res->hdr);
+
+	DUK_DDD(DUK_DDDPRINT("allocated hbuffer: %p", (void *) res));
+	return res;
+
+ error:
+	DUK_DD(DUK_DDPRINT("hbuffer allocation failed"));
+
+	DUK_FREE(heap, res);
+	return NULL;
+}
+
+/* For indirect allocs. */
+
+void *duk_hbuffer_get_dynalloc_ptr(void *ud) {
+	duk_hbuffer_dynamic *buf = (duk_hbuffer_dynamic *) ud;
+	return (void *) buf->curr_alloc;
+}
+#line 1 "duk_hbuffer_ops.c"
+/*
+ *  duk_hbuffer operations such as resizing and inserting/appending data to
+ *  a dynamic buffer.
+ *
+ *  Append operations append to the end of the buffer and they are relatively
+ *  efficient: the buffer is grown with a "spare" part relative to the buffer
+ *  size to minimize reallocations.  Insert operations need to move existing
+ *  data forward in the buffer with memmove() and are not very efficient.
+ *  They are used e.g. by the regexp compiler to "backpatch" regexp bytecode.
+ */
+
+/* include removed: duk_internal.h */
+
+/*
+ *  Resizing
+ */
+
+static duk_size_t duk__add_spare(duk_size_t size) {
+	duk_size_t spare = (size / DUK_HBUFFER_SPARE_DIVISOR) + DUK_HBUFFER_SPARE_ADD;
+	duk_size_t res;
+
+	res = size + spare;
+	if (res < size) {
+		/* XXX: handle corner cases where size is close to size limit (wraparound) */
+		DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "duk_size_t wrapped");
+	}
+	DUK_ASSERT(res >= size);
+
+	return res;
+}
+
+void duk_hbuffer_resize(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_size_t new_size, duk_size_t new_usable_size) {
+	duk_size_t new_alloc_size;
+	void *res;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(buf != NULL);
+	DUK_ASSERT(new_usable_size >= new_size);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
+
+	/*
+	 *  Maximum size check
+	 *
+	 *  XXX: check against usable size?
+	 */
+
+	if (new_size > DUK_HBUFFER_MAX_BYTELEN) {
+		DUK_ERROR(thr, DUK_ERR_RANGE_ERROR, "buffer too long");
+	}
+
+	/*
+	 *  Note: use indirect realloc variant just in case mark-and-sweep
+	 *  (finalizers) might resize this same buffer during garbage
+	 *  collection.
+	 */
+
+	new_alloc_size = new_usable_size;
+	res = DUK_REALLOC_INDIRECT(thr->heap, duk_hbuffer_get_dynalloc_ptr, (void *) buf, new_alloc_size);
+	if (res != NULL || new_alloc_size == 0) {
+		/* 'res' may be NULL if new allocation size is 0. */
+
+		DUK_DDD(DUK_DDDPRINT("resized dynamic buffer %p:%ld:%ld -> %p:%ld:%ld",
+		                     (void *) buf->curr_alloc, (long) buf->size, (long) buf->usable_size,
+		                     (void *) res, (long) new_size, (long) new_usable_size));
+
+		/*
+		 *  The entire allocated buffer area, regardless of actual used
+		 *  size, is kept zeroed in resizes for simplicity.  If the buffer
+		 *  is grown, zero the new part.  Another policy would be to
+		 *  ensure data is zeroed as the used part is extended.  The
+		 *  current approach is much more simple and is not a big deal
+		 *  because the spare part is relatively small.
+		 */
+
+		if (new_alloc_size > buf->usable_size) {
+			DUK_ASSERT(new_alloc_size - buf->usable_size > 0);
+#ifdef DUK_USE_ZERO_BUFFER_DATA
+			DUK_MEMZERO((void *) ((char *) res + buf->usable_size),
+			            new_alloc_size - buf->usable_size);
+#endif
+		}
+
+		buf->size = new_size;
+		buf->usable_size = new_usable_size;
+		buf->curr_alloc = res;
+	} else {
+		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "buffer resize failed: %ld:%ld to %ld:%ld",
+		          (long) buf->size, (long) buf->usable_size,
+		          (long) new_size, (long) new_usable_size);
+	}
+
+	DUK_ASSERT(res != NULL || new_alloc_size == 0);
+}
+
+void duk_hbuffer_reset(duk_hthread *thr, duk_hbuffer_dynamic *buf) {
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(buf != NULL);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
+
+	duk_hbuffer_resize(thr, buf, 0, 0);
+}
+
+void duk_hbuffer_compact(duk_hthread *thr, duk_hbuffer_dynamic *buf) {
+	duk_size_t curr_size;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(buf != NULL);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
+
+	curr_size = DUK_HBUFFER_GET_SIZE(buf);
+	duk_hbuffer_resize(thr, buf, curr_size, curr_size);
+}
+
+/*
+ *  Inserts
+ */
+
+void duk_hbuffer_insert_bytes(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_size_t offset, duk_uint8_t *data, duk_size_t length) {
+	duk_uint8_t *p;
+
+	/* XXX: allow inserts with offset > curr_size? i.e., insert zeroes automatically? */
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(buf != NULL);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
+	DUK_ASSERT_DISABLE(offset >= 0);  /* unsigned, so always true */
+	DUK_ASSERT(offset <= DUK_HBUFFER_GET_SIZE(buf));  /* equality is OK (= append) */
+	DUK_ASSERT(data != NULL);
+	DUK_ASSERT_DISABLE(length >= 0);  /* unsigned, so always true */
+
+	if (length == 0) {
+		return;
+	}
+
+	if (DUK_HBUFFER_DYNAMIC_GET_SPARE_SIZE(buf) < length) {
+		duk_hbuffer_resize(thr,
+		                   buf,
+		                   DUK_HBUFFER_GET_SIZE(buf),
+		                   duk__add_spare(DUK_HBUFFER_GET_SIZE(buf) + length));
+	}
+	DUK_ASSERT(DUK_HBUFFER_DYNAMIC_GET_SPARE_SIZE(buf) >= length);
+
+	p = (duk_uint8_t *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(buf);
+	if (offset < DUK_HBUFFER_GET_SIZE(buf)) {
+		/* not an append */
+
+		DUK_ASSERT(DUK_HBUFFER_GET_SIZE(buf) - offset > 0);
+		DUK_MEMMOVE((void *) (p + offset + length),
+		            (void *) (p + offset),
+		            DUK_HBUFFER_GET_SIZE(buf) - offset);
+	}
+
+	DUK_ASSERT(length > 0);
+	DUK_MEMCPY((void *) (p + offset),
+	           data,
+	           length);
+
+	buf->size += length;
+}
+
+void duk_hbuffer_insert_byte(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_size_t offset, duk_uint8_t byte) {
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(buf != NULL);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
+
+	duk_hbuffer_insert_bytes(thr, buf, offset, &byte, 1);
+}
+
+duk_size_t duk_hbuffer_insert_cstring(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_size_t offset, const char *str) {
+	duk_size_t len;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(buf != NULL);
+	DUK_ASSERT(str != NULL);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
+
+	len = DUK_STRLEN(str);
+	duk_hbuffer_insert_bytes(thr, buf, offset, (duk_uint8_t *) str, len);
+	return len;
+}
+
+duk_size_t duk_hbuffer_insert_hstring(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_size_t offset, duk_hstring *str) {
+	duk_size_t len;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(buf != NULL);
+	DUK_ASSERT(str != NULL);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
+
+	len = DUK_HSTRING_GET_BYTELEN(str);
+	duk_hbuffer_insert_bytes(thr, buf, offset, (duk_uint8_t *) DUK_HSTRING_GET_DATA(str), len);
+	return len;
+}
+
+duk_size_t duk_hbuffer_insert_xutf8(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_size_t offset, duk_ucodepoint_t codepoint) {
+	duk_uint8_t tmp[DUK_UNICODE_MAX_XUTF8_LENGTH];
+	duk_size_t len;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(buf != NULL);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
+	/* No range assertion for 'codepoint' */
+
+	/* Intentionally no fast path: insertion is not that central */
+
+	len = (duk_size_t) duk_unicode_encode_xutf8(codepoint, tmp);
+	duk_hbuffer_insert_bytes(thr, buf, offset, tmp, len);
+	return len;
+}
+
+/* Append a Unicode codepoint to the buffer in CESU-8 format, i.e., convert
+ * non-BMP characters to surrogate pairs which are then "UTF-8" encoded.
+ * If the codepoint is initially a surrogate, it is also encoded into CESU-8.
+ * Codepoints above valid Unicode range (> U+10FFFF) are mangled.
+ */
+
+duk_size_t duk_hbuffer_insert_cesu8(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_size_t offset, duk_ucodepoint_t codepoint) {
+	duk_uint8_t tmp[DUK_UNICODE_MAX_CESU8_LENGTH];
+	duk_size_t len;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(buf != NULL);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
+	DUK_ASSERT_DISABLE(codepoint >= 0);  /* unsigned */
+	DUK_ASSERT(codepoint <= 0x10ffff);  /* if not in this range, results are garbage (but no crash) */
+
+	/* Intentionally no fast path: insertion is not that central */
+
+	len = (duk_size_t) duk_unicode_encode_cesu8(codepoint, tmp);
+	duk_hbuffer_insert_bytes(thr, buf, offset, tmp, len);
+	return len;
+}
+
+/*
+ *  Appends
+ *
+ *  Note: an optimized duk_hbuffer_append_bytes() could be implemented, but
+ *  it is more compact to use duk_hbuffer_insert_bytes() instead.  The
+ *  important fast paths bypass these functions. anyway.
+ */
+
+void duk_hbuffer_append_bytes(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_uint8_t *data, duk_size_t length) {
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(buf != NULL);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
+	DUK_ASSERT(data != NULL);
+
+	duk_hbuffer_insert_bytes(thr, buf, DUK_HBUFFER_GET_SIZE(buf), data, length);
+}
+
+void duk_hbuffer_append_byte(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_uint8_t byte) {
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(buf != NULL);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
+
+	duk_hbuffer_insert_bytes(thr, buf, DUK_HBUFFER_GET_SIZE(buf), &byte, 1);
+}
+
+duk_size_t duk_hbuffer_append_cstring(duk_hthread *thr, duk_hbuffer_dynamic *buf, const char *str) {
+	duk_size_t len;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(buf != NULL);
+	DUK_ASSERT(str != NULL);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
+
+	len = DUK_STRLEN(str);
+	duk_hbuffer_insert_bytes(thr, buf, DUK_HBUFFER_GET_SIZE(buf), (duk_uint8_t *) str, len);
+	return len;
+}
+
+duk_size_t duk_hbuffer_append_hstring(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_hstring *str) {
+	duk_size_t len;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(buf != NULL);
+	DUK_ASSERT(str != NULL);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
+
+	len = DUK_HSTRING_GET_BYTELEN(str);
+	duk_hbuffer_insert_bytes(thr, buf, DUK_HBUFFER_GET_SIZE(buf), (duk_uint8_t *) DUK_HSTRING_GET_DATA(str), len);
+	return len;
+}
+
+/* Append a Unicode codepoint to the buffer in extended UTF-8 format, i.e.
+ * allow codepoints above standard Unicode range (> U+10FFFF) up to seven
+ * byte encoding (36 bits, but argument type is 32 bits).  In particular,
+ * allows encoding of all unsigned 32-bit integers.  If the codepoint is
+ * initially a surrogate, it is encoded without checking (and will become,
+ * effectively, CESU-8 encoded).
+ */
+
+duk_size_t duk_hbuffer_append_xutf8(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_ucodepoint_t codepoint) {
+	duk_uint8_t tmp[DUK_UNICODE_MAX_XUTF8_LENGTH];
+	duk_size_t len;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(buf != NULL);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
+	/* No range assertion for 'codepoint' */
+
+	if (DUK_LIKELY(codepoint < 0x80 && DUK_HBUFFER_DYNAMIC_GET_SPARE_SIZE(buf) > 0)) {
+		/* fast path: ASCII and there is spare */
+		duk_uint8_t *p = ((duk_uint8_t *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(buf));
+		p[buf->size++] = (duk_uint8_t) codepoint;
+		return 1;
+	}
+
+	len = (duk_size_t) duk_unicode_encode_xutf8(codepoint, tmp);
+	duk_hbuffer_insert_bytes(thr, buf, DUK_HBUFFER_GET_SIZE(buf), tmp, len);
+	return len;
+}
+
+/* Append a Unicode codepoint to the buffer in CESU-8 format, i.e., convert
+ * non-BMP characters to surrogate pairs which are then "UTF-8" encoded.
+ * If the codepoint is initially a surrogate, it is also encoded into CESU-8.
+ * Codepoints above valid Unicode range (> U+10FFFF) are mangled.
+ */
+
+duk_size_t duk_hbuffer_append_cesu8(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_ucodepoint_t codepoint) {
+	duk_uint8_t tmp[DUK_UNICODE_MAX_CESU8_LENGTH];
+	duk_size_t len;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(buf != NULL);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
+	DUK_ASSERT_DISABLE(codepoint >= 0);  /* unsigned */
+	DUK_ASSERT(codepoint <= 0x10ffff);  /* if not in this range, results are garbage (but no crash) */
+
+	if (DUK_LIKELY(codepoint < 0x80 && DUK_HBUFFER_DYNAMIC_GET_SPARE_SIZE(buf) > 0)) {
+		/* fast path: ASCII and there is spare */
+		duk_uint8_t *p = ((duk_uint8_t *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(buf));
+		p[buf->size++] = (duk_uint8_t) codepoint;
+		return 1;
+	}
+
+	len = (duk_size_t) duk_unicode_encode_cesu8(codepoint, tmp);
+	duk_hbuffer_insert_bytes(thr, buf, DUK_HBUFFER_GET_SIZE(buf), tmp, len);
+	return len;
+}
+
+/* Append an duk_uint32_t in native byte order.  This is used to emit bytecode
+ * instructions.
+ */
+
+void duk_hbuffer_append_native_u32(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_uint32_t val) {
+	/* relies on duk_uint32_t being exactly right size */
+	DUK_ASSERT(sizeof(val) == 4);
+	duk_hbuffer_insert_bytes(thr,
+	                         buf,
+	                         DUK_HBUFFER_GET_SIZE(buf),
+	                         (duk_uint8_t *) &val,
+	                         sizeof(duk_uint32_t));
+}
+
+/*
+ *  In-buffer "slices"
+ *
+ *  Slices are identified with an offset+length pair, referring to the current
+ *  buffer data.  A caller cannot otherwise reliably refer to existing data,
+ *  because the buffer may be reallocated before a data pointer is referenced.
+ */
+
+void duk_hbuffer_remove_slice(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_size_t offset, duk_size_t length) {
+	duk_uint8_t *p;
+	duk_size_t end_offset;
+
+	DUK_UNREF(thr);
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(buf != NULL);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
+	DUK_ASSERT_DISABLE(offset >= 0);                               /* always true */
+	DUK_ASSERT(offset <= DUK_HBUFFER_GET_SIZE(buf));               /* allow equality */
+	DUK_ASSERT_DISABLE(length >= 0);                               /* always true */
+	DUK_ASSERT(offset + length <= DUK_HBUFFER_GET_SIZE(buf));      /* allow equality */
+
+	if (length == 0) {
+		return;
+	}
+
+	p = (duk_uint8_t *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(buf);
+
+	end_offset = offset + length;
+
+	if (end_offset < DUK_HBUFFER_GET_SIZE(buf)) {
+		/* not strictly from end of buffer; need to shuffle data */
+		DUK_ASSERT(DUK_HBUFFER_GET_SIZE(buf) - end_offset > 0);
+		DUK_MEMMOVE(p + offset,
+		            p + end_offset,
+		            DUK_HBUFFER_GET_SIZE(buf) - end_offset);
+	}
+
+	/* Here we want to zero data even with automatic buffer zeroing
+	 * disabled as we depend on this internally too.
+	 */
+	DUK_ASSERT(length > 0);
+	DUK_MEMZERO(p + DUK_HBUFFER_GET_SIZE(buf) - length,
+	            length);
+
+	buf->size -= length;
+
+	/* Note: no shrink check, intentional */
+}
+
+void duk_hbuffer_insert_slice(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_size_t dst_offset, duk_size_t src_offset, duk_size_t length) {
+	duk_uint8_t *p;
+	duk_size_t src_end_offset;  /* source end (exclusive) in initial buffer */
+	duk_size_t len;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(buf != NULL);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
+	DUK_ASSERT_DISABLE(dst_offset >= 0);                           /* always true */
+	DUK_ASSERT(dst_offset <= DUK_HBUFFER_GET_SIZE(buf));           /* allow equality */
+	DUK_ASSERT_DISABLE(src_offset >= 0);                           /* always true */
+	DUK_ASSERT(src_offset <= DUK_HBUFFER_GET_SIZE(buf));           /* allow equality */
+	DUK_ASSERT_DISABLE(length >= 0);                               /* always true */
+	DUK_ASSERT(src_offset + length <= DUK_HBUFFER_GET_SIZE(buf));  /* allow equality */
+
+	if (length == 0) {
+		return;
+	}
+
+	if (DUK_HBUFFER_DYNAMIC_GET_SPARE_SIZE(buf) < length) {
+		duk_hbuffer_resize(thr,
+		                   buf,
+		                   DUK_HBUFFER_GET_SIZE(buf),
+		                   duk__add_spare(DUK_HBUFFER_GET_SIZE(buf) + length));
+	}
+	DUK_ASSERT(DUK_HBUFFER_DYNAMIC_GET_SPARE_SIZE(buf) >= length);
+
+	p = (duk_uint8_t *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(buf);
+	DUK_ASSERT(p != NULL);  /* must be the case because length > 0, and buffer has been resized if necessary */
+
+	/*
+	 *  src_offset and dst_offset refer to the state of the buffer
+	 *  before any changes are made.  This must be taken into account
+	 *  when moving data around; in particular, the source data may
+	 *  "straddle" the dst_offset, so the insert may need to be handled
+	 *  in two pieces.
+	 */
+
+	src_end_offset = src_offset + length;
+
+	/* create a hole for the insert */
+	len = DUK_HBUFFER_GET_SIZE(buf) - dst_offset;
+	DUK_MEMMOVE(p + dst_offset + length,
+	            p + dst_offset,
+	            len);  /* zero size is not an issue: pointers are valid */
+
+	if (src_offset < dst_offset) {
+		if (src_end_offset <= dst_offset) {
+			/* entire source is before 'dst_offset' */
+			DUK_MEMCPY(p + dst_offset,
+			           p + src_offset,
+			           length);
+		} else {
+			/* part of the source is before 'dst_offset'; straddles */
+			len = dst_offset - src_offset;
+			DUK_ASSERT(len >= 1 && len < length);
+			DUK_ASSERT(length - len >= 1);
+			DUK_MEMCPY(p + dst_offset,
+			           p + src_offset,
+			           len);
+			DUK_MEMCPY(p + dst_offset + len,
+			           p + src_offset + length + len,  /* take above memmove() into account */
+			           length - len);
+		}
+	} else {
+		/* entire source is after 'dst_offset' */
+		DUK_MEMCPY(p + dst_offset,
+		           p + src_offset + length,  /* take above memmove() into account */
+		           length);
+	}
+
+	buf->size += length;
+}
+
+void duk_hbuffer_append_slice(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_size_t src_offset, duk_size_t length) {
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(buf != NULL);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
+	DUK_ASSERT_DISABLE(src_offset >= 0);                           /* always true */
+	DUK_ASSERT(src_offset <= DUK_HBUFFER_GET_SIZE(buf));           /* allow equality */
+	DUK_ASSERT_DISABLE(length >= 0);                               /* always true */
+	DUK_ASSERT(src_offset + length <= DUK_HBUFFER_GET_SIZE(buf));  /* allow equality */
+
+	duk_hbuffer_insert_slice(thr,
+	                         buf,
+	                         DUK_HBUFFER_GET_SIZE(buf),
+	                         src_offset,
+	                         length);
+}
+#line 1 "duk_heap_alloc.c"
+/*
+ *  duk_heap allocation and freeing.
+ */
+
+/* include removed: duk_internal.h */
+
+/* constants for built-in string data depacking */
+#define DUK__BITPACK_LETTER_LIMIT  26
+#define DUK__BITPACK_UNDERSCORE    26
+#define DUK__BITPACK_FF            27
+#define DUK__BITPACK_SWITCH1       29
+#define DUK__BITPACK_SWITCH        30
+#define DUK__BITPACK_SEVENBIT      31
+
+/*
+ *  Free a heap object.
+ *
+ *  Free heap object and its internal (non-heap) pointers.  Assumes that
+ *  caller has removed the object from heap allocated list or the string
+ *  intern table, and any weak references (which strings may have) have
+ *  been already dealt with.
+ */
+
+static void duk__free_hobject_inner(duk_heap *heap, duk_hobject *h) {
+	DUK_ASSERT(heap != NULL);
+	DUK_ASSERT(h != NULL);
+
+	DUK_FREE(heap, h->p);
+
+	if (DUK_HOBJECT_IS_COMPILEDFUNCTION(h)) {
+		duk_hcompiledfunction *f = (duk_hcompiledfunction *) h;
+		DUK_UNREF(f);
+		/* Currently nothing to free; 'data' is a heap object */
+	} else if (DUK_HOBJECT_IS_NATIVEFUNCTION(h)) {
+		duk_hnativefunction *f = (duk_hnativefunction *) h;
+		DUK_UNREF(f);
+		/* Currently nothing to free */
+	} else if (DUK_HOBJECT_IS_THREAD(h)) {
+		duk_hthread *t = (duk_hthread *) h;
+		DUK_FREE(heap, t->valstack);
+		DUK_FREE(heap, t->callstack);
+		DUK_FREE(heap, t->catchstack);
+		/* Don't free h->resumer because it exists in the heap.
+		 * Callstack entries also contain function pointers which
+		 * are not freed for the same reason.
+		 */
+
+		/* XXX: with 'caller' property the callstack would need
+		 * to be unwound to update the 'caller' properties of
+		 * functions in the callstack.
+		 */
+	}
+}
+
+static void duk__free_hbuffer_inner(duk_heap *heap, duk_hbuffer *h) {
+	DUK_ASSERT(heap != NULL);
+	DUK_ASSERT(h != NULL);
+
+	if (DUK_HBUFFER_HAS_DYNAMIC(h)) {
+		duk_hbuffer_dynamic *g = (duk_hbuffer_dynamic *) h;
+		DUK_DDD(DUK_DDDPRINT("free dynamic buffer %p", (void *) g->curr_alloc));
+		DUK_FREE(heap, g->curr_alloc);
+	}
+}
+
+void duk_heap_free_heaphdr_raw(duk_heap *heap, duk_heaphdr *hdr) {
+	DUK_ASSERT(heap);
+	DUK_ASSERT(hdr);
+
+	DUK_DDD(DUK_DDDPRINT("free heaphdr %p, htype %ld", (void *) hdr, (long) DUK_HEAPHDR_GET_TYPE(hdr)));
+
+	switch ((int) DUK_HEAPHDR_GET_TYPE(hdr)) {
+	case DUK_HTYPE_STRING:
+		/* no inner refs to free */
+		break;
+	case DUK_HTYPE_OBJECT:
+		duk__free_hobject_inner(heap, (duk_hobject *) hdr);
+		break;
+	case DUK_HTYPE_BUFFER:
+		duk__free_hbuffer_inner(heap, (duk_hbuffer *) hdr);
+		break;
+	default:
+		DUK_UNREACHABLE();
+	}
+
+	DUK_FREE(heap, hdr);
+}
+
+/*
+ *  Free the heap.
+ *
+ *  Frees heap-related non-heap-tracked allocations such as the
+ *  string intern table; then frees the heap allocated objects;
+ *  and finally frees the heap structure itself.  Reference counts
+ *  and GC markers are ignored (and not updated) in this process,
+ *  and finalizers won't be called.
+ *
+ *  The heap pointer and heap object pointers must not be used
+ *  after this call.
+ */
+
+static void duk__free_allocated(duk_heap *heap) {
+	duk_heaphdr *curr;
+	duk_heaphdr *next;
+
+	curr = heap->heap_allocated;
+	while (curr) {
+		/* We don't log or warn about freeing zero refcount objects
+		 * because they may happen with finalizer processing.
+		 */
+
+		DUK_DDD(DUK_DDDPRINT("FINALFREE (allocated): %!iO",
+		                     (duk_heaphdr *) curr));
+		next = DUK_HEAPHDR_GET_NEXT(curr);
+		duk_heap_free_heaphdr_raw(heap, curr);
+		curr = next;
+	}
+}
+
+#ifdef DUK_USE_REFERENCE_COUNTING
+static void duk__free_refzero_list(duk_heap *heap) {
+	duk_heaphdr *curr;
+	duk_heaphdr *next;
+
+	curr = heap->refzero_list;
+	while (curr) {
+		DUK_DDD(DUK_DDDPRINT("FINALFREE (refzero_list): %!iO",
+		                     (duk_heaphdr *) curr));
+		next = DUK_HEAPHDR_GET_NEXT(curr);
+		duk_heap_free_heaphdr_raw(heap, curr);
+		curr = next;
+	}
+}
+#endif
+
+#ifdef DUK_USE_MARK_AND_SWEEP
+static void duk__free_markandsweep_finalize_list(duk_heap *heap) {
+	duk_heaphdr *curr;
+	duk_heaphdr *next;
+
+	curr = heap->finalize_list;
+	while (curr) {
+		DUK_DDD(DUK_DDDPRINT("FINALFREE (finalize_list): %!iO",
+		                     (duk_heaphdr *) curr));
+		next = DUK_HEAPHDR_GET_NEXT(curr);
+		duk_heap_free_heaphdr_raw(heap, curr);
+		curr = next;
+	}
+}
+#endif
+
+static void duk__free_stringtable(duk_heap *heap) {
+	duk_uint_fast32_t i;
+
+	/* strings are only tracked by stringtable */
+	if (heap->st) {
+		for (i = 0; i < (duk_uint_fast32_t) heap->st_size; i++) {
+			duk_hstring *e = heap->st[i];
+			if (e == DUK_STRTAB_DELETED_MARKER(heap)) {
+				continue;
+			}
+
+			/* strings have no inner allocations so free directly */
+			DUK_DDD(DUK_DDDPRINT("FINALFREE (string): %!iO",
+			                     (duk_heaphdr *) e));
+			DUK_FREE(heap, e);
+#if 0  /* not strictly necessary */
+			heap->st[i] = NULL;
+#endif
+		}
+		DUK_FREE(heap, heap->st);
+#if 0  /* not strictly necessary */
+		heap->st = NULL;
+#endif
+	}
+}
+
+static void duk__free_run_finalizers(duk_heap *heap) {
+	duk_heaphdr *curr;
+#ifdef DUK_USE_DEBUG
+	duk_size_t count_obj = 0;
+#endif
+
+	DUK_ASSERT(heap != NULL);
+	DUK_ASSERT(heap->heap_thread != NULL);
+#ifdef DUK_USE_REFERENCE_COUNTING
+	DUK_ASSERT(heap->refzero_list == NULL);  /* refzero not running -> must be empty */
+#endif
+#ifdef DUK_USE_MARK_AND_SWEEP
+	DUK_ASSERT(heap->finalize_list == NULL);  /* mark-and-sweep not running -> must be empty */
+#endif
+
+	/* FIXME: here again finalizer thread is the heap_thread which needs
+	 * to be coordinated with finalizer thread fixes.
+	 */
+
+	curr = heap->heap_allocated;
+	while (curr) {
+		if (DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT) {
+			/* Only objects in heap_allocated may have finalizers. */
+			DUK_ASSERT(heap->heap_thread != NULL);
+			DUK_ASSERT(curr != NULL);
+			duk_hobject_run_finalizer(heap->heap_thread, (duk_hobject *) curr);
+#ifdef DUK_USE_DEBUG
+			count_obj++;
+#endif
+		}
+		curr = DUK_HEAPHDR_GET_NEXT(curr);
+	}
+
+	/* Note: count includes all objects, not only those with an actual finalizer. */
+#ifdef DUK_USE_DEBUG
+	DUK_D(DUK_DPRINT("checked %ld objects for finalizers before freeing heap", (long) count_obj));
+#endif
+}
+
+void duk_heap_free(duk_heap *heap) {
+	DUK_D(DUK_DPRINT("free heap: %p", (void *) heap));
+
+	/* Execute finalizers before freeing the heap, even for reachable
+	 * objects, and regardless of whether or not mark-and-sweep is
+	 * enabled.  This gives finalizers the chance to free any native
+	 * resources like file handles, allocations made outside Duktape,
+	 * etc.
+	 *
+	 * FIXME: this perhaps requires an execution time limit.
+	 */
+	DUK_D(DUK_DPRINT("execute finalizers before freeing heap"));
+#ifdef DUK_USE_MARK_AND_SWEEP
+	/* run mark-and-sweep a few times just in case (unreachable
+	 * object finalizers run already here)
+	 */
+	duk_heap_mark_and_sweep(heap, 0);
+	duk_heap_mark_and_sweep(heap, 0);
+#endif
+	duk__free_run_finalizers(heap);
+
+	/* Note: heap->heap_thread, heap->curr_thread, heap->heap_object,
+	 * and heap->log_buffer are on the heap allocated list.
+	 */
+
+	DUK_D(DUK_DPRINT("freeing heap objects of heap: %p", (void *) heap));
+	duk__free_allocated(heap);
+
+#ifdef DUK_USE_REFERENCE_COUNTING
+	DUK_D(DUK_DPRINT("freeing refzero list of heap: %p", (void *) heap));
+	duk__free_refzero_list(heap);
+#endif
+
+#ifdef DUK_USE_MARK_AND_SWEEP
+	DUK_D(DUK_DPRINT("freeing mark-and-sweep finalize list of heap: %p", (void *) heap));
+	duk__free_markandsweep_finalize_list(heap);
+#endif
+
+	DUK_D(DUK_DPRINT("freeing string table of heap: %p", (void *) heap));
+	duk__free_stringtable(heap);
+
+	DUK_D(DUK_DPRINT("freeing heap structure: %p", (void *) heap));
+	heap->free_func(heap->alloc_udata, heap);
+}
+
+/*
+ *  Allocate a heap.
+ *
+ *  String table is initialized with built-in strings from genstrings.py.
+ */
+
+/* intern built-in strings from precooked data (genstrings.py) */
+static int duk__init_heap_strings(duk_heap *heap) {
+	duk_bitdecoder_ctx bd_ctx;
+	duk_bitdecoder_ctx *bd = &bd_ctx;  /* convenience */
+	duk_small_uint_t i, j;
+
+	DUK_MEMZERO(&bd_ctx, sizeof(bd_ctx));
+	bd->data = (const duk_uint8_t *) duk_strings_data;
+	bd->length = (duk_size_t) DUK_STRDATA_DATA_LENGTH;
+
+	for (i = 0; i < DUK_HEAP_NUM_STRINGS; i++) {
+		duk_uint8_t tmp[DUK_STRDATA_MAX_STRLEN];
+		duk_hstring *h;
+		duk_small_uint_t len;
+		duk_small_uint_t mode;
+		duk_small_uint_t t;
+
+		len = duk_bd_decode(bd, 5);
+		mode = 32;		/* 0 = uppercase, 32 = lowercase (= 'a' - 'A') */
+		for (j = 0; j < len; j++) {
+			t = duk_bd_decode(bd, 5);
+			if (t < DUK__BITPACK_LETTER_LIMIT) {
+				t = t + DUK_ASC_UC_A + mode;
+			} else if (t == DUK__BITPACK_UNDERSCORE) {
+				t = DUK_ASC_UNDERSCORE;
+			} else if (t == DUK__BITPACK_FF) {
+				/* Internal keys are prefixed with 0xFF in the stringtable
+				 * (which makes them invalid UTF-8 on purpose).
+				 */
+				t = 0xff;
+			} else if (t == DUK__BITPACK_SWITCH1) {
+				t = duk_bd_decode(bd, 5);
+				DUK_ASSERT_DISABLE(t >= 0);  /* unsigned */
+				DUK_ASSERT(t <= 25);
+				t = t + DUK_ASC_UC_A + (mode ^ 32);
+			} else if (t == DUK__BITPACK_SWITCH) {
+				mode = mode ^ 32;
+				t = duk_bd_decode(bd, 5);
+				DUK_ASSERT_DISABLE(t >= 0);
+				DUK_ASSERT(t <= 25);
+				t = t + DUK_ASC_UC_A + mode;
+			} else if (t == DUK__BITPACK_SEVENBIT) {
+				t = duk_bd_decode(bd, 7);
+			}
+			tmp[j] = (duk_uint8_t) t;
+		}
+
+		DUK_DDD(DUK_DDDPRINT("intern built-in string %ld", (long) i));
+		h = duk_heap_string_intern(heap, tmp, len);
+		if (!h) {
+			goto error;
+		}
+
+		/* special flags */
+
+		if (len > 0 && tmp[0] == (duk_uint8_t) 0xff) {
+			DUK_HSTRING_SET_INTERNAL(h);
+		}
+		if (i == DUK_STRIDX_EVAL || i == DUK_STRIDX_LC_ARGUMENTS) {
+			DUK_HSTRING_SET_EVAL_OR_ARGUMENTS(h);
+		}
+		if (i >= DUK_STRIDX_START_RESERVED && i < DUK_STRIDX_END_RESERVED) {
+			DUK_HSTRING_SET_RESERVED_WORD(h);
+			if (i >= DUK_STRIDX_START_STRICT_RESERVED) {
+				DUK_HSTRING_SET_STRICT_RESERVED_WORD(h);
+			}
+		}
+
+		DUK_DDD(DUK_DDDPRINT("interned: %!O", (duk_heaphdr *) h));
+
+		/* XXX: The incref macro takes a thread pointer but doesn't
+		 * use it right now.
+		 */
+		DUK_HSTRING_INCREF(_never_referenced_, h);
+
+		heap->strs[i] = h;
+	}
+
+	return 1;
+
+ error:
+	return 0;
+}
+
+static int duk__init_heap_thread(duk_heap *heap) {
+	duk_hthread *thr;
+	
+	DUK_DD(DUK_DDPRINT("heap init: alloc heap thread"));
+	thr = duk_hthread_alloc(heap,
+	                        DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                        DUK_HOBJECT_FLAG_THREAD |
+	                        DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_THREAD));
+	if (!thr) {
+		DUK_D(DUK_DPRINT("failed to alloc heap_thread"));
+		return 0;
+	}
+	thr->state = DUK_HTHREAD_STATE_INACTIVE;
+	thr->strs = heap->strs;
+
+	heap->heap_thread = thr;
+	DUK_HTHREAD_INCREF(thr, thr);  /* Note: first argument not really used */
+
+	/* 'thr' is now reachable */
+
+	if (!duk_hthread_init_stacks(heap, thr)) {
+		return 0;
+	}
+
+	/* FIXME: this may now fail, and is not handled correctly */
+	duk_hthread_create_builtin_objects(thr);
+
+	/* default prototype (Note: 'thr' must be reachable) */
+	DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, (duk_hobject *) thr, thr->builtins[DUK_BIDX_THREAD_PROTOTYPE]);
+
+	return 1;
+}
+
+#ifdef DUK_USE_DEBUG
+#define DUK__DUMPSZ(t)  do { \
+		DUK_D(DUK_DPRINT("" #t "=%ld", (long) sizeof(t))); \
+	} while (0)
+
+/* These is not 100% because format would need to be non-portable "long long".
+ * Also print out as doubles to catch cases where the "long" type is not wide
+ * enough; the limits will then not be printed accurately but the magnitude
+ * will be correct.
+ */
+#define DUK__DUMPLM_SIGNED_RAW(t,a,b)  do { \
+		DUK_D(DUK_DPRINT(t "=[%ld,%ld]=[%lf,%lf]", \
+		                 (long) (a), (long) (b), \
+		                 (double) (a), (double) (b))); \
+	} while(0)
+#define DUK__DUMPLM_UNSIGNED_RAW(t,a,b)  do { \
+		DUK_D(DUK_DPRINT(t "=[%lu,%lu]=[%lf,%lf]", \
+		                 (unsigned long) (a), (unsigned long) (b), \
+		                 (double) (a), (double) (b))); \
+	} while(0)
+#define DUK__DUMPLM_SIGNED(t)  do { \
+		DUK__DUMPLM_SIGNED_RAW("DUK_" #t "_{MIN,MAX}", DUK_##t##_MIN, DUK_##t##_MAX); \
+	} while(0)
+#define DUK__DUMPLM_UNSIGNED(t)  do { \
+		DUK__DUMPLM_UNSIGNED_RAW("DUK_" #t "_{MIN,MAX}", DUK_##t##_MIN, DUK_##t##_MAX); \
+	} while(0)
+
+static void duk__dump_type_sizes(void) {
+	DUK_D(DUK_DPRINT("sizeof()"));
+
+	/* basic platform types */
+	DUK__DUMPSZ(char);
+	DUK__DUMPSZ(short);
+	DUK__DUMPSZ(int);
+	DUK__DUMPSZ(long);
+	DUK__DUMPSZ(double);
+	DUK__DUMPSZ(void *);
+	DUK__DUMPSZ(size_t);
+
+	/* basic types from duk_features.h */
+	DUK__DUMPSZ(duk_uint8_t);
+	DUK__DUMPSZ(duk_int8_t);
+	DUK__DUMPSZ(duk_uint16_t);
+	DUK__DUMPSZ(duk_int16_t);
+	DUK__DUMPSZ(duk_uint32_t);
+	DUK__DUMPSZ(duk_int32_t);
+	DUK__DUMPSZ(duk_uint64_t);
+	DUK__DUMPSZ(duk_int64_t);
+	DUK__DUMPSZ(duk_uint_least8_t);
+	DUK__DUMPSZ(duk_int_least8_t);
+	DUK__DUMPSZ(duk_uint_least16_t);
+	DUK__DUMPSZ(duk_int_least16_t);
+	DUK__DUMPSZ(duk_uint_least32_t);
+	DUK__DUMPSZ(duk_int_least32_t);
+#if defined(DUK_USE_64BIT_OPS)
+	DUK__DUMPSZ(duk_uint_least64_t);
+	DUK__DUMPSZ(duk_int_least64_t);
+#endif
+	DUK__DUMPSZ(duk_uint_fast8_t);
+	DUK__DUMPSZ(duk_int_fast8_t);
+	DUK__DUMPSZ(duk_uint_fast16_t);
+	DUK__DUMPSZ(duk_int_fast16_t);
+	DUK__DUMPSZ(duk_uint_fast32_t);
+	DUK__DUMPSZ(duk_int_fast32_t);
+#if defined(DUK_USE_64BIT_OPS)
+	DUK__DUMPSZ(duk_uint_fast64_t);
+	DUK__DUMPSZ(duk_int_fast64_t);
+#endif
+	DUK__DUMPSZ(duk_uintptr_t);
+	DUK__DUMPSZ(duk_intptr_t);
+	DUK__DUMPSZ(duk_uintmax_t);
+	DUK__DUMPSZ(duk_intmax_t);
+	DUK__DUMPSZ(duk_double_t);
+
+	/* important chosen base types */
+	DUK__DUMPSZ(duk_int_t);
+	DUK__DUMPSZ(duk_uint_t);
+	DUK__DUMPSZ(duk_int_fast_t);
+	DUK__DUMPSZ(duk_uint_fast_t);
+	DUK__DUMPSZ(duk_small_int_t);
+	DUK__DUMPSZ(duk_small_uint_t);
+	DUK__DUMPSZ(duk_small_int_fast_t);
+	DUK__DUMPSZ(duk_small_uint_fast_t);
+
+	/* some derived types */
+	DUK__DUMPSZ(duk_codepoint_t);
+	DUK__DUMPSZ(duk_ucodepoint_t);
+	DUK__DUMPSZ(duk_idx_t);
+	DUK__DUMPSZ(duk_errcode_t);
+	DUK__DUMPSZ(duk_uarridx_t);
+
+	/* tval */
+	DUK__DUMPSZ(duk_double_union);
+	DUK__DUMPSZ(duk_tval);
+
+	/* structs from duk_forwdecl.h */
+	DUK__DUMPSZ(duk_jmpbuf);
+	DUK__DUMPSZ(duk_heaphdr);
+	DUK__DUMPSZ(duk_heaphdr_string);
+	DUK__DUMPSZ(duk_hstring);
+	DUK__DUMPSZ(duk_hobject);
+	DUK__DUMPSZ(duk_hcompiledfunction);
+	DUK__DUMPSZ(duk_hnativefunction);
+	DUK__DUMPSZ(duk_hthread);
+	DUK__DUMPSZ(duk_hbuffer);
+	DUK__DUMPSZ(duk_hbuffer_fixed);
+	DUK__DUMPSZ(duk_hbuffer_dynamic);
+	DUK__DUMPSZ(duk_propaccessor);
+	DUK__DUMPSZ(duk_propvalue);
+	DUK__DUMPSZ(duk_propdesc);
+	DUK__DUMPSZ(duk_heap);
+	DUK__DUMPSZ(duk_activation);
+	DUK__DUMPSZ(duk_catcher);
+	DUK__DUMPSZ(duk_strcache);
+	DUK__DUMPSZ(duk_ljstate);
+	DUK__DUMPSZ(duk_fixedbuffer);
+	DUK__DUMPSZ(duk_bitdecoder_ctx);
+	DUK__DUMPSZ(duk_bitencoder_ctx);
+	DUK__DUMPSZ(duk_token);
+	DUK__DUMPSZ(duk_re_token);
+	DUK__DUMPSZ(duk_lexer_point);
+	DUK__DUMPSZ(duk_lexer_ctx);
+	DUK__DUMPSZ(duk_compiler_instr);
+	DUK__DUMPSZ(duk_compiler_func);
+	DUK__DUMPSZ(duk_compiler_ctx);
+	DUK__DUMPSZ(duk_re_matcher_ctx);
+	DUK__DUMPSZ(duk_re_compiler_ctx);
+}
+static void duk__dump_type_limits(void) {
+	DUK_D(DUK_DPRINT("limits"));
+
+	/* basic types */
+	DUK__DUMPLM_SIGNED(INT8);
+	DUK__DUMPLM_UNSIGNED(UINT8);
+	DUK__DUMPLM_SIGNED(INT_FAST8);
+	DUK__DUMPLM_UNSIGNED(UINT_FAST8);
+	DUK__DUMPLM_SIGNED(INT_LEAST8);
+	DUK__DUMPLM_UNSIGNED(UINT_LEAST8);
+	DUK__DUMPLM_SIGNED(INT16);
+	DUK__DUMPLM_UNSIGNED(UINT16);
+	DUK__DUMPLM_SIGNED(INT_FAST16);
+	DUK__DUMPLM_UNSIGNED(UINT_FAST16);
+	DUK__DUMPLM_SIGNED(INT_LEAST16);
+	DUK__DUMPLM_UNSIGNED(UINT_LEAST16);
+	DUK__DUMPLM_SIGNED(INT32);
+	DUK__DUMPLM_UNSIGNED(UINT32);
+	DUK__DUMPLM_SIGNED(INT_FAST32);
+	DUK__DUMPLM_UNSIGNED(UINT_FAST32);
+	DUK__DUMPLM_SIGNED(INT_LEAST32);
+	DUK__DUMPLM_UNSIGNED(UINT_LEAST32);
+#if defined(DUK_USE_64BIT_OPS)
+	DUK__DUMPLM_SIGNED(INT64);
+	DUK__DUMPLM_UNSIGNED(UINT64);
+	DUK__DUMPLM_SIGNED(INT_FAST64);
+	DUK__DUMPLM_UNSIGNED(UINT_FAST64);
+	DUK__DUMPLM_SIGNED(INT_LEAST64);
+	DUK__DUMPLM_UNSIGNED(UINT_LEAST64);
+#endif
+	DUK__DUMPLM_SIGNED(INTPTR);
+	DUK__DUMPLM_UNSIGNED(UINTPTR);
+	DUK__DUMPLM_SIGNED(INTMAX);
+	DUK__DUMPLM_UNSIGNED(UINTMAX);
+
+	/* derived types */
+	DUK__DUMPLM_SIGNED(INT);
+	DUK__DUMPLM_UNSIGNED(UINT);
+	DUK__DUMPLM_SIGNED(INT_FAST);
+	DUK__DUMPLM_UNSIGNED(UINT_FAST);
+	DUK__DUMPLM_SIGNED(SMALL_INT);
+	DUK__DUMPLM_UNSIGNED(SMALL_UINT);
+	DUK__DUMPLM_SIGNED(SMALL_INT_FAST);
+	DUK__DUMPLM_UNSIGNED(SMALL_UINT_FAST);
+}
+#undef DUK__DUMPSZ
+#undef DUK__DUMPLM_SIGNED_RAW
+#undef DUK__DUMPLM_UNSIGNED_RAW
+#undef DUK__DUMPLM_SIGNED
+#undef DUK__DUMPLM_UNSIGNED
+#endif  /* DUK_USE_DEBUG */
+
+duk_heap *duk_heap_alloc(duk_alloc_function alloc_func,
+                         duk_realloc_function realloc_func,
+                         duk_free_function free_func,
+                         void *alloc_udata,
+                         duk_fatal_function fatal_func) {
+	duk_heap *res = NULL;
+
+	DUK_D(DUK_DPRINT("allocate heap"));
+
+	/* Debug dump type sizes */
+#ifdef DUK_USE_DEBUG
+	duk__dump_type_sizes();
+	duk__dump_type_limits();
+#endif
+
+	/* If selftests enabled, run them as early as possible. */
+#ifdef DUK_USE_SELF_TESTS
+	DUK_D(DUK_DPRINT("running self tests"));
+	duk_selftest_run_tests();
+	DUK_D(DUK_DPRINT("self tests passed"));
+#endif
+
+#ifdef DUK_USE_COMPUTED_NAN
+	do {
+		/* Workaround for some exotic platforms where NAN is missing
+		 * and the expression (0.0 / 0.0) does NOT result in a NaN.
+		 * Such platforms use the global 'duk_computed_nan' which must
+		 * be initialized at runtime.  Use 'volatile' to ensure that
+		 * the compiler will actually do the computation and not try
+		 * to do constant folding which might result in the original
+		 * problem.
+		 */
+		volatile double dbl1 = 0.0;
+		volatile double dbl2 = 0.0;
+		duk_computed_nan = dbl1 / dbl2;
+	} while (0);
+#endif
+
+#ifdef DUK_USE_COMPUTED_INFINITY
+	do {
+		/* Similar workaround for INFINITY. */
+		volatile double dbl1 = 1.0;
+		volatile double dbl2 = 0.0;
+		duk_computed_infinity = dbl1 / dbl2;
+	} while (0);
+#endif
+
+	/* use a raw call, all macros expect the heap to be initialized */
+	res = (duk_heap *) alloc_func(alloc_udata, sizeof(duk_heap));
+	if (!res) {
+		goto error;
+	}
+
+	/* zero everything */
+	DUK_MEMZERO(res, sizeof(*res));
+
+	/* explicit NULL inits */
+#ifdef DUK_USE_EXPLICIT_NULL_INIT
+	res->alloc_udata = NULL;
+	res->heap_allocated = NULL;
+#ifdef DUK_USE_REFERENCE_COUNTING
+	res->refzero_list = NULL;
+	res->refzero_list_tail = NULL;
+#endif
+#ifdef DUK_USE_MARK_AND_SWEEP
+	res->finalize_list = NULL;
+#endif
+	res->heap_thread = NULL;
+	res->curr_thread = NULL;
+	res->heap_object = NULL;
+	res->log_buffer = NULL;
+	res->st = NULL;
+	{
+		duk_small_uint_t i;
+	        for (i = 0; i < DUK_HEAP_NUM_STRINGS; i++) {
+        	        res->strs[i] = NULL;
+	        }
+	}
+#endif
+
+	/* initialize the structure, roughly in order */
+	res->alloc_func = alloc_func;
+	res->realloc_func = realloc_func;
+	res->free_func = free_func;
+	res->alloc_udata = alloc_udata;
+	res->fatal_func = fatal_func;
+
+	/* res->mark_and_sweep_trigger_counter == 0 -> now causes immediate GC; which is OK */
+
+	res->call_recursion_depth = 0;
+	res->call_recursion_limit = DUK_HEAP_DEFAULT_CALL_RECURSION_LIMIT;
+
+	/* FIXME: use the pointer as a seed for now: mix in time at least */
+
+	/* The casts through duk_intr_pt is to avoid the following GCC warning:
+	 *
+	 *   warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
+	 *
+	 * This still generates a /Wp64 warning on VS2010 when compiling for x86.
+	 */
+	res->hash_seed = (duk_uint32_t) (duk_intptr_t) res;
+	res->rnd_state = (duk_uint32_t) (duk_intptr_t) res;
+
+#ifdef DUK_USE_INTERRUPT_COUNTER
+	/* zero value causes an interrupt before executing first instruction */
+	DUK_ASSERT(res->interrupt_counter == 0);
+	DUK_ASSERT(res->interrupt_init == 0);
+#endif
+
+#ifdef DUK_USE_EXPLICIT_NULL_INIT
+	res->lj.jmpbuf_ptr = NULL;
+#endif
+	DUK_ASSERT(res->lj.type == DUK_LJ_TYPE_UNKNOWN);  /* zero */
+
+	DUK_TVAL_SET_UNDEFINED_UNUSED(&res->lj.value1);
+	DUK_TVAL_SET_UNDEFINED_UNUSED(&res->lj.value2);
+
+#if (DUK_STRTAB_INITIAL_SIZE < DUK_UTIL_MIN_HASH_PRIME)
+#error initial heap stringtable size is defined incorrectly
+#endif
+
+	res->st = (duk_hstring **) alloc_func(alloc_udata, sizeof(duk_hstring *) * DUK_STRTAB_INITIAL_SIZE);
+	if (!res->st) {
+		goto error;
+	}
+	res->st_size = DUK_STRTAB_INITIAL_SIZE;
+#ifdef DUK_USE_EXPLICIT_NULL_INIT
+	{
+		duk_small_uint_t i;
+		DUK_ASSERT(res->st_size == DUK_STRTAB_INITIAL_SIZE);
+	        for (i = 0; i < DUK_STRTAB_INITIAL_SIZE; i++) {
+        	        res->st[i] = NULL;
+	        }
+	}
+#else
+	DUK_MEMZERO(res->st, sizeof(duk_hstring *) * DUK_STRTAB_INITIAL_SIZE);
+#endif
+
+	/* strcache init */
+#ifdef DUK_USE_EXPLICIT_NULL_INIT
+	{
+		duk_small_uint_t i;
+		for (i = 0; i < DUK_HEAP_STRCACHE_SIZE; i++) {
+			res->strcache[i].h = NULL;
+		}
+	}
+#endif
+
+	/* FIXME: error handling is incomplete.  It would be cleanest if
+	 * there was a setjmp catchpoint, so that all init code could
+	 * freely throw errors.  If that were the case, the return code
+	 * passing here could be removed.
+	 */
+
+	/* built-in strings */
+	DUK_DD(DUK_DDPRINT("HEAP: INIT STRINGS"));
+	if (!duk__init_heap_strings(res)) {
+		goto error;
+	}
+
+	/* heap thread */
+	DUK_DD(DUK_DDPRINT("HEAP: INIT HEAP THREAD"));
+	if (!duk__init_heap_thread(res)) {
+		goto error;
+	}
+
+	/* heap object */
+	DUK_DD(DUK_DDPRINT("HEAP: INIT HEAP OBJECT"));
+	DUK_ASSERT(res->heap_thread != NULL);
+	res->heap_object = duk_hobject_alloc(res, DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                                          DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT));
+	if (!res->heap_object) {
+		goto error;
+	}
+	DUK_HOBJECT_INCREF(res->heap_thread, res->heap_object);
+
+	/* log buffer */
+	DUK_DD(DUK_DDPRINT("HEAP: INIT LOG BUFFER"));
+	res->log_buffer = (duk_hbuffer_dynamic *) duk_hbuffer_alloc(res,
+	                                                            DUK_BI_LOGGER_SHORT_MSG_LIMIT,
+	                                                            1 /*dynamic*/);
+	if (!res->log_buffer) {
+		goto error;
+	}
+	DUK_HBUFFER_INCREF(res->heap_thread, res->log_buffer);
+
+	DUK_D(DUK_DPRINT("allocated heap: %p", (void *) res));
+	return res;
+
+ error:
+	DUK_D(DUK_DPRINT("heap allocation failed"));
+
+	if (res) {
+		/* assumes that allocated pointers and alloc funcs are valid
+		 * if res exists
+		 */
+		DUK_ASSERT(res->alloc_func != NULL);
+		DUK_ASSERT(res->realloc_func != NULL);
+		DUK_ASSERT(res->free_func != NULL);
+		duk_heap_free(res);
+	}
+	return NULL;
+}
+#line 1 "duk_heap_hashstring.c"
+/*
+ *  String hash computation (interning).
+ */
+
+/* include removed: duk_internal.h */
+
+/* constants for duk_hashstring() */
+#define DUK__STRHASH_SHORTSTRING   4096L
+#define DUK__STRHASH_MEDIUMSTRING  (256L * 1024L)
+#define DUK__STRHASH_BLOCKSIZE     256L
+
+duk_uint32_t duk_heap_hashstring(duk_heap *heap, duk_uint8_t *str, duk_size_t len) {
+	/*
+	 *  Sampling long strings by byte skipping (like Lua does) is potentially
+	 *  a cache problem.  Here we do 'block skipping' instead for long strings:
+	 *  hash an initial part, and then sample the rest of the string with
+	 *  reasonably sized chunks.
+	 *
+	 *  Skip should depend on length and bound the total time to roughly
+	 *  logarithmic.
+	 *
+	 *  With current values:
+	 *
+	 *    1M string => 256 * 241 = 61696 bytes (0.06M) of hashing
+	 *    1G string => 256 * 16321 = 4178176 bytes (3.98M) of hashing
+	 *
+	 *  After an initial part has been hashed, an offset is applied before
+	 *  starting the sampling.  The initial offset is computed from the
+	 *  hash of the initial part of the string.  The idea is to avoid the
+	 *  case that all long strings have certain offset ranges that are never
+	 *  sampled.
+	 */
+	
+	/* note: mixing len into seed improves hashing when skipping */
+	duk_uint32_t str_seed = heap->hash_seed ^ len;
+
+	if (len <= DUK__STRHASH_SHORTSTRING) {
+		return duk_util_hashbytes(str, len, str_seed);
+	} else {
+		duk_uint32_t hash;
+		duk_size_t off;
+		duk_size_t skip;
+
+		if (len <= DUK__STRHASH_MEDIUMSTRING) {
+			skip = (duk_size_t) (16 * DUK__STRHASH_BLOCKSIZE + DUK__STRHASH_BLOCKSIZE);
+		} else {
+			skip = (duk_size_t) (256 * DUK__STRHASH_BLOCKSIZE + DUK__STRHASH_BLOCKSIZE);
+		}
+
+		hash = duk_util_hashbytes(str, (duk_size_t) DUK__STRHASH_SHORTSTRING, str_seed);
+		off = DUK__STRHASH_SHORTSTRING + (skip * (hash % 256)) / 256;
+
+		/* XXX: inefficient loop */
+		while (off < len) {
+			duk_size_t left = len - off;
+			duk_size_t now = (duk_size_t) (left > DUK__STRHASH_BLOCKSIZE ? DUK__STRHASH_BLOCKSIZE : left);
+			hash ^= duk_util_hashbytes(str + off, now, str_seed);
+			off += skip;
+		}
+
+		return hash;
+	}
+}
+#line 1 "duk_heap_markandsweep.c"
+/*
+ *  Mark-and-sweep garbage collection.
+ */
+
+/* include removed: duk_internal.h */
+
+#ifdef DUK_USE_MARK_AND_SWEEP
+
+static void duk__mark_heaphdr(duk_heap *heap, duk_heaphdr *h);
+static void duk__mark_tval(duk_heap *heap, duk_tval *tv);
+
+/*
+ *  Misc
+ */
+
+/* Select a thread for mark-and-sweep use.
+ *
+ * FIXME: This needs to change later.
+ */
+static duk_hthread *duk__get_temp_hthread(duk_heap *heap) {
+	if (heap->curr_thread) {
+		return heap->curr_thread;
+	}
+	return heap->heap_thread;  /* may be NULL, too */
+}
+
+/*
+ *  Marking functions for heap types: mark children recursively
+ */
+
+static void duk__mark_hstring(duk_heap *heap, duk_hstring *h) {
+	DUK_UNREF(heap);
+	DUK_UNREF(h);
+
+	DUK_DDD(DUK_DDDPRINT("duk__mark_hstring: %p", (void *) h));
+	DUK_ASSERT(h);
+
+	/* nothing to process */
+}
+
+static void duk__mark_hobject(duk_heap *heap, duk_hobject *h) {
+	duk_uint_fast32_t i;
+
+	DUK_DDD(DUK_DDDPRINT("duk__mark_hobject: %p", (void *) h));
+
+	DUK_ASSERT(h);
+
+	/* XXX: use advancing pointers instead of index macros -> faster and smaller? */
+
+	for (i = 0; i < (duk_uint_fast32_t) h->e_used; i++) {
+		duk_hstring *key = DUK_HOBJECT_E_GET_KEY(h, i);
+		if (!key) {
+			continue;
+		}
+		duk__mark_heaphdr(heap, (duk_heaphdr *) key);
+		if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(h, i)) {
+			duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_HOBJECT_E_GET_VALUE_PTR(h, i)->a.get);
+			duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_HOBJECT_E_GET_VALUE_PTR(h, i)->a.set);
+		} else {
+			duk__mark_tval(heap, &DUK_HOBJECT_E_GET_VALUE_PTR(h, i)->v);
+		}
+	}
+
+	for (i = 0; i < (duk_uint_fast32_t) h->a_size; i++) {
+		duk__mark_tval(heap, DUK_HOBJECT_A_GET_VALUE_PTR(h, i));
+	}
+
+	/* hash part is a 'weak reference' and does not contribute */
+
+	duk__mark_heaphdr(heap, (duk_heaphdr *) h->prototype);
+
+	if (DUK_HOBJECT_IS_COMPILEDFUNCTION(h)) {
+		duk_hcompiledfunction *f = (duk_hcompiledfunction *) h;
+		duk_tval *tv, *tv_end;
+		duk_hobject **funcs, **funcs_end;
+
+		/* 'data' is reachable through every compiled function which
+		 * contains a reference.
+		 */
+
+		duk__mark_heaphdr(heap, (duk_heaphdr *) f->data);
+
+		tv = DUK_HCOMPILEDFUNCTION_GET_CONSTS_BASE(f);
+		tv_end = DUK_HCOMPILEDFUNCTION_GET_CONSTS_END(f);
+		while (tv < tv_end) {
+			duk__mark_tval(heap, tv);
+			tv++;
+		}
+
+		funcs = DUK_HCOMPILEDFUNCTION_GET_FUNCS_BASE(f);
+		funcs_end = DUK_HCOMPILEDFUNCTION_GET_FUNCS_END(f);
+		while (funcs < funcs_end) {
+			duk__mark_heaphdr(heap, (duk_heaphdr *) *funcs);
+			funcs++;
+		}
+	} else if (DUK_HOBJECT_IS_NATIVEFUNCTION(h)) {
+		duk_hnativefunction *f = (duk_hnativefunction *) h;
+		DUK_UNREF(f);
+		/* nothing to mark */
+	} else if (DUK_HOBJECT_IS_THREAD(h)) {
+		duk_hthread *t = (duk_hthread *) h;
+		duk_tval *tv;
+
+		tv = t->valstack;
+		while (tv < t->valstack_end) {
+			duk__mark_tval(heap, tv);
+			tv++;
+		}
+
+		for (i = 0; i < (duk_uint_fast32_t) t->callstack_top; i++) {
+			duk_activation *act = t->callstack + i;
+			duk__mark_heaphdr(heap, (duk_heaphdr *) act->func);
+			duk__mark_heaphdr(heap, (duk_heaphdr *) act->var_env);
+			duk__mark_heaphdr(heap, (duk_heaphdr *) act->lex_env);
+#ifdef DUK_USE_NONSTD_FUNC_CALLER_PROPERTY
+			duk__mark_heaphdr(heap, (duk_heaphdr *) act->prev_caller);
+#endif
+		}
+
+#if 0  /* nothing now */
+		for (i = 0; i < (duk_uint_fast32_t) t->catchstack_top; i++) {
+			duk_catcher *cat = t->catchstack + i;
+		}
+#endif
+
+		duk__mark_heaphdr(heap, (duk_heaphdr *) t->resumer);
+
+		/* XXX: duk_small_uint_t would be enough for this loop */
+		for (i = 0; i < DUK_NUM_BUILTINS; i++) {
+			duk__mark_heaphdr(heap, (duk_heaphdr *) t->builtins[i]);
+		}
+	}
+}
+
+/* recursion tracking happens here only */
+static void duk__mark_heaphdr(duk_heap *heap, duk_heaphdr *h) {
+	DUK_DDD(DUK_DDDPRINT("duk__mark_heaphdr %p, type %ld",
+	                     (void *) h,
+	                     (h != NULL ? (long) DUK_HEAPHDR_GET_TYPE(h) : (long) -1)));
+	if (!h) {
+		return;
+	}
+
+	if (DUK_HEAPHDR_HAS_REACHABLE(h)) {
+		DUK_DDD(DUK_DDDPRINT("already marked reachable, skip"));
+		return;
+	}
+	DUK_HEAPHDR_SET_REACHABLE(h);
+
+	if (heap->mark_and_sweep_recursion_depth >= DUK_HEAP_MARK_AND_SWEEP_RECURSION_LIMIT) {
+		/* log this with a normal debug level because this should be relatively rare */
+		DUK_D(DUK_DPRINT("mark-and-sweep recursion limit reached, marking as temproot: %p", (void *) h));
+		DUK_HEAP_SET_MARKANDSWEEP_RECLIMIT_REACHED(heap);
+		DUK_HEAPHDR_SET_TEMPROOT(h);
+		return;
+	}
+
+	heap->mark_and_sweep_recursion_depth++;
+
+	switch ((int) DUK_HEAPHDR_GET_TYPE(h)) {
+	case DUK_HTYPE_STRING:
+		duk__mark_hstring(heap, (duk_hstring *) h);
+		break;
+	case DUK_HTYPE_OBJECT:
+		duk__mark_hobject(heap, (duk_hobject *) h);
+		break;
+	case DUK_HTYPE_BUFFER:
+		/* nothing to mark */
+		break;
+	default:
+		DUK_D(DUK_DPRINT("attempt to mark heaphdr %p with invalid htype %ld", (void *) h, (long) DUK_HEAPHDR_GET_TYPE(h)));
+		DUK_UNREACHABLE();
+	}
+
+	heap->mark_and_sweep_recursion_depth--;
+}
+
+static void duk__mark_tval(duk_heap *heap, duk_tval *tv) {
+	DUK_DDD(DUK_DDDPRINT("duk__mark_tval %p", (void *) tv));
+	if (!tv) {
+		return;
+	}
+	if (DUK_TVAL_IS_HEAP_ALLOCATED(tv)) {
+		duk__mark_heaphdr(heap, DUK_TVAL_GET_HEAPHDR(tv)); 
+	}
+}
+
+/*
+ *  Mark the heap.
+ */
+
+static void duk__mark_roots_heap(duk_heap *heap) {
+	duk_small_uint_t i;
+
+	DUK_DD(DUK_DDPRINT("duk__mark_roots_heap: %p", (void *) heap));
+
+	duk__mark_heaphdr(heap, (duk_heaphdr *) heap->heap_thread);
+	duk__mark_heaphdr(heap, (duk_heaphdr *) heap->heap_object);
+	duk__mark_heaphdr(heap, (duk_heaphdr *) heap->log_buffer);
+
+	for (i = 0; i < DUK_HEAP_NUM_STRINGS; i++) {
+		duk_hstring *h = heap->strs[i];
+		duk__mark_heaphdr(heap, (duk_heaphdr *) h);
+	}
+
+	duk__mark_tval(heap, &heap->lj.value1);
+	duk__mark_tval(heap, &heap->lj.value2);
+}
+
+/*
+ *  Mark refzero_list objects.
+ *
+ *  Objects on the refzero_list have no inbound references.  They might have
+ *  outbound references to objects that we might free, which would invalidate
+ *  any references held by the refzero objects.  A refzero object might also
+ *  be rescued by refcount finalization.  Refzero objects are treated as
+ *  reachability roots to ensure they (or anything they point to) are not
+ *  freed in mark-and-sweep.
+ */
+
+#ifdef DUK_USE_REFERENCE_COUNTING
+static void duk__mark_refzero_list(duk_heap *heap) {
+	duk_heaphdr *hdr;
+
+	DUK_DD(DUK_DDPRINT("duk__mark_refzero_list: %p", (void *) heap));
+
+	hdr = heap->refzero_list;
+	while (hdr) {
+		duk__mark_heaphdr(heap, hdr);
+		hdr = DUK_HEAPHDR_GET_NEXT(hdr);
+	}
+}
+#endif
+
+/*
+ *  Mark unreachable, finalizable objects.
+ *
+ *  Such objects will be moved aside and their finalizers run later.  They have
+ *  to be treated as reachability roots for their properties etc to remain
+ *  allocated.  This marking is only done for unreachable values which would
+ *  be swept later (refzero_list is thus excluded).
+ *
+ *  Objects are first marked FINALIZABLE and only then marked as reachability
+ *  roots; otherwise circular references might be handled inconsistently.
+ */
+
+static void duk__mark_finalizable(duk_heap *heap) {
+	duk_hthread *thr;
+	duk_heaphdr *hdr;
+	duk_size_t count_finalizable = 0;
+
+	DUK_DD(DUK_DDPRINT("duk__mark_finalizable: %p", (void *) heap));
+
+	thr = duk__get_temp_hthread(heap);
+	DUK_ASSERT(thr != NULL);
+
+	hdr = heap->heap_allocated;
+	while (hdr) {
+		/* A finalizer is looked up from the object and up its prototype chain
+		 * (which allows inherited finalizers).
+		 */
+		if (!DUK_HEAPHDR_HAS_REACHABLE(hdr) &&
+		    DUK_HEAPHDR_GET_TYPE(hdr) == DUK_HTYPE_OBJECT &&
+		    !DUK_HEAPHDR_HAS_FINALIZED(hdr) &&
+		    duk_hobject_hasprop_raw(thr, (duk_hobject *) hdr, DUK_HTHREAD_STRING_INT_FINALIZER(thr))) {
+
+			/* heaphdr:
+			 *  - is not reachable
+			 *  - is an object
+			 *  - is not a finalized object
+			 *  - has a finalizer
+			 */
+
+			DUK_DD(DUK_DDPRINT("unreachable heap object will be "
+			                   "finalized -> mark as finalizable "
+			                   "and treat as a reachability root: %p",
+			                   (void *) hdr));
+			DUK_HEAPHDR_SET_FINALIZABLE(hdr);
+			count_finalizable ++;
+		}
+
+		hdr = DUK_HEAPHDR_GET_NEXT(hdr);
+	}
+
+	if (count_finalizable == 0) {
+		return;
+	}
+
+	DUK_DD(DUK_DDPRINT("marked %ld heap objects as finalizable, now mark them reachable",
+	                   (long) count_finalizable));
+
+	hdr = heap->heap_allocated;
+	while (hdr) {
+		if (DUK_HEAPHDR_HAS_FINALIZABLE(hdr)) {
+			duk__mark_heaphdr(heap, hdr);
+		}
+
+		hdr = DUK_HEAPHDR_GET_NEXT(hdr);
+	}
+
+	/* Caller will finish the marking process if we hit a recursion limit. */
+}
+
+/*
+ *  Fallback marking handler if recursion limit is reached.
+ *
+ *  Iterates 'temproots' until recursion limit is no longer hit.  Note
+ *  that temproots may reside either in heap allocated list or the
+ *  refzero work list.  This is a slow scan, but guarantees that we
+ *  finish with a bounded C stack.
+ *
+ *  Note that nodes may have been marked as temproots before this
+ *  scan begun, OR they may have been marked during the scan (as
+ *  we process nodes recursively also during the scan).  This is
+ *  intended behavior.
+ */
+
+#ifdef DUK_USE_DEBUG
+static void duk__handle_temproot(duk_heap *heap, duk_heaphdr *hdr, duk_size_t *count) {
+#else
+static void duk__handle_temproot(duk_heap *heap, duk_heaphdr *hdr) {
+#endif
+	if (!DUK_HEAPHDR_HAS_TEMPROOT(hdr)) {
+		DUK_DDD(DUK_DDDPRINT("not a temp root: %p", (void *) hdr));
+		return;
+	}
+
+	DUK_DDD(DUK_DDDPRINT("found a temp root: %p", (void *) hdr));
+	DUK_HEAPHDR_CLEAR_TEMPROOT(hdr);
+	DUK_HEAPHDR_CLEAR_REACHABLE(hdr);  /* done so that duk__mark_heaphdr() works correctly */
+	duk__mark_heaphdr(heap, hdr);
+
+#ifdef DUK_USE_DEBUG
+	(*count)++;
+#endif
+}
+
+static void duk__mark_temproots_by_heap_scan(duk_heap *heap) {
+	duk_heaphdr *hdr;
+#ifdef DUK_USE_DEBUG
+	duk_size_t count;
+#endif
+
+	DUK_DD(DUK_DDPRINT("duk__mark_temproots_by_heap_scan: %p", (void *) heap));
+
+	while (DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap)) {
+		DUK_DD(DUK_DDPRINT("recursion limit reached, doing heap scan to continue from temproots"));
+
+#ifdef DUK_USE_DEBUG
+		count = 0;
+#endif
+		DUK_HEAP_CLEAR_MARKANDSWEEP_RECLIMIT_REACHED(heap);
+
+		hdr = heap->heap_allocated;
+		while (hdr) {
+#ifdef DUK_USE_DEBUG
+			duk__handle_temproot(heap, hdr, &count);
+#else
+			duk__handle_temproot(heap, hdr);
+#endif
+			hdr = DUK_HEAPHDR_GET_NEXT(hdr);
+		}
+
+		/* must also check refzero_list */
+#ifdef DUK_USE_REFERENCE_COUNTING
+		hdr = heap->refzero_list;
+		while (hdr) {
+#ifdef DUK_USE_DEBUG
+			duk__handle_temproot(heap, hdr, &count);
+#else
+			duk__handle_temproot(heap, hdr);
+#endif
+			hdr = DUK_HEAPHDR_GET_NEXT(hdr);
+		}
+#endif  /* DUK_USE_REFERENCE_COUNTING */
+
+#ifdef DUK_USE_DEBUG
+		DUK_DD(DUK_DDPRINT("temproot mark heap scan processed %ld temp roots", (long) count));
+#endif
+	}
+}
+
+/*
+ *  Finalize refcounts for heap elements just about to be freed.
+ *  This must be done for all objects before freeing to avoid any
+ *  stale pointer dereferences.
+ *
+ *  Note that this must deduce the set of objects to be freed
+ *  identically to duk__sweep_heap().
+ */
+
+#ifdef DUK_USE_REFERENCE_COUNTING
+static void duk__finalize_refcounts(duk_heap *heap) {
+	duk_hthread *thr;
+	duk_heaphdr *hdr;
+
+	thr = duk__get_temp_hthread(heap);
+	DUK_ASSERT(thr != NULL);
+
+	DUK_DD(DUK_DDPRINT("duk__finalize_refcounts: heap=%p, hthread=%p",
+	                   (void *) heap, (void *) thr));
+
+	hdr = heap->heap_allocated;
+	while (hdr) {
+		if (!DUK_HEAPHDR_HAS_REACHABLE(hdr)) {
+			/*
+			 *  Unreachable object about to be swept.  Finalize target refcounts
+			 *  (objects which the unreachable object points to) without doing
+			 *  refzero processing.  Recursive decrefs are also prevented when
+			 *  refzero processing is disabled.
+			 *
+			 *  Value cannot be a finalizable object, as they have been made
+			 *  temporarily reachable for this round.
+			 */
+
+			DUK_DDD(DUK_DDDPRINT("unreachable object, refcount finalize before sweeping: %p", (void *) hdr));
+			duk_heap_refcount_finalize_heaphdr(thr, hdr);
+		}
+
+		hdr = DUK_HEAPHDR_GET_NEXT(hdr);
+	}
+}
+#endif  /* DUK_USE_REFERENCE_COUNTING */
+
+/*
+ *  Clear (reachable) flags of refzero work list.
+ */
+
+#ifdef DUK_USE_REFERENCE_COUNTING
+static void duk__clear_refzero_list_flags(duk_heap *heap) {
+	duk_heaphdr *hdr;
+
+	DUK_DD(DUK_DDPRINT("duk__clear_refzero_list_flags: %p", (void *) heap));
+
+	hdr = heap->refzero_list;
+	while (hdr) {
+		DUK_HEAPHDR_CLEAR_REACHABLE(hdr);
+		DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(hdr));
+		DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(hdr));
+		DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(hdr));
+		hdr = DUK_HEAPHDR_GET_NEXT(hdr);
+	}
+}
+#endif  /* DUK_USE_REFERENCE_COUNTING */
+
+/*
+ *  Sweep stringtable
+ */
+
+static void duk__sweep_stringtable(duk_heap *heap, duk_size_t *out_count_keep) {
+	duk_hstring *h;
+	duk_uint_fast32_t i;
+#ifdef DUK_USE_DEBUG
+	duk_size_t count_free = 0;
+#endif
+	duk_size_t count_keep = 0;
+
+	DUK_DD(DUK_DDPRINT("duk__sweep_stringtable: %p", (void *) heap));
+
+	for (i = 0; i < heap->st_size; i++) {
+		h = heap->st[i];
+		if (h == NULL || h == DUK_STRTAB_DELETED_MARKER(heap)) {
+			continue;
+		} else if (DUK_HEAPHDR_HAS_REACHABLE((duk_heaphdr *) h)) {
+			DUK_HEAPHDR_CLEAR_REACHABLE((duk_heaphdr *) h);
+			count_keep++;
+			continue;
+		}
+
+#ifdef DUK_USE_DEBUG
+		count_free++;
+#endif
+
+#if defined(DUK_USE_REFERENCE_COUNTING)
+		/* Non-zero refcounts should not happen for unreachable strings,
+		 * because we refcount finalize all unreachable objects which
+		 * should have decreased unreachable string refcounts to zero
+		 * (even for cycles).
+		 */
+		DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h) == 0);
+#endif
+
+		DUK_DDD(DUK_DDDPRINT("sweep string, not reachable: %p", (void *) h));
+
+		/* deal with weak references first */
+		duk_heap_strcache_string_remove(heap, (duk_hstring *) h);
+
+		/* remove the string (mark DELETED), could also call
+		 * duk_heap_string_remove() but that would be slow and
+		 * pointless because we already know the slot.
+		 */
+		heap->st[i] = DUK_STRTAB_DELETED_MARKER(heap);
+
+		/* then free */
+#if 1
+		DUK_FREE(heap, (duk_heaphdr *) h);  /* no inner refs/allocs, just free directly */
+#else
+		duk_heap_free_heaphdr_raw(heap, (duk_heaphdr *) h);  /* this would be OK but unnecessary */
+#endif
+	}
+
+#ifdef DUK_USE_DEBUG
+	DUK_D(DUK_DPRINT("mark-and-sweep sweep stringtable: %ld freed, %ld kept",
+	                 (long) count_free, (long) count_keep));
+#endif
+	*out_count_keep = count_keep;
+}
+
+/*
+ *  Sweep heap
+ */
+
+static void duk__sweep_heap(duk_heap *heap, duk_int_t flags, duk_size_t *out_count_keep) {
+	duk_heaphdr *prev;  /* last element that was left in the heap */
+	duk_heaphdr *curr;
+	duk_heaphdr *next;
+#ifdef DUK_USE_DEBUG
+	duk_size_t count_free = 0;
+	duk_size_t count_finalize = 0;
+	duk_size_t count_rescue = 0;
+#endif
+	duk_size_t count_keep = 0;
+
+	DUK_UNREF(flags);
+	DUK_DD(DUK_DDPRINT("duk__sweep_heap: %p", (void *) heap));
+
+	prev = NULL;
+	curr = heap->heap_allocated;
+	heap->heap_allocated = NULL;
+	while (curr) {
+		/* strings are never placed on the heap allocated list */
+		DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) != DUK_HTYPE_STRING);
+
+		next = DUK_HEAPHDR_GET_NEXT(curr);
+
+		if (DUK_HEAPHDR_HAS_REACHABLE(curr)) {
+			/*
+			 *  Reachable object, keep
+			 */
+
+			DUK_DDD(DUK_DDDPRINT("sweep, reachable: %p", (void *) curr));
+
+			if (DUK_HEAPHDR_HAS_FINALIZABLE(curr)) {
+				/*
+				 *  If object has been marked finalizable, move it to the
+				 *  "to be finalized" work list.  It will be collected on
+				 *  the next mark-and-sweep if it is still unreachable
+				 *  after running the finalizer.
+				 */
+
+				DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr));
+				DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT);
+				DUK_DDD(DUK_DDDPRINT("object has finalizer, move to finalization work list: %p", (void *) curr));
+
+#ifdef DUK_USE_DOUBLE_LINKED_HEAP
+				if (heap->finalize_list) {
+					DUK_HEAPHDR_SET_PREV(heap->finalize_list, curr);
+				}
+				DUK_HEAPHDR_SET_PREV(curr, NULL);
+#endif
+				DUK_HEAPHDR_SET_NEXT(curr, heap->finalize_list);
+				heap->finalize_list = curr;
+#ifdef DUK_USE_DEBUG
+				count_finalize++;
+#endif
+			} else {
+				/*
+				 *  Object will be kept; queue object back to heap_allocated (to tail)
+				 */
+
+				if (DUK_HEAPHDR_HAS_FINALIZED(curr)) {
+					/*
+					 *  Object's finalizer was executed on last round, and
+					 *  object has been happily rescued.
+					 */
+
+					DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr));
+					DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT);
+					DUK_DD(DUK_DDPRINT("object rescued during mark-and-sweep finalization: %p", (void *) curr));
+#ifdef DUK_USE_DEBUG
+					count_rescue++;
+#endif
+				} else {
+					/*
+					 *  Plain, boring reachable object.
+					 */
+					count_keep++;
+				}
+
+				if (!heap->heap_allocated) {
+					heap->heap_allocated = curr;
+				}
+				if (prev) {
+					DUK_HEAPHDR_SET_NEXT(prev, curr);
+				}
+#ifdef DUK_USE_DOUBLE_LINKED_HEAP
+				DUK_HEAPHDR_SET_PREV(curr, prev);
+#endif
+				prev = curr;
+			}
+
+			DUK_HEAPHDR_CLEAR_REACHABLE(curr);
+			DUK_HEAPHDR_CLEAR_FINALIZED(curr);
+			DUK_HEAPHDR_CLEAR_FINALIZABLE(curr);
+
+			DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(curr));
+			DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr));
+			DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr));
+
+			curr = next;
+		} else {
+			/*
+			 *  Unreachable object, free
+			 */
+
+			DUK_DDD(DUK_DDDPRINT("sweep, not reachable: %p", (void *) curr));
+
+#if defined(DUK_USE_REFERENCE_COUNTING)
+			/* Non-zero refcounts should not happen because we refcount
+			 * finalize all unreachable objects which should cancel out
+			 * refcounts (even for cycles).
+			 */
+			DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(curr) == 0);
+#endif
+			DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr));
+
+			if (DUK_HEAPHDR_HAS_FINALIZED(curr)) {
+				DUK_DDD(DUK_DDDPRINT("finalized object not rescued: %p", (void *) curr));
+			}
+
+			/* Note: object cannot be a finalizable unreachable object, as
+			 * they have been marked temporarily reachable for this round,
+			 * and are handled above.
+			 */
+
+#ifdef DUK_USE_DEBUG
+			count_free++;
+#endif
+
+			/* weak refs should be handled here, but no weak refs for
+			 * any non-string objects exist right now.
+			 */
+
+			/* free object and all auxiliary (non-heap) allocs */
+			duk_heap_free_heaphdr_raw(heap, curr);
+
+			curr = next;
+		}
+	}
+	if (prev) {
+		DUK_HEAPHDR_SET_NEXT(prev, NULL);
+	}
+
+#ifdef DUK_USE_DEBUG
+	DUK_D(DUK_DPRINT("mark-and-sweep sweep objects (non-string): %ld freed, %ld kept, %ld rescued, %ld queued for finalization",
+	                 (long) count_free, (long) count_keep, (long) count_rescue, (long) count_finalize));
+#endif
+	*out_count_keep = count_keep;
+}
+
+/*
+ *  Run (object) finalizers in the "to be finalized" work list.
+ */
+
+static void duk__run_object_finalizers(duk_heap *heap) {
+	duk_heaphdr *curr;
+	duk_heaphdr *next;
+#ifdef DUK_USE_DEBUG
+	duk_size_t count = 0;
+#endif
+	duk_hthread *thr;
+
+	DUK_DD(DUK_DDPRINT("duk__run_object_finalizers: %p", (void *) heap));
+
+	thr = duk__get_temp_hthread(heap);
+	DUK_ASSERT(thr != NULL);
+
+	curr = heap->finalize_list;
+	while (curr) {
+		DUK_DDD(DUK_DDDPRINT("mark-and-sweep finalize: %p", (void *) curr));
+
+		DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT);  /* only objects have finalizers */
+		DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(curr));                /* flags have been already cleared */
+		DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(curr));
+		DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr));
+		DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr));
+
+		/* run the finalizer */
+		duk_hobject_run_finalizer(thr, (duk_hobject *) curr);  /* must never longjmp */
+
+		/* mark FINALIZED, for next mark-and-sweep (will collect unless has become reachable;
+		 * prevent running finalizer again if reachable)
+		 */
+		DUK_HEAPHDR_SET_FINALIZED(curr);
+
+		/* queue back to heap_allocated */
+		next = DUK_HEAPHDR_GET_NEXT(curr);
+		DUK_HEAP_INSERT_INTO_HEAP_ALLOCATED(heap, curr);
+
+		curr = next;
+#ifdef DUK_USE_DEBUG
+		count++;
+#endif
+	}
+
+	/* finalize_list will always be processed completely */
+	heap->finalize_list = NULL;
+
+#ifdef DUK_USE_DEBUG
+	DUK_D(DUK_DPRINT("mark-and-sweep finalize objects: %ld finalizers called", (long) count));
+#endif
+}
+
+/*
+ *  Object compaction.
+ *
+ *  Compaction is assumed to never throw an error.
+ */
+
+static int duk__protected_compact_object(duk_context *ctx) {
+	/* XXX: for threads, compact value stack, call stack, catch stack? */
+
+	duk_hobject *obj = duk_get_hobject(ctx, -1);
+	DUK_ASSERT(obj != NULL);
+	duk_hobject_compact_props((duk_hthread *) ctx, obj);
+	return 0;
+}
+
+#ifdef DUK_USE_DEBUG
+static void duk__compact_object_list(duk_heap *heap, duk_hthread *thr, duk_heaphdr *start, duk_size_t *p_count_check, duk_size_t *p_count_compact, duk_size_t *p_count_bytes_saved) {
+#else
+static void duk__compact_object_list(duk_heap *heap, duk_hthread *thr, duk_heaphdr *start) {
+#endif
+	duk_heaphdr *curr;
+#ifdef DUK_USE_DEBUG
+	duk_size_t old_size, new_size;
+#endif
+	duk_hobject *obj;
+
+	DUK_UNREF(heap);
+
+	curr = start;
+	while (curr) {
+		DUK_DDD(DUK_DDDPRINT("mark-and-sweep compact: %p", (void *) curr));
+
+		if (DUK_HEAPHDR_GET_TYPE(curr) != DUK_HTYPE_OBJECT) {
+			goto next;	
+		}
+		obj = (duk_hobject *) curr;
+
+#ifdef DUK_USE_DEBUG
+		old_size = DUK_HOBJECT_P_COMPUTE_SIZE(obj->e_size, obj->a_size, obj->h_size);
+#endif
+
+		DUK_DD(DUK_DDPRINT("compact object: %p", (void *) obj));
+		duk_push_hobject((duk_context *) thr, obj);
+		/* XXX: disable error handlers for duration of compaction? */
+		duk_safe_call((duk_context *) thr, duk__protected_compact_object, 1, 0);
+
+#ifdef DUK_USE_DEBUG
+		new_size = DUK_HOBJECT_P_COMPUTE_SIZE(obj->e_size, obj->a_size, obj->h_size);
+#endif
+
+#ifdef DUK_USE_DEBUG
+		(*p_count_compact)++;
+		(*p_count_bytes_saved) += (duk_size_t) (old_size - new_size);
+#endif
+
+	 next:
+		curr = DUK_HEAPHDR_GET_NEXT(curr);
+#ifdef DUK_USE_DEBUG
+		(*p_count_check)++;
+#endif
+	}
+}
+
+static void duk__compact_objects(duk_heap *heap) {
+	/* XXX: which lists should participate?  to be finalized? */
+#ifdef DUK_USE_DEBUG
+	duk_size_t count_check = 0;
+	duk_size_t count_compact = 0;
+	duk_size_t count_bytes_saved = 0;
+#endif
+	duk_hthread *thr;
+
+	DUK_DD(DUK_DDPRINT("duk__compact_objects: %p", (void *) heap));
+
+	thr = duk__get_temp_hthread(heap);
+	DUK_ASSERT(thr != NULL);
+
+#ifdef DUK_USE_DEBUG
+	duk__compact_object_list(heap, thr, heap->heap_allocated, &count_check, &count_compact, &count_bytes_saved);
+	duk__compact_object_list(heap, thr, heap->finalize_list, &count_check, &count_compact, &count_bytes_saved);
+#ifdef DUK_USE_REFERENCE_COUNTING
+	duk__compact_object_list(heap, thr, heap->refzero_list, &count_check, &count_compact, &count_bytes_saved);
+#endif
+#else
+	duk__compact_object_list(heap, thr, heap->heap_allocated);
+	duk__compact_object_list(heap, thr, heap->finalize_list);
+#ifdef DUK_USE_REFERENCE_COUNTING
+	duk__compact_object_list(heap, thr, heap->refzero_list);
+#endif
+#endif
+
+#ifdef DUK_USE_DEBUG
+	DUK_D(DUK_DPRINT("mark-and-sweep compact objects: %ld checked, %ld compaction attempts, %ld bytes saved by compaction",
+	                 (long) count_check, (long) count_compact, (long) count_bytes_saved));
+#endif
+}
+
+/*
+ *  Assertion helpers.
+ */
+
+#ifdef DUK_USE_ASSERTIONS
+static void duk__assert_heaphdr_flags(duk_heap *heap) {
+	duk_heaphdr *hdr;
+
+	hdr = heap->heap_allocated;
+	while (hdr) {
+		DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(hdr));
+		DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(hdr));
+		DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(hdr));
+		/* may have FINALIZED */
+		hdr = DUK_HEAPHDR_GET_NEXT(hdr);
+	}
+
+#ifdef DUK_USE_REFERENCE_COUNTING
+	hdr = heap->refzero_list;
+	while (hdr) {
+		DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(hdr));
+		DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(hdr));
+		DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(hdr));
+		DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(hdr));
+		hdr = DUK_HEAPHDR_GET_NEXT(hdr);
+	}
+#endif  /* DUK_USE_REFERENCE_COUNTING */
+}
+
+#ifdef DUK_USE_REFERENCE_COUNTING
+static void duk__assert_valid_refcounts(duk_heap *heap) {
+	duk_heaphdr *hdr = heap->heap_allocated;
+	while (hdr) {
+		if (DUK_HEAPHDR_GET_REFCOUNT(hdr) == 0 &&
+		    DUK_HEAPHDR_HAS_FINALIZED(hdr)) {
+			/* An object may be in heap_allocated list with a zero
+			 * refcount if it has just been finalized and is waiting
+			 * to be collected by the next cycle.
+			 */
+		} else if (DUK_HEAPHDR_GET_REFCOUNT(hdr) == 0) {
+			/* An object may be in heap_allocated list with a zero
+			 * refcount also if it is a temporary object created by
+			 * a finalizer; because finalization now runs inside
+			 * mark-and-sweep, such objects will not be queued to
+			 * refzero_list and will thus appear here with refcount
+			 * zero.
+			 */
+#if 0  /* this case can no longer occur because refcount is unsigned */
+		} else if (DUK_HEAPHDR_GET_REFCOUNT(hdr) < 0) {
+			DUK_D(DUK_DPRINT("invalid refcount: %ld, %p -> %!O",
+			                 (hdr != NULL ? (long) DUK_HEAPHDR_GET_REFCOUNT(hdr) : (long) 0),
+			                 (void *) hdr, (duk_heaphdr *) hdr));
+			DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(hdr) > 0);
+#endif
+		}
+		hdr = DUK_HEAPHDR_GET_NEXT(hdr);
+	}
+}
+#endif  /* DUK_USE_REFERENCE_COUNTING */
+#endif  /* DUK_USE_ASSERTIONS */
+
+/*
+ *  Main mark-and-sweep function.
+ *
+ *  'flags' represents the features requested by the caller.  The current
+ *  heap->mark_and_sweep_base_flags is ORed automatically into the flags;
+ *  the base flags mask typically prevents certain mark-and-sweep operations
+ *  to avoid trouble.
+ */
+
+duk_bool_t duk_heap_mark_and_sweep(duk_heap *heap, duk_small_uint_t flags) {
+	duk_size_t count_keep_obj;
+	duk_size_t count_keep_str;
+	duk_size_t tmp;
+
+	/* FIXME: thread selection for mark-and-sweep is currently a hack.
+	 * If we don't have a thread, the entire mark-and-sweep is now
+	 * skipped (although we could just skip finalizations).
+	 */
+	if (duk__get_temp_hthread(heap) == NULL) {
+		DUK_D(DUK_DPRINT("temporary hack: gc skipped because we don't have a temp thread"));
+
+		/* reset voluntary gc trigger count */
+#ifdef DUK_USE_VOLUNTARY_GC
+		heap->mark_and_sweep_trigger_counter = DUK_HEAP_MARK_AND_SWEEP_TRIGGER_SKIP;
+#endif
+		return 0;  /* OK */
+	}
+
+	DUK_D(DUK_DPRINT("garbage collect (mark-and-sweep) starting, requested flags: 0x%08lx, effective flags: 0x%08lx",
+	                 (unsigned long) flags, (unsigned long) (flags | heap->mark_and_sweep_base_flags)));
+
+	flags |= heap->mark_and_sweep_base_flags;
+
+	/*
+	 *  Assertions before
+	 */
+
+#ifdef DUK_USE_ASSERTIONS
+	DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap));
+	DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap));
+	DUK_ASSERT(heap->mark_and_sweep_recursion_depth == 0);
+	duk__assert_heaphdr_flags(heap);
+#ifdef DUK_USE_REFERENCE_COUNTING
+	/* Note: DUK_HEAP_HAS_REFZERO_FREE_RUNNING(heap) may be true; a refcount
+	 * finalizer may trigger a mark-and-sweep.
+	 */
+	duk__assert_valid_refcounts(heap);
+#endif  /* DUK_USE_REFERENCE_COUNTING */
+#endif  /* DUK_USE_ASSERTIONS */
+
+	/*
+	 *  Begin
+	 */
+
+	DUK_HEAP_SET_MARKANDSWEEP_RUNNING(heap);
+
+	/*
+	 *  Mark roots, hoping that recursion limit is not normally hit.
+	 *  If recursion limit is hit, run additional reachability rounds
+	 *  starting from "temproots" until marking is complete.
+	 *
+	 *  Marking happens in two phases: first we mark actual reachability
+	 *  roots (and run "temproots" to complete the process).  Then we
+	 *  check which objects are unreachable and are finalizable; such
+	 *  objects are marked as FINALIZABLE and marked as reachability
+	 *  (and "temproots" is run again to complete the process).
+	 */
+
+	duk__mark_roots_heap(heap);               /* main reachability roots */
+#ifdef DUK_USE_REFERENCE_COUNTING
+	duk__mark_refzero_list(heap);             /* refzero_list treated as reachability roots */
+#endif
+	duk__mark_temproots_by_heap_scan(heap);   /* temproots */
+
+	duk__mark_finalizable(heap);              /* mark finalizable as reachability roots */
+	duk__mark_temproots_by_heap_scan(heap);   /* temproots */
+
+	/*
+	 *  Sweep garbage and remove marking flags, and move objects with
+	 *  finalizers to the finalizer work list.
+	 *
+	 *  Objects to be swept need to get their refcounts finalized before
+	 *  they are swept.  In other words, their target object refcounts
+	 *  need to be decreased.  This has to be done before freeing any
+	 *  objects to avoid decref'ing dangling pointers (which may happen
+	 *  even without bugs, e.g. with reference loops)
+	 *
+	 *  Because strings don't point to other heap objects, similar
+	 *  finalization is not necessary for strings.
+	 */
+
+	/* XXX: more emergency behavior, e.g. find smaller hash sizes etc */
+
+#ifdef DUK_USE_REFERENCE_COUNTING
+	duk__finalize_refcounts(heap);
+#endif
+	duk__sweep_heap(heap, flags, &count_keep_obj);
+	duk__sweep_stringtable(heap, &count_keep_str);
+#ifdef DUK_USE_REFERENCE_COUNTING
+	duk__clear_refzero_list_flags(heap);
+#endif
+
+	/*
+	 *  Object compaction (emergency only).
+	 *
+	 *  Object compaction is a separate step after sweeping, as there is
+	 *  more free memory for it to work with.  Also, currently compaction
+	 *  may insert new objects into the heap allocated list and the string
+	 *  table which we don't want to do during a sweep (the reachability
+	 *  flags of such objects would be incorrect).  The objects inserted
+	 *  are currently:
+	 *
+	 *    - a temporary duk_hbuffer for a new properties allocation
+	 *    - if array part is abandoned, string keys are interned
+	 *
+	 *  The object insertions go to the front of the list, so they do not
+	 *  cause an infinite loop (they are not compacted).
+	 */
+
+	if ((flags & DUK_MS_FLAG_EMERGENCY) &&
+	    !(flags & DUK_MS_FLAG_NO_OBJECT_COMPACTION)) {
+		duk__compact_objects(heap);
+	}
+
+	/*
+	 *  String table resize check.
+	 *
+	 *  Note: this may silently (and safely) fail if GC is caused by an
+	 *  allocation call in stringtable resize_hash().  Resize_hash()
+	 *  will prevent a recursive call to itself by setting the
+	 *  DUK_MS_FLAG_NO_STRINGTABLE_RESIZE in heap->mark_and_sweep_base_flags.
+	 */
+
+	/* XXX: stringtable emergency compaction? */
+
+#if defined(DUK_USE_MS_STRINGTABLE_RESIZE)
+	if (!(flags & DUK_MS_FLAG_NO_STRINGTABLE_RESIZE)) {
+		DUK_DD(DUK_DDPRINT("resize stringtable: %p", (void *) heap));
+		duk_heap_force_stringtable_resize(heap);
+	} else {
+		DUK_D(DUK_DPRINT("stringtable resize skipped because DUK_MS_FLAG_NO_STRINGTABLE_RESIZE is set"));
+	}
+#endif
+
+	/*
+	 *  Finalize objects in the finalization work list.  Finalized
+	 *  objects are queued back to heap_allocated with FINALIZED set.
+	 *
+	 *  Since finalizers may cause arbitrary side effects, they are
+	 *  prevented during string table and object property allocation
+	 *  resizing using the DUK_MS_FLAG_NO_FINALIZERS flag in
+	 *  heap->mark_and_sweep_base_flags.  In this case the objects
+	 *  remain in the finalization work list after mark-and-sweep
+	 *  exits and they may be finalized on the next pass.
+	 *
+	 *  Finalization currently happens inside "MARKANDSWEEP_RUNNING"
+	 *  protection (no mark-and-sweep may be triggered by the
+	 *  finalizers).  As a side effect:
+	 *
+	 *    1) an out-of-memory error inside a finalizer will not
+	 *       cause a mark-and-sweep and may cause the finalizer
+	 *       to fail unnecessarily
+	 *
+	 *    2) any temporary objects whose refcount decreases to zero
+	 *       during finalization will not be put into refzero_list;
+	 *       they can only be collected by another mark-and-sweep
+	 *
+	 *  This is not optimal, but since the sweep for this phase has
+	 *  already happened, this is probably good enough for now.
+	 */
+
+	if (!(flags & DUK_MS_FLAG_NO_FINALIZERS)) {
+		duk__run_object_finalizers(heap);
+	} else {
+		DUK_D(DUK_DPRINT("finalizer run skipped because DUK_MS_FLAG_NO_FINALIZERS is set"));
+	}
+
+	/*
+	 *  Finish
+	 */
+
+	DUK_HEAP_CLEAR_MARKANDSWEEP_RUNNING(heap);
+
+	/*
+	 *  Assertions after
+	 */
+
+#ifdef DUK_USE_ASSERTIONS
+	DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap));
+	DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap));
+	DUK_ASSERT(heap->mark_and_sweep_recursion_depth == 0);
+	duk__assert_heaphdr_flags(heap);
+#ifdef DUK_USE_REFERENCE_COUNTING
+	/* Note: DUK_HEAP_HAS_REFZERO_FREE_RUNNING(heap) may be true; a refcount
+	 * finalizer may trigger a mark-and-sweep.
+	 */
+	duk__assert_valid_refcounts(heap);
+#endif  /* DUK_USE_REFERENCE_COUNTING */
+#endif  /* DUK_USE_ASSERTIONS */
+
+	/*
+	 *  Reset trigger counter
+	 */
+
+#ifdef DUK_USE_VOLUNTARY_GC
+	tmp = (count_keep_obj + count_keep_str) / 256;
+	heap->mark_and_sweep_trigger_counter = (duk_int_t) (
+	    (tmp * DUK_HEAP_MARK_AND_SWEEP_TRIGGER_MULT) +
+	    DUK_HEAP_MARK_AND_SWEEP_TRIGGER_ADD);
+	DUK_D(DUK_DPRINT("garbage collect (mark-and-sweep) finished: %ld objects kept, %ld strings kept, trigger reset to %ld",
+	                 (long) count_keep_obj, (long) count_keep_str, (long) heap->mark_and_sweep_trigger_counter));
+#else
+	DUK_D(DUK_DPRINT("garbage collect (mark-and-sweep) finished: %ld objects kept, %ld strings kept, no voluntary trigger",
+	                 (long) count_keep_obj, (long) count_keep_str));
+#endif
+	return 0;  /* OK */
+}
+
+#else  /* DUK_USE_MARK_AND_SWEEP */
+
+/* no mark-and-sweep gc */
+
+#endif  /* DUK_USE_MARK_AND_SWEEP */
+#line 1 "duk_heap_memory.c"
+/*
+ *  Memory allocation handling.
+ */
+
+/* include removed: duk_internal.h */
+
+/*
+ *  Helpers
+ *
+ *  The fast path checks are done within a macro to ensure "inlining"
+ *  while the slow path actions use a helper (which won't typically be
+ *  inlined in size optimized builds).
+ */
+
+#if defined(DUK_USE_MARK_AND_SWEEP) && defined(DUK_USE_VOLUNTARY_GC)
+#define DUK__VOLUNTARY_PERIODIC_GC(heap)  do { \
+		(heap)->mark_and_sweep_trigger_counter--; \
+		if ((heap)->mark_and_sweep_trigger_counter <= 0) { \
+			duk__run_voluntary_gc(heap); \
+		} \
+	} while (0)
+
+static void duk__run_voluntary_gc(duk_heap *heap) {
+	if (DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
+		DUK_DD(DUK_DDPRINT("mark-and-sweep in progress -> skip voluntary mark-and-sweep now"));
+	} else {
+		duk_small_uint_t flags;
+		duk_bool_t rc;
+
+		DUK_D(DUK_DPRINT("triggering voluntary mark-and-sweep"));
+		flags = 0;
+		rc = duk_heap_mark_and_sweep(heap, flags);
+		DUK_UNREF(rc);
+	}
+}
+#else
+#define DUK__VOLUNTARY_PERIODIC_GC(heap)  /* no voluntary gc */
+#endif  /* DUK_USE_MARK_AND_SWEEP && DUK_USE_VOLUNTARY_GC */
+
+/*
+ *  Allocate memory with garbage collection
+ */
+
+#ifdef DUK_USE_MARK_AND_SWEEP
+void *duk_heap_mem_alloc(duk_heap *heap, duk_size_t size) {
+	void *res;
+	duk_bool_t rc;
+	duk_small_int_t i;
+
+	DUK_ASSERT(heap != NULL);
+	DUK_ASSERT_DISABLE(size >= 0);
+
+	/*
+	 *  Voluntary periodic GC (if enabled)
+	 */
+
+	DUK__VOLUNTARY_PERIODIC_GC(heap);
+
+	/*
+	 *  First attempt
+	 */
+
+#ifdef DUK_USE_GC_TORTURE
+	/* simulate alloc failure on every alloc (except when mark-and-sweep is running) */
+	if (!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
+		DUK_DDD(DUK_DDDPRINT("gc torture enabled, pretend that first alloc attempt fails"));
+		res = NULL;
+		DUK_UNREF(res);
+		goto skip_attempt;
+	}
+#endif
+	res = heap->alloc_func(heap->alloc_udata, size);
+	if (res || size == 0) {
+		/* for zero size allocations NULL is allowed */
+		return res;
+	}
+#ifdef DUK_USE_GC_TORTURE
+ skip_attempt:
+#endif
+
+	DUK_D(DUK_DPRINT("first alloc attempt failed, attempt to gc and retry"));
+
+	/*
+	 *  Avoid a GC if GC is already running.  This can happen at a late
+	 *  stage in a GC when we try to e.g. resize the stringtable
+	 *  or compact objects.
+	 */
+
+	if (DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
+		DUK_D(DUK_DPRINT("duk_heap_mem_alloc() failed, gc in progress (gc skipped), alloc size %ld", (long) size));
+		return NULL;
+	}
+
+	/*
+	 *  Retry with several GC attempts.  Initial attempts are made without
+	 *  emergency mode; later attempts use emergency mode which minimizes
+	 *  memory allocations forcibly.
+	 */
+
+	for (i = 0; i < DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_LIMIT; i++) {
+		duk_small_uint_t flags;
+
+		flags = 0;
+		if (i >= DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_EMERGENCY_LIMIT - 1) {
+			flags |= DUK_MS_FLAG_EMERGENCY;
+		}
+
+		rc = duk_heap_mark_and_sweep(heap, flags);
+		DUK_UNREF(rc);
+
+		res = heap->alloc_func(heap->alloc_udata, size);
+		if (res) {
+			DUK_D(DUK_DPRINT("duk_heap_mem_alloc() succeeded after gc (pass %ld), alloc size %ld",
+			                 (long) (i + 1), (long) size));
+			return res;
+		}
+	}
+
+	DUK_D(DUK_DPRINT("duk_heap_mem_alloc() failed even after gc, alloc size %ld", (long) size));
+	return NULL;
+}
+#else  /* DUK_USE_MARK_AND_SWEEP */
+/*
+ *  Compared to a direct macro expansion this wrapper saves a few
+ *  instructions because no heap dereferencing is required.
+ */
+void *duk_heap_mem_alloc(duk_heap *heap, duk_size_t size) {
+	DUK_ASSERT(heap != NULL);
+	DUK_ASSERT_DISABLE(size >= 0);
+
+	return heap->alloc_func(heap->alloc_udata, size);
+}
+#endif  /* DUK_USE_MARK_AND_SWEEP */
+
+void *duk_heap_mem_alloc_zeroed(duk_heap *heap, duk_size_t size) {
+	void *res;
+
+	DUK_ASSERT(heap != NULL);
+	DUK_ASSERT_DISABLE(size >= 0);
+
+	res = DUK_ALLOC(heap, size);
+	if (res) {
+		/* assume memset with zero size is OK */
+		DUK_MEMZERO(res, size);
+	}
+	return res;
+}
+
+/*
+ *  Reallocate memory with garbage collection
+ */
+
+#ifdef DUK_USE_MARK_AND_SWEEP
+void *duk_heap_mem_realloc(duk_heap *heap, void *ptr, duk_size_t newsize) {
+	void *res;
+	duk_bool_t rc;
+	duk_small_int_t i;
+
+	DUK_ASSERT(heap != NULL);
+	/* ptr may be NULL */
+	DUK_ASSERT_DISABLE(newsize >= 0);
+
+	/*
+	 *  Voluntary periodic GC (if enabled)
+	 */
+
+	DUK__VOLUNTARY_PERIODIC_GC(heap);
+
+	/*
+	 *  First attempt
+	 */
+
+#ifdef DUK_USE_GC_TORTURE
+	/* simulate alloc failure on every realloc (except when mark-and-sweep is running) */
+	if (!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
+		DUK_DDD(DUK_DDDPRINT("gc torture enabled, pretend that first realloc attempt fails"));
+		res = NULL;
+		DUK_UNREF(res);
+		goto skip_attempt;
+	}
+#endif
+	res = heap->realloc_func(heap->alloc_udata, ptr, newsize);
+	if (res || newsize == 0) {
+		/* for zero size allocations NULL is allowed */
+		return res;
+	}
+#ifdef DUK_USE_GC_TORTURE
+ skip_attempt:
+#endif
+
+	DUK_D(DUK_DPRINT("first realloc attempt failed, attempt to gc and retry"));
+
+	/*
+	 *  Avoid a GC if GC is already running.  See duk_heap_mem_alloc().
+	 */
+
+	if (DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
+		DUK_D(DUK_DPRINT("duk_heap_mem_realloc() failed, gc in progress (gc skipped), alloc size %ld", (long) newsize));
+		return NULL;
+	}
+
+	/*
+	 *  Retry with several GC attempts.  Initial attempts are made without
+	 *  emergency mode; later attempts use emergency mode which minimizes
+	 *  memory allocations forcibly.
+	 */
+
+	for (i = 0; i < DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_LIMIT; i++) {
+		duk_small_uint_t flags;
+
+		flags = 0;
+		if (i >= DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_EMERGENCY_LIMIT - 1) {
+			flags |= DUK_MS_FLAG_EMERGENCY;
+		}
+
+		rc = duk_heap_mark_and_sweep(heap, flags);
+		DUK_UNREF(rc);
+
+		res = heap->realloc_func(heap->alloc_udata, ptr, newsize);
+		if (res) {
+			DUK_D(DUK_DPRINT("duk_heap_mem_realloc() succeeded after gc (pass %ld), alloc size %ld",
+			                 (long) (i + 1), (long) newsize));
+			return res;
+		}
+	}
+
+	DUK_D(DUK_DPRINT("duk_heap_mem_realloc() failed even after gc, alloc size %ld", (long) newsize));
+	return NULL;
+}
+#else  /* DUK_USE_MARK_AND_SWEEP */
+/* saves a few instructions to have this wrapper (see comment on duk_heap_mem_alloc) */
+void *duk_heap_mem_realloc(duk_heap *heap, void *ptr, duk_size_t newsize) {
+	DUK_ASSERT(heap != NULL);
+	/* ptr may be NULL */
+	DUK_ASSERT_DISABLE(newsize >= 0);
+
+	return heap->realloc_func(heap->alloc_udata, ptr, newsize);
+}
+#endif  /* DUK_USE_MARK_AND_SWEEP */
+
+/*
+ *  Reallocate memory with garbage collection, using a callback to provide
+ *  the current allocated pointer.  This variant is used when a mark-and-sweep
+ *  (e.g. finalizers) might change the original pointer.
+ */
+
+#ifdef DUK_USE_MARK_AND_SWEEP
+void *duk_heap_mem_realloc_indirect(duk_heap *heap, duk_mem_getptr cb, void *ud, duk_size_t newsize) {
+	void *res;
+	duk_bool_t rc;
+	duk_small_int_t i;
+
+	DUK_ASSERT(heap != NULL);
+	DUK_ASSERT_DISABLE(newsize >= 0);
+
+	/*
+	 *  Voluntary periodic GC (if enabled)
+	 */
+
+	DUK__VOLUNTARY_PERIODIC_GC(heap);
+
+	/*
+	 *  First attempt
+	 */
+
+#ifdef DUK_USE_GC_TORTURE
+	/* simulate alloc failure on every realloc (except when mark-and-sweep is running) */
+	if (!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
+		DUK_DDD(DUK_DDDPRINT("gc torture enabled, pretend that first indirect realloc attempt fails"));
+		res = NULL;
+		DUK_UNREF(res);
+		goto skip_attempt;
+	}
+#endif
+	res = heap->realloc_func(heap->alloc_udata, cb(ud), newsize);
+	if (res || newsize == 0) {
+		/* for zero size allocations NULL is allowed */
+		return res;
+	}
+#ifdef DUK_USE_GC_TORTURE
+ skip_attempt:
+#endif
+
+	DUK_D(DUK_DPRINT("first indirect realloc attempt failed, attempt to gc and retry"));
+
+	/*
+	 *  Avoid a GC if GC is already running.  See duk_heap_mem_alloc().
+	 */
+
+	if (DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
+		DUK_D(DUK_DPRINT("duk_heap_mem_realloc_indirect() failed, gc in progress (gc skipped), alloc size %ld", (long) newsize));
+		return NULL;
+	}
+
+	/*
+	 *  Retry with several GC attempts.  Initial attempts are made without
+	 *  emergency mode; later attempts use emergency mode which minimizes
+	 *  memory allocations forcibly.
+	 */
+
+	for (i = 0; i < DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_LIMIT; i++) {
+		duk_small_uint_t flags;
+
+#ifdef DUK_USE_ASSERTIONS
+		void *ptr_pre;  /* ptr before mark-and-sweep */
+		void *ptr_post;
+#endif
+
+#ifdef DUK_USE_ASSERTIONS
+		ptr_pre = cb(ud);
+#endif
+		flags = 0;
+		if (i >= DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_EMERGENCY_LIMIT - 1) {
+			flags |= DUK_MS_FLAG_EMERGENCY;
+		}
+
+		rc = duk_heap_mark_and_sweep(heap, flags);
+		DUK_UNREF(rc);
+#ifdef DUK_USE_ASSERTIONS
+		ptr_post = cb(ud);
+		if (ptr_pre != ptr_post) {
+			/* useful for debugging */
+			DUK_DD(DUK_DDPRINT("note: base pointer changed by mark-and-sweep: %p -> %p",
+			                   (void *) ptr_pre, (void *) ptr_post));
+		}
+#endif
+	
+		/* Note: key issue here is to re-lookup the base pointer on every attempt.
+		 * The pointer being reallocated may change after every mark-and-sweep.
+		 */
+
+		res = heap->realloc_func(heap->alloc_udata, cb(ud), newsize);
+		if (res) {
+			DUK_D(DUK_DPRINT("duk_heap_mem_realloc_indirect() succeeded after gc (pass %ld), alloc size %ld",
+			                 (long) (i + 1), (long) newsize));
+			return res;
+		}
+	}
+
+	DUK_D(DUK_DPRINT("duk_heap_mem_realloc_indirect() failed even after gc, alloc size %ld", (long) newsize));
+	return NULL;
+}
+#else  /* DUK_USE_MARK_AND_SWEEP */
+/* saves a few instructions to have this wrapper (see comment on duk_heap_mem_alloc) */
+void *duk_heap_mem_realloc_indirect(duk_heap *heap, duk_mem_getptr cb, void *ud, duk_size_t newsize) {
+	return heap->realloc_func(heap->alloc_udata, cb(ud), newsize);
+}
+#endif  /* DUK_USE_MARK_AND_SWEEP */
+
+/*
+ *  Free memory
+ */
+
+#ifdef DUK_USE_MARK_AND_SWEEP
+void duk_heap_mem_free(duk_heap *heap, void *ptr) {
+	DUK_ASSERT(heap != NULL);
+	/* ptr may be NULL */
+
+	/* Must behave like a no-op with NULL and any pointer returned from
+	 * malloc/realloc with zero size.
+	 */
+	heap->free_func(heap->alloc_udata, ptr);
+
+	/* Count free operations toward triggering a GC but never actually trigger
+	 * a GC from a free.  Otherwise code which frees internal structures would
+	 * need to put in NULLs at every turn to ensure the object is always in
+	 * consistent state for a mark-and-sweep.
+	 */
+#ifdef DUK_USE_VOLUNTARY_GC
+	heap->mark_and_sweep_trigger_counter--;
+#endif
+}
+#else
+/* saves a few instructions to have this wrapper (see comment on duk_heap_mem_alloc) */
+void duk_heap_mem_free(duk_heap *heap, void *ptr) {
+	DUK_ASSERT(heap != NULL);
+	/* ptr may be NULL */
+
+	/* Note: must behave like a no-op with NULL and any pointer
+	 * returned from malloc/realloc with zero size.
+	 */
+	heap->free_func(heap->alloc_udata, ptr);
+}
+#endif
+
+/*
+ *  Checked variants
+ */
+
+#ifdef DUK_USE_VERBOSE_ERRORS
+void *duk_heap_mem_alloc_checked(duk_hthread *thr, duk_size_t size, const char *filename, duk_int_t line) {
+#else
+void *duk_heap_mem_alloc_checked(duk_hthread *thr, duk_size_t size) {
+#endif
+	void *res;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT_DISABLE(size >= 0);
+
+	res = DUK_ALLOC(thr->heap, size);
+	if (!res) {
+#ifdef DUK_USE_VERBOSE_ERRORS
+		DUK_ERROR_RAW(filename, line, thr, DUK_ERR_ALLOC_ERROR, "memory alloc failed");
+#else
+		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "memory alloc failed");
+#endif
+	}
+	return res;
+}
+
+#ifdef DUK_USE_VERBOSE_ERRORS
+void *duk_heap_mem_alloc_checked_zeroed(duk_hthread *thr, duk_size_t size, const char *filename, duk_int_t line) {
+#else
+void *duk_heap_mem_alloc_checked_zeroed(duk_hthread *thr, duk_size_t size) {
+#endif
+	void *res;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT_DISABLE(size >= 0);
+
+	res = DUK_ALLOC(thr->heap, size);
+	if (!res) {
+#ifdef DUK_USE_VERBOSE_ERRORS
+		DUK_ERROR_RAW(filename, line, thr, DUK_ERR_ALLOC_ERROR, "memory alloc failed");
+#else
+		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "memory alloc failed");
+#endif
+	}
+	/* assume memset with zero size is OK */
+	DUK_MEMZERO(res, size);
+	return res;
+}
+
+#ifdef DUK_USE_VERBOSE_ERRORS
+void *duk_heap_mem_realloc_checked(duk_hthread *thr, void *ptr, duk_size_t newsize, const char *filename, duk_int_t line) {
+#else
+void *duk_heap_mem_realloc_checked(duk_hthread *thr, void *ptr, duk_size_t newsize) {
+#endif
+	void *res;
+
+	DUK_ASSERT(thr != NULL);
+	/* ptr may be NULL */
+	DUK_ASSERT_DISABLE(newsize >= 0);
+
+	res = DUK_REALLOC(thr->heap, ptr, newsize);
+	if (!res) {
+#ifdef DUK_USE_VERBOSE_ERRORS
+		DUK_ERROR_RAW(filename, line, thr, DUK_ERR_ALLOC_ERROR, "memory realloc failed");
+#else
+		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "memory realloc failed");
+#endif
+	}
+	return res;
+}
+
+#ifdef DUK_USE_VERBOSE_ERRORS
+void *duk_heap_mem_realloc_indirect_checked(duk_hthread *thr, duk_mem_getptr cb, void *ud, duk_size_t newsize, const char *filename, duk_int_t line) {
+#else
+void *duk_heap_mem_realloc_indirect_checked(duk_hthread *thr, duk_mem_getptr cb, void *ud, duk_size_t newsize) {
+#endif
+	void *res;
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT_DISABLE(newsize >= 0);
+
+	res = DUK_REALLOC_INDIRECT(thr->heap, cb, ud, newsize);
+	if (!res) {
+#ifdef DUK_USE_VERBOSE_ERRORS
+		DUK_ERROR_RAW(filename, line, thr, DUK_ERR_ALLOC_ERROR, "memory realloc failed");
+#else
+		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "memory realloc failed");
+#endif
+	}
+	return res;
+}
+
+/* Note: no need for duk_heap_mem_free_checked(), as free must not fail.
+ * There is a DUK_FREE_CHECKED() macro just in case, though.
+ */
+#line 1 "duk_heap_misc.c"
+/*
+ *  Support functions for duk_heap.
+ */
+
+/* include removed: duk_internal.h */
+
+#if defined(DUK_USE_DOUBLE_LINKED_HEAP) && defined(DUK_USE_REFERENCE_COUNTING)
+/* arbitrary remove only works with double linked heap, and is only required by
+ * reference counting so far.
+ */
+void duk_heap_remove_any_from_heap_allocated(duk_heap *heap, duk_heaphdr *hdr) {
+	DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(hdr) != DUK_HTYPE_STRING);
+
+	if (DUK_HEAPHDR_GET_PREV(hdr)) {
+		DUK_HEAPHDR_SET_NEXT(DUK_HEAPHDR_GET_PREV(hdr), DUK_HEAPHDR_GET_NEXT(hdr));
+	} else {
+		heap->heap_allocated = DUK_HEAPHDR_GET_NEXT(hdr);
+	}
+	if (DUK_HEAPHDR_GET_NEXT(hdr)) {
+		DUK_HEAPHDR_SET_PREV(DUK_HEAPHDR_GET_NEXT(hdr), DUK_HEAPHDR_GET_PREV(hdr));
+	} else {
+		;
+	}
+}
+#endif
+
+void duk_heap_insert_into_heap_allocated(duk_heap *heap, duk_heaphdr *hdr) {
+	DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(hdr) != DUK_HTYPE_STRING);
+
+#ifdef DUK_USE_DOUBLE_LINKED_HEAP
+	if (heap->heap_allocated) {
+		DUK_ASSERT(DUK_HEAPHDR_GET_PREV(heap->heap_allocated) == NULL);
+		DUK_HEAPHDR_SET_PREV(heap->heap_allocated, hdr);
+	}
+	DUK_HEAPHDR_SET_PREV(hdr, NULL);
+#endif
+	DUK_HEAPHDR_SET_NEXT(hdr, heap->heap_allocated);
+	heap->heap_allocated = hdr;
+}
+
+#ifdef DUK_USE_INTERRUPT_COUNTER
+void duk_heap_switch_thread(duk_heap *heap, duk_hthread *new_thr) {
+	/* Copy currently active interrupt counter from the active thread
+	 * back to the heap structure.  It doesn't need to be copied to
+	 * the target thread, as the bytecode executor does that when it
+	 * resumes execution for a new thread.
+	 */
+	if (heap->curr_thread != NULL) {
+		heap->interrupt_counter = heap->curr_thread->interrupt_counter;
+	}
+	heap->curr_thread = new_thr;  /* may be NULL */
+}
+#endif  /* DUK_USE_INTERRUPT_COUNTER */
+#line 1 "duk_heap_refcount.c"
+/*
+ *  Reference counting implementation.
+ */
+
+/* include removed: duk_internal.h */
+
+#ifdef DUK_USE_REFERENCE_COUNTING
+
+#ifndef DUK_USE_DOUBLE_LINKED_HEAP
+#error internal error, reference counting requires a double linked heap
+#endif
+
+/*
+ *  Misc
+ */
+
+static void duk__queue_refzero(duk_heap *heap, duk_heaphdr *hdr) {
+	/* tail insert: don't disturb head in case refzero is running */
+
+	if (heap->refzero_list != NULL) {
+		duk_heaphdr *hdr_prev;
+
+		hdr_prev = heap->refzero_list_tail;
+		DUK_ASSERT(hdr_prev != NULL);
+		DUK_ASSERT(DUK_HEAPHDR_GET_NEXT(hdr_prev) == NULL);
+
+		DUK_HEAPHDR_SET_NEXT(hdr, NULL);
+		DUK_HEAPHDR_SET_PREV(hdr, hdr_prev);
+		DUK_HEAPHDR_SET_NEXT(hdr_prev, hdr);
+		heap->refzero_list_tail = hdr;
+	} else {
+		DUK_ASSERT(heap->refzero_list_tail == NULL);
+		DUK_HEAPHDR_SET_NEXT(hdr, NULL);
+		DUK_HEAPHDR_SET_PREV(hdr, NULL);
+		heap->refzero_list = hdr;
+		heap->refzero_list_tail = hdr;
+	}
+}
+
+/*
+ *  Heap object refcount finalization.
+ *
+ *  When an object is about to be freed, all other objects it refers to must
+ *  be decref'd.  Refcount finalization does NOT free the object or its inner
+ *  allocations (mark-and-sweep shares these helpers), it just manipulates
+ *  the refcounts.
+ *
+ *  Note that any of the decref's may cause a refcount to drop to zero, BUT
+ *  it will not be processed inline; instead, because refzero is already
+ *  running, the objects will just be queued to refzero list and processed
+ *  later.  This eliminates C recursion.
+ */
+
+static void duk__refcount_finalize_hobject(duk_hthread *thr, duk_hobject *h) {
+	duk_uint_fast32_t i;
+
+	DUK_ASSERT(h);
+	DUK_ASSERT(DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) h) == DUK_HTYPE_OBJECT);
+
+	/* XXX: better to get base and walk forwards? */
+
+	for (i = 0; i < (duk_uint_fast32_t) h->e_used; i++) {
+		duk_hstring *key = DUK_HOBJECT_E_GET_KEY(h, i);
+		if (!key) {
+			continue;
+		}
+		duk_heap_heaphdr_decref(thr, (duk_heaphdr *) key);
+		if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(h, i)) {
+			duk_heap_heaphdr_decref(thr, (duk_heaphdr *) DUK_HOBJECT_E_GET_VALUE_GETTER(h, i));
+			duk_heap_heaphdr_decref(thr, (duk_heaphdr *) DUK_HOBJECT_E_GET_VALUE_SETTER(h, i));
+		} else {
+			duk_heap_tval_decref(thr, DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(h, i));
+		}
+	}
+
+	for (i = 0; i < (duk_uint_fast32_t) h->a_size; i++) {
+		duk_heap_tval_decref(thr, DUK_HOBJECT_A_GET_VALUE_PTR(h, i));
+	}
+
+	/* hash part is a 'weak reference' and does not contribute */
+
+	duk_heap_heaphdr_decref(thr, (duk_heaphdr *) h->prototype);
+
+	if (DUK_HOBJECT_IS_COMPILEDFUNCTION(h)) {
+		duk_hcompiledfunction *f = (duk_hcompiledfunction *) h;
+		duk_tval *tv, *tv_end;
+		duk_hobject **funcs, **funcs_end;
+
+		DUK_ASSERT(f->data != NULL);  /* compiled functions must be created 'atomically' */
+
+		tv = DUK_HCOMPILEDFUNCTION_GET_CONSTS_BASE(f);
+		tv_end = DUK_HCOMPILEDFUNCTION_GET_CONSTS_END(f);
+		while (tv < tv_end) {
+			duk_heap_tval_decref(thr, tv);
+			tv++;
+		}
+
+		funcs = DUK_HCOMPILEDFUNCTION_GET_FUNCS_BASE(f);
+		funcs_end = DUK_HCOMPILEDFUNCTION_GET_FUNCS_END(f);
+		while (funcs < funcs_end) {
+			duk_heap_heaphdr_decref(thr, (duk_heaphdr *) *funcs);
+			funcs++;
+		}
+
+		duk_heap_heaphdr_decref(thr, (duk_heaphdr *) f->data);
+	} else if (DUK_HOBJECT_IS_NATIVEFUNCTION(h)) {
+		duk_hnativefunction *f = (duk_hnativefunction *) h;
+		DUK_UNREF(f);
+		/* nothing to finalize */
+	} else if (DUK_HOBJECT_IS_THREAD(h)) {
+		duk_hthread *t = (duk_hthread *) h;
+		duk_tval *tv;
+
+		tv = t->valstack;
+		while (tv < t->valstack_end) {
+			duk_heap_tval_decref(thr, tv);
+			tv++;
+		}
+
+		for (i = 0; i < (duk_uint_fast32_t) t->callstack_top; i++) {
+			duk_activation *act = t->callstack + i;
+			duk_heap_heaphdr_decref(thr, (duk_heaphdr *) act->func);
+			duk_heap_heaphdr_decref(thr, (duk_heaphdr *) act->var_env);
+			duk_heap_heaphdr_decref(thr, (duk_heaphdr *) act->lex_env);
+#ifdef DUK_USE_NONSTD_FUNC_CALLER_PROPERTY
+			duk_heap_heaphdr_decref(thr, (duk_heaphdr *) act->prev_caller);
+#endif
+		}
+
+#if 0  /* nothing now */
+		for (i = 0; i < (duk_uint_fast32_t) t->catchstack_top; i++) {
+			duk_catcher *cat = t->catchstack + i;
+		}
+#endif
+
+		for (i = 0; i < DUK_NUM_BUILTINS; i++) {
+			duk_heap_heaphdr_decref(thr, (duk_heaphdr *) t->builtins[i]);
+		}
+
+		duk_heap_heaphdr_decref(thr, (duk_heaphdr *) t->resumer);
+	}
+}
+
+void duk_heap_refcount_finalize_heaphdr(duk_hthread *thr, duk_heaphdr *hdr) {
+	DUK_ASSERT(hdr);
+
+	switch ((int) DUK_HEAPHDR_GET_TYPE(hdr)) {
+	case DUK_HTYPE_OBJECT:
+		duk__refcount_finalize_hobject(thr, (duk_hobject *) hdr);
+		break;
+	case DUK_HTYPE_BUFFER:
+		/* nothing to finalize */
+		break;
+	case DUK_HTYPE_STRING:
+		/* cannot happen: strings are not put into refzero list (they don't even have the next/prev pointers) */
+	default:
+		DUK_UNREACHABLE();
+	}
+}
+
+/*
+ *  Refcount memory freeing loop.
+ *
+ *  Frees objects in the refzero_pending list until the list becomes
+ *  empty.  When an object is freed, its references get decref'd and
+ *  may cause further objects to be queued for freeing.
+ *
+ *  This could be expanded to allow incremental freeing: just bail out
+ *  early and resume at a future alloc/decref/refzero.
+ */
+
+static void duk__refzero_free_pending(duk_hthread *thr) {
+	duk_heaphdr *h1, *h2;
+	duk_heap *heap;
+	duk_int_t count = 0;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->heap != NULL);
+	heap = thr->heap;
+	DUK_ASSERT(heap != NULL);
+
+	/*
+	 *  Detect recursive invocation
+	 */
+
+	if (DUK_HEAP_HAS_REFZERO_FREE_RUNNING(heap)) {
+		DUK_DDD(DUK_DDDPRINT("refzero free running, skip run"));
+		return;
+	}
+
+	/*
+	 *  Churn refzero_list until empty
+	 */
+
+	DUK_HEAP_SET_REFZERO_FREE_RUNNING(heap);
+	while (heap->refzero_list) {
+		duk_hobject *obj;
+		duk_bool_t rescued = 0;
+
+		/*
+		 *  Pick an object from the head (don't remove yet).
+		 */
+
+		h1 = heap->refzero_list;
+		obj = (duk_hobject *) h1;
+		DUK_DD(DUK_DDPRINT("refzero processing %p: %!O", (void *) h1, (duk_heaphdr *) h1));
+		DUK_ASSERT(DUK_HEAPHDR_GET_PREV(h1) == NULL);
+		DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(h1) == DUK_HTYPE_OBJECT);  /* currently, always the case */
+
+		/*
+		 *  Finalizer check.
+		 *
+		 *  Note: running a finalizer may have arbitrary side effects, e.g.
+		 *  queue more objects on refzero_list (tail), or even trigger a
+		 *  mark-and-sweep.
+		 *
+		 *  Note: quick reject check should match vast majority of
+		 *  objects and must be safe (not throw any errors, ever).
+		 */
+
+		/* FIXME: If object has FINALIZED, it was finalized by mark-and-sweep on
+		 * its previous run.  Any point in running finalizer again here?  If
+		 * finalization semantics is changed so that finalizer is only run once,
+		 * checking for FINALIZED would happen here.
+		 */
+
+		/* A finalizer is looked up from the object and up its prototype chain
+		 * (which allows inherited finalizers).
+		 */
+		if (duk_hobject_hasprop_raw(thr, obj, DUK_HTHREAD_STRING_INT_FINALIZER(thr))) {
+			DUK_DDD(DUK_DDDPRINT("object has a finalizer, run it"));
+
+			DUK_ASSERT(h1->h_refcount == 0);
+			h1->h_refcount++;  /* bump refcount to prevent refzero during finalizer processing */
+
+			duk_hobject_run_finalizer(thr, obj);  /* must never longjmp */
+
+			h1->h_refcount--;  /* remove artificial bump */
+			DUK_ASSERT_DISABLE(h1->h_refcount >= 0);  /* refcount is unsigned, so always true */
+
+			if (h1->h_refcount != 0) {
+				DUK_DDD(DUK_DDDPRINT("-> object refcount after finalization non-zero, object will be rescued"));
+				rescued = 1;
+			} else {
+				DUK_DDD(DUK_DDDPRINT("-> object refcount still zero after finalization, object will be freed"));
+			}
+		}
+
+  		/* Refzero head is still the same.  This is the case even if finalizer
+		 * inserted more refzero objects; they are inserted to the tail.
+		 */
+		DUK_ASSERT(h1 == heap->refzero_list);
+
+		/*
+		 *  Remove the object from the refzero list.  This cannot be done
+		 *  before a possible finalizer has been executed; the finalizer
+		 *  may trigger a mark-and-sweep, and mark-and-sweep must be able
+		 *  to traverse a complete refzero_list.
+		 */
+
+		h2 = DUK_HEAPHDR_GET_NEXT(h1);
+		if (h2) {
+			DUK_HEAPHDR_SET_PREV(h2, NULL);  /* not strictly necessary */
+			heap->refzero_list = h2;
+		} else {
+			heap->refzero_list = NULL;
+			heap->refzero_list_tail = NULL;
+		}
+
+		/*
+		 *  Rescue or free.
+		 */
+
+		if (rescued) {
+			/* yes -> move back to heap allocated */
+			DUK_DD(DUK_DDPRINT("object rescued during refcount finalization: %p", (void *) h1));
+			DUK_HEAPHDR_SET_PREV(h1, NULL);
+			DUK_HEAPHDR_SET_NEXT(h1, heap->heap_allocated);
+			heap->heap_allocated = h1;
+		} else {
+			/* no -> decref members, then free */
+			duk__refcount_finalize_hobject(thr, obj);
+			duk_heap_free_heaphdr_raw(heap, h1);
+		}
+
+		count++;
+	}
+	DUK_HEAP_CLEAR_REFZERO_FREE_RUNNING(heap);
+
+	DUK_DDD(DUK_DDDPRINT("refzero processed %ld objects", (long) count));
+
+	/*
+	 *  Once the whole refzero cascade has been freed, check for
+	 *  a voluntary mark-and-sweep.
+	 */
+
+#if defined(DUK_USE_MARK_AND_SWEEP) && defined(DUK_USE_VOLUNTARY_GC)
+	/* 'count' is more or less comparable to normal trigger counter update
+	 * which happens in memory block (re)allocation.
+	 */
+	heap->mark_and_sweep_trigger_counter -= count;
+	if (heap->mark_and_sweep_trigger_counter <= 0) {
+		duk_bool_t rc;
+		duk_small_uint_t flags = 0;  /* not emergency */
+		DUK_D(DUK_DPRINT("refcount triggering mark-and-sweep"));
+		rc = duk_heap_mark_and_sweep(heap, flags);
+		DUK_UNREF(rc);
+		DUK_D(DUK_DPRINT("refcount triggered mark-and-sweep => rc %ld", (long) rc));
+	}
+#endif  /* DUK_USE_MARK_AND_SWEEP && DUK_USE_VOLUNTARY_GC */
+}
+
+/*
+ *  Incref and decref functions.
+ *
+ *  Decref may trigger immediate refzero handling, which may free and finalize
+ *  an arbitrary number of objects.
+ *  
+ */
+
+void duk_heap_tval_incref(duk_tval *tv) {
+#if 0
+	DUK_DDD(DUK_DDDPRINT("tval incref %p (%ld->%ld): %!T",
+	                     (void *) tv,
+	                     (tv != NULL && DUK_TVAL_IS_HEAP_ALLOCATED(tv) ? (long) DUK_TVAL_GET_HEAPHDR(tv)->h_refcount : (long) 0),
+	                     (tv != NULL && DUK_TVAL_IS_HEAP_ALLOCATED(tv) ? (long) (DUK_TVAL_GET_HEAPHDR(tv)->h_refcount + 1) : (long) 0),
+	                     (duk_tval *) tv));
+#endif
+
+	if (!tv) {
+		return;
+	}
+
+	if (DUK_TVAL_IS_HEAP_ALLOCATED(tv)) {
+		duk_heaphdr *h = DUK_TVAL_GET_HEAPHDR(tv);
+		if (h) {
+			DUK_ASSERT(DUK_HEAPHDR_HTYPE_VALID(h));
+			DUK_ASSERT_DISABLE(h->h_refcount >= 0);
+			h->h_refcount++;
+		}
+	}
+}
+
+void duk_heap_tval_decref(duk_hthread *thr, duk_tval *tv) {
+#if 0
+	DUK_DDD(DUK_DDDPRINT("tval decref %p (%ld->%ld): %!T",
+	                     (void *) tv,
+	                     (tv != NULL && DUK_TVAL_IS_HEAP_ALLOCATED(tv) ? (long) DUK_TVAL_GET_HEAPHDR(tv)->h_refcount : (long) 0),
+	                     (tv != NULL && DUK_TVAL_IS_HEAP_ALLOCATED(tv) ? (long) (DUK_TVAL_GET_HEAPHDR(tv)->h_refcount - 1) : (long) 0),
+	                     (duk_tval *) tv));
+#endif
+
+	if (!tv) {
+		return;
+	}
+
+	if (DUK_TVAL_IS_HEAP_ALLOCATED(tv)) {
+		duk_heap_heaphdr_decref(thr, DUK_TVAL_GET_HEAPHDR(tv));
+	}
+}
+
+void duk_heap_heaphdr_incref(duk_heaphdr *h) {
+#if 0
+	DUK_DDD(DUK_DDDPRINT("heaphdr incref %p (%ld->%ld): %!O",
+	                     (void *) h,
+	                     (h != NULL ? (long) h->h_refcount : (long) 0),
+	                     (h != NULL ? (long) (h->h_refcount + 1) : (long) 0),
+	                     (duk_heaphdr *) h));
+#endif
+
+	if (!h) {
+		return;
+	}
+	DUK_ASSERT(DUK_HEAPHDR_HTYPE_VALID(h));
+	DUK_ASSERT_DISABLE(h->h_refcount >= 0);
+
+	h->h_refcount++;
+}
+
+void duk_heap_heaphdr_decref(duk_hthread *thr, duk_heaphdr *h) {
+	duk_heap *heap;
+
+#if 0
+	DUK_DDD(DUK_DDDPRINT("heaphdr decref %p (%ld->%ld): %!O",
+	                     (void *) h,
+	                     (h != NULL ? (long) h->h_refcount : (long) 0),
+	                     (h != NULL ? (long) (h->h_refcount - 1) : (long) 0),
+	                     (duk_heaphdr *) h));
+#endif
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->heap != NULL);
+
+	if (!h) {
+		return;
+	}
+	DUK_ASSERT(DUK_HEAPHDR_HTYPE_VALID(h));
+	DUK_ASSERT(h->h_refcount >= 1);
+
+	if (--h->h_refcount != 0) {
+		return;
+	}
+
+	heap = thr->heap;
+	DUK_DDD(DUK_DDDPRINT("refzero %p: %!O", (void *) h, (duk_heaphdr *) h));
+
+#ifdef DUK_USE_MARK_AND_SWEEP
+	/*
+	 *  If mark-and-sweep is running, don't process 'refzero' situations at all.
+	 *  They may happen because mark-and-sweep needs to finalize refcounts for
+	 *  each object it sweeps.  Otherwise the target objects of swept objects
+	 *  would have incorrect refcounts.
+	 *
+	 *  Note: mark-and-sweep could use a separate decref handler to avoid coming
+	 *  here at all.  However, mark-and-sweep may also call finalizers, which
+	 *  can do arbitrary operations and would use this decref variant anyway.
+	 */
+	if (DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
+		DUK_DDD(DUK_DDDPRINT("refzero handling suppressed when mark-and-sweep running, object: %p", (void *) h));
+		return;
+	}
+#endif
+
+	switch ((duk_small_int_t) DUK_HEAPHDR_GET_TYPE(h)) {
+	case DUK_HTYPE_STRING:
+		/*
+		 *  Strings have no internal references but do have "weak"
+		 *  references in the string cache.  Also note that strings
+		 *  are not on the heap_allocated list like other heap
+		 *  elements.
+		 */
+
+		duk_heap_strcache_string_remove(heap, (duk_hstring *) h);
+		duk_heap_string_remove(heap, (duk_hstring *) h);
+		duk_heap_free_heaphdr_raw(heap, h);
+		break;
+
+	case DUK_HTYPE_OBJECT:
+		/*
+		 *  Objects have internal references.  Must finalize through
+		 *  the "refzero" work list.
+		 */
+
+		duk_heap_remove_any_from_heap_allocated(heap, h);
+		duk__queue_refzero(heap, h);
+		duk__refzero_free_pending(thr);
+		break;
+
+	case DUK_HTYPE_BUFFER:
+		/*
+		 *  Buffers have no internal references.  However, a dynamic
+		 *  buffer has a separate allocation for the buffer.  This is
+		 *  freed by duk_heap_free_heaphdr_raw().
+		 */
+
+		duk_heap_remove_any_from_heap_allocated(heap, h);
+		duk_heap_free_heaphdr_raw(heap, h);
+		break;
+
+	default:
+		DUK_D(DUK_DPRINT("invalid heap type in decref: %ld", (long) DUK_HEAPHDR_GET_TYPE(h)));
+		DUK_UNREACHABLE();
+	}
+}
+
+#else
+
+/* no refcounting */
+
+#endif  /* DUK_USE_REFERENCE_COUNTING */
+#line 1 "duk_heap_stringcache.c"
+/*
+ *  String cache.
+ *
+ *  Provides a cache to optimize indexed string lookups.  The cache keeps
+ *  track of (byte offset, char offset) states for a fixed number of strings.
+ *  Otherwise we'd need to scan from either end of the string, as we store
+ *  strings in (extended) UTF-8.
+ */
+
+/* include removed: duk_internal.h */
+
+/*
+ *  Delete references to given hstring from the heap string cache.
+ *
+ *  String cache references are 'weak': they are not counted towards
+ *  reference counts, nor serve as roots for mark-and-sweep.  When an
+ *  object is about to be freed, such references need to be removed.
+ */
+
+void duk_heap_strcache_string_remove(duk_heap *heap, duk_hstring *h) {
+	duk_small_int_t i;
+	for (i = 0; i < DUK_HEAP_STRCACHE_SIZE; i++) {
+		duk_strcache *c = heap->strcache + i;
+		if (c->h == h) {
+			DUK_DD(DUK_DDPRINT("deleting weak strcache reference to hstring %p from heap %p",
+			                   (void *) h, (void *) heap));
+			c->h = NULL;
+
+			/* XXX: the string shouldn't appear twice, but we now loop to the
+			 * end anyway; if fixed, add a looping assertion to ensure there
+			 * is no duplicate.
+			 */
+		}
+	}
+}
+
+/*
+ *  String scanning helpers
+ */
+
+static duk_uint8_t *duk__scan_forwards(duk_uint8_t *p, duk_uint8_t *q, duk_uint_fast32_t n) {
+	while (n > 0) {
+		for (;;) {
+			p++;
+			if (p >= q) {
+				return NULL;
+			}
+			if ((*p & 0xc0) != 0x80) {
+				break;
+			}
+		}
+		n--;
+	}
+	return p;
+}
+
+static duk_uint8_t *duk__scan_backwards(duk_uint8_t *p, duk_uint8_t *q, duk_uint_fast32_t n) {
+	while (n > 0) {
+		for (;;) {
+			p--;
+			if (p < q) {
+				return NULL;
+			}
+			if ((*p & 0xc0) != 0x80) {
+				break;
+			}
+		}
+		n--;
+	}
+	return p;
+}
+
+/*
+ *  Convert char offset to byte offset
+ *
+ *  Avoid using the string cache if possible: for ASCII strings byte and
+ *  char offsets are equal and for short strings direct scanning may be
+ *  better than using the string cache (which may evict a more important
+ *  entry).
+ *
+ *  Typing now assumes 32-bit string byte/char offsets (duk_uint_fast32_t).
+ *  Better typing might be to use duk_size_t.
+ */
+
+duk_uint_fast32_t duk_heap_strcache_offset_char2byte(duk_hthread *thr, duk_hstring *h, duk_uint_fast32_t char_offset) {
+	duk_heap *heap;
+	duk_strcache *sce;
+	duk_uint_fast32_t byte_offset;
+	duk_small_int_t i;
+	duk_bool_t use_cache;
+	duk_uint_fast32_t dist_start, dist_end, dist_sce;
+	duk_uint8_t *p_start;
+	duk_uint8_t *p_end;
+	duk_uint8_t *p_found;
+
+	if (char_offset > DUK_HSTRING_GET_CHARLEN(h)) {
+		goto error;
+	}
+
+	/*
+	 *  For ASCII strings, the answer is simple.
+	 */
+
+	if (DUK_HSTRING_IS_ASCII(h)) {
+		/* clen == blen -> pure ascii */
+		return char_offset;
+	}
+
+	/*
+	 *  For non-ASCII strings, we need to scan forwards or backwards
+	 *  from some starting point.  The starting point may be the start
+	 *  or end of the string, or some cached midpoint in the string
+	 *  cache.
+	 *
+	 *  For "short" strings we simply scan without checking or updating
+	 *  the cache.  For longer strings we check and update the cache as
+	 *  necessary, inserting a new cache entry if none exists.
+	 */
+
+	DUK_DDD(DUK_DDDPRINT("non-ascii string %p, char_offset=%ld, clen=%ld, blen=%ld",
+	                     (void *) h, (long) char_offset,
+	                     (long) DUK_HSTRING_GET_CHARLEN(h),
+	                     (long) DUK_HSTRING_GET_BYTELEN(h)));
+
+	heap = thr->heap;
+	sce = NULL;
+	use_cache = (DUK_HSTRING_GET_CHARLEN(h) > DUK_HEAP_STRINGCACHE_NOCACHE_LIMIT);
+
+	if (use_cache) {
+#ifdef DUK_USE_DDDPRINT
+		DUK_DDD(DUK_DDDPRINT("stringcache before char2byte (using cache):"));
+		for (i = 0; i < DUK_HEAP_STRCACHE_SIZE; i++) {
+			duk_strcache *c = heap->strcache + i;
+			DUK_DDD(DUK_DDDPRINT("  [%ld] -> h=%p, cidx=%ld, bidx=%ld",
+			                     (long) i, (void *) c->h, (long) c->cidx, (long) c->bidx));
+		}
+#endif
+
+		for (i = 0; i < DUK_HEAP_STRCACHE_SIZE; i++) {
+			duk_strcache *c = heap->strcache + i;
+
+			if (c->h == h) {
+				sce = c;
+				break;
+			}
+		}
+	}
+
+	/*
+	 *  Scan from shortest distance:
+	 *    - start of string
+	 *    - end of string
+	 *    - cache entry (if exists)
+	 */
+
+	DUK_ASSERT(DUK_HSTRING_GET_CHARLEN(h) >= char_offset);
+	dist_start = char_offset;
+	dist_end = DUK_HSTRING_GET_CHARLEN(h) - char_offset;
+	dist_sce = 0; DUK_UNREF(dist_sce);  /* initialize for debug prints, needed if sce==NULL */
+
+	p_start = (duk_uint8_t *) DUK_HSTRING_GET_DATA(h);
+	p_end = (duk_uint8_t *) (p_start + DUK_HSTRING_GET_BYTELEN(h));
+	p_found = NULL;
+
+	if (sce) {
+		if (char_offset >= sce->cidx) {
+			dist_sce = char_offset - sce->cidx;
+			if ((dist_sce <= dist_start) && (dist_sce <= dist_end)) {
+				DUK_DDD(DUK_DDDPRINT("non-ascii string, use_cache=%ld, sce=%p:%ld:%ld, "
+				                     "dist_start=%ld, dist_end=%ld, dist_sce=%ld => "
+				                     "scan forwards from sce",
+				                     (long) use_cache, (void *) (sce ? sce->h : NULL),
+				                     (sce ? (long) sce->cidx : (long) -1),
+				                     (sce ? (long) sce->bidx : (long) -1),
+				                     (long) dist_start, (long) dist_end, (long) dist_sce));
+
+				p_found = duk__scan_forwards(p_start + sce->bidx,
+				                             p_end,
+				                             dist_sce);
+				goto scan_done;
+			}
+		} else {
+			dist_sce = sce->cidx - char_offset;
+			if ((dist_sce <= dist_start) && (dist_sce <= dist_end)) {
+				DUK_DDD(DUK_DDDPRINT("non-ascii string, use_cache=%ld, sce=%p:%ld:%ld, "
+				                     "dist_start=%ld, dist_end=%ld, dist_sce=%ld => "
+				                     "scan backwards from sce",
+				                     (long) use_cache, (void *) (sce ? sce->h : NULL),
+				                     (sce ? (long) sce->cidx : (long) -1),
+				                     (sce ? (long) sce->bidx : (long) -1),
+				                     (long) dist_start, (long) dist_end, (long) dist_sce));
+
+				p_found = duk__scan_backwards(p_start + sce->bidx,
+				                              p_start,
+				                              dist_sce);
+				goto scan_done;
+			}
+		}
+	}
+
+	/* no sce, or sce scan not best */
+
+	if (dist_start <= dist_end) {
+		DUK_DDD(DUK_DDDPRINT("non-ascii string, use_cache=%ld, sce=%p:%ld:%ld, "
+		                     "dist_start=%ld, dist_end=%ld, dist_sce=%ld => "
+		                     "scan forwards from string start",
+		                     (long) use_cache, (void *) (sce ? sce->h : NULL),
+		                     (sce ? (long) sce->cidx : (long) -1),
+		                     (sce ? (long) sce->bidx : (long) -1),
+		                     (long) dist_start, (long) dist_end, (long) dist_sce));
+
+		p_found = duk__scan_forwards(p_start,
+		                             p_end,
+		                             dist_start);
+	} else {
+		DUK_DDD(DUK_DDDPRINT("non-ascii string, use_cache=%ld, sce=%p:%ld:%ld, "
+		                     "dist_start=%ld, dist_end=%ld, dist_sce=%ld => "
+		                     "scan backwards from string end",
+		                     (long) use_cache, (void *) (sce ? sce->h : NULL),
+		                     (sce ? (long) sce->cidx : (long) -1),
+		                     (sce ? (long) sce->bidx : (long) -1),
+		                     (long) dist_start, (long) dist_end, (long) dist_sce));
+
+		p_found = duk__scan_backwards(p_end,
+		                              p_start,
+		                              dist_end);
+	}
+
+ scan_done:
+
+	if (!p_found) {
+		/* Scan error: this shouldn't normally happen; it could happen if
+		 * string is not valid UTF-8 data, and clen/blen are not consistent
+		 * with the scanning algorithm.
+		 */
+		goto error;
+	}
+
+	DUK_ASSERT(p_found >= p_start);
+	DUK_ASSERT(p_found <= p_end);  /* may be equal */
+	byte_offset = (duk_uint32_t) (p_found - p_start);
+
+	DUK_DDD(DUK_DDDPRINT("-> string %p, cidx %ld -> bidx %ld",
+	                     (void *) h, (long) char_offset, (long) byte_offset));
+
+	/*
+	 *  Update cache entry (allocating if necessary), and move the
+	 *  cache entry to the first place (in an "LRU" policy).
+	 */
+	
+	if (use_cache) {
+		/* update entry, allocating if necessary */
+		if (!sce) {
+			sce = heap->strcache + DUK_HEAP_STRCACHE_SIZE - 1;  /* take last entry */
+			sce->h = h;
+		}
+		DUK_ASSERT(sce != NULL);
+		sce->bidx = (duk_uint32_t) (p_found - p_start);
+		sce->cidx = (duk_uint32_t) char_offset;
+
+		/* LRU: move our entry to first */
+		if (sce > &heap->strcache[0]) {
+			/*
+			 *   A                  C
+			 *   B                  A
+			 *   C <- sce    ==>    B
+			 *   D                  D
+			 */
+			duk_strcache tmp;
+
+			tmp = *sce;
+			DUK_MEMMOVE((void *) (&heap->strcache[1]),
+			            (void *) (&heap->strcache[0]),
+			            (size_t) (((char *) sce) - ((char *) &heap->strcache[0])));
+			heap->strcache[0] = tmp;
+
+			/* 'sce' points to the wrong entry here, but is no longer used */
+		}
+#ifdef DUK_USE_DDDPRINT
+		DUK_DDD(DUK_DDDPRINT("stringcache after char2byte (using cache):"));
+		for (i = 0; i < DUK_HEAP_STRCACHE_SIZE; i++) {
+			duk_strcache *c = heap->strcache + i;
+			DUK_DDD(DUK_DDDPRINT("  [%ld] -> h=%p, cidx=%ld, bidx=%ld",
+			                     (long) i, (void *) c->h, (long) c->cidx, (long) c->bidx));
+		}
+#endif
+	}
+
+	return byte_offset;
+
+ error:
+	DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "string scan error");
+	return 0;
+}
+#line 1 "duk_heap_stringtable.c"
+/*
+ *  Heap stringtable handling, string interning.
+ */
+
+/* include removed: duk_internal.h */
+
+#define DUK__HASH_INITIAL(hash,h_size)        DUK_STRTAB_HASH_INITIAL((hash),(h_size))
+#define DUK__HASH_PROBE_STEP(hash)            DUK_STRTAB_HASH_PROBE_STEP((hash))
+#define DUK__DELETED_MARKER(heap)             DUK_STRTAB_DELETED_MARKER((heap))
+
+/*
+ *  Create a hstring and insert into the heap.  The created object
+ *  is directly garbage collectable with reference count zero.
+ *
+ *  The caller must place the interned string into the stringtable
+ *  immediately (without chance of a longjmp); otherwise the string
+ *  is lost.
+ */
+
+static duk_hstring *duk__alloc_init_hstring(duk_heap *heap,
+                                            duk_uint8_t *str,
+                                            duk_uint32_t blen,
+                                            duk_uint32_t strhash) {
+	duk_hstring *res = NULL;
+	duk_uint8_t *data;
+	duk_size_t alloc_size;
+	duk_uarridx_t dummy;
+
+	/* NUL terminate for convenient C access */
+
+	alloc_size = (duk_size_t) (sizeof(duk_hstring) + blen + 1);
+	res = (duk_hstring *) DUK_ALLOC(heap, alloc_size);
+	if (!res) {
+		goto error;
+	}
+
+	DUK_MEMZERO(res, sizeof(duk_hstring));
+#ifdef DUK_USE_EXPLICIT_NULL_INIT
+	DUK_HEAPHDR_STRING_INIT_NULLS(&res->hdr);
+#endif
+	DUK_HEAPHDR_SET_TYPE_AND_FLAGS(&res->hdr, DUK_HTYPE_STRING, 0);
+
+	if (duk_js_to_arrayindex_raw_string(str, blen, &dummy)) {
+		DUK_HSTRING_SET_ARRIDX(res);
+	}
+
+	res->hash = strhash;
+	res->blen = blen;
+	res->clen = (duk_uint32_t) duk_unicode_unvalidated_utf8_length(str, (duk_size_t) blen);  /* clen <= blen */
+
+	data = (duk_uint8_t *) (res + 1);
+	DUK_MEMCPY(data, str, blen);
+	data[blen] = (duk_uint8_t) 0;
+
+	DUK_DDD(DUK_DDDPRINT("interned string, hash=0x%08lx, blen=%ld, clen=%ld, has_arridx=%ld",
+	                     (unsigned long) DUK_HSTRING_GET_HASH(res),
+	                     (long) DUK_HSTRING_GET_BYTELEN(res),
+	                     (long) DUK_HSTRING_GET_CHARLEN(res),
+	                     (long) DUK_HSTRING_HAS_ARRIDX(res) ? 1 : 0));
+
+	return res;
+
+ error:
+	DUK_FREE(heap, res);
+	return NULL;
+}
+
+/*
+ *  Count actually used (non-NULL, non-DELETED) entries
+ */
+
+static duk_int_t duk__count_used(duk_heap *heap) {
+	duk_int_t res = 0;
+	duk_uint_fast32_t i, n;
+
+	n = (duk_uint_fast32_t) heap->st_size;
+	for (i = 0; i < n; i++) {
+		if (heap->st[i] != NULL && heap->st[i] != DUK__DELETED_MARKER(heap)) {
+			res++;
+		}
+	}
+	return res;
+}
+
+/*
+ *  Hashtable lookup and insert helpers
+ */
+
+static void duk__insert_hstring(duk_heap *heap, duk_hstring **entries, duk_uint32_t size, duk_uint32_t *p_used, duk_hstring *h) {
+	duk_uint32_t i;
+	duk_uint32_t step;
+
+	DUK_ASSERT(size > 0);
+
+	i = DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(h), size);
+	step = DUK__HASH_PROBE_STEP(DUK_HSTRING_GET_HASH(h)); 
+	for (;;) {
+		duk_hstring *e;
+		
+		e = entries[i];
+		if (e == NULL) {
+			DUK_DDD(DUK_DDDPRINT("insert hit (null): %ld", (long) i));
+			entries[i] = h;
+			(*p_used)++;
+			break;
+		} else if (e == DUK__DELETED_MARKER(heap)) {
+			/* st_used remains the same, DELETED is counted as used */
+			DUK_DDD(DUK_DDDPRINT("insert hit (deleted): %ld", (long) i));
+			entries[i] = h;
+			break;
+		}
+		DUK_DDD(DUK_DDDPRINT("insert miss: %ld", (long) i));
+		i = (i + step) % size;
+
+		/* looping should never happen */
+		DUK_ASSERT(i != DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(h), size));
+	}
+}
+
+static duk_hstring *duk__find_matching_string(duk_heap *heap, duk_hstring **entries, duk_uint32_t size, duk_uint8_t *str, duk_uint32_t blen, duk_uint32_t strhash) {
+	duk_uint32_t i;
+	duk_uint32_t step;
+
+	DUK_ASSERT(size > 0);
+
+	i = DUK__HASH_INITIAL(strhash, size);
+	step = DUK__HASH_PROBE_STEP(strhash);
+	for (;;) {
+		duk_hstring *e;
+
+		e = entries[i];
+		if (!e) {
+			return NULL;
+		}
+		if (e != DUK__DELETED_MARKER(heap) && DUK_HSTRING_GET_BYTELEN(e) == blen) {
+			if (DUK_MEMCMP(str, DUK_HSTRING_GET_DATA(e), blen) == 0) {
+				DUK_DDD(DUK_DDDPRINT("find matching hit: %ld (step %ld, size %ld)",
+				                     (long) i, (long) step, (long) size));
+				return e;
+			}
+		}
+		DUK_DDD(DUK_DDDPRINT("find matching miss: %ld (step %ld, size %ld)",
+		                     (long) i, (long) step, (long) size));
+		i = (i + step) % size;
+
+		/* looping should never happen */
+		DUK_ASSERT(i != DUK__HASH_INITIAL(strhash, size));
+	}
+	DUK_UNREACHABLE();
+}
+
+static void duk__remove_matching_hstring(duk_heap *heap, duk_hstring **entries, duk_uint32_t size, duk_hstring *h) {
+	duk_uint32_t i;
+	duk_uint32_t step;
+
+	DUK_ASSERT(size > 0);
+
+	i = DUK__HASH_INITIAL(h->hash, size);
+	step = DUK__HASH_PROBE_STEP(h->hash);
+	for (;;) {
+		duk_hstring *e;
+
+		e = entries[i];
+		if (!e) {
+			DUK_UNREACHABLE();
+			break;
+		}
+		if (e == h) {
+			/* st_used remains the same, DELETED is counted as used */
+			DUK_DDD(DUK_DDDPRINT("free matching hit: %ld", (long) i));
+			entries[i] = DUK__DELETED_MARKER(heap);
+			break;
+		}
+
+		DUK_DDD(DUK_DDDPRINT("free matching miss: %ld", (long) i));
+		i = (i + step) % size;
+
+		/* looping should never happen */
+		DUK_ASSERT(i != DUK__HASH_INITIAL(h->hash, size));
+	}
+}
+
+/*
+ *  Hash resizing and resizing policy
+ */
+
+static duk_bool_t duk__resize_strtab_raw(duk_heap *heap, duk_uint32_t new_size) {
+#ifdef DUK_USE_MARK_AND_SWEEP
+	duk_small_uint_t prev_mark_and_sweep_base_flags;
+#endif
+#ifdef DUK_USE_DEBUG
+	duk_uint32_t old_used = heap->st_used;
+#endif
+	duk_uint32_t old_size = heap->st_size;
+	duk_hstring **old_entries = heap->st;
+	duk_hstring **new_entries = NULL;
+	duk_uint32_t new_used = 0;
+	duk_uint32_t i;
+
+#ifdef DUK_USE_DEBUG
+	DUK_UNREF(old_used);  /* unused with some debug level combinations */
+#endif
+
+#ifdef DUK_USE_DDDPRINT
+	DUK_DDD(DUK_DDDPRINT("attempt to resize stringtable: %ld entries, %ld bytes, %ld used, %ld%% load -> %ld entries, %ld bytes, %ld used, %ld%% load",
+	                     (long) old_size, (long) (sizeof(duk_hstring *) * old_size), (long) old_used,
+	                     (long) (((double) old_used) / ((double) old_size) * 100.0),
+	                     (long) new_size, (long) (sizeof(duk_hstring *) * new_size), (long) duk__count_used(heap),
+	                     (long) (((double) duk__count_used(heap)) / ((double) new_size) * 100.0)));
+#endif
+
+	DUK_ASSERT(new_size > (duk_uint32_t) duk__count_used(heap));  /* required for rehash to succeed, equality not that useful */
+	DUK_ASSERT(old_entries);
+#ifdef DUK_USE_MARK_AND_SWEEP
+	DUK_ASSERT((heap->mark_and_sweep_base_flags & DUK_MS_FLAG_NO_STRINGTABLE_RESIZE) == 0);
+#endif
+
+	/*
+	 *  The attempt to allocate may cause a GC.  Such a GC must not attempt to resize
+	 *  the stringtable (though it can be swept); finalizer execution and object
+	 *  compaction must also be postponed to avoid the pressure to add strings to the
+	 *  string table.
+	 */
+
+#ifdef DUK_USE_MARK_AND_SWEEP
+	prev_mark_and_sweep_base_flags = heap->mark_and_sweep_base_flags;
+	heap->mark_and_sweep_base_flags |= \
+	        DUK_MS_FLAG_NO_STRINGTABLE_RESIZE |  /* avoid recursive call here */
+	        DUK_MS_FLAG_NO_FINALIZERS |          /* avoid pressure to add/remove strings */
+	        DUK_MS_FLAG_NO_OBJECT_COMPACTION;    /* avoid array abandoning which interns strings */
+#endif
+
+	new_entries = (duk_hstring **) DUK_ALLOC(heap, sizeof(duk_hstring *) * new_size);
+
+#ifdef DUK_USE_MARK_AND_SWEEP
+	heap->mark_and_sweep_base_flags = prev_mark_and_sweep_base_flags;
+#endif
+
+	if (!new_entries) {
+		goto error;
+	}
+
+#ifdef DUK_USE_EXPLICIT_NULL_INIT
+	for (i = 0; i < new_size; i++) {
+		new_entries[i] = NULL;
+	}
+#else
+	DUK_MEMZERO(new_entries, sizeof(duk_hstring *) * new_size);
+#endif
+
+	/* Because new_size > duk__count_used(heap), guaranteed to work */
+	for (i = 0; i < old_size; i++) {
+		duk_hstring *e;
+
+		e = old_entries[i];
+		if (e == NULL || e == DUK__DELETED_MARKER(heap)) {
+			continue;
+		}
+		/* checking for DUK__DELETED_MARKER is not necessary here, but helper does it now */
+		duk__insert_hstring(heap, new_entries, new_size, &new_used, e);
+	}
+
+#ifdef DUK_USE_DDPRINT
+	DUK_DD(DUK_DDPRINT("resized stringtable: %ld entries, %ld bytes, %ld used, %ld%% load -> %ld entries, %ld bytes, %ld used, %ld%% load",
+	                   (long) old_size, (long) (sizeof(duk_hstring *) * old_size), (long) old_used,
+	                   (long) (((double) old_used) / ((double) old_size) * 100.0),
+	                   (long) new_size, (long) (sizeof(duk_hstring *) * new_size), (long) new_used,
+	                   (long) (((double) new_used) / ((double) new_size) * 100.0)));
+#endif
+
+	DUK_FREE(heap, heap->st);
+	heap->st = new_entries;
+	heap->st_size = new_size;
+	heap->st_used = new_used;  /* may be less, since DELETED entries are NULLed by rehash */
+
+	return 0;  /* OK */
+
+ error:
+	DUK_FREE(heap, new_entries);
+	return 1;  /* FAIL */
+}
+
+static duk_bool_t duk__resize_strtab(duk_heap *heap) {
+	duk_uint32_t new_size;
+	duk_bool_t ret;
+
+	new_size = (duk_uint32_t) duk__count_used(heap);
+	if (new_size >= 0x80000000UL) {
+		new_size = DUK_STRTAB_HIGHEST_32BIT_PRIME;
+	} else {
+		new_size = duk_util_get_hash_prime(DUK_STRTAB_GROW_ST_SIZE(new_size));
+		new_size = duk_util_get_hash_prime(new_size);
+	}
+	DUK_ASSERT(new_size > 0);
+
+	/* rehash even if old and new sizes are the same to get rid of
+	 * DELETED entries.
+	*/ 
+
+	ret = duk__resize_strtab_raw(heap, new_size);
+
+	return ret;
+}
+
+static duk_bool_t duk__recheck_strtab_size(duk_heap *heap, duk_uint32_t new_used) {
+	duk_uint32_t new_free;
+	duk_uint32_t tmp1;
+	duk_uint32_t tmp2;
+
+	DUK_ASSERT(new_used <= heap->st_size);  /* grow by at most one */
+	new_free = heap->st_size - new_used;    /* unsigned intentionally */
+
+	/* new_free / size <= 1 / DIV  <=>  new_free <= size / DIV */
+	/* new_used / size <= 1 / DIV  <=>  new_used <= size / DIV */
+
+	tmp1 = heap->st_size / DUK_STRTAB_MIN_FREE_DIVISOR;
+	tmp2 = heap->st_size / DUK_STRTAB_MIN_USED_DIVISOR;
+
+	if (new_free <= tmp1 || new_used <= tmp2) {
+		/* load factor too low or high, count actually used entries and resize */
+		return duk__resize_strtab(heap);
+	} else {
+		return 0;  /* OK */
+	}
+}
+
+/*
+ *  Raw intern and lookup
+ */
+
+static duk_hstring *duk__do_intern(duk_heap *heap, duk_uint8_t *str, duk_uint32_t blen, duk_uint32_t strhash) {
+	duk_hstring *res;
+
+	if (duk__recheck_strtab_size(heap, heap->st_used + 1)) {
+		return NULL;
+	}
+
+	/* For manual testing only. */
+#if 0
+	{
+		duk_size_t i;
+		printf("INTERN: \"");
+		for (i = 0; i < blen; i++) {
+			duk_uint8_t x = str[i];
+			if (x >= 0x20 && x <= 0x7e && x != '"' && x != '\\') {
+				printf("%c", (int) x);  /* char: use int cast */
+			} else {
+				printf("\\x%02lx", (long) x);
+			}
+		}
+		printf("\"\n");
+	}
+#endif
+
+	res = duk__alloc_init_hstring(heap, str, blen, strhash);
+	if (!res) {
+		return NULL;
+	}
+
+	duk__insert_hstring(heap, heap->st, heap->st_size, &heap->st_used, res);  /* guaranteed to succeed */
+
+	/* Note: hstring is in heap but has refcount zero and is not strongly reachable.
+	 * Caller should increase refcount and make the hstring reachable before any
+	 * operations which require allocation (and possible gc).
+	 */
+
+	return res;
+}
+
+static duk_hstring *duk__do_lookup(duk_heap *heap, duk_uint8_t *str, duk_uint32_t blen, duk_uint32_t *out_strhash) {
+	duk_hstring *res;
+
+	DUK_ASSERT(out_strhash);
+
+	*out_strhash = duk_heap_hashstring(heap, str, (duk_size_t) blen);
+	res = duk__find_matching_string(heap, heap->st, heap->st_size, str, blen, *out_strhash);
+	return res;
+}
+
+/*
+ *  Exposed calls
+ */
+
+duk_hstring *duk_heap_string_lookup(duk_heap *heap, duk_uint8_t *str, duk_uint32_t blen) {
+	duk_uint32_t strhash;  /* dummy */
+	return duk__do_lookup(heap, str, blen, &strhash);
+}
+
+duk_hstring *duk_heap_string_intern(duk_heap *heap, duk_uint8_t *str, duk_uint32_t blen) {
+	duk_hstring *res;
+	duk_uint32_t strhash;
+
+	/* caller is responsible for ensuring this */
+	DUK_ASSERT(blen <= DUK_HSTRING_MAX_BYTELEN);
+
+	res = duk__do_lookup(heap, str, blen, &strhash);
+	if (res) {
+		return res;
+	}
+
+	res = duk__do_intern(heap, str, blen, strhash);
+	return res;  /* may be NULL */
+}
+
+duk_hstring *duk_heap_string_intern_checked(duk_hthread *thr, duk_uint8_t *str, duk_uint32_t blen) {
+	duk_hstring *res = duk_heap_string_intern(thr->heap, str, blen);
+	if (!res) {
+		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "failed to intern string");
+	}
+	return res;
+}
+
+duk_hstring *duk_heap_string_lookup_u32(duk_heap *heap, duk_uint32_t val) {
+	char buf[DUK_STRTAB_U32_MAX_STRLEN+1];
+	DUK_SNPRINTF(buf, sizeof(buf), "%lu", (unsigned long) val);
+	buf[sizeof(buf) - 1] = (char) 0;
+	DUK_ASSERT(DUK_STRLEN(buf) <= DUK_UINT32_MAX);  /* formatted result limited */
+	return duk_heap_string_lookup(heap, (duk_uint8_t *) buf, (duk_uint32_t) DUK_STRLEN(buf));
+}
+
+duk_hstring *duk_heap_string_intern_u32(duk_heap *heap, duk_uint32_t val) {
+	char buf[DUK_STRTAB_U32_MAX_STRLEN+1];
+	DUK_SNPRINTF(buf, sizeof(buf), "%lu", (unsigned long) val);
+	buf[sizeof(buf) - 1] = (char) 0;
+	DUK_ASSERT(DUK_STRLEN(buf) <= DUK_UINT32_MAX);  /* formatted result limited */
+	return duk_heap_string_intern(heap, (duk_uint8_t *) buf, (duk_uint32_t) DUK_STRLEN(buf));
+}
+
+duk_hstring *duk_heap_string_intern_u32_checked(duk_hthread *thr, duk_uint32_t val) {
+	duk_hstring *res = duk_heap_string_intern_u32(thr->heap, val);
+	if (!res) {
+		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "failed to intern string");
+	}
+	return res;
+}
+
+/* find and remove string from stringtable; caller must free the string itself */
+void duk_heap_string_remove(duk_heap *heap, duk_hstring *h) {
+	DUK_DDD(DUK_DDDPRINT("remove string from stringtable: %!O", (duk_heaphdr *) h));
+	duk__remove_matching_hstring(heap, heap->st, heap->st_size, h);
+}
+
+#if defined(DUK_USE_MARK_AND_SWEEP) && defined(DUK_USE_MS_STRINGTABLE_RESIZE)
+void duk_heap_force_stringtable_resize(duk_heap *heap) {
+	/* Force a resize so that DELETED entries are eliminated.
+	 * Another option would be duk__recheck_strtab_size(); but since
+	 * that happens on every intern anyway, this whole check
+	 * can now be disabled.
+	 */
+	duk__resize_strtab(heap);
+}
+#endif
+
+/* Undefine local defines */
+#undef DUK__HASH_INITIAL
+#undef DUK__HASH_PROBE_STEP
+#undef DUK__DELETED_MARKER
+#line 1 "duk_hobject_alloc.c"
+/*
+ *  Hobject allocation.
+ *
+ *  Provides primitive allocation functions for all object types (plain object,
+ *  compiled function, native function, thread).  The object return is not yet
+ *  in "heap allocated" list and has a refcount of zero, so caller must careful.
+ */
+
+/* include removed: duk_internal.h */
+
+static void duk__init_object_parts(duk_heap *heap, duk_hobject *obj, duk_uint_t hobject_flags) {
+#ifdef DUK_USE_EXPLICIT_NULL_INIT
+	obj->p = NULL;
+#endif
+
+	/* FIXME: macro? sets both heaphdr and object flags */
+	obj->hdr.h_flags = hobject_flags;
+	DUK_HEAPHDR_SET_TYPE(&obj->hdr, DUK_HTYPE_OBJECT);  /* also goes into flags */
+
+        DUK_HEAP_INSERT_INTO_HEAP_ALLOCATED(heap, &obj->hdr);
+
+	/*
+	 *  obj->p is intentionally left as NULL, and duk_hobject_props.c must deal
+	 *  with this properly.  This is intentional: empty objects consume a minimum
+	 *  amount of memory.  Further, an initial allocation might fail and cause
+	 *  'obj' to "leak" (require a mark-and-sweep) since it is not reachable yet.
+	 */
+}
+
+/*
+ *  Allocate an duk_hobject.
+ *
+ *  The allocated object has no allocation for properties; the caller may
+ *  want to force a resize if a desired size is known.
+ *
+ *  The allocated object has zero reference count and is not reachable.
+ *  The caller MUST make the object reachable and increase its reference
+ *  count before invoking any operation that might require memory allocation.
+ */
+
+duk_hobject *duk_hobject_alloc(duk_heap *heap, duk_uint_t hobject_flags) {
+	duk_hobject *res;
+
+	DUK_ASSERT(heap != NULL);
+
+	/* different memory layout, alloc size, and init */
+	DUK_ASSERT((hobject_flags & DUK_HOBJECT_FLAG_COMPILEDFUNCTION) == 0);
+	DUK_ASSERT((hobject_flags & DUK_HOBJECT_FLAG_NATIVEFUNCTION) == 0);
+	DUK_ASSERT((hobject_flags & DUK_HOBJECT_FLAG_THREAD) == 0);
+
+	res = (duk_hobject *) DUK_ALLOC(heap, sizeof(duk_hobject));
+	if (!res) {
+		return NULL;
+	}
+	DUK_MEMZERO(res, sizeof(duk_hobject));
+
+	duk__init_object_parts(heap, res, hobject_flags);
+
+	return res;
+}
+
+duk_hcompiledfunction *duk_hcompiledfunction_alloc(duk_heap *heap, duk_uint_t hobject_flags) {
+	duk_hcompiledfunction *res;
+
+	res = (duk_hcompiledfunction *) DUK_ALLOC(heap, sizeof(duk_hcompiledfunction));
+	if (!res) {
+		return NULL;
+	}
+	DUK_MEMZERO(res, sizeof(duk_hcompiledfunction));
+
+	duk__init_object_parts(heap, &res->obj, hobject_flags);
+
+#ifdef DUK_USE_EXPLICIT_NULL_INIT
+	res->data = NULL;
+	res->funcs = NULL;
+	res->bytecode = NULL;
+#endif
+
+	return res;
+}
+
+duk_hnativefunction *duk_hnativefunction_alloc(duk_heap *heap, duk_uint_t hobject_flags) {
+	duk_hnativefunction *res;
+
+	res = (duk_hnativefunction *) DUK_ALLOC(heap, sizeof(duk_hnativefunction));
+	if (!res) {
+		return NULL;
+	}
+	DUK_MEMZERO(res, sizeof(duk_hnativefunction));
+
+	duk__init_object_parts(heap, &res->obj, hobject_flags);
+
+#ifdef DUK_USE_EXPLICIT_NULL_INIT
+	res->func = NULL;
+#endif
+
+	return res;
+}
+
+/*
+ *  Allocate a new thread.
+ *
+ *  Leaves the built-ins array uninitialized.  The caller must either
+ *  initialize a new global context or share existing built-ins from
+ *  another thread.
+ */
+
+duk_hthread *duk_hthread_alloc(duk_heap *heap, duk_uint_t hobject_flags) {
+	duk_hthread *res;
+
+	res = (duk_hthread *) DUK_ALLOC(heap, sizeof(duk_hthread));
+	if (!res) {
+		return NULL;
+	}
+	DUK_MEMZERO(res, sizeof(duk_hthread));
+
+	duk__init_object_parts(heap, &res->obj, hobject_flags);
+
+#ifdef DUK_USE_EXPLICIT_NULL_INIT
+	res->heap = NULL;
+	res->valstack = NULL;
+	res->valstack_end = NULL;
+	res->valstack_bottom = NULL;
+	res->valstack_top = NULL;
+	res->callstack = NULL;
+	res->catchstack = NULL;
+	res->resumer = NULL;
+	res->strs = NULL;
+	{
+		int i;
+		for (i = 0; i < DUK_NUM_BUILTINS; i++) {
+			res->builtins[i] = NULL;
+		}
+	}
+#endif
+	/* when nothing is running, API calls are in non-strict mode */
+	DUK_ASSERT(res->strict == 0);
+
+	res->heap = heap;
+	res->valstack_max = DUK_VALSTACK_DEFAULT_MAX;
+	res->callstack_max = DUK_CALLSTACK_DEFAULT_MAX;
+	res->catchstack_max = DUK_CATCHSTACK_DEFAULT_MAX;
+
+	return res;
+}
+
+/* FIXME: unused now, remove */
+duk_hobject *duk_hobject_alloc_checked(duk_hthread *thr, duk_uint_t hobject_flags) {
+	duk_hobject *res = duk_hobject_alloc(thr->heap, hobject_flags);
+	if (!res) {
+		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "failed to allocate an object");
+	}
+	return res;
+}
+#line 1 "duk_hobject_class.c"
+/*
+ *  Hobject Ecmascript [[Class]].
+ */
+
+/* include removed: duk_internal.h */
+
+/* Maybe better to check these elsewhere */
+#if (DUK_STRIDX_UC_ARGUMENTS > 255)
+#error constant too large
+#endif
+#if (DUK_STRIDX_ARRAY > 255)
+#error constant too large
+#endif
+#if (DUK_STRIDX_UC_BOOLEAN > 255)
+#error constant too large
+#endif
+#if (DUK_STRIDX_DATE > 255)
+#error constant too large
+#endif
+#if (DUK_STRIDX_UC_ERROR > 255)
+#error constant too large
+#endif
+#if (DUK_STRIDX_UC_FUNCTION > 255)
+#error constant too large
+#endif
+#if (DUK_STRIDX_JSON > 255)
+#error constant too large
+#endif
+#if (DUK_STRIDX_MATH > 255)
+#error constant too large
+#endif
+#if (DUK_STRIDX_UC_NUMBER > 255)
+#error constant too large
+#endif
+#if (DUK_STRIDX_UC_OBJECT > 255)
+#error constant too large
+#endif
+#if (DUK_STRIDX_REG_EXP > 255)
+#error constant too large
+#endif
+#if (DUK_STRIDX_UC_STRING > 255)
+#error constant too large
+#endif
+#if (DUK_STRIDX_GLOBAL > 255)
+#error constant too large
+#endif
+#if (DUK_STRIDX_EMPTY_STRING > 255)
+#error constant too large
+#endif
+
+/* Note: assumes that these string indexes are 8-bit, genstrings.py must ensure that */
+duk_uint8_t duk_class_number_to_stridx[32] = {
+	DUK_STRIDX_EMPTY_STRING,  /* UNUSED, intentionally empty */
+	DUK_STRIDX_UC_ARGUMENTS,
+	DUK_STRIDX_ARRAY,
+	DUK_STRIDX_UC_BOOLEAN,
+	DUK_STRIDX_DATE,
+	DUK_STRIDX_UC_ERROR,
+	DUK_STRIDX_UC_FUNCTION,
+	DUK_STRIDX_JSON,
+	DUK_STRIDX_MATH,
+	DUK_STRIDX_UC_NUMBER,
+	DUK_STRIDX_UC_OBJECT,
+	DUK_STRIDX_REG_EXP,
+	DUK_STRIDX_UC_STRING,
+	DUK_STRIDX_GLOBAL,
+	DUK_STRIDX_OBJ_ENV,
+	DUK_STRIDX_DEC_ENV,
+	DUK_STRIDX_UC_BUFFER,
+	DUK_STRIDX_UC_POINTER,
+	DUK_STRIDX_UC_THREAD,     /* UNUSED, intentionally empty */
+	DUK_STRIDX_EMPTY_STRING,  /* UNUSED, intentionally empty */
+	DUK_STRIDX_EMPTY_STRING,  /* UNUSED, intentionally empty */
+	DUK_STRIDX_EMPTY_STRING,  /* UNUSED, intentionally empty */
+	DUK_STRIDX_EMPTY_STRING,  /* UNUSED, intentionally empty */
+	DUK_STRIDX_EMPTY_STRING,  /* UNUSED, intentionally empty */
+	DUK_STRIDX_EMPTY_STRING,  /* UNUSED, intentionally empty */
+	DUK_STRIDX_EMPTY_STRING,  /* UNUSED, intentionally empty */
+	DUK_STRIDX_EMPTY_STRING,  /* UNUSED, intentionally empty */
+	DUK_STRIDX_EMPTY_STRING,  /* UNUSED, intentionally empty */
+	DUK_STRIDX_EMPTY_STRING,  /* UNUSED, intentionally empty */
+	DUK_STRIDX_EMPTY_STRING,  /* UNUSED, intentionally empty */
+	DUK_STRIDX_EMPTY_STRING,  /* UNUSED, intentionally empty */
+	DUK_STRIDX_EMPTY_STRING,  /* UNUSED, intentionally empty */
+};
+#line 1 "duk_hobject_enum.c"
+/*
+ *  Hobject enumeration support.
+ *
+ *  Creates an internal enumeration state object to be used e.g. with for-in
+ *  enumeration.  The state object contains a snapshot of target object keys
+ *  and internal control state for enumeration.  Enumerator flags allow caller
+ *  to e.g. request internal/non-enumerable properties, and to enumerate only
+ *  "own" properties.
+ *
+ *  Also creates the result value for e.g. Object.keys() based on the same
+ *  internal structure.
+ *
+ *  This snapshot-based enumeration approach is used to simplify enumeration:
+ *  non-snapshot-based approaches are difficult to reconcile with mutating
+ *  the enumeration target, running multiple long-lived enumerators at the
+ *  same time, garbage collection details, etc.  The downside is that the
+ *  enumerator object is memory inefficient especially for iterating arrays.
+ */
+
+/* include removed: duk_internal.h */
+
+/* FIXME: identify enumeration target with an object index (not top of stack) */
+
+/* must match exactly the number of internal properties inserted to enumerator */
+#define DUK__ENUM_START_INDEX  2
+
+/*
+ *  Helper to sort array index keys.  The keys are in the enumeration object
+ *  entry part, starting from DUK__ENUM_START_INDEX, and the entry part is dense.
+ *
+ *  We use insertion sort because it is simple (leading to compact code,)
+ *  works nicely in-place, and minimizes operations if data is already sorted
+ *  or nearly sorted (which is a very common case here).  It also minimizes
+ *  the use of element comparisons in general.  This is nice because element
+ *  comparisons here involve re-parsing the string keys into numbers each
+ *  time, which is naturally very expensive.
+ *
+ *  Note that the entry part values are all "true", e.g.
+ *
+ *    "1" -> true, "3" -> true, "2" -> true
+ *
+ *  so it suffices to only work in the key part without exchanging any keys,
+ *  simplifying the sort.
+ *
+ *  http://en.wikipedia.org/wiki/Insertion_sort
+ *
+ *  (Compiles to about 160 bytes now as a stand-alone function.)
+ */
+
+static void duk__sort_array_indices(duk_hobject *h_obj) {
+	duk_hstring **keys;
+	duk_hstring **p_curr, **p_insert, **p_end;
+	duk_hstring *h_curr;
+	duk_uarridx_t val_highest, val_curr, val_insert;
+
+	DUK_ASSERT(h_obj != NULL);
+	DUK_ASSERT(h_obj->e_used >= 2);  /* control props */
+
+	if (h_obj->e_used <= 1 + DUK__ENUM_START_INDEX) {
+		return;
+	}
+
+	keys = DUK_HOBJECT_E_GET_KEY_BASE(h_obj);
+	p_end = keys + h_obj->e_used;
+	keys += DUK__ENUM_START_INDEX;
+
+	DUK_DDD(DUK_DDDPRINT("keys=%p, p_end=%p (after skipping enum props)",
+	                     (void *) keys, (void *) p_end));
+
+#ifdef DUK_USE_DDDPRINT
+	{
+		duk_uint_fast32_t i;
+		for (i = 0; i < (duk_uint_fast32_t) h_obj->e_used; i++) {
+			DUK_DDD(DUK_DDDPRINT("initial: %ld %p -> %!O",
+			                     (long) i,
+			                     (void *) DUK_HOBJECT_E_GET_KEY_PTR(h_obj, i),
+			                     (duk_heaphdr *) DUK_HOBJECT_E_GET_KEY(h_obj, i)));
+		}
+	}
+#endif
+
+	val_highest = DUK_HSTRING_GET_ARRIDX_SLOW(keys[0]);
+	for (p_curr = keys + 1; p_curr < p_end; p_curr++) {
+		DUK_ASSERT(*p_curr != NULL);
+		val_curr = DUK_HSTRING_GET_ARRIDX_SLOW(*p_curr);
+
+		if (val_curr >= val_highest) {
+			DUK_DDD(DUK_DDDPRINT("p_curr=%p, p_end=%p, val_highest=%ld, val_curr=%ld -> "
+			                     "already in correct order, next",
+			                     (void *) p_curr, (void *) p_end, (long) val_highest, (long) val_curr));
+			val_highest = val_curr;
+			continue;
+		}
+
+		DUK_DDD(DUK_DDDPRINT("p_curr=%p, p_end=%p, val_highest=%ld, val_curr=%ld -> "
+		                     "needs to be inserted",
+		                     (void *) p_curr, (void *) p_end, (long) val_highest, (long) val_curr));
+	
+		/* Needs to be inserted; scan backwards, since we optimize
+		 * for the case where elements are nearly in order.
+		 */
+
+		p_insert = p_curr - 1;
+		for (;;) {
+			val_insert = DUK_HSTRING_GET_ARRIDX_SLOW(*p_insert);
+			if (val_insert < val_curr) {
+				DUK_DDD(DUK_DDDPRINT("p_insert=%p, val_insert=%ld, val_curr=%ld -> insert after this",
+				                     (void *) p_insert, (long) val_insert, (long) val_curr));
+				p_insert++;
+				break;
+			}
+			if (p_insert == keys) {
+				DUK_DDD(DUK_DDDPRINT("p_insert=%p -> out of keys, insert to beginning", (void *) p_insert));
+				break;
+			}
+			DUK_DDD(DUK_DDDPRINT("p_insert=%p, val_insert=%ld, val_curr=%ld -> search backwards",
+			                     (void *) p_insert, (long) val_insert, (long) val_curr));
+			p_insert--;
+		}
+
+		DUK_DDD(DUK_DDDPRINT("final p_insert=%p", (void *) p_insert));
+
+		/*        .-- p_insert   .-- p_curr
+		 *        v              v
+		 *  | ... | insert | ... | curr
+		 */
+
+		h_curr = *p_curr;
+		DUK_DDD(DUK_DDDPRINT("memmove: dest=%p, src=%p, size=%ld, h_curr=%p",
+		                     (void *) (p_insert + 1), (void *) p_insert,
+		                     (long) (p_curr - p_insert), (void *) h_curr));
+
+		DUK_MEMMOVE((void *) (p_insert + 1),
+		            (void *) p_insert,
+		            (size_t) ((p_curr - p_insert) * sizeof(duk_hstring *)));
+		*p_insert = h_curr;
+		/* keep val_highest */
+	}
+
+#ifdef DUK_USE_DDDPRINT
+	{
+		duk_uint_fast32_t i;
+		for (i = 0; i < (duk_uint_fast32_t) h_obj->e_used; i++) {
+			DUK_DDD(DUK_DDDPRINT("final: %ld %p -> %!O",
+			                     (long) i,
+			                     (void *) DUK_HOBJECT_E_GET_KEY_PTR(h_obj, i),
+			                     (duk_heaphdr *) DUK_HOBJECT_E_GET_KEY(h_obj, i)));
+		}
+	}
+#endif
+}
+
+/*
+ *  Create an internal enumerator object E, which has its keys ordered
+ *  to match desired enumeration ordering.  Also initialize internal control
+ *  properties for enumeration.
+ *
+ *  Note: if an array was used to hold enumeration keys instead, an array
+ *  scan would be needed to eliminate duplicates found in the prototype chain.
+ */
+
+void duk_hobject_enumerator_create(duk_context *ctx, duk_small_uint_t enum_flags) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hobject *enum_target;
+	duk_hobject *curr;
+	duk_hobject *res;
+#if defined(DUK_USE_ES6_PROXY)
+	duk_hobject *h_proxy_target;
+	duk_hobject *h_proxy_handler;
+	duk_hobject *h_trap_result;
+#endif
+	duk_uint_fast32_t i, len;  /* used for array, stack, and entry indices */
+
+	DUK_ASSERT(ctx != NULL);
+
+	DUK_DDD(DUK_DDDPRINT("create enumerator, stack top: %ld", (long) duk_get_top(ctx)));
+
+	enum_target = duk_require_hobject(ctx, -1);
+	DUK_ASSERT(enum_target != NULL);
+
+	duk_push_object_internal(ctx);
+	res = duk_require_hobject(ctx, -1);
+
+	DUK_DDD(DUK_DDDPRINT("created internal object"));
+
+	/* [enum_target res] */
+
+	/* Target must be stored so that we can recheck whether or not
+	 * keys still exist when we enumerate.  This is not done if the
+	 * enumeration result comes from a proxy trap as there is no
+	 * real object to check against.
+	 */
+	duk_push_hobject(ctx, enum_target);
+	duk_put_prop_stridx(ctx, -2, DUK_STRIDX_INT_TARGET);
+
+	/* Initialize index so that we skip internal control keys. */
+	duk_push_int(ctx, DUK__ENUM_START_INDEX);
+	duk_put_prop_stridx(ctx, -2, DUK_STRIDX_INT_NEXT);
+
+	/*
+	 *  Proxy object handling
+	 */
+
+#if defined(DUK_USE_ES6_PROXY)
+	if (DUK_LIKELY((enum_flags & DUK_ENUM_NO_PROXY_BEHAVIOR) != 0)) {
+		goto skip_proxy;
+	}
+	if (DUK_LIKELY(!duk_hobject_proxy_check(thr,
+	                                        enum_target,
+	                                        &h_proxy_target,
+	                                        &h_proxy_handler))) {
+		goto skip_proxy;
+	}
+
+	DUK_DDD(DUK_DDDPRINT("proxy enumeration"));
+	duk_push_hobject(ctx, h_proxy_handler);
+	if (!duk_get_prop_stridx(ctx, -1, DUK_STRIDX_ENUMERATE)) {
+		/* No need to replace the 'enum_target' value in stack, only the
+		 * enum_target reference.  This also ensures that the original
+		 * enum target is reachable, which keeps the proxy and the proxy
+		 * target reachable.  We do need to replace the internal _target.
+		 */
+		DUK_DDD(DUK_DDDPRINT("no enumerate trap, enumerate proxy target instead"));
+		DUK_DDD(DUK_DDDPRINT("h_proxy_target=%!O", (duk_heaphdr *) h_proxy_target));
+		enum_target = h_proxy_target;
+
+		duk_push_hobject(ctx, enum_target);  /* -> [ ... enum_target res handler undefined target ] */
+		duk_put_prop_stridx(ctx, -4, DUK_STRIDX_INT_TARGET);
+
+		duk_pop_2(ctx);  /* -> [ ... enum_target res ] */
+		goto skip_proxy;
+	}
+
+	/* [ ... enum_target res handler trap ] */
+	duk_insert(ctx, -2);
+	duk_push_hobject(ctx, h_proxy_target);    /* -> [ ... enum_target res trap handler target ] */
+	duk_call_method(ctx, 1 /*nargs*/);        /* -> [ ... enum_target res trap_result ] */
+	h_trap_result = duk_require_hobject(ctx, -1);
+	DUK_UNREF(h_trap_result);
+
+	/* Copy trap result keys into the enumerator object. */
+	len = (duk_uint_fast32_t) duk_get_length(ctx, -1);
+	for (i = 0; i < len; i++) {
+		/* XXX: not sure what the correct semantic details are here,
+		 * e.g. handling of missing values (gaps), handling of non-array
+		 * trap results, etc.
+		 *
+		 * For keys, we simply skip non-string keys which seems to be
+		 * consistent with how e.g. Object.keys() will process proxy trap
+		 * results (ES6 draft, Section 19.1.2.14).
+		 */
+		if (duk_get_prop_index(ctx, -1, i) && duk_is_string(ctx, -1)) {
+			/* [ ... enum_target res trap_result val ] */
+			duk_push_true(ctx);
+			/* [ ... enum_target res trap_result val true ] */
+			duk_put_prop(ctx, -4);
+		} else {
+			duk_pop(ctx);
+		}
+	}
+	/* [ ... enum_target res trap_result ] */
+	duk_pop(ctx);
+	duk_remove(ctx, -2);
+
+	/* [ ... res ] */
+
+	/* The internal _target property is kept pointing to the original
+	 * enumeration target (the proxy object), so that the enumerator
+	 * 'next' operation can read property values if so requested.  The
+	 * fact that the _target is a proxy disables key existence check
+	 * during enumeration.
+	 */
+	DUK_DDD(DUK_DDDPRINT("proxy enumeration, final res: %!O", (duk_heaphdr *) res));
+	goto compact_and_return;
+
+ skip_proxy:
+#endif  /* DUK_USE_ES6_PROXY */
+
+	curr = enum_target;
+	while (curr) {
+		/*
+		 *  Virtual properties.
+		 *
+		 *  String and buffer indices are virtual and always enumerable,
+		 *  'length' is virtual and non-enumerable.  Array and arguments
+		 *  object props have special behavior but are concrete.
+		 */
+
+		if (DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(curr) ||
+		    DUK_HOBJECT_HAS_EXOTIC_BUFFEROBJ(curr)) {
+			/* String and buffer enumeration behavior is identical now,
+			 * so use shared handler.
+			 */
+			if (DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(curr)) {
+				duk_hstring *h_val;
+				h_val = duk_hobject_get_internal_value_string(thr->heap, curr);
+				DUK_ASSERT(h_val != NULL);  /* string objects must not created without internal value */
+				len = (duk_uint_fast32_t) DUK_HSTRING_GET_CHARLEN(h_val);
+			} else {
+				duk_hbuffer *h_val;
+				DUK_ASSERT(DUK_HOBJECT_HAS_EXOTIC_BUFFEROBJ(curr));
+				h_val = duk_hobject_get_internal_value_buffer(thr->heap, curr);
+				DUK_ASSERT(h_val != NULL);  /* buffer objects must not created without internal value */
+				len = (duk_uint_fast32_t) DUK_HBUFFER_GET_SIZE(h_val);
+			}
+
+			for (i = 0; i < len; i++) {
+				duk_hstring *k;
+
+				k = duk_heap_string_intern_u32_checked(thr, i);
+				DUK_ASSERT(k);
+				duk_push_hstring(ctx, k);
+				duk_push_true(ctx);
+
+				/* [enum_target res key true] */
+				duk_put_prop(ctx, -3);
+
+				/* [enum_target res] */
+			}
+
+			/* 'length' property is not enumerable, but is included if
+			 * non-enumerable properties are requested.
+			 */
+
+			if (enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE) {
+				duk_push_hstring_stridx(ctx, DUK_STRIDX_LENGTH);
+				duk_push_true(ctx);
+				duk_put_prop(ctx, -3);
+			}
+		} else if (DUK_HOBJECT_HAS_EXOTIC_DUKFUNC(curr)) {
+			if (enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE) {
+				duk_push_hstring_stridx(ctx, DUK_STRIDX_LENGTH);
+				duk_push_true(ctx);
+				duk_put_prop(ctx, -3);
+			}
+		}
+
+		/*
+		 *  Array part
+		 *
+		 *  Note: ordering between array and entry part must match 'abandon array'
+		 *  behavior in duk_hobject_props.c: key order after an array is abandoned
+		 *  must be the same.
+		 */
+
+		for (i = 0; i < (duk_uint_fast32_t) curr->a_size; i++) {
+			duk_hstring *k;
+			duk_tval *tv;
+
+			tv = DUK_HOBJECT_A_GET_VALUE_PTR(curr, i);
+			if (DUK_TVAL_IS_UNDEFINED_UNUSED(tv)) {
+				continue;
+			}
+			k = duk_heap_string_intern_u32_checked(thr, i);
+			DUK_ASSERT(k);
+
+			duk_push_hstring(ctx, k);
+			duk_push_true(ctx);
+
+			/* [enum_target res key true] */
+			duk_put_prop(ctx, -3);
+
+			/* [enum_target res] */
+		}
+
+		/*
+		 *  Entries part
+		 */
+
+		for (i = 0; i < (duk_uint_fast32_t) curr->e_used; i++) {
+			duk_hstring *k;
+
+			k = DUK_HOBJECT_E_GET_KEY(curr, i);
+			if (!k) {
+				continue;
+			}
+			if (!DUK_HOBJECT_E_SLOT_IS_ENUMERABLE(curr, i) &&
+			    !(enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE)) {
+				continue;
+			}
+			if (DUK_HSTRING_HAS_INTERNAL(k) &&
+			    !(enum_flags & DUK_ENUM_INCLUDE_INTERNAL)) {
+				continue;
+			}
+			if ((enum_flags & DUK_ENUM_ARRAY_INDICES_ONLY) &&
+			    (DUK_HSTRING_GET_ARRIDX_SLOW(k) == DUK_HSTRING_NO_ARRAY_INDEX)) {
+				continue;
+			}
+
+			DUK_ASSERT(DUK_HOBJECT_E_SLOT_IS_ACCESSOR(curr, i) ||
+			           !DUK_TVAL_IS_UNDEFINED_UNUSED(&DUK_HOBJECT_E_GET_VALUE_PTR(curr, i)->v));
+
+			duk_push_hstring(ctx, k);
+			duk_push_true(ctx);
+
+			/* [enum_target res key true] */
+			duk_put_prop(ctx, -3);
+
+			/* [enum_target res] */
+		}
+
+		if (enum_flags & DUK_ENUM_OWN_PROPERTIES_ONLY) {
+			break;
+		}
+
+		curr = curr->prototype;
+	}
+
+	/* [enum_target res] */
+
+	duk_remove(ctx, -2);
+
+	/* [res] */
+
+	if ((enum_flags & (DUK_ENUM_ARRAY_INDICES_ONLY | DUK_ENUM_SORT_ARRAY_INDICES)) ==
+	                  (DUK_ENUM_ARRAY_INDICES_ONLY | DUK_ENUM_SORT_ARRAY_INDICES)) {
+		/*
+		 *  Some E5/E5.1 algorithms require that array indices are iterated
+		 *  in a strictly ascending order.  This is the case for e.g.
+		 *  Array.prototype.forEach() and JSON.stringify() PropertyList
+		 *  handling.
+		 *
+		 *  To ensure this property for arrays with an array part (and
+		 *  arbitrary objects too, since e.g. forEach() can be applied
+		 *  to an array), the caller can request that we sort the keys
+		 *  here.
+		 */
+
+		/* FIXME: avoid this at least when enum_target is an Array, it has an
+		 * array part, and no ancestor properties were included?  Not worth
+		 * it for JSON, but maybe worth it for forEach().
+		 */
+
+		/* FIXME: may need a 'length' filter for forEach()
+		 */
+		DUK_DDD(DUK_DDDPRINT("sort array indices by caller request"));
+		duk__sort_array_indices(res);
+	}
+
+#if defined(DUK_USE_ES6_PROXY)
+ compact_and_return:
+#endif
+	/* compact; no need to seal because object is internal */
+	duk_hobject_compact_props(thr, res);
+
+	DUK_DDD(DUK_DDDPRINT("created enumerator object: %!iT", (duk_tval *) duk_get_tval(ctx, -1)));
+}
+
+/*
+ *  Returns non-zero if a key and/or value was enumerated, and:
+ *
+ *   [enum] -> [key]        (get_value == 0)
+ *   [enum] -> [key value]  (get_value == 1)
+ *
+ *  Returns zero without pushing anything on the stack otherwise.
+ */
+duk_bool_t duk_hobject_enumerator_next(duk_context *ctx, duk_bool_t get_value) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hobject *e;
+	duk_hobject *enum_target;
+	duk_hstring *res = NULL;
+	duk_uint_fast32_t idx;
+	duk_bool_t check_existence;
+
+	DUK_ASSERT(ctx != NULL);
+
+	/* [... enum] */
+
+	e = duk_require_hobject(ctx, -1);
+
+	/* XXX use get tval ptr, more efficient */
+	duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_NEXT);
+	idx = (duk_uint_fast32_t) duk_require_uint(ctx, -1);
+	duk_pop(ctx);
+	DUK_DDD(DUK_DDDPRINT("enumeration: index is: %ld", (long) idx));
+
+	/* Enumeration keys are checked against the enumeration target (to see
+	 * that they still exist).  In the proxy enumeration case _target will
+	 * be the proxy, and checking key existence against the proxy is not
+	 * required (or sensible, as the keys may be fully virtual).
+	 */
+	duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_TARGET);
+	enum_target = duk_require_hobject(ctx, -1);
+	DUK_ASSERT(enum_target != NULL);
+#if defined(DUK_USE_ES6_PROXY)
+	/* FIXME: typing issue here? */
+	check_existence = (!DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(enum_target));
+#else
+	check_existence = 1;
+#endif
+	duk_pop(ctx);  /* still reachable */
+
+	DUK_DDD(DUK_DDDPRINT("getting next enum value, enum_target=%!iO, enumerator=%!iT",
+	                     (duk_heaphdr *) enum_target, (duk_tval *) duk_get_tval(ctx, -1)));
+
+	/* no array part */
+	for (;;) {
+		duk_hstring *k;
+
+		if (idx >= e->e_used) {
+			DUK_DDD(DUK_DDDPRINT("enumeration: ran out of elements"));
+			break;
+		}
+
+		/* we know these because enum objects are internally created */
+		k = DUK_HOBJECT_E_GET_KEY(e, idx);
+		DUK_ASSERT(k != NULL);
+		DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(e, idx));
+		DUK_ASSERT(!DUK_TVAL_IS_UNDEFINED_UNUSED(&DUK_HOBJECT_E_GET_VALUE(e, idx).v));
+
+		idx++;
+
+		/* recheck that the property still exists */
+		if (check_existence && !duk_hobject_hasprop_raw(thr, enum_target, k)) {
+			DUK_DDD(DUK_DDDPRINT("property deleted during enumeration, skip"));
+			continue;
+		}
+
+		DUK_DDD(DUK_DDDPRINT("enumeration: found element, key: %!O", (duk_heaphdr *) k));
+		res = k;
+		break;
+	}
+
+	DUK_DDD(DUK_DDDPRINT("enumeration: updating next index to %ld", (long) idx));
+
+	duk_push_number(ctx, (double) idx);
+	duk_put_prop_stridx(ctx, -2, DUK_STRIDX_INT_NEXT);
+
+	/* [... enum] */
+
+	if (res) {
+		duk_push_hstring(ctx, res);
+		if (get_value) {
+			duk_push_hobject(ctx, enum_target);
+			duk_dup(ctx, -2);      /* -> [... enum key enum_target key] */
+			duk_get_prop(ctx, -2); /* -> [... enum key enum_target val] */
+			duk_remove(ctx, -2);   /* -> [... enum key val] */
+			duk_remove(ctx, -3);   /* -> [... key val] */
+		} else {
+			duk_remove(ctx, -2);   /* -> [... key] */
+		}
+		return 1;
+	} else {
+		duk_pop(ctx);  /* -> [...] */
+		return 0;
+	}
+}
+
+/*
+ *  Get enumerated keys in an Ecmascript array.  Matches Object.keys() behavior
+ *  described in E5 Section 15.2.3.14.
+ */
+
+duk_ret_t duk_hobject_get_enumerated_keys(duk_context *ctx, duk_small_uint_t enum_flags) {
+	duk_hobject *e;
+	duk_uint_fast32_t i;
+	duk_uint_fast32_t idx;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(duk_get_hobject(ctx, -1) != NULL);
+
+	/* Create a temporary enumerator to get the (non-duplicated) key list;
+	 * the enumerator state is initialized without being needed, but that
+	 * has little impact.
+	 */
+
+	duk_hobject_enumerator_create(ctx, enum_flags);
+	duk_push_array(ctx);
+
+	/* [enum_target enum res] */
+
+	e = duk_require_hobject(ctx, -2);
+	DUK_ASSERT(e != NULL);
+
+	idx = 0;
+	for (i = DUK__ENUM_START_INDEX; i < (duk_uint_fast32_t) e->e_used; i++) {
+		duk_hstring *k;
+
+		k = DUK_HOBJECT_E_GET_KEY(e, i);
+		DUK_ASSERT(k);  /* enumerator must have no keys deleted */
+
+		/* [enum_target enum res] */
+		duk_push_hstring(ctx, k);
+		duk_put_prop_index(ctx, -2, idx);
+		idx++;
+	}
+
+	/* [enum_target enum res] */
+	duk_remove(ctx, -2);
+
+	/* [enum_target res] */
+
+	return 1;  /* return 1 to allow callers to tail call */
+}
+#line 1 "duk_hobject_finalizer.c"
+/*
+ *  Run an duk_hobject finalizer.  Used for both reference counting
+ *  and mark-and-sweep algorithms.  Must never throw an error.
+ *
+ *  There is no return value.  Any return value or error thrown by
+ *  the finalizer is ignored (although errors are debug logged).
+ *
+ *  Notes:
+ *
+ *    - The thread used for calling the finalizer is the same as the
+ *      'thr' argument.  This may need to change later.
+ *
+ *    - The finalizer thread 'top' assertions are there because it is
+ *      critical that strict stack policy is observed (i.e. no cruft
+ *      left on the finalizer stack).
+ */
+
+/* include removed: duk_internal.h */
+
+static duk_ret_t duk__finalize_helper(duk_context *ctx) {
+	DUK_ASSERT(ctx != NULL);
+
+	DUK_DDD(DUK_DDDPRINT("protected finalization helper running"));
+
+	/* [... obj] */
+
+	/* FIXME: finalizer lookup should traverse the prototype chain (to allow
+	 * inherited finalizers) but should not invoke accessors or proxy object
+	 * behavior.
+	 */
+
+	duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_FINALIZER);  /* -> [... obj finalizer] */
+	if (!duk_is_callable(ctx, -1)) {
+		DUK_DDD(DUK_DDDPRINT("-> no finalizer or finalizer not callable"));
+		return 0;
+	}
+	duk_dup(ctx, -2);  /* -> [... obj finalizer obj] */
+	DUK_DDD(DUK_DDDPRINT("-> finalizer found, calling finalizer"));
+	duk_call(ctx, 1);  /* -> [... obj retval] */
+	DUK_DDD(DUK_DDDPRINT("finalizer finished successfully"));
+	return 0;
+
+	/* Note: we rely on duk_safe_call() to fix up the stack for the caller,
+	 * so we don't need to pop stuff here.  There is no return value;
+	 * caller determines rescued status based on object refcount.
+	 */
+}
+
+void duk_hobject_run_finalizer(duk_hthread *thr, duk_hobject *obj) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_ret_t rc;
+#ifdef DUK_USE_ASSERTIONS
+	duk_idx_t entry_top;
+#endif
+
+	DUK_DDD(DUK_DDDPRINT("running object finalizer for object: %p", (void *) obj));
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(obj != NULL);
+
+	/* FIXME: assert stack space */
+
+#ifdef DUK_USE_ASSERTIONS
+	entry_top = duk_get_top(ctx);
+#endif
+	/*
+	 *  Get and call the finalizer.  All of this must be wrapped
+	 *  in a protected call, because even getting the finalizer
+	 *  may trigger an error (getter may throw one, for instance).
+	 */
+
+	/* FIXME: use a NULL error handler for the finalizer call? */
+
+	DUK_DDD(DUK_DDDPRINT("-> finalizer found, calling wrapped finalize helper"));
+	duk_push_hobject(ctx, obj);  /* this also increases refcount by one */
+	rc = duk_safe_call(ctx, duk__finalize_helper, 0 /*nargs*/, 1 /*nrets*/);  /* -> [... obj retval/error] */
+	DUK_ASSERT_TOP(ctx, entry_top + 2);  /* duk_safe_call discipline */
+
+	if (rc != DUK_EXEC_SUCCESS) {
+		/* Note: we ask for one return value from duk_safe_call to get this
+		 * error debugging here.
+		 */
+		DUK_D(DUK_DPRINT("wrapped finalizer call failed for object %p (ignored); error: %!T",
+		                 (void *) obj, (duk_tval *) duk_get_tval(ctx, -1)));
+	}
+	duk_pop_2(ctx);  /* -> [...] */
+
+	DUK_ASSERT_TOP(ctx, entry_top);
+}
+#line 1 "duk_hobject_misc.c"
+/*
+ *  Misc support functions
+ */
+
+/* include removed: duk_internal.h */
+
+duk_bool_t duk_hobject_prototype_chain_contains(duk_hthread *thr, duk_hobject *h, duk_hobject *p) {
+	duk_uint_t sanity;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(h != NULL);
+	/* allow 'p' to be NULL; then the result is always false */
+
+	sanity = DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY;
+	do {
+		if (h == p) {
+			return 1;
+		}
+
+		if (sanity-- == 0) {
+			DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, DUK_STR_PROTOTYPE_CHAIN_LIMIT);
+		}
+		h = h->prototype;
+	} while (h);
+
+	return 0;
+}
+
+/* FIXME: needed? */
+void duk_hobject_set_prototype(duk_hthread *thr, duk_hobject *h, duk_hobject *p) {
+#ifdef DUK_USE_REFERENCE_COUNTING
+	duk_hobject *tmp;
+
+	DUK_ASSERT(h);
+	tmp = h->prototype;
+	h->prototype = p;
+	DUK_HOBJECT_INCREF(thr, p);  /* avoid problems if p == h->prototype */
+	DUK_HOBJECT_DECREF(thr, tmp);
+#else
+	DUK_ASSERT(h);
+	h->prototype = p;
+#endif
+}
+#line 1 "duk_hobject_pc2line.c"
+/*
+ *  Helpers for creating and querying pc2line debug data, which
+ *  converts a bytecode program counter to a source line number.
+ *
+ *  The run-time pc2line data is bit-packed, and documented in:
+ *
+ *    doc/function-objects.txt
+ */
+
+/* include removed: duk_internal.h */
+
+#if defined(DUK_USE_PC2LINE)
+
+/* Generate pc2line data for an instruction sequence, leaving a buffer on stack top. */
+void duk_hobject_pc2line_pack(duk_hthread *thr, duk_compiler_instr *instrs, duk_uint_fast32_t length) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_hbuffer_dynamic *h_buf;
+	duk_bitencoder_ctx be_ctx_alloc;
+	duk_bitencoder_ctx *be_ctx = &be_ctx_alloc;
+	duk_uint32_t *hdr;
+	duk_size_t new_size;
+	duk_uint_fast32_t num_header_entries;
+	duk_uint_fast32_t curr_offset;
+	duk_int_fast32_t curr_line, next_line, diff_line;
+	duk_uint_fast32_t curr_pc;
+	duk_uint_fast32_t hdr_index;
+
+	DUK_ASSERT(length <= DUK_COMPILER_MAX_BYTECODE_LENGTH);
+
+	/* FIXME: add proper spare handling to dynamic buffer, to minimize
+	 * reallocs; currently there is no spare at all.
+	 */
+
+	num_header_entries = (length + DUK_PC2LINE_SKIP - 1) / DUK_PC2LINE_SKIP;
+	curr_offset = (duk_uint_fast32_t) (sizeof(duk_uint32_t) + num_header_entries * sizeof(duk_uint32_t) * 2);
+
+	duk_push_dynamic_buffer(ctx, (duk_size_t) curr_offset);
+	h_buf = (duk_hbuffer_dynamic *) duk_get_hbuffer(ctx, -1);
+	DUK_ASSERT(h_buf != NULL);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(h_buf));
+
+	hdr = (duk_uint32_t *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(h_buf);
+	DUK_ASSERT(hdr != NULL);
+	hdr[0] = (duk_uint32_t) length;  /* valid pc range is [0, length[ */
+
+	curr_pc = 0U;
+	while (curr_pc < length) {
+		new_size = (duk_size_t) (curr_offset + DUK_PC2LINE_MAX_DIFF_LENGTH);
+		duk_hbuffer_resize(thr, h_buf, new_size, new_size);
+
+		hdr = (duk_uint32_t *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(h_buf);
+		DUK_ASSERT(hdr != NULL);
+		DUK_ASSERT(curr_pc < length);
+		hdr_index = 1 + (curr_pc / DUK_PC2LINE_SKIP) * 2;
+		curr_line = (duk_int_fast32_t) instrs[curr_pc].line;
+		hdr[hdr_index + 0] = (duk_uint32_t) curr_line;
+		hdr[hdr_index + 1] = (duk_uint32_t) curr_offset;
+
+#if 0
+		DUK_DDD(DUK_DDDPRINT("hdr[%ld]: pc=%ld line=%ld offset=%ld",
+		                     (long) (curr_pc / DUK_PC2LINE_SKIP),
+		                     (long) curr_pc,
+		                     (long) hdr[hdr_index + 0],
+		                     (long) hdr[hdr_index + 1]));
+#endif
+
+		DUK_MEMZERO(be_ctx, sizeof(*be_ctx));
+		be_ctx->data = ((duk_uint8_t *) hdr) + curr_offset;
+		be_ctx->length = (duk_size_t) DUK_PC2LINE_MAX_DIFF_LENGTH;
+
+		for (;;) {
+			curr_pc++;
+			if ( ((curr_pc % DUK_PC2LINE_SKIP) == 0) ||  /* end of diff run */
+			     (curr_pc >= length) ) {                 /* end of bytecode */
+				break;
+			}
+			DUK_ASSERT(curr_pc < length);
+			next_line = (duk_int32_t) instrs[curr_pc].line;
+			diff_line = next_line - curr_line;
+
+#if 0
+			DUK_DDD(DUK_DDDPRINT("curr_line=%ld, next_line=%ld -> diff_line=%ld",
+			                     (long) curr_line, (long) next_line, (long) diff_line));
+#endif
+
+			if (diff_line == 0) {
+				/* 0 */
+				duk_be_encode(be_ctx, 0, 1);
+			} else if (diff_line >= 1 && diff_line <= 4) {
+				/* 1 0 <2 bits> */
+				duk_be_encode(be_ctx, (0x02 << 2) + (diff_line - 1), 4);
+			} else if (diff_line >= -0x80 && diff_line <= 0x7f) {
+				/* 1 1 0 <8 bits> */
+				DUK_ASSERT(diff_line + 0x80 >= 0 && diff_line + 0x80 <= 0xff);
+				duk_be_encode(be_ctx, (0x06 << 8) + (diff_line + 0x80), 11);
+			} else {
+				/* 1 1 1 <32 bits>
+				 * Encode in two parts to avoid bitencode 24-bit limitation
+				 */
+				duk_be_encode(be_ctx, (0x07 << 16) + ((next_line >> 16) & 0xffffU), 19);
+				duk_be_encode(be_ctx, next_line & 0xffffU, 16);
+			}
+
+			curr_line = next_line;
+		}
+
+		duk_be_finish(be_ctx);
+		DUK_ASSERT(!be_ctx->truncated);
+
+		/* be_ctx->offset == length of encoded bitstream */
+		curr_offset += (duk_uint_fast32_t) be_ctx->offset;
+	}
+
+	/* compact */
+	new_size = (duk_size_t) curr_offset;
+	duk_hbuffer_resize(thr, h_buf, new_size, new_size);
+
+	(void) duk_to_fixed_buffer(ctx, -1, NULL);
+
+	DUK_DDD(DUK_DDDPRINT("final pc2line data: pc_limit=%ld, length=%ld, %lf bits/opcode --> %!ixT",
+	                     (long) length, (long) new_size, (double) new_size * 8.0 / (double) length,
+	                     (duk_tval *) duk_get_tval(ctx, -1)));
+}
+
+/* PC is unsigned.  If caller does PC arithmetic and gets a negative result,
+ * it will map to a large PC which is out of bounds and causes a zero to be
+ * returned.
+ */
+static duk_uint_fast32_t duk__hobject_pc2line_query_raw(duk_hbuffer_fixed *buf, duk_uint_fast32_t pc) {
+	duk_bitdecoder_ctx bd_ctx_alloc;
+	duk_bitdecoder_ctx *bd_ctx = &bd_ctx_alloc;
+	duk_uint32_t *hdr;
+	duk_uint_fast32_t start_offset;
+	duk_uint_fast32_t pc_limit;
+	duk_uint_fast32_t hdr_index;
+	duk_uint_fast32_t pc_base;
+	duk_uint_fast32_t n;
+	duk_uint_fast32_t curr_line;
+
+	DUK_ASSERT(buf != NULL);
+	DUK_ASSERT(!DUK_HBUFFER_HAS_DYNAMIC((duk_hbuffer *) buf));
+
+	hdr_index = pc / DUK_PC2LINE_SKIP;
+	pc_base = hdr_index * DUK_PC2LINE_SKIP;
+	n = pc - pc_base;
+
+	if (DUK_HBUFFER_FIXED_GET_SIZE(buf) <= sizeof(duk_uint32_t)) {
+		DUK_DD(DUK_DDPRINT("pc2line lookup failed: buffer is smaller than minimal header"));
+		goto error;
+	}
+
+	hdr = (duk_uint32_t *) DUK_HBUFFER_FIXED_GET_DATA_PTR(buf);
+	pc_limit = hdr[0];
+	if (pc >= pc_limit) {
+		/* Note: pc is unsigned and cannot be negative */
+		DUK_DD(DUK_DDPRINT("pc2line lookup failed: pc out of bounds (pc=%ld, limit=%ld)",
+		                   (long) pc, (long) pc_limit));
+		goto error;
+	}
+
+	curr_line = hdr[1 + hdr_index * 2];
+	start_offset = hdr[1 + hdr_index * 2 + 1];
+	if ((duk_size_t) start_offset > DUK_HBUFFER_FIXED_GET_SIZE(buf)) {
+		DUK_DD(DUK_DDPRINT("pc2line lookup failed: start_offset out of bounds (start_offset=%ld, buffer_size=%ld)",
+		                   (long) start_offset, (long) DUK_HBUFFER_GET_SIZE((duk_hbuffer *) buf)));
+		goto error;
+	}
+
+	DUK_MEMZERO(bd_ctx, sizeof(*bd_ctx));
+	bd_ctx->data = ((duk_uint8_t *) hdr) + start_offset;
+	bd_ctx->length = (duk_size_t) (DUK_HBUFFER_FIXED_GET_SIZE(buf) - start_offset);
+
+#if 0
+	DUK_DDD(DUK_DDDPRINT("pc2line lookup: pc=%ld -> hdr_index=%ld, pc_base=%ld, n=%ld, start_offset=%ld",
+	                     (long) pc, (long) hdr_index, (long) pc_base, (long) n, (long) start_offset));
+#endif
+
+	while (n > 0) {
+#if 0
+		DUK_DDD(DUK_DDDPRINT("lookup: n=%ld, curr_line=%ld", (long) n, (long) curr_line));
+#endif
+
+		if (duk_bd_decode_flag(bd_ctx)) {
+			if (duk_bd_decode_flag(bd_ctx)) {
+				if (duk_bd_decode_flag(bd_ctx)) {
+					/* 1 1 1 <32 bits> */
+					duk_uint_fast32_t t;
+					t = duk_bd_decode(bd_ctx, 16);  /* workaround: max nbits = 24 now */
+					t = (t << 16) + duk_bd_decode(bd_ctx, 16);
+					curr_line = t;
+				} else {
+					/* 1 1 0 <8 bits> */
+					duk_uint_fast32_t t;
+					t = duk_bd_decode(bd_ctx, 8);
+					curr_line = curr_line + t - 0x80;
+				}
+			} else {
+				/* 1 0 <2 bits> */
+				duk_uint_fast32_t t;
+				t = duk_bd_decode(bd_ctx, 2);
+				curr_line = curr_line + t + 1;
+			}
+		} else {
+			/* 0: no change */
+		}
+
+		n--;
+	}
+
+	DUK_DDD(DUK_DDDPRINT("pc2line lookup result: pc %ld -> line %ld", (long) pc, (long) curr_line));
+	return curr_line;
+
+ error:
+	DUK_D(DUK_DPRINT("pc2line conversion failed for pc=%ld", (long) pc));
+	return 0;
+}
+
+duk_uint_fast32_t duk_hobject_pc2line_query(duk_context *ctx, duk_idx_t idx_func, duk_uint_fast32_t pc) {
+	duk_hbuffer_fixed *pc2line;
+	duk_uint_fast32_t line;
+
+	duk_get_prop_stridx(ctx, idx_func, DUK_STRIDX_INT_PC2LINE);
+	pc2line = (duk_hbuffer_fixed *) duk_get_hbuffer(ctx, -1);
+	if (pc2line != NULL) {
+		DUK_ASSERT(!DUK_HBUFFER_HAS_DYNAMIC((duk_hbuffer *) pc2line));
+		line = duk__hobject_pc2line_query_raw(pc2line, (duk_uint_fast32_t) pc);
+	} else {
+		line = 0;
+	}
+	duk_pop(ctx);
+
+	return line;
+}
+#endif  /* DUK_USE_PC2LINE */
+#line 1 "duk_hobject_props.c"
+/*
+ *  Hobject property set/get functionality.
+ *
+ *  This is very central functionality for size, performance, and compliance.
+ *  It is also rather intricate; see hobject-algorithms.txt for discussion on
+ *  the algorithms and memory-management.txt for discussion on refcounts and
+ *  side effect issues.
+ *
+ *  Notes:
+ *
+ *    - It might be tempting to assert "refcount nonzero" for objects
+ *      being operated on, but that's not always correct: objects with
+ *      a zero refcount may be operated on by the refcount implementation
+ *      (finalization) for instance.  Hence, no refcount assertions are made.
+ *
+ *    - Many operations (memory allocation, identifier operations, etc)
+ *      may cause arbitrary side effects (e.g. through GC and finalization).
+ *      These side effects may invalidate duk_tval pointers which point to
+ *      areas subject to reallocation (like value stack).  Heap objects
+ *      themselves have stable pointers.  Holding heap object pointers or
+ *      duk_tval copies is not problematic with respect to side effects;
+ *      care must be taken when holding and using argument duk_tval pointers.
+ *
+ *    - If a finalizer is executed, it may operate on the the same object
+ *      we're currently dealing with.  For instance, the finalizer might
+ *      delete a certain property which has already been looked up and
+ *      confirmed to exist.  Ideally finalizers would be disabled if GC
+ *      happens during property access.  At the moment property table realloc
+ *      disables finalizers, and all DECREFs may cause arbitrary changes so
+ *      handle DECREF carefully.
+ *
+ *    - The order of operations for a DECREF matters.  When DECREF is executed,
+ *      the entire object graph must be consistent; note that a refzero may
+ *      lead to a mark-and-sweep through a refcount finalizer.
+ */
+
+/*
+ *  XXX: array indices are mostly typed as duk_uint32_t here; duk_uarridx_t
+ *  might be more appropriate.
+ */
+
+/*
+ *  XXX: duk_uint_fast32_t should probably be used in many places here.
+ */
+
+/* include removed: duk_internal.h */
+
+/*
+ *  Local defines
+ */
+
+#define DUK__NO_ARRAY_INDEX             DUK_HSTRING_NO_ARRAY_INDEX
+
+/* hash probe sequence */
+#define DUK__HASH_INITIAL(hash,h_size)  DUK_HOBJECT_HASH_INITIAL((hash),(h_size))
+#define DUK__HASH_PROBE_STEP(hash)      DUK_HOBJECT_HASH_PROBE_STEP((hash))
+
+/* marker values for hash part */
+#define DUK__HASH_UNUSED                DUK_HOBJECT_HASHIDX_UNUSED
+#define DUK__HASH_DELETED               DUK_HOBJECT_HASHIDX_DELETED
+
+/* valstack space that suffices for all local calls, including recursion
+ * of other than Duktape calls (getters etc)
+ */
+#define DUK__VALSTACK_SPACE             10
+
+/* valstack space allocated especially for proxy lookup which does a
+ * recursive property lookup
+ */
+#define DUK__VALSTACK_PROXY_LOOKUP      20
+
+/*
+ *  Local prototypes
+ */
+
+static duk_bool_t duk__check_arguments_map_for_get(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *temp_desc);
+static void duk__check_arguments_map_for_put(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *temp_desc, duk_bool_t throw_flag);
+static void duk__check_arguments_map_for_delete(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *temp_desc);
+
+static duk_bool_t duk__handle_put_array_length_smaller(duk_hthread *thr, duk_hobject *obj, duk_uint32_t old_len, duk_uint32_t new_len, duk_uint32_t *out_result_len);
+static duk_bool_t duk__handle_put_array_length(duk_hthread *thr, duk_hobject *obj);
+
+static duk_bool_t duk__get_property_desc(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *out_desc, duk_bool_t push_value);
+static duk_bool_t duk__get_own_property_desc_raw(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_uint32_t arr_idx, duk_propdesc *out_desc, duk_bool_t push_value);
+static duk_bool_t duk__get_own_property_desc(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *out_desc, duk_bool_t push_value);
+
+/*
+ *  Misc helpers
+ */
+
+/* Convert a duk_tval number (caller checks) to a 32-bit index.  Returns
+ * DUK__NO_ARRAY_INDEX if the number is not whole or not a valid array
+ * index.
+ */
+static duk_uint32_t duk__tval_number_to_arr_idx(duk_tval *tv) {
+	duk_double_t dbl;
+	duk_uint32_t idx;
+
+	DUK_ASSERT(tv != NULL);
+	DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
+
+	dbl = DUK_TVAL_GET_NUMBER(tv);
+	idx = (duk_uint32_t) dbl;
+	if ((duk_double_t) idx == dbl) {
+	        /* Is whole and within 32 bit range.  If the value happens to be 0xFFFFFFFF,
+		 * it's not a valid array index but will then match DUK__NO_ARRAY_INDEX.
+		 */
+		return idx;
+	}
+	return DUK__NO_ARRAY_INDEX;
+}
+
+/* Push an arbitrary duk_tval to the stack, coerce it to string, and return
+ * both a duk_hstring pointer and an array index (or DUK__NO_ARRAY_INDEX).
+ */
+static duk_uint32_t duk__push_tval_to_hstring_arr_idx(duk_context *ctx, duk_tval *tv, duk_hstring **out_h) {
+	duk_uint32_t arr_idx;
+	duk_hstring *h;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(tv != NULL);
+	DUK_ASSERT(out_h != NULL);
+
+	duk_push_tval(ctx, tv);
+	duk_to_string(ctx, -1);
+	h = duk_get_hstring(ctx, -1);
+	DUK_ASSERT(h != NULL);
+	*out_h = h;
+
+	arr_idx = DUK_HSTRING_GET_ARRIDX_FAST(h);
+	return arr_idx;
+}
+
+/*
+ *  Helpers for managing property storage size
+ */
+
+/* Get default hash part size for a certain entry part size. */
+static duk_uint32_t duk__get_default_h_size(duk_uint32_t e_size) {
+	DUK_ASSERT(e_size <= DUK_HOBJECT_MAX_PROPERTIES);
+
+	if (e_size >= DUK_HOBJECT_E_USE_HASH_LIMIT) {
+		duk_uint32_t res;
+
+		/* result: hash_prime(floor(1.2 * e_size)) */
+		res = duk_util_get_hash_prime(e_size + e_size / DUK_HOBJECT_H_SIZE_DIVISOR);
+
+		/* if fails, e_size will be zero = not an issue, except performance-wise */
+		DUK_ASSERT(res == 0 || res > e_size);
+		return res;
+	} else {
+		return 0;
+	}
+}
+
+/* Get minimum entry part growth for a certain size. */
+static duk_uint32_t duk__get_min_grow_e(duk_uint32_t e_size) {
+	duk_uint32_t res;
+
+	DUK_ASSERT(e_size <= DUK_HOBJECT_MAX_PROPERTIES);
+
+	res = (e_size + DUK_HOBJECT_E_MIN_GROW_ADD) / DUK_HOBJECT_E_MIN_GROW_DIVISOR;
+	DUK_ASSERT(res >= 1);  /* important for callers */
+	return res;
+}
+
+/* Get minimum array part growth for a certain size. */
+static duk_uint32_t duk__get_min_grow_a(duk_uint32_t a_size) {
+	duk_uint32_t res;
+
+	DUK_ASSERT((duk_size_t) a_size <= DUK_HOBJECT_MAX_PROPERTIES);
+
+	res = (a_size + DUK_HOBJECT_A_MIN_GROW_ADD) / DUK_HOBJECT_A_MIN_GROW_DIVISOR;
+	DUK_ASSERT(res >= 1);  /* important for callers */
+	return res;
+}
+
+/* Count actually used entry part entries (non-NULL keys). */
+static duk_uint32_t duk__count_used_e_keys(duk_hobject *obj) {
+	duk_uint_fast32_t i;
+	duk_uint_fast32_t n = 0;
+	duk_hstring **e;
+
+	DUK_ASSERT(obj != NULL);
+
+	e = DUK_HOBJECT_E_GET_KEY_BASE(obj);
+	for (i = 0; i < obj->e_used; i++) {
+		if (*e++) {
+			n++;
+		}
+	}
+	return (duk_uint32_t) n;
+}
+
+/* Count actually used array part entries and array minimum size.
+ * NOTE: 'out_min_size' can be computed much faster by starting from the
+ * end and breaking out early when finding first used entry, but this is
+ * not needed now.
+ */
+static void duk__compute_a_stats(duk_hobject *obj, duk_uint32_t *out_used, duk_uint32_t *out_min_size) {
+	duk_uint_fast32_t i;
+	duk_uint_fast32_t used = 0;
+	duk_uint_fast32_t highest_idx = (duk_uint_fast32_t) -1;  /* see below */
+	duk_tval *a;
+
+	DUK_ASSERT(obj != NULL);
+	DUK_ASSERT(out_used != NULL);
+	DUK_ASSERT(out_min_size != NULL);
+
+	a = DUK_HOBJECT_A_GET_BASE(obj);
+	for (i = 0; i < obj->a_size; i++) {
+		duk_tval *tv = a++;
+		if (!DUK_TVAL_IS_UNDEFINED_UNUSED(tv)) {
+			used++;
+			highest_idx = i;
+		}
+	}
+
+	/* Initial value for highest_idx is -1 coerced to unsigned.  This
+	 * is a bit odd, but (highest_idx + 1) will then wrap to 0 below
+	 * for out_min_size as intended.
+	 */
+
+	*out_used = used;
+	*out_min_size = highest_idx + 1;  /* 0 if no used entries */
+}
+
+/* Check array density and indicate whether or not the array part should be abandoned. */
+static duk_bool_t duk__abandon_array_density_check(duk_uint32_t a_used, duk_uint32_t a_size) {
+	/*
+	 *  Array abandon check; abandon if:
+	 *
+	 *    new_used / new_size < limit
+	 *    new_used < limit * new_size        || limit is 3 bits fixed point
+	 *    new_used < limit' / 8 * new_size   || *8
+	 *    8*new_used < limit' * new_size     || :8
+	 *    new_used < limit' * (new_size / 8)
+	 *
+	 *  Here, new_used = a_used, new_size = a_size.
+	 *
+	 *  Note: some callers use approximate values for a_used and/or a_size
+	 *  (e.g. dropping a '+1' term).  This doesn't affect the usefulness
+	 *  of the check, but may confuse debugging.
+	 */
+
+	return (a_used < DUK_HOBJECT_A_ABANDON_LIMIT * (a_size >> 3));
+}
+
+/* Fast check for extending array: check whether or not a slow density check is required. */
+static duk_bool_t duk__abandon_array_slow_check_required(duk_uint32_t arr_idx, duk_uint32_t old_size) {
+	/*
+	 *  In a fast check we assume old_size equals old_used (i.e., existing
+	 *  array is fully dense).
+	 *
+	 *  Slow check if:
+	 *
+	 *    (new_size - old_size) / old_size > limit
+	 *    new_size - old_size > limit * old_size
+	 *    new_size > (1 + limit) * old_size        || limit' is 3 bits fixed point
+	 *    new_size > (1 + (limit' / 8)) * old_size || * 8
+	 *    8 * new_size > (8 + limit') * old_size   || : 8
+	 *    new_size > (8 + limit') * (old_size / 8)
+	 *    new_size > limit'' * (old_size / 8)      || limit'' = 9 -> max 25% increase
+	 *    arr_idx + 1 > limit'' * (old_size / 8)
+	 *
+	 *  This check doesn't work well for small values, so old_size is rounded
+	 *  up for the check (and the '+ 1' of arr_idx can be ignored in practice):
+	 *
+	 *    arr_idx > limit'' * ((old_size + 7) / 8)
+	 */
+
+	return (arr_idx > DUK_HOBJECT_A_FAST_RESIZE_LIMIT * ((old_size + 7) >> 3));
+}
+
+/*
+ *  Proxy helpers
+ */
+
+#if defined(DUK_USE_ES6_PROXY)
+duk_bool_t duk_hobject_proxy_check(duk_hthread *thr, duk_hobject *obj, duk_hobject **out_target, duk_hobject **out_handler) {
+	duk_tval *tv_target;
+	duk_tval *tv_handler;
+	duk_hobject *h_target;
+	duk_hobject *h_handler;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(obj != NULL);
+	DUK_ASSERT(out_target != NULL);
+	DUK_ASSERT(out_handler != NULL);
+
+	/* Caller doesn't need to check exotic proxy behavior (but does so for
+	 * some fast paths).
+	 */
+	if (DUK_LIKELY(!DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(obj))) {
+		return 0;
+	}
+
+	tv_handler = duk_hobject_find_existing_entry_tval_ptr(obj, DUK_HTHREAD_STRING_INT_HANDLER(thr));
+	if (!tv_handler) {
+		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_PROXY_REVOKED);
+		return 0;
+	}
+	DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv_handler));
+	h_handler = DUK_TVAL_GET_OBJECT(tv_handler);
+	DUK_ASSERT(h_handler != NULL);
+	*out_handler = h_handler;
+	tv_handler = NULL;  /* avoid issues with relocation */
+
+	tv_target = duk_hobject_find_existing_entry_tval_ptr(obj, DUK_HTHREAD_STRING_INT_TARGET(thr));
+	if (!tv_target) {
+		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_PROXY_REVOKED);
+		return 0;
+	}
+	DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv_target));
+	h_target = DUK_TVAL_GET_OBJECT(tv_target);
+	DUK_ASSERT(h_target != NULL);
+	*out_target = h_target;
+	tv_target = NULL;  /* avoid issues with relocation */
+
+	return 1;
+}
+#endif
+
+#if defined(DUK_USE_ES6_PROXY)
+static duk_bool_t duk__proxy_check_prop(duk_hthread *thr, duk_hobject *obj, duk_small_uint_t stridx_trap, duk_tval *tv_key, duk_hobject **out_target) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_hobject *h_handler;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(obj != NULL);
+	DUK_ASSERT(tv_key != NULL);
+	DUK_ASSERT(out_target != NULL);
+
+	if (!duk_hobject_proxy_check(thr, obj, out_target, &h_handler)) {
+		return 0;
+	}
+	DUK_ASSERT(*out_target != NULL);
+	DUK_ASSERT(h_handler != NULL);
+
+	/* XXX: At the moment Duktape accesses internal keys like _finalizer using a
+	 * normal property set/get which would allow a proxy handler to interfere with
+	 * such behavior and to get access to internal key strings.  This is not a problem
+	 * as such because internal key strings can be created in other ways too (e.g.
+	 * through buffers).  The best fix is to change Duktape internal lookups to
+	 * skip proxy behavior.  Until that, internal property accesses bypass the
+	 * proxy and are applied to the target (as if the handler did not exist).
+	 * This has some side effects, see test-bi-proxy-internal-keys.js.
+	 */
+
+	if (DUK_TVAL_IS_STRING(tv_key)) {
+		duk_hstring *h_key = (duk_hstring *) DUK_TVAL_GET_STRING(tv_key);
+		DUK_ASSERT(h_key != NULL);
+		if (DUK_HSTRING_HAS_INTERNAL(h_key)) {
+			DUK_DDD(DUK_DDDPRINT("internal key, skip proxy handler and apply to target"));
+			return 0;
+		}
+	}
+
+	/* The handler is looked up with a normal property lookup; it may be an
+	 * accessor or the handler object itself may be a proxy object.  If the
+	 * handler is a proxy, we need to extend the valstack as we make a
+	 * recursive proxy check without a function call in between (in fact
+	 * there is no limit to the potential recursion here).
+	 *
+	 * (For sanity, proxy creation rejects another proxy object as either
+	 * the handler or the target at the moment so recursive proxy cases
+	 * are not realized now.)
+	 */
+
+	/* XXX: C recursion limit if proxies are allowed as handler/target values */
+
+	duk_require_stack(ctx, DUK__VALSTACK_PROXY_LOOKUP);
+	duk_push_hobject(ctx, h_handler);
+	if (duk_get_prop_stridx(ctx, -1, stridx_trap)) {
+		/* -> [ ... handler trap ] */
+		duk_insert(ctx, -2);  /* -> [ ... trap handler ] */
+
+		/* stack prepped for func call: [ ... trap handler ] */
+		return 1;
+	} else {
+		duk_pop_2(ctx);
+		return 0;
+	}
+}
+#endif  /* DUK_USE_ES6_PROXY */
+
+/*
+ *  Reallocate property allocation, moving properties to the new allocation.
+ *
+ *  Includes key compaction, rehashing, and can also optionally abandoning
+ *  the array part, 'migrating' array entries into the beginning of the
+ *  new entry part.  Arguments are not validated here, so e.g. new_h_size
+ *  MUST be a valid prime.
+ *
+ *  There is no support for in-place reallocation or just compacting keys
+ *  without resizing the property allocation.  This is intentional to keep
+ *  code size minimal.
+ *
+ *  The implementation is relatively straightforward, except for the array
+ *  abandonment process.  Array abandonment requires that new string keys
+ *  are interned, which may trigger GC.  All keys interned so far must be
+ *  reachable for GC at all times; valstack is used for that now.
+ *
+ *  Also, a GC triggered during this reallocation process must not interfere
+ *  with the object being resized.  This is currently controlled by using
+ *  heap->mark_and_sweep_base_flags to indicate that no finalizers will be
+ *  executed (as they can affect ANY object) and no objects are compacted
+ *  (it would suffice to protect this particular object only, though).
+ *
+ *  Note: a non-checked variant would be nice but is a bit tricky to
+ *  implement for the array abandonment process.  It's easy for
+ *  everything else.
+ *
+ *  Note: because we need to potentially resize the valstack (as part
+ *  of abandoning the array part), any tval pointers to the valstack
+ *  will become invalid after this call.
+ */
+
+static void duk__realloc_props(duk_hthread *thr,
+                               duk_hobject *obj,
+                               duk_uint32_t new_e_size,
+                               duk_uint32_t new_a_size,
+                               duk_uint32_t new_h_size,
+                               duk_bool_t abandon_array) {
+	duk_context *ctx = (duk_context *) thr;
+#ifdef DUK_USE_MARK_AND_SWEEP
+	duk_small_uint_t prev_mark_and_sweep_base_flags;
+#endif
+	duk_uint32_t new_alloc_size;
+	duk_uint32_t new_e_size_adjusted;
+	duk_uint8_t *new_p;
+	duk_hstring **new_e_k;
+	duk_propvalue *new_e_pv;
+	duk_uint8_t *new_e_f;
+	duk_tval *new_a;
+	duk_uint32_t *new_h;
+	duk_uint32_t new_e_used;
+	duk_uint_fast32_t i;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(obj != NULL);
+	DUK_ASSERT(!abandon_array || new_a_size == 0);  /* if abandon_array, new_a_size must be 0 */
+	DUK_ASSERT(obj->p != NULL || (obj->e_size == 0 && obj->a_size == 0));
+	DUK_ASSERT(new_h_size == 0 || new_h_size >= new_e_size);  /* required to guarantee success of rehashing,
+	                                                           * intentionally use unadjusted new_e_size
+	                                                           */	
+	DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
+
+	/*
+	 *  Pre resize assertions.
+	 */
+
+#ifdef DUK_USE_ASSERTIONS
+	/* XXX: pre checks (such as no duplicate keys) */
+#endif
+
+	/*
+	 *  For property layout 1, tweak e_size to ensure that the whole entry
+	 *  part (key + val + flags) is a suitable multiple for alignment
+	 *  (platform specific).
+	 *
+	 *  Property layout 2 does not require this tweaking and is preferred
+	 *  on low RAM platforms requiring alignment.
+	 */
+
+#if defined(DUK_USE_HOBJECT_LAYOUT_2) || defined(DUK_USE_HOBJECT_LAYOUT_3)
+	DUK_DDD(DUK_DDDPRINT("using layout 2 or 3, no need to pad e_size: %ld", (long) new_e_size));
+	new_e_size_adjusted = new_e_size;
+#elif defined(DUK_USE_HOBJECT_LAYOUT_1) && (DUK_HOBJECT_ALIGN_TARGET == 1)
+	DUK_DDD(DUK_DDDPRINT("using layout 1, but no need to pad e_size: %ld", (long) new_e_size));
+	new_e_size_adjusted = new_e_size;
+#elif defined(DUK_USE_HOBJECT_LAYOUT_1) && ((DUK_HOBJECT_ALIGN_TARGET == 4) || (DUK_HOBJECT_ALIGN_TARGET == 8))
+	new_e_size_adjusted = (new_e_size + DUK_HOBJECT_ALIGN_TARGET - 1) & (~(DUK_HOBJECT_ALIGN_TARGET - 1));
+	DUK_DDD(DUK_DDDPRINT("using layout 1, and alignment target is %ld, adjusted e_size: %ld -> %ld",
+	                     (long) DUK_HOBJECT_ALIGN_TARGET, (long) new_e_size, (long) new_e_size_adjusted));
+	DUK_ASSERT(new_e_size_adjusted >= new_e_size);
+#else
+#error invalid hobject layout defines
+#endif
+
+	/*
+	 *  Debug logging after adjustment.
+	 */
+
+	DUK_DDD(DUK_DDDPRINT("attempt to resize hobject %p props (%ld -> %ld bytes), from {p=%p,e_size=%ld,e_used=%ld,a_size=%ld,h_size=%ld} to "
+	                     "{e_size=%ld,a_size=%ld,h_size=%ld}, abandon_array=%ld, unadjusted new_e_size=%ld",
+	                     (void *) obj,
+	                     (long) DUK_HOBJECT_P_COMPUTE_SIZE(obj->e_size, obj->a_size, obj->h_size),
+	                     (long) DUK_HOBJECT_P_COMPUTE_SIZE(new_e_size_adjusted, new_a_size, new_h_size),
+	                     (void *) obj->p,
+	                     (long) obj->e_size,
+	                     (long) obj->e_used,
+	                     (long) obj->a_size,
+	                     (long) obj->h_size,
+	                     (long) new_e_size_adjusted,
+	                     (long) new_a_size,
+	                     (long) new_h_size,
+	                     (long) abandon_array,
+	                     (long) new_e_size));
+
+	/*
+	 *  Property count check.  This is the only point where we ensure that
+	 *  we don't get more (allocated) property space that we can handle.
+	 *  There aren't hard limits as such, but some algorithms fail (e.g.
+	 *  finding next higher prime, selecting hash part size) if we get too
+	 *  close to the 4G property limit.
+	 *
+	 *  Since this works based on allocation size (not actually used size),
+	 *  the limit is a bit approximate but good enough in practice.
+	 */
+
+	if (new_e_size_adjusted + new_a_size > DUK_HOBJECT_MAX_PROPERTIES) {
+		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, DUK_STR_OBJECT_PROPERTY_LIMIT);
+	}
+
+	/*
+	 *  Compute new alloc size and alloc new area.
+	 *
+	 *  The new area is allocated as a dynamic buffer and placed into the
+	 *  valstack for reachability.  The actual buffer is then detached at
+	 *  the end.
+	 *
+	 *  Note: heap_mark_and_sweep_base_flags are altered here to ensure
+	 *  no-one touches this object while we're resizing and rehashing it.
+	 *  The flags must be reset on every exit path after it.  Finalizers
+	 *  and compaction is prevented currently for all objects while it
+	 *  would be enough to restrict it only for the current object.
+	 */
+
+#ifdef DUK_USE_MARK_AND_SWEEP
+	prev_mark_and_sweep_base_flags = thr->heap->mark_and_sweep_base_flags;
+	thr->heap->mark_and_sweep_base_flags |=
+                DUK_MS_FLAG_NO_FINALIZERS |         /* avoid attempts to add/remove object keys */
+	        DUK_MS_FLAG_NO_OBJECT_COMPACTION;   /* avoid attempt to compact the current object */
+#endif
+
+	new_alloc_size = DUK_HOBJECT_P_COMPUTE_SIZE(new_e_size_adjusted, new_a_size, new_h_size);
+	DUK_DDD(DUK_DDDPRINT("new hobject allocation size is %ld", (long) new_alloc_size));
+	if (new_alloc_size == 0) {
+		/* for zero size, don't push anything on valstack */
+		DUK_ASSERT(new_e_size_adjusted == 0);
+		DUK_ASSERT(new_a_size == 0);
+		DUK_ASSERT(new_h_size == 0);
+		new_p = NULL;
+	} else {
+		/* This may trigger mark-and-sweep with arbitrary side effects,
+		 * including an attempted resize of the object we're resizing,
+		 * executing a finalizer which may add or remove properties of
+		 * the object we're resizing etc.
+		 */
+
+		/* Note: buffer is dynamic so that we can 'steal' the actual
+		 * allocation later.
+		 */
+
+		new_p = (duk_uint8_t *) duk_push_dynamic_buffer(ctx, new_alloc_size);  /* errors out if out of memory */
+		DUK_ASSERT(new_p != NULL);  /* since new_alloc_size > 0 */
+	}
+
+	/* Set up pointers to the new property area: this is hidden behind a macro
+	 * because it is memory layout specific.
+	 */
+	DUK_HOBJECT_P_SET_REALLOC_PTRS(new_p, new_e_k, new_e_pv, new_e_f, new_a, new_h,
+	                               new_e_size_adjusted, new_a_size, new_h_size);
+	new_e_used = 0;
+
+	/* if new_p == NULL, all of these pointers are NULL */
+	DUK_ASSERT((new_p != NULL) ||
+	           (new_e_k == NULL && new_e_pv == NULL && new_e_f == NULL &&
+	            new_a == NULL && new_h == NULL));
+
+	DUK_DDD(DUK_DDDPRINT("new alloc size %ld, new_e_k=%p, new_e_pv=%p, new_e_f=%p, new_a=%p, new_h=%p",
+	                     (long) new_alloc_size, (void *) new_e_k, (void *) new_e_pv, (void *) new_e_f,
+	                     (void *) new_a, (void *) new_h));
+
+	/*
+	 *  Migrate array to start of entries if requested.
+	 *
+	 *  Note: from an enumeration perspective the order of entry keys matters.
+	 *  Array keys should appear wherever they appeared before the array abandon
+	 *  operation.
+	 */
+
+	if (abandon_array) {
+		/*
+		 *  Note: assuming new_a_size == 0, and that entry part contains
+		 *  no conflicting keys, refcounts do not need to be adjusted for
+		 *  the values, as they remain exactly the same.
+		 *
+		 *  The keys, however, need to be interned, incref'd, and be
+		 *  reachable for GC.  Any intern attempt may trigger a GC and
+		 *  claim any non-reachable strings, so every key must be reachable
+		 *  at all times.
+		 *
+		 *  A longjmp must not occur here, as the new_p allocation would
+		 *  be freed without these keys being decref'd, hence the messy
+		 *  decref handling if intern fails.
+		 */
+		DUK_ASSERT(new_a_size == 0);
+
+		for (i = 0; i < obj->a_size; i++) {
+			duk_tval *tv1;
+			duk_tval *tv2;
+			duk_hstring *key;
+
+			DUK_ASSERT(obj->p != NULL);
+
+			tv1 = DUK_HOBJECT_A_GET_VALUE_PTR(obj, i);
+			if (DUK_TVAL_IS_UNDEFINED_UNUSED(tv1)) {
+				continue;
+			}
+
+			DUK_ASSERT(new_p != NULL && new_e_k != NULL &&
+			           new_e_pv != NULL && new_e_f != NULL);
+
+			/*
+			 *  Intern key via the valstack to ensure reachability behaves
+			 *  properly.  We must avoid longjmp's here so use non-checked
+			 *  primitives.
+			 *
+			 *  Note: duk_check_stack() potentially reallocs the valstack,
+			 *  invalidating any duk_tval pointers to valstack.  Callers
+			 *  must be careful.
+			 */
+
+			/* never shrinks; auto-adds DUK_VALSTACK_INTERNAL_EXTRA, which is generous */
+			if (!duk_check_stack(ctx, 1)) {
+				goto abandon_error;
+			}
+			DUK_ASSERT_VALSTACK_SPACE(thr, 1);
+			key = duk_heap_string_intern_u32(thr->heap, i);
+			if (!key) {
+				goto abandon_error;
+			}
+			duk_push_hstring(ctx, key);  /* keep key reachable for GC etc; guaranteed not to fail */
+
+			/* key is now reachable in the valstack */
+
+			DUK_HSTRING_INCREF(thr, key);   /* second incref for the entry reference */
+			new_e_k[new_e_used] = key;
+			tv2 = &new_e_pv[new_e_used].v;  /* array entries are all plain values */
+			DUK_TVAL_SET_TVAL(tv2, tv1);
+			new_e_f[new_e_used] = DUK_PROPDESC_FLAG_WRITABLE |
+			                      DUK_PROPDESC_FLAG_ENUMERABLE |
+			                      DUK_PROPDESC_FLAG_CONFIGURABLE;
+			new_e_used++;
+
+			/* Note: new_e_used matches pushed temp key count, and nothing can
+			 * fail above between the push and this point.
+			 */
+		}
+
+		DUK_DDD(DUK_DDDPRINT("abandon array: pop %ld key temps from valstack", (long) new_e_used));
+		duk_pop_n(ctx, new_e_used);
+	}
+
+	/*
+	 *  Copy keys and values in the entry part (compacting them at the same time).
+	 */
+
+	for (i = 0; i < obj->e_used; i++) {
+		duk_hstring *key;
+
+		DUK_ASSERT(obj->p != NULL);
+
+		key = DUK_HOBJECT_E_GET_KEY(obj, i);
+		if (!key) {
+			continue;
+		}
+
+		DUK_ASSERT(new_p != NULL && new_e_k != NULL &&
+		           new_e_pv != NULL && new_e_f != NULL);
+
+		new_e_k[new_e_used] = key;
+		new_e_pv[new_e_used] = DUK_HOBJECT_E_GET_VALUE(obj, i);
+		new_e_f[new_e_used] = DUK_HOBJECT_E_GET_FLAGS(obj, i);
+		new_e_used++;
+	}
+	/* the entries [new_e_used, new_e_size_adjusted[ are left uninitialized on purpose (ok, not gc reachable) */
+
+	/*
+	 *  Copy array elements to new array part.
+	 */
+
+	if (new_a_size > obj->a_size) {
+		/* copy existing entries as is */
+		DUK_ASSERT(new_p != NULL && new_a != NULL);
+		if (obj->a_size > 0) {
+			/* Avoid zero copy with an invalid pointer.  If obj->p is NULL,
+			 * the 'new_a' pointer will be invalid which is not allowed even
+			 * when copy size is zero.
+			 */
+			DUK_ASSERT(obj->p != NULL);
+			DUK_ASSERT(obj->a_size > 0);
+			DUK_MEMCPY((void *) new_a, (void *) DUK_HOBJECT_A_GET_BASE(obj), sizeof(duk_tval) * obj->a_size);
+		}
+
+		/* fill new entries with -unused- (required, gc reachable) */
+		for (i = obj->a_size; i < new_a_size; i++) {
+			duk_tval *tv = &new_a[i];
+			DUK_TVAL_SET_UNDEFINED_UNUSED(tv);
+		}
+	} else {
+#ifdef DUK_USE_ASSERTIONS
+		/* caller must have decref'd values above new_a_size (if that is necessary) */
+		if (!abandon_array) {
+			for (i = new_a_size; i < obj->a_size; i++) {
+				duk_tval *tv;
+				tv = DUK_HOBJECT_A_GET_VALUE_PTR(obj, i);
+
+				/* current assertion is quite strong: decref's and set to unused */
+				DUK_ASSERT(DUK_TVAL_IS_UNDEFINED_UNUSED(tv));
+			}
+		}
+#endif
+		if (new_a_size > 0) {
+			/* Avoid zero copy with an invalid pointer.  If obj->p is NULL,
+			 * the 'new_a' pointer will be invalid which is not allowed even
+			 * when copy size is zero.
+			 */
+			DUK_ASSERT(obj->p != NULL);
+			DUK_ASSERT(new_a_size > 0);
+			DUK_MEMCPY((void *) new_a, (void *) DUK_HOBJECT_A_GET_BASE(obj), sizeof(duk_tval) * new_a_size);
+		}
+	}
+
+	/*
+	 *  Rebuild the hash part always from scratch (guaranteed to finish).
+	 *
+	 *  Any resize of hash part requires rehashing.  In addition, by rehashing
+	 *  get rid of any elements marked deleted (DUK__HASH_DELETED) which is critical
+	 *  to ensuring the hash part never fills up.
+	 */
+
+	if (DUK_UNLIKELY(new_h_size > 0)) {
+		DUK_ASSERT(new_h != NULL);
+
+		/* fill new_h with u32 0xff = UNUSED */
+		DUK_ASSERT(obj->p != NULL);
+		DUK_ASSERT(new_h_size > 0);
+		DUK_MEMSET(new_h, 0xff, sizeof(duk_uint32_t) * new_h_size);
+
+		DUK_ASSERT(new_e_used <= new_h_size);  /* equality not actually possible */
+		for (i = 0; i < new_e_used; i++) {
+			duk_hstring *key = new_e_k[i];
+			duk_uint32_t j, step;
+
+			DUK_ASSERT(key != NULL);
+			j = DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key), new_h_size);
+			step = DUK__HASH_PROBE_STEP(DUK_HSTRING_GET_HASH(key));
+
+			for (;;) {
+				DUK_ASSERT(new_h[j] != DUK__HASH_DELETED);  /* should never happen */
+				if (new_h[j] == DUK__HASH_UNUSED) {
+					DUK_DDD(DUK_DDDPRINT("rebuild hit %ld -> %ld", (long) j, (long) i));
+					new_h[j] = i;
+					break;
+				}
+				DUK_DDD(DUK_DDDPRINT("rebuild miss %ld, step %ld", (long) j, (long) step));
+				j = (j + step) % new_h_size;
+
+				/* guaranteed to finish */
+				DUK_ASSERT(j != (duk_uint32_t) DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key), new_h_size));
+			}
+		}
+	} else {
+		DUK_DDD(DUK_DDDPRINT("no hash part, no rehash"));
+	}
+
+	/*
+	 *  Nice debug log.
+	 */
+
+	DUK_DD(DUK_DDPRINT("resized hobject %p props (%ld -> %ld bytes), from {p=%p,e_size=%ld,e_used=%ld,a_size=%ld,h_size=%ld} to "
+	                   "{p=%p,e_size=%ld,e_used=%ld,a_size=%ld,h_size=%ld}, abandon_array=%ld, unadjusted new_e_size=%ld",
+	                   (void *) obj,
+	                   (long) DUK_HOBJECT_P_COMPUTE_SIZE(obj->e_size, obj->a_size, obj->h_size),
+	                   (long) new_alloc_size,
+	                   (void *) obj->p,
+	                   (long) obj->e_size,
+	                   (long) obj->e_used,
+	                   (long) obj->a_size,
+	                   (long) obj->h_size,
+	                   (void *) new_p,
+	                   (long) new_e_size_adjusted,
+	                   (long) new_e_used,
+	                   (long) new_a_size,
+	                   (long) new_h_size,
+	                   (long) abandon_array,
+	                   (long) new_e_size));
+
+	/*
+	 *  All done, switch properties ('p') allocation to new one.
+	 */
+
+	DUK_FREE(thr->heap, obj->p);  /* NULL obj->p is OK */
+	obj->p = new_p;
+	obj->e_size = new_e_size_adjusted;
+	obj->e_used = new_e_used;
+	obj->a_size = new_a_size;
+	obj->h_size = new_h_size;
+
+	if (new_p) {
+		/*
+		 *  Detach actual buffer from dynamic buffer in valstack, and
+		 *  pop it from the stack.
+		 *
+		 *  XXX: the buffer object is certainly not reachable at this point,
+		 *  so it would be nice to free it forcibly even with only
+		 *  mark-and-sweep enabled.  Not a big issue though.
+		 */
+		duk_hbuffer_dynamic *buf;
+		DUK_ASSERT(new_alloc_size > 0);
+		DUK_ASSERT(duk_is_buffer(ctx, -1));
+		buf = (duk_hbuffer_dynamic *) duk_require_hbuffer(ctx, -1);
+		DUK_ASSERT(buf != NULL);
+		DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
+		buf->curr_alloc = NULL;
+		buf->size = 0;  /* these size resets are not strictly necessary, but nice for consistency */
+		buf->usable_size = 0;
+		duk_pop(ctx);
+	} else {
+		DUK_ASSERT(new_alloc_size == 0);
+		/* no need to pop, nothing was pushed */
+	}
+
+	/* clear array part flag only after switching */
+	if (abandon_array) {
+		DUK_HOBJECT_CLEAR_ARRAY_PART(obj);
+	}
+
+	DUK_DDD(DUK_DDDPRINT("resize result: %!O", (duk_heaphdr *) obj));
+
+#ifdef DUK_USE_MARK_AND_SWEEP
+	thr->heap->mark_and_sweep_base_flags = prev_mark_and_sweep_base_flags;
+#endif
+
+	/*
+	 *  Post resize assertions.
+	 */
+
+#ifdef DUK_USE_ASSERTIONS
+	/* XXX: post checks (such as no duplicate keys) */
+#endif
+	return;
+
+	/*
+	 *  Abandon array failed, need to decref keys already inserted
+	 *  into the beginning of new_e_k before unwinding valstack.
+	 */
+
+ abandon_error:
+	DUK_D(DUK_DPRINT("hobject resize failed during abandon array, decref keys"));
+	i = new_e_used;
+	while (i > 0) {
+		i--;
+		DUK_ASSERT(new_e_k != NULL);
+		DUK_ASSERT(new_e_k[i] != NULL);
+		DUK_HSTRING_DECREF(thr, new_e_k[i]);
+	}
+
+#ifdef DUK_USE_MARK_AND_SWEEP
+	thr->heap->mark_and_sweep_base_flags = prev_mark_and_sweep_base_flags;
+#endif
+
+	DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, DUK_STR_OBJECT_RESIZE_FAILED);
+}
+
+/*
+ *  Helpers to resize properties allocation on specific needs.
+ */
+
+/* Grow entry part allocation for one additional entry. */
+static void duk__grow_props_for_new_entry_item(duk_hthread *thr, duk_hobject *obj) {
+	duk_uint32_t new_e_size;
+	duk_uint32_t new_a_size;
+	duk_uint32_t new_h_size;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(obj != NULL);
+
+	new_e_size = obj->e_size + duk__get_min_grow_e(obj->e_size);
+	new_h_size = duk__get_default_h_size(new_e_size);
+	new_a_size = obj->a_size;
+	DUK_ASSERT(new_e_size >= obj->e_size + 1);  /* duk__get_min_grow_e() is always >= 1 */
+
+	duk__realloc_props(thr, obj, new_e_size, new_a_size, new_h_size, 0);
+}
+
+/* Grow array part for a new highest array index. */
+static void duk__grow_props_for_array_item(duk_hthread *thr, duk_hobject *obj, duk_uint32_t highest_arr_idx) {
+	duk_uint32_t new_e_size;
+	duk_uint32_t new_a_size;
+	duk_uint32_t new_h_size;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(obj != NULL);
+	DUK_ASSERT(highest_arr_idx >= obj->a_size);
+
+	/* minimum new length is highest_arr_idx + 1 */
+
+	new_e_size = obj->e_size;
+	new_h_size = obj->h_size;
+	new_a_size = highest_arr_idx + duk__get_min_grow_a(highest_arr_idx);
+	DUK_ASSERT(new_a_size >= highest_arr_idx + 1);  /* duk__get_min_grow_a() is always >= 1 */
+
+	duk__realloc_props(thr, obj, new_e_size, new_a_size, new_h_size, 0);
+}
+
+/* Abandon array part, moving array entries into entries part.
+ * This requires a props resize, which is a heavy operation.
+ * We also compact the entries part while we're at it, although
+ * this is not strictly required.
+ */
+static void duk__abandon_array_checked(duk_hthread *thr, duk_hobject *obj) {
+	duk_uint32_t new_e_size;
+	duk_uint32_t new_a_size;
+	duk_uint32_t new_h_size;
+	duk_uint32_t e_used;
+	duk_uint32_t a_used;
+	duk_uint32_t a_size;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(obj != NULL);
+
+	e_used = duk__count_used_e_keys(obj);
+	duk__compute_a_stats(obj, &a_used, &a_size);
+
+	/*
+	 *  Must guarantee all actually used array entries will fit into
+	 *  new entry part.  Add one growth step to ensure we don't run out
+	 *  of space right away.
+	 */
+
+	new_e_size = e_used + a_used;
+	new_e_size = new_e_size + duk__get_min_grow_e(new_e_size);
+	new_a_size = 0;
+	new_h_size = duk__get_default_h_size(new_e_size);
+
+	DUK_DD(DUK_DDPRINT("abandon array part for hobject %p, "
+	                   "array stats before: e_used=%ld, a_used=%ld, a_size=%ld; "
+	                   "resize to e_size=%ld, a_size=%ld, h_size=%ld",
+	                   (void *) obj, (long) e_used, (long) a_used, (long) a_size,
+	                   (long) new_e_size, (long) new_a_size, (long) new_h_size));
+
+	duk__realloc_props(thr, obj, new_e_size, new_a_size, new_h_size, 1);
+}
+
+/*
+ *  Compact an object.  Minimizes allocation size for objects which are
+ *  not likely to be extended.  This is useful for internal and non-
+ *  extensible objects, but can also be called for non-extensible objects.
+ *  May abandon the array part if it is computed to be too sparse.
+ *
+ *  This call is relatively expensive, as it needs to scan both the
+ *  entries and the array part.
+ *
+ *  The call may fail due to allocation error.
+ */
+
+void duk_hobject_compact_props(duk_hthread *thr, duk_hobject *obj) {
+	duk_uint32_t e_size;       /* currently used -> new size */
+	duk_uint32_t a_size;       /* currently required */
+	duk_uint32_t a_used;       /* actually used */
+	duk_uint32_t h_size;
+	duk_bool_t abandon_array;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(obj != NULL);
+
+	e_size = duk__count_used_e_keys(obj);
+	duk__compute_a_stats(obj, &a_used, &a_size);
+
+	DUK_DD(DUK_DDPRINT("compacting hobject, used e keys %ld, used a keys %ld, min a size %ld, "
+	                   "resized array density would be: %ld/%ld = %lf",
+	                   (long) e_size, (long) a_used, (long) a_size,
+	                   (long) a_used, (long) a_size,
+	                   (double) a_used / (double) a_size));
+
+	if (duk__abandon_array_density_check(a_used, a_size)) {
+		DUK_DD(DUK_DDPRINT("decided to abandon array during compaction, a_used=%ld, a_size=%ld",
+		                   (long) a_used, (long) a_size));
+		abandon_array = 1;
+		e_size += a_used;
+		a_size = 0;
+	} else {
+		DUK_DD(DUK_DDPRINT("decided to keep array during compaction"));
+		abandon_array = 0;
+	}
+
+	if (e_size >= DUK_HOBJECT_E_USE_HASH_LIMIT) {
+		h_size = duk__get_default_h_size(e_size);
+	} else {
+		h_size = 0;
+	}
+
+	DUK_DD(DUK_DDPRINT("compacting hobject -> new e_size %ld, new a_size=%ld, new h_size=%ld, abandon_array=%ld",
+	                   (long) e_size, (long) a_size, (long) h_size, (long) abandon_array));
+
+	duk__realloc_props(thr, obj, e_size, a_size, h_size, abandon_array);
+}
+
+/*
+ *  Find an existing key from entry part either by linear scan or by
+ *  using the hash index (if it exists).
+ *
+ *  Sets entry index (and possibly the hash index) to output variables,
+ *  which allows the caller to update the entry and hash entries in-place.
+ *  If entry is not found, both values are set to -1.  If entry is found
+ *  but there is no hash part, h_idx is set to -1.
+ */
+
+void duk_hobject_find_existing_entry(duk_hobject *obj, duk_hstring *key, duk_int_t *e_idx, duk_int_t *h_idx) {
+	DUK_ASSERT(obj != NULL);
+	DUK_ASSERT(key != NULL);
+	DUK_ASSERT(e_idx != NULL);
+	DUK_ASSERT(h_idx != NULL);
+
+	if (DUK_LIKELY(obj->h_size == 0)) {
+		/* Linear scan: more likely because most objects are small.
+		 * This is an important fast path.
+		 *
+		 * XXX: this might be worth inlining for property lookups.
+		 */
+		duk_uint_fast32_t i;
+		duk_uint_fast32_t n;
+		duk_hstring **h_keys_base;
+		DUK_DDD(DUK_DDDPRINT("duk_hobject_find_existing_entry() using linear scan for lookup"));
+
+		h_keys_base = DUK_HOBJECT_E_GET_KEY_BASE(obj);
+		n = obj->e_used;
+		for (i = 0; i < n; i++) {
+			if (h_keys_base[i] == key) {
+				*e_idx = i;
+				*h_idx = -1;
+				return;
+			}
+		}
+	} else {
+		/* hash lookup */
+		duk_uint32_t n;
+		duk_uint32_t i, step;
+		duk_uint32_t *h_base;
+
+		DUK_DDD(DUK_DDDPRINT("duk_hobject_find_existing_entry() using hash part for lookup"));
+
+		h_base = DUK_HOBJECT_H_GET_BASE(obj);
+		n = obj->h_size;
+		i = DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key), n);
+		step = DUK__HASH_PROBE_STEP(DUK_HSTRING_GET_HASH(key));
+
+		for (;;) {
+			duk_uint32_t t;
+
+			DUK_ASSERT_DISABLE(i >= 0);  /* unsigned */
+			DUK_ASSERT(i < obj->h_size);
+			t = h_base[i];
+			DUK_ASSERT(t == DUK__HASH_UNUSED || t == DUK__HASH_DELETED ||
+			           (t < obj->e_size));  /* t >= 0 always true, unsigned */
+
+			if (t == DUK__HASH_UNUSED) {
+				break;
+			} else if (t == DUK__HASH_DELETED) {
+				DUK_DDD(DUK_DDDPRINT("lookup miss (deleted) i=%ld, t=%ld",
+				                     (long) i, (long) t));
+			} else {
+				DUK_ASSERT(t < obj->e_size);
+				if (DUK_HOBJECT_E_GET_KEY(obj, t) == key) {
+					DUK_DDD(DUK_DDDPRINT("lookup hit i=%ld, t=%ld -> key %p",
+					                     (long) i, (long) t, (void *) key));
+					*e_idx = t;
+					*h_idx = i;
+					return;
+				}
+				DUK_DDD(DUK_DDDPRINT("lookup miss i=%ld, t=%ld",
+				                     (long) i, (long) t));
+			}
+			i = (i + step) % n;
+
+			/* guaranteed to finish, as hash is never full */
+			DUK_ASSERT(i != (duk_uint32_t) DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key), n));
+		}
+	}
+
+	/* not found */
+	*e_idx = -1;
+	*h_idx = -1;
+}
+
+/* For internal use: get non-accessor entry value */
+duk_tval *duk_hobject_find_existing_entry_tval_ptr(duk_hobject *obj, duk_hstring *key) {
+	duk_int_t e_idx;
+	duk_int_t h_idx;
+
+	DUK_ASSERT(obj != NULL);
+	DUK_ASSERT(key != NULL);
+
+	duk_hobject_find_existing_entry(obj, key, &e_idx, &h_idx);
+	if (e_idx >= 0 && !DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, e_idx)) {
+		return DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, e_idx);
+	} else {
+		return NULL;
+	}
+}
+
+/* For internal use: get non-accessor entry value and attributes */
+duk_tval *duk_hobject_find_existing_entry_tval_ptr_and_attrs(duk_hobject *obj, duk_hstring *key, duk_int_t *out_attrs) {
+	duk_int_t e_idx;
+	duk_int_t h_idx;
+
+	DUK_ASSERT(obj != NULL);
+	DUK_ASSERT(key != NULL);
+	DUK_ASSERT(out_attrs != NULL);
+
+	duk_hobject_find_existing_entry(obj, key, &e_idx, &h_idx);
+	if (e_idx >= 0 && !DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, e_idx)) {
+		*out_attrs = DUK_HOBJECT_E_GET_FLAGS(obj, e_idx);
+		return DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, e_idx);
+	} else {
+		*out_attrs = 0;
+		return NULL;
+	}
+}
+
+/* For internal use: get array part value */
+duk_tval *duk_hobject_find_existing_array_entry_tval_ptr(duk_hobject *obj, duk_uarridx_t i) {
+	duk_tval *tv;
+
+	DUK_ASSERT(obj != NULL);
+
+	if (!DUK_HOBJECT_HAS_ARRAY_PART(obj)) {
+		return NULL;
+	}
+	if (i >= obj->a_size) {
+		return NULL;
+	}
+	tv = DUK_HOBJECT_A_GET_VALUE_PTR(obj, i);
+	return tv;
+}
+
+/*
+ *  Allocate and initialize a new entry, resizing the properties allocation
+ *  if necessary.  Returns entry index (e_idx) or throws an error if alloc fails.
+ *
+ *  Sets the key of the entry (increasing the key's refcount), and updates
+ *  the hash part if it exists.  Caller must set value and flags, and update
+ *  the entry value refcount.  A decref for the previous value is not necessary.
+ */
+
+static duk_bool_t duk__alloc_entry_checked(duk_hthread *thr, duk_hobject *obj, duk_hstring *key) {
+	duk_uint32_t idx;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(obj != NULL);
+	DUK_ASSERT(key != NULL);
+	DUK_ASSERT(obj->e_used <= obj->e_size);
+
+#ifdef DUK_USE_ASSERTIONS
+	/* key must not already exist in entry part */
+	{
+		duk_uint_fast32_t i;
+		for (i = 0; i < obj->e_used; i++) {
+			DUK_ASSERT(DUK_HOBJECT_E_GET_KEY(obj, i) != key);
+		}
+	}
+#endif
+
+	if (obj->e_used >= obj->e_size) {
+		/* only need to guarantee 1 more slot, but allocation growth is in chunks */
+		DUK_DDD(DUK_DDDPRINT("entry part full, allocate space for one more entry"));
+		duk__grow_props_for_new_entry_item(thr, obj);
+	}
+	DUK_ASSERT(obj->e_used < obj->e_size);
+	idx = obj->e_used++;
+
+	/* previous value is assumed to be garbage, so don't touch it */
+	DUK_HOBJECT_E_SET_KEY(obj, idx, key);
+	DUK_HSTRING_INCREF(thr, key);
+
+	if (DUK_UNLIKELY(obj->h_size > 0)) {
+		duk_uint32_t n;
+		duk_uint32_t i, step;
+		duk_uint32_t *h_base = DUK_HOBJECT_H_GET_BASE(obj);
+
+		n = obj->h_size;
+		i = DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key), n);
+		step = DUK__HASH_PROBE_STEP(DUK_HSTRING_GET_HASH(key));
+
+		for (;;) {
+			duk_uint32_t t = h_base[i];
+			if (t == DUK__HASH_UNUSED || t == DUK__HASH_DELETED) {
+				DUK_DDD(DUK_DDDPRINT("duk__alloc_entry_checked() inserted key into hash part, %ld -> %ld",
+				                     (long) i, (long) idx));
+				DUK_ASSERT_DISABLE(i >= 0);  /* unsigned */
+				DUK_ASSERT(i < obj->h_size);
+				DUK_ASSERT_DISABLE(idx >= 0);
+				DUK_ASSERT(idx < obj->e_size);
+				h_base[i] = idx;
+				break;
+			}
+			DUK_DDD(DUK_DDDPRINT("duk__alloc_entry_checked() miss %ld", (long) i));
+			i = (i + step) % n;
+
+			/* guaranteed to find an empty slot */
+			DUK_ASSERT(i != (duk_uint32_t) DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key), obj->h_size));
+		}
+	}
+
+	/* Note: we could return the hash index here too, but it's not
+	 * needed right now.
+	 */
+
+	DUK_ASSERT_DISABLE(idx >= 0);
+	DUK_ASSERT(idx < obj->e_size);
+	DUK_ASSERT(idx < obj->e_used);
+	return idx;
+}
+
+/*
+ *  Object internal value
+ *
+ *  Returned value is guaranteed to be reachable / incref'd, caller does not need 
+ *  to incref OR decref.  No proxies or accessors are invoked, no prototype walk.
+ */
+
+duk_bool_t duk_hobject_get_internal_value(duk_heap *heap, duk_hobject *obj, duk_tval *tv_out) {
+	duk_int_t e_idx;
+	duk_int_t h_idx;
+
+	DUK_ASSERT(heap != NULL);
+	DUK_ASSERT(obj != NULL);
+	DUK_ASSERT(tv_out != NULL);
+
+	DUK_TVAL_SET_UNDEFINED_UNUSED(tv_out);
+
+	/* always in entry part, no need to look up parents etc */
+	duk_hobject_find_existing_entry(obj, DUK_HEAP_STRING_INT_VALUE(heap), &e_idx, &h_idx);
+	if (e_idx >= 0) {
+		DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, e_idx));
+		DUK_TVAL_SET_TVAL(tv_out, DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, e_idx));
+		return 1;
+	}
+	return 0;
+}
+
+duk_hstring *duk_hobject_get_internal_value_string(duk_heap *heap, duk_hobject *obj) {
+	duk_tval tv;
+
+	DUK_ASSERT(heap != NULL);
+	DUK_ASSERT(obj != NULL);
+
+	if (duk_hobject_get_internal_value(heap, obj, &tv)) {
+		duk_hstring *h;
+		DUK_ASSERT(DUK_TVAL_IS_STRING(&tv));
+		h = DUK_TVAL_GET_STRING(&tv);
+		return h;
+	}
+
+	return NULL;
+}
+
+duk_hbuffer *duk_hobject_get_internal_value_buffer(duk_heap *heap, duk_hobject *obj) {
+	duk_tval tv;
+
+	DUK_ASSERT(heap != NULL);
+	DUK_ASSERT(obj != NULL);
+
+	if (duk_hobject_get_internal_value(heap, obj, &tv)) {
+		duk_hbuffer *h;
+		DUK_ASSERT(DUK_TVAL_IS_BUFFER(&tv));
+		h = DUK_TVAL_GET_BUFFER(&tv);
+		return h;
+	}
+
+	return NULL;
+}
+
+/*
+ *  Arguments handling helpers (argument map mainly).
+ *
+ *  An arguments object has exotic behavior for some numeric indices.
+ *  Accesses may translate to identifier operations which may have
+ *  arbitrary side effects (potentially invalidating any duk_tval
+ *  pointers).
+ */
+
+/* Lookup 'key' from arguments internal 'map', perform a variable lookup
+ * if mapped, and leave the result on top of stack (and return non-zero).
+ * Used in E5 Section 10.6 algorithms [[Get]] and [[GetOwnProperty]].
+ */
+static duk_bool_t duk__lookup_arguments_map(duk_hthread *thr,
+                                            duk_hobject *obj,
+                                            duk_hstring *key,
+                                            duk_propdesc *temp_desc,
+                                            duk_hobject **out_map,
+                                            duk_hobject **out_varenv) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_hobject *map;
+	duk_hobject *varenv;
+	duk_bool_t rc;
+
+	DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
+
+	DUK_DDD(DUK_DDDPRINT("arguments map lookup: thr=%p, obj=%p, key=%p, temp_desc=%p "
+	                     "(obj -> %!O, key -> %!O)",
+	                     (void *) thr, (void *) obj, (void *) key, (void *) temp_desc,
+	                     (duk_heaphdr *) obj, (duk_heaphdr *) key));
+
+	if (!duk__get_own_property_desc(thr, obj, DUK_HTHREAD_STRING_INT_MAP(thr), temp_desc, 1)) {  /* push_value = 1 */
+		DUK_DDD(DUK_DDDPRINT("-> no 'map'"));
+		return 0;
+	}
+
+	map = duk_require_hobject(ctx, -1);
+	DUK_ASSERT(map != NULL);
+	duk_pop(ctx);  /* map is reachable through obj */
+	
+	if (!duk__get_own_property_desc(thr, map, key, temp_desc, 1)) {  /* push_value = 1 */
+		DUK_DDD(DUK_DDDPRINT("-> 'map' exists, but key not in map"));
+		return 0;
+	}
+
+	/* [... varname] */
+	DUK_DDD(DUK_DDDPRINT("-> 'map' exists, and contains key, key is mapped to argument/variable binding %!T",
+	                     (duk_tval *) duk_get_tval(ctx, -1)));
+	DUK_ASSERT(duk_is_string(ctx, -1));  /* guaranteed when building arguments */
+
+	/* get varenv for varname (callee's declarative lexical environment) */
+	rc = duk__get_own_property_desc(thr, obj, DUK_HTHREAD_STRING_INT_VARENV(thr), temp_desc, 1);  /* push_value = 1 */
+	DUK_UNREF(rc);
+	DUK_ASSERT(rc != 0);  /* arguments MUST have an initialized lexical environment reference */
+	varenv = duk_require_hobject(ctx, -1);
+	DUK_ASSERT(varenv != NULL);
+	duk_pop(ctx);  /* varenv remains reachable through 'obj' */
+
+	DUK_DDD(DUK_DDDPRINT("arguments varenv is: %!dO", (duk_heaphdr *) varenv));
+
+	/* success: leave varname in stack */
+	*out_map = map;
+	*out_varenv = varenv;
+	return 1;  /* [... varname] */
+}
+
+/* Lookup 'key' from arguments internal 'map', and leave replacement value
+ * on stack top if mapped (and return non-zero).
+ * Used in E5 Section 10.6 algorithm for [[GetOwnProperty]] (used by [[Get]]).
+ */
+static duk_bool_t duk__check_arguments_map_for_get(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *temp_desc) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_hobject *map;
+	duk_hobject *varenv;
+	duk_hstring *varname;
+
+	DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
+
+	if (!duk__lookup_arguments_map(thr, obj, key, temp_desc, &map, &varenv)) {
+		DUK_DDD(DUK_DDDPRINT("arguments: key not mapped, no exotic get behavior"));
+		return 0;
+	}
+
+	/* [... varname] */
+
+	varname = duk_require_hstring(ctx, -1);
+	DUK_ASSERT(varname != NULL);
+	duk_pop(ctx);  /* varname is still reachable */
+
+	DUK_DDD(DUK_DDDPRINT("arguments object automatic getvar for a bound variable; "
+	                     "key=%!O, varname=%!O",
+	                     (duk_heaphdr *) key,
+	                     (duk_heaphdr *) varname));
+
+	(void) duk_js_getvar_envrec(thr, varenv, varname, 1 /*throw*/);
+
+	/* [... value this_binding] */
+
+	duk_pop(ctx);
+
+	/* leave result on stack top */
+	return 1;
+}
+
+/* Lookup 'key' from arguments internal 'map', perform a variable write if mapped.
+ * Used in E5 Section 10.6 algorithm for [[DefineOwnProperty]] (used by [[Put]]).
+ * Assumes stack top contains 'put' value (which is NOT popped).
+ */
+static void duk__check_arguments_map_for_put(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *temp_desc, duk_bool_t throw_flag) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_hobject *map;
+	duk_hobject *varenv;
+	duk_hstring *varname;
+
+	DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
+
+	if (!duk__lookup_arguments_map(thr, obj, key, temp_desc, &map, &varenv)) {
+		DUK_DDD(DUK_DDDPRINT("arguments: key not mapped, no exotic put behavior"));
+		return;
+	}
+
+	/* [... put_value varname] */
+
+	varname = duk_require_hstring(ctx, -1);
+	DUK_ASSERT(varname != NULL);
+	duk_pop(ctx);  /* varname is still reachable */
+
+	DUK_DDD(DUK_DDDPRINT("arguments object automatic putvar for a bound variable; "
+	                     "key=%!O, varname=%!O, value=%!T",
+	                     (duk_heaphdr *) key,
+	                     (duk_heaphdr *) varname,
+	                     (duk_tval *) duk_require_tval(ctx, -1)));
+
+	/* [... put_value] */
+
+	/*
+	 *  Note: although arguments object variable mappings are only established
+	 *  for non-strict functions (and a call to a non-strict function created
+	 *  the arguments object in question), an inner strict function may be doing
+	 *  the actual property write.  Hence the throw_flag applied here comes from
+	 *  the property write call.
+	 */
+
+	duk_js_putvar_envrec(thr, varenv, varname, duk_require_tval(ctx, -1), throw_flag);
+
+	/* [... put_value] */
+}
+
+/* Lookup 'key' from arguments internal 'map', delete mapping if found.
+ * Used in E5 Section 10.6 algorithm for [[Delete]].  Note that the
+ * variable/argument itself (where the map points) is not deleted.
+ */
+static void duk__check_arguments_map_for_delete(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *temp_desc) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_hobject *map;
+
+	DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
+
+	if (!duk__get_own_property_desc(thr, obj, DUK_HTHREAD_STRING_INT_MAP(thr), temp_desc, 1)) {  /* push_value = 1 */
+		DUK_DDD(DUK_DDDPRINT("arguments: key not mapped, no exotic delete behavior"));
+		return;
+	}
+
+	map = duk_require_hobject(ctx, -1);
+	DUK_ASSERT(map != NULL);
+	duk_pop(ctx);  /* map is reachable through obj */
+	
+	DUK_DDD(DUK_DDDPRINT("-> have 'map', delete key %!O from map (if exists)); ignore result",
+	                     (duk_heaphdr *) key));
+
+	/* Note: no recursion issue, we can trust 'map' to behave */
+	DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_BEHAVIOR(map));
+	DUK_DDD(DUK_DDDPRINT("map before deletion: %!O", (duk_heaphdr *) map));
+	(void) duk_hobject_delprop_raw(thr, map, key, 0);  /* ignore result */
+	DUK_DDD(DUK_DDDPRINT("map after deletion: %!O", (duk_heaphdr *) map));
+}
+
+/*
+ *  Ecmascript compliant [[GetOwnProperty]](P), for internal use only.
+ *
+ *  If property is found:
+ *    - Fills descriptor fields to 'out_desc'
+ *    - If 'push_value' is non-zero, pushes a value related to the
+ *      property onto the stack ('undefined' for accessor properties).
+ *    - Returns non-zero
+ *
+ *  If property is not found:
+ *    - 'out_desc' is left in untouched state (possibly garbage)
+ *    - Nothing is pushed onto the stack (not even with push_value set)
+ *    - Returns zero
+ *
+ *  Notes:
+ *
+ *    - Getting a property descriptor may cause an allocation (and hence
+ *      GC) to take place, hence reachability and refcount of all related
+ *      values matter.  Reallocation of value stack, properties, etc may
+ *      invalidate many duk_tval pointers (concretely, those which reside
+ *      in memory areas subject to reallocation).  However, heap object
+ *      pointers are never affected (heap objects have stable pointers).
+ *
+ *    - The value of a plain property is always reachable and has a non-zero
+ *      reference count.
+ *
+ *    - The value of a virtual property is not necessarily reachable from
+ *      elsewhere and may have a refcount of zero.  Hence we push it onto
+ *      the valstack for the caller, which ensures it remains reachable
+ *      while it is needed.
+ *
+ *    - There are no virtual accessor properties.  Hence, all getters and
+ *      setters are always related to concretely stored properties, which
+ *      ensures that the get/set functions in the resulting descriptor are
+ *      reachable and have non-zero refcounts.  Should there be virtual
+ *      accessor properties later, this would need to change.
+ */
+
+static duk_bool_t duk__get_own_property_desc_raw(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_uint32_t arr_idx, duk_propdesc *out_desc, duk_bool_t push_value) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_tval *tv;
+
+	DUK_DDD(DUK_DDDPRINT("duk__get_own_property_desc: thr=%p, obj=%p, key=%p, out_desc=%p, push_value=%ld, "
+	                     "arr_idx=%ld (obj -> %!O, key -> %!O)",
+	                     (void *) thr, (void *) obj, (void *) key, (void *) out_desc,
+	                     (long) push_value, (long) arr_idx,
+	                     (duk_heaphdr *) obj, (duk_heaphdr *) key));
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->heap != NULL);
+	DUK_ASSERT(obj != NULL);
+	DUK_ASSERT(key != NULL);
+	DUK_ASSERT(out_desc != NULL);
+	DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
+
+	/* XXX: optimize this filling behavior later */
+	out_desc->flags = 0;
+	out_desc->get = NULL;
+	out_desc->set = NULL;
+	out_desc->e_idx = -1;
+	out_desc->h_idx = -1;
+	out_desc->a_idx = -1;
+
+	/*
+	 *  Array part
+	 */
+
+	if (DUK_HOBJECT_HAS_ARRAY_PART(obj) && arr_idx != DUK__NO_ARRAY_INDEX) {
+		if (arr_idx < obj->a_size) {
+			tv = DUK_HOBJECT_A_GET_VALUE_PTR(obj, arr_idx);
+			if (!DUK_TVAL_IS_UNDEFINED_UNUSED(tv)) {
+				DUK_DDD(DUK_DDDPRINT("-> found in array part"));
+				if (push_value) {
+					duk_push_tval(ctx, tv);
+				}
+				/* implicit attributes */
+				out_desc->flags = DUK_PROPDESC_FLAG_WRITABLE |
+				                  DUK_PROPDESC_FLAG_CONFIGURABLE |
+				                  DUK_PROPDESC_FLAG_ENUMERABLE;
+				out_desc->a_idx = arr_idx;
+				goto prop_found;
+			}
+		}
+		/* assume array part is comprehensive (contains all array indexed elements
+		 * or none of them); hence no need to check the entries part here.
+		 */
+		DUK_DDD(DUK_DDDPRINT("-> not found as a concrete property (has array part, "
+		                     "should be there if present)"));
+		goto prop_not_found_concrete;
+	}
+
+	/*
+	 *  Entries part
+	 */
+
+	duk_hobject_find_existing_entry(obj, key, &out_desc->e_idx, &out_desc->h_idx);
+	if (out_desc->e_idx >= 0) {
+		duk_int_t e_idx = out_desc->e_idx;
+		out_desc->flags = DUK_HOBJECT_E_GET_FLAGS(obj, e_idx);
+		if (out_desc->flags & DUK_PROPDESC_FLAG_ACCESSOR) {
+			DUK_DDD(DUK_DDDPRINT("-> found accessor property in entry part"));
+			out_desc->get = DUK_HOBJECT_E_GET_VALUE_GETTER(obj, e_idx);
+			out_desc->set = DUK_HOBJECT_E_GET_VALUE_SETTER(obj, e_idx);
+			if (push_value) {
+				/* a dummy undefined value is pushed to make valstack
+				 * behavior uniform for caller
+				 */
+				duk_push_undefined(ctx);
+			}
+		} else {
+			DUK_DDD(DUK_DDDPRINT("-> found plain property in entry part"));
+			tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, e_idx);
+			if (push_value) {
+				duk_push_tval(ctx, tv);
+			}
+		}
+		goto prop_found;
+	}
+
+	/*
+	 *  Not found as a concrete property, check whether a String object
+	 *  virtual property matches.
+	 */
+
+ prop_not_found_concrete:
+
+	if (DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(obj)) {
+		DUK_DDD(DUK_DDDPRINT("string object exotic property get for key: %!O, arr_idx: %ld",
+		                     (duk_heaphdr *) key, (long) arr_idx));
+
+		if (arr_idx != DUK__NO_ARRAY_INDEX) {
+			duk_hstring *h_val;
+
+			DUK_DDD(DUK_DDDPRINT("array index exists"));
+
+ 			h_val = duk_hobject_get_internal_value_string(thr->heap, obj);
+			DUK_ASSERT(h_val);
+			if (arr_idx < DUK_HSTRING_GET_CHARLEN(h_val)) {
+				DUK_DDD(DUK_DDDPRINT("-> found, array index inside string"));
+				if (push_value) {
+					duk_push_hstring(ctx, h_val);
+					duk_substring(ctx, -1, arr_idx, arr_idx + 1);  /* [str] -> [substr] */
+				}
+				out_desc->flags = DUK_PROPDESC_FLAG_ENUMERABLE |  /* E5 Section 15.5.5.2 */
+				                  DUK_PROPDESC_FLAG_VIRTUAL;
+
+				DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj));
+				return 1;  /* cannot be e.g. arguments exotic, since exotic 'traits' are mutually exclusive */
+			} else {
+				/* index is above internal string length -> property is fully normal */
+				DUK_DDD(DUK_DDDPRINT("array index outside string -> normal property"));
+			}
+		} else if (key == DUK_HTHREAD_STRING_LENGTH(thr)) {
+			duk_hstring *h_val;
+
+			DUK_DDD(DUK_DDDPRINT("-> found, key is 'length', length exotic behavior"));
+
+ 			h_val = duk_hobject_get_internal_value_string(thr->heap, obj);
+			DUK_ASSERT(h_val != NULL);
+			if (push_value) {
+				duk_push_uint(ctx, (duk_uint_t) DUK_HSTRING_GET_CHARLEN(h_val));
+			}
+			out_desc->flags = DUK_PROPDESC_FLAG_VIRTUAL;  /* E5 Section 15.5.5.1 */
+
+			DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj));
+			return 1;  /* cannot be arguments exotic */
+		}
+	} else if (DUK_HOBJECT_HAS_EXOTIC_BUFFEROBJ(obj)) {
+		DUK_DDD(DUK_DDDPRINT("buffer object exotic property get for key: %!O, arr_idx: %ld",
+		                     (duk_heaphdr *) key, (long) arr_idx));
+
+		if (arr_idx != DUK__NO_ARRAY_INDEX) {
+			duk_hbuffer *h_val;
+
+			DUK_DDD(DUK_DDDPRINT("array index exists"));
+
+			h_val = duk_hobject_get_internal_value_buffer(thr->heap, obj);
+			DUK_ASSERT(h_val);
+			/* SCANBUILD: h_val is known to be non-NULL but scan-build cannot
+			 * know it, so it produces NULL pointer dereference warnings for
+			 * 'h_val'.
+			 */
+
+			if (arr_idx < DUK_HBUFFER_GET_SIZE(h_val)) {
+				DUK_DDD(DUK_DDDPRINT("-> found, array index inside buffer"));
+				if (push_value) {
+					duk_push_int(ctx, ((duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(h_val))[arr_idx]);
+				}
+				out_desc->flags = DUK_PROPDESC_FLAG_WRITABLE |
+				                  DUK_PROPDESC_FLAG_ENUMERABLE |
+				                  DUK_PROPDESC_FLAG_VIRTUAL;
+
+				DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj));
+				return 1;  /* cannot be e.g. arguments exotic, since exotic 'traits' are mutually exclusive */
+			} else {
+				/* index is above internal buffer length -> property is fully normal */
+				DUK_DDD(DUK_DDDPRINT("array index outside buffer -> normal property"));
+			}
+		} else if (key == DUK_HTHREAD_STRING_LENGTH(thr)) {
+			duk_hbuffer *h_val;
+
+			DUK_DDD(DUK_DDDPRINT("-> found, key is 'length', length exotic behavior"));
+
+			/* XXX: buffer length should be writable and have exotic behavior
+			 * like arrays.  For now, make it read-only and use explicit methods
+			 * to operate on buffer length.
+			 */
+
+			h_val = duk_hobject_get_internal_value_buffer(thr->heap, obj);
+			DUK_ASSERT(h_val != NULL);
+			if (push_value) {
+				duk_push_uint(ctx, (duk_uint_t) DUK_HBUFFER_GET_SIZE(h_val));
+			}
+			out_desc->flags = DUK_PROPDESC_FLAG_VIRTUAL;
+
+			DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj));
+			return 1;  /* cannot be arguments exotic */
+		}
+	} else if (DUK_HOBJECT_HAS_EXOTIC_DUKFUNC(obj)) {
+		DUK_DDD(DUK_DDDPRINT("duktape/c object exotic property get for key: %!O, arr_idx: %ld",
+		                     (duk_heaphdr *) key, (long) arr_idx));
+
+		if (key == DUK_HTHREAD_STRING_LENGTH(thr)) {
+			DUK_DDD(DUK_DDDPRINT("-> found, key is 'length', length exotic behavior"));
+
+			if (push_value) {
+				duk_int16_t func_nargs = ((duk_hnativefunction *) obj)->nargs;
+				duk_push_int(ctx, func_nargs == DUK_HNATIVEFUNCTION_NARGS_VARARGS ? 0 : func_nargs);
+			}
+			out_desc->flags = DUK_PROPDESC_FLAG_VIRTUAL;  /* not enumerable */
+
+			DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj));
+			return 1;  /* cannot be arguments exotic */
+		}
+	}
+
+	/* Array properties have exotic behavior but they are concrete,
+	 * so no special handling here.
+	 *
+	 * Arguments exotic behavior (E5 Section 10.6, [[GetOwnProperty]]
+	 * is only relevant as a post-check implemented below; hence no
+	 * check here.
+	 */
+
+	/*
+	 *  Not found as concrete or virtual
+	 */
+
+	DUK_DDD(DUK_DDDPRINT("-> not found (virtual, entry part, or array part)"));
+	return 0;
+
+	/*
+	 *  Found
+	 *
+	 *  Arguments object has exotic post-processing, see E5 Section 10.6,
+	 *  description of [[GetOwnProperty]] variant for arguments.
+	 */
+
+ prop_found:
+	DUK_DDD(DUK_DDDPRINT("-> property found, checking for arguments exotic post-behavior"));
+
+	/* Notes:
+	 *  - only numbered indices are relevant, so arr_idx fast reject is good
+	 *    (this is valid unless there are more than 4**32-1 arguments).
+	 *  - since variable lookup has no side effects, this can be skipped if
+	 *    push_value == 0.
+	 */
+
+	if (DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj) &&
+	    arr_idx != DUK__NO_ARRAY_INDEX &&
+	    push_value) {
+		duk_propdesc temp_desc;
+
+		/* Magically bound variable cannot be an accessor.  However,
+		 * there may be an accessor property (or a plain property) in
+		 * place with magic behavior removed.  This happens e.g. when
+		 * a magic property is redefined with defineProperty().
+		 * Cannot assert for "not accessor" here.
+		 */
+
+		/* replaces top of stack with new value if necessary */
+		DUK_ASSERT(push_value != 0);
+
+		if (duk__check_arguments_map_for_get(thr, obj, key, &temp_desc)) {
+			DUK_DDD(DUK_DDDPRINT("-> arguments exotic behavior overrides result: %!T -> %!T",
+			                     (duk_tval *) duk_get_tval(ctx, -2),
+			                     (duk_tval *) duk_get_tval(ctx, -1)));
+			/* [... old_result result] -> [... result] */
+			duk_remove(ctx, -2);
+		}
+	}
+
+	return 1;
+}
+
+static duk_bool_t duk__get_own_property_desc(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *out_desc, duk_bool_t push_value) {
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(obj != NULL);
+	DUK_ASSERT(key != NULL);
+	DUK_ASSERT(out_desc != NULL);
+	DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
+
+	return duk__get_own_property_desc_raw(thr, obj, key, DUK_HSTRING_GET_ARRIDX_SLOW(key), out_desc, push_value);
+}
+
+/*
+ *  Ecmascript compliant [[GetProperty]](P), for internal use only.
+ *
+ *  If property is found:
+ *    - Fills descriptor fields to 'out_desc'
+ *    - If 'push_value' is non-zero, pushes a value related to the
+ *      property onto the stack ('undefined' for accessor properties).
+ *    - Returns non-zero
+ *
+ *  If property is not found:
+ *    - 'out_desc' is left in untouched state (possibly garbage)
+ *    - Nothing is pushed onto the stack (not even with push_value set)
+ *    - Returns zero
+ *
+ *  May cause arbitrary side effects and invalidate (most) duk_tval
+ *  pointers.
+ */
+
+static duk_bool_t duk__get_property_desc(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *out_desc, duk_bool_t push_value) {
+	duk_hobject *curr;
+	duk_uint32_t arr_idx;
+	duk_uint_t sanity;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->heap != NULL);
+	DUK_ASSERT(obj != NULL);
+	DUK_ASSERT(key != NULL);
+	DUK_ASSERT(out_desc != NULL);
+	DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
+
+	arr_idx = DUK_HSTRING_GET_ARRIDX_FAST(key);
+
+	DUK_DDD(DUK_DDDPRINT("duk__get_property_desc: thr=%p, obj=%p, key=%p, out_desc=%p, push_value=%ld, "
+	                     "arr_idx=%ld (obj -> %!O, key -> %!O)",
+	                     (void *) thr, (void *) obj, (void *) key, (void *) out_desc,
+	                     (long) push_value, (long) arr_idx,
+	                     (duk_heaphdr *) obj, (duk_heaphdr *) key));
+
+	curr = obj;
+	DUK_ASSERT(curr != NULL);
+	sanity = DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY;
+	do {
+		if (duk__get_own_property_desc_raw(thr, curr, key, arr_idx, out_desc, push_value)) {
+			/* stack contains value, 'out_desc' is set */
+			return 1;
+		}
+
+		/* not found in 'curr', next in prototype chain; impose max depth */
+		if (sanity-- == 0) {
+			DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, DUK_STR_PROTOTYPE_CHAIN_LIMIT);
+		}
+		curr = curr->prototype;
+	} while (curr);
+
+	/* out_desc is left untouched (possibly garbage), caller must use return
+	 * value to determine whether out_desc can be looked up
+	 */
+
+	return 0;
+}
+
+/*
+ *  Shallow fast path checks for accessing array elements with numeric
+ *  indices.  The goal is to try to avoid coercing an array index to an
+ *  (interned) string for the most common lookups, in particular, for
+ *  standard Array objects.
+ *
+ *  Interning is avoided but only for a very narrow set of cases:
+ *    - Object has array part, index is within array allocation, and
+ *      value is not unused (= key exists)
+ *    - Object has no interfering exotic behavior (e.g. arguments or
+ *      string object exotic behaviors interfere, array exotic
+ *      behavior does not).
+ *
+ *  Current shortcoming: if key does not exist (even if it is within
+ *  the array allocation range) a slow path lookup with interning is
+ *  always required.  This can probably be fixed so that there is a
+ *  quick fast path for non-existent elements as well, at least for
+ *  standard Array objects.
+ */
+
+#if 0  /* XXX: unused now */
+static duk_tval *duk__shallow_fast_path_array_check_u32(duk_hobject *obj, duk_uint32_t key_idx) {
+	duk_tval *tv;
+
+	if ((!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj)) &&
+	    (!DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(obj)) &&
+	    (!DUK_HOBJECT_HAS_EXOTIC_BUFFEROBJ(obj)) &&
+	    (!DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(obj)) &&
+	    (DUK_HOBJECT_HAS_ARRAY_PART(obj)) &&
+	    (key_idx < obj->a_size)) {
+		/* technically required to check, but obj->a_size check covers this */
+		DUK_ASSERT(key_idx != 0xffffffffUL);
+
+		DUK_DDD(DUK_DDDPRINT("fast path attempt (key is an array index, no exotic "
+		                     "string/arguments/buffer behavior, object has array part, key "
+		                     "inside array size)"));
+
+		DUK_ASSERT(obj->a_size > 0);  /* true even for key_idx == 0 */
+		tv = DUK_HOBJECT_A_GET_VALUE_PTR(obj, key_idx);
+		if (!DUK_TVAL_IS_UNDEFINED_UNUSED(tv)) {
+			DUK_DDD(DUK_DDDPRINT("-> fast path successful"));
+			return tv;
+		}
+
+		/*
+		 *  Not found, fall back to slow path.
+		 *
+		 *  Note: this approach has the unfortunate side effect that accesses
+		 *  to undefined entries (or entries outside valid array range) cause
+		 *  a string intern operation.
+		 */
+
+		DUK_DDD(DUK_DDDPRINT("fast path attempt failed, fall back to slow path"));
+	}
+
+	return NULL;
+}
+#endif
+
+static duk_tval *duk__shallow_fast_path_array_check_tval(duk_hobject *obj, duk_tval *key_tv) {
+	duk_tval *tv;
+
+	if (DUK_TVAL_IS_NUMBER(key_tv) &&
+	    (!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj)) &&
+	    (!DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(obj)) &&
+	    (!DUK_HOBJECT_HAS_EXOTIC_BUFFEROBJ(obj)) &&
+	    (!DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(obj)) &&
+	    (DUK_HOBJECT_HAS_ARRAY_PART(obj))) {
+		duk_uint32_t idx;
+
+		DUK_DDD(DUK_DDDPRINT("fast path attempt (key is a number, no exotic string/arguments/buffer "
+		                     "behavior, object has array part)"));
+
+		idx = duk__tval_number_to_arr_idx(key_tv);
+
+		if (idx != DUK__NO_ARRAY_INDEX) {
+			/* Note: idx is not necessarily a valid array index (0xffffffffUL is not valid) */
+			DUK_ASSERT_DISABLE(idx >= 0);  /* disabled because idx is duk_uint32_t so always true */
+			DUK_ASSERT_DISABLE(idx <= 0xffffffffUL);  /* same */
+
+			if (idx < obj->a_size) {
+				/* technically required to check, but obj->a_size check covers this */
+				DUK_ASSERT(idx != 0xffffffffUL);
+
+				DUK_DDD(DUK_DDDPRINT("key is a valid array index and inside array part"));
+				tv = DUK_HOBJECT_A_GET_VALUE_PTR(obj, idx);
+				if (!DUK_TVAL_IS_UNDEFINED_UNUSED(tv)) {
+					DUK_DDD(DUK_DDDPRINT("-> fast path successful"));
+					return tv;
+				}
+			} else {
+				DUK_DDD(DUK_DDDPRINT("key is outside array part"));
+			}
+		} else {
+			DUK_DDD(DUK_DDDPRINT("key is not a valid array index"));
+		}
+
+		/*
+		 *  Not found in array part, use slow path.
+		 */
+
+		DUK_DDD(DUK_DDDPRINT("fast path attempt failed, fall back to slow path"));
+	}
+
+	return NULL;
+}
+
+/*
+ *  GETPROP: Ecmascript property read.
+ */
+
+duk_bool_t duk_hobject_getprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_tval tv_obj_copy;
+	duk_tval tv_key_copy;
+	duk_hobject *curr = NULL;
+	duk_hstring *key = NULL;
+	duk_uint32_t arr_idx = DUK__NO_ARRAY_INDEX;
+	duk_propdesc desc;
+	duk_uint_t sanity;
+
+	DUK_DDD(DUK_DDDPRINT("getprop: thr=%p, obj=%p, key=%p (obj -> %!T, key -> %!T)",
+	                     (void *) thr, (void *) tv_obj, (void *) tv_key,
+	                     (duk_tval *) tv_obj, (duk_tval *) tv_key));
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->heap != NULL);
+	DUK_ASSERT(tv_obj != NULL);
+	DUK_ASSERT(tv_key != NULL);
+
+	DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
+
+	/*
+	 *  Make a copy of tv_obj, tv_key, and tv_val to avoid any issues of
+	 *  them being invalidated by a valstack resize.
+	 *
+	 *  XXX: this is now an overkill for many fast paths.  Rework this
+	 *  to be faster (although switching to a valstack discipline might
+	 *  be a better solution overall).
+	 */
+
+	DUK_TVAL_SET_TVAL(&tv_obj_copy, tv_obj);
+	DUK_TVAL_SET_TVAL(&tv_key_copy, tv_key);
+	tv_obj = &tv_obj_copy;
+	tv_key = &tv_key_copy;
+
+	/*
+	 *  Coercion and fast path processing
+	 */
+
+	switch (DUK_TVAL_GET_TAG(tv_obj)) {
+	case DUK_TAG_UNDEFINED:
+	case DUK_TAG_NULL: {
+		/* Note: unconditional throw */
+		DUK_DDD(DUK_DDDPRINT("base object is undefined or null -> reject"));
+		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_INVALID_BASE);
+		return 0;
+	}
+
+	case DUK_TAG_BOOLEAN: {
+		DUK_DDD(DUK_DDDPRINT("base object is a boolean, start lookup from boolean prototype"));
+		curr = thr->builtins[DUK_BIDX_BOOLEAN_PROTOTYPE];
+		break;
+	}
+
+	case DUK_TAG_STRING: {
+		duk_hstring *h = DUK_TVAL_GET_STRING(tv_obj);
+		duk_int_t pop_count;
+
+		if (DUK_TVAL_IS_NUMBER(tv_key)) {
+			arr_idx = duk__tval_number_to_arr_idx(tv_key);
+			DUK_DDD(DUK_DDDPRINT("base object string, key is a fast-path number; arr_idx %ld", (long) arr_idx));
+			pop_count = 0;
+		} else {
+			arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
+			DUK_ASSERT(key != NULL);
+			DUK_DDD(DUK_DDDPRINT("base object string, key is a non-fast-path number; after "
+			                     "coercion key is %!T, arr_idx %ld",
+			                     (duk_tval *) duk_get_tval(ctx, -1), (long) arr_idx));
+			pop_count = 1;
+		}
+
+		if (arr_idx != DUK__NO_ARRAY_INDEX &&
+		    arr_idx < DUK_HSTRING_GET_CHARLEN(h)) {
+			duk_pop_n(ctx, pop_count);
+			duk_push_hstring(ctx, h);
+			duk_substring(ctx, -1, arr_idx, arr_idx + 1);  /* [str] -> [substr] */
+
+			DUK_DDD(DUK_DDDPRINT("-> %!T (base is string, key is an index inside string length "
+			                     "after coercion -> return char)",
+			                     (duk_tval *) duk_get_tval(ctx, -1)));
+			return 1;
+		}
+
+		if (pop_count == 0) {
+			/* This is a pretty awkward control flow, but we need to recheck the
+			 * key coercion here.
+			 */
+			arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
+			DUK_ASSERT(key != NULL);
+			DUK_DDD(DUK_DDDPRINT("base object string, key is a non-fast-path number; after "
+			                     "coercion key is %!T, arr_idx %ld",
+			                     (duk_tval *) duk_get_tval(ctx, -1), (long) arr_idx));
+		}
+
+		if (key == DUK_HTHREAD_STRING_LENGTH(thr)) {
+			duk_pop(ctx);  /* [key] -> [] */
+			duk_push_uint(ctx, (duk_uint_t) DUK_HSTRING_GET_CHARLEN(h));  /* [] -> [res] */
+
+			DUK_DDD(DUK_DDDPRINT("-> %!T (base is string, key is 'length' after coercion -> "
+			                     "return string length)",
+			                     (duk_tval *) duk_get_tval(ctx, -1)));
+			return 1;
+		}
+		DUK_DDD(DUK_DDDPRINT("base object is a string, start lookup from string prototype"));
+		curr = thr->builtins[DUK_BIDX_STRING_PROTOTYPE];
+		goto lookup;  /* avoid double coercion */
+	}
+
+	case DUK_TAG_OBJECT: {
+		duk_tval *tmp;
+
+		curr = DUK_TVAL_GET_OBJECT(tv_obj);
+		DUK_ASSERT(curr != NULL);
+
+#if defined(DUK_USE_ES6_PROXY)
+		if (DUK_UNLIKELY(DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(curr))) {
+			duk_hobject *h_target;
+
+			if (duk__proxy_check_prop(thr, curr, DUK_STRIDX_GET, tv_key, &h_target)) {
+				/* -> [ ... trap handler ] */
+				DUK_DDD(DUK_DDDPRINT("-> proxy object 'get' for key %!T", (duk_tval *) tv_key));
+				duk_push_hobject(ctx, h_target);  /* target */
+				duk_push_tval(ctx, tv_key);       /* P */
+				duk_push_tval(ctx, tv_obj);       /* Receiver: Proxy object */
+				duk_call_method(ctx, 3 /*nargs*/);
+
+				/* Target object must be checked for a conflicting
+				 * non-configurable property.
+				 */
+				arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
+				DUK_ASSERT(key != NULL);
+
+				if (duk__get_own_property_desc_raw(thr, h_target, key, arr_idx, &desc, 1 /*push_value*/)) {
+					duk_tval *tv_hook = duk_require_tval(ctx, -3);  /* value from hook */
+					duk_tval *tv_targ = duk_require_tval(ctx, -1);  /* value from target */
+					duk_bool_t datadesc_reject;
+					duk_bool_t accdesc_reject;
+
+					DUK_DDD(DUK_DDDPRINT("proxy 'get': target has matching property %!O, check for "
+					                     "conflicting property; tv_hook=%!T, tv_targ=%!T, desc.flags=0x%08lx, "
+					                     "desc.get=%p, desc.set=%p",
+					                     (duk_heaphdr *) key, (duk_tval *) tv_hook, (duk_tval *) tv_targ,
+					                     (unsigned long) desc.flags,
+					                     (void *) desc.get, (void *) desc.set));
+
+					datadesc_reject = !(desc.flags & DUK_PROPDESC_FLAG_ACCESSOR) &&
+					                  !(desc.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) &&
+					                  !(desc.flags & DUK_PROPDESC_FLAG_WRITABLE) &&
+					                  !duk_js_samevalue(tv_hook, tv_targ);
+					accdesc_reject = (desc.flags & DUK_PROPDESC_FLAG_ACCESSOR) &&
+					                 !(desc.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) &&
+					                 (desc.get == NULL) &&
+					                 !DUK_TVAL_IS_UNDEFINED(tv_hook);
+					if (datadesc_reject || accdesc_reject) {
+						DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_PROXY_REJECTED);
+					}
+
+					duk_pop_2(ctx);
+				} else {
+					duk_pop(ctx);
+				}
+				return 1;  /* return value */
+			}
+
+			curr = h_target;  /* resume lookup from target */
+			DUK_TVAL_SET_OBJECT(tv_obj, curr);
+		}
+#endif  /* DUK_USE_ES6_PROXY */
+
+		tmp = duk__shallow_fast_path_array_check_tval(curr, tv_key);
+		if (tmp) {
+			duk_push_tval(ctx, tmp);
+
+			DUK_DDD(DUK_DDDPRINT("-> %!T (base is object, key is a number, array part "
+			                     "fast path)",
+			                     (duk_tval *) duk_get_tval(ctx, -1)));
+			return 1;
+		}
+
+		if (DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(curr)) {
+			arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
+			DUK_ASSERT(key != NULL);
+
+			if (duk__check_arguments_map_for_get(thr, curr, key, &desc)) {
+				DUK_DDD(DUK_DDDPRINT("-> %!T (base is object with arguments exotic behavior, "
+				                     "key matches magically bound property -> skip standard "
+				                     "Get with replacement value)",
+				                     (duk_tval *) duk_get_tval(ctx, -1)));
+
+				/* no need for 'caller' post-check, because 'key' must be an array index */
+
+				duk_remove(ctx, -2);  /* [key result] -> [result] */
+				return 1;
+			}
+
+			goto lookup;  /* avoid double coercion */
+		}
+		break;
+	}
+
+	/* Buffer has virtual properties similar to string, but indexed values
+	 * are numbers, not 1-byte buffers/strings which would perform badly.
+	 */
+	case DUK_TAG_BUFFER: {
+		duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv_obj);
+		duk_int_t pop_count;
+
+		/*
+		 *  Because buffer values are often looped over, a number fast path
+		 *  is important.
+		 */
+
+		if (DUK_TVAL_IS_NUMBER(tv_key)) {
+			arr_idx = duk__tval_number_to_arr_idx(tv_key);
+			DUK_DDD(DUK_DDDPRINT("base object buffer, key is a fast-path number; arr_idx %ld", (long) arr_idx));
+			pop_count = 0;
+		} else {
+			arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
+			DUK_ASSERT(key != NULL);
+			DUK_DDD(DUK_DDDPRINT("base object buffer, key is a non-fast-path number; after "
+			                     "coercion key is %!T, arr_idx %ld",
+			                     (duk_tval *) duk_get_tval(ctx, -1), (long) arr_idx));
+			pop_count = 1;
+		}
+
+		if (arr_idx != DUK__NO_ARRAY_INDEX &&
+		    arr_idx < DUK_HBUFFER_GET_SIZE(h)) {
+			duk_pop_n(ctx, pop_count);
+			duk_push_int(ctx, ((duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(h))[arr_idx]);
+
+			DUK_DDD(DUK_DDDPRINT("-> %!T (base is buffer, key is an index inside buffer length "
+			                     "after coercion -> return byte as number)",
+			                     (duk_tval *) duk_get_tval(ctx, -1)));
+			return 1;
+		}
+
+		if (pop_count == 0) {
+			/* This is a pretty awkward control flow, but we need to recheck the
+			 * key coercion here.
+			 */
+			arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
+			DUK_ASSERT(key != NULL);
+			DUK_DDD(DUK_DDDPRINT("base object buffer, key is a non-fast-path number; after "
+			                     "coercion key is %!T, arr_idx %ld",
+			                     (duk_tval *) duk_get_tval(ctx, -1), (long) arr_idx));
+		}
+
+		if (key == DUK_HTHREAD_STRING_LENGTH(thr)) {
+			duk_pop(ctx);  /* [key] -> [] */
+			duk_push_uint(ctx, (duk_uint_t) DUK_HBUFFER_GET_SIZE(h));  /* [] -> [res] */
+
+			DUK_DDD(DUK_DDDPRINT("-> %!T (base is buffer, key is 'length' after coercion -> "
+			                     "return buffer length)",
+			                     (duk_tval *) duk_get_tval(ctx, -1)));
+			return 1;
+		}
+
+		DUK_DDD(DUK_DDDPRINT("base object is a buffer, start lookup from buffer prototype"));
+		curr = thr->builtins[DUK_BIDX_BUFFER_PROTOTYPE];
+		goto lookup;  /* avoid double coercion */
+	}
+
+	case DUK_TAG_POINTER: {
+		DUK_DDD(DUK_DDDPRINT("base object is a pointer, start lookup from pointer prototype"));
+		curr = thr->builtins[DUK_BIDX_POINTER_PROTOTYPE];
+		break;
+	}
+
+	default: {
+		/* number */
+		DUK_DDD(DUK_DDDPRINT("base object is a number, start lookup from number prototype"));
+		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_obj));
+		curr = thr->builtins[DUK_BIDX_NUMBER_PROTOTYPE];
+		break;
+	}
+	}
+
+	/* key coercion (unless already coerced above) */
+	DUK_ASSERT(key == NULL);
+	arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
+	DUK_ASSERT(key != NULL);
+
+	/*
+	 *  Property lookup
+	 */
+
+ lookup:
+	/* [key] (coerced) */
+	DUK_ASSERT(curr != NULL);
+	DUK_ASSERT(key != NULL);
+
+	sanity = DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY;
+	do {
+		if (!duk__get_own_property_desc_raw(thr, curr, key, arr_idx, &desc, 1 /*push_value*/)) {
+			goto next_in_chain;
+		}
+
+		if (desc.get != NULL) {
+			/* accessor with defined getter */
+			DUK_ASSERT((desc.flags & DUK_PROPDESC_FLAG_ACCESSOR) != 0);
+
+			duk_pop(ctx);                     /* [key undefined] -> [key] */
+			duk_push_hobject(ctx, desc.get);
+			duk_push_tval(ctx, tv_obj);       /* note: original, uncoerced base */
+#ifdef DUK_USE_NONSTD_GETTER_KEY_ARGUMENT
+			duk_dup(ctx, -3);
+			duk_call_method(ctx, 1);          /* [key getter this key] -> [key retval] */
+#else
+			duk_call_method(ctx, 0);          /* [key getter this] -> [key retval] */
+#endif
+		} else {
+			/* [key value] or [key undefined] */
+
+			/* data property or accessor without getter */
+			DUK_ASSERT(((desc.flags & DUK_PROPDESC_FLAG_ACCESSOR) == 0) ||
+			           (desc.get == NULL));
+
+			/* if accessor without getter, return value is undefined */
+			DUK_ASSERT(((desc.flags & DUK_PROPDESC_FLAG_ACCESSOR) == 0) ||
+			           duk_is_undefined(ctx, -1));
+
+			/* Note: for an accessor without getter, falling through to
+			 * check for "caller" exotic behavior is unnecessary as
+			 * "undefined" will never activate the behavior.  But it does
+			 * no harm, so we'll do it anyway.
+			 */
+		}
+
+		goto found;  /* [key result] */
+
+	 next_in_chain:
+		if (sanity-- == 0) {
+			DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, DUK_STR_PROTOTYPE_CHAIN_LIMIT);
+		}
+		curr = curr->prototype;
+	} while (curr);
+
+	/*
+	 *  Not found
+	 */
+
+	duk_to_undefined(ctx, -1);  /* [key] -> [undefined] (default value) */
+
+	DUK_DDD(DUK_DDDPRINT("-> %!T (not found)", (duk_tval *) duk_get_tval(ctx, -1)));
+	return 0;
+
+	/*
+	 *  Found; post-processing (Function and arguments objects)
+	 */
+
+ found:
+	/* [key result] */
+
+#if !defined(DUK_USE_NONSTD_FUNC_CALLER_PROPERTY)
+	/* This exotic behavior is disabled when the non-standard 'caller' property
+	 * is enabled, as it conflicts with the free use of 'caller'.
+	 */
+	if (key == DUK_HTHREAD_STRING_CALLER(thr) &&
+	    DUK_TVAL_IS_OBJECT(tv_obj)) {
+		duk_hobject *orig = DUK_TVAL_GET_OBJECT(tv_obj);
+
+		if (DUK_HOBJECT_IS_NONBOUND_FUNCTION(orig) ||
+		    DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(orig)) {
+			duk_hobject *h;
+
+			/* FIXME: is this behavior desired for bound functions too?
+			 * E5.1 Section 15.3.4.5 step 6 seems to indicate so, while
+			 * E5.1 Section 15.3.5.4 "NOTE" indicates that bound functions
+			 * have a default [[Get]] method.
+			 *
+			 * Also, must the value Function object be a non-bound function?
+			 */
+
+			DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(orig));
+
+			h = duk_get_hobject(ctx, -1);  /* NULL if not an object */
+			if (h &&
+			    DUK_HOBJECT_IS_FUNCTION(h) &&
+			    DUK_HOBJECT_HAS_STRICT(h)) {
+				/* XXX: sufficient to check 'strict', assert for 'is function' */
+				DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_STRICT_CALLER_READ);
+			}
+		}
+	}
+#endif   /* !DUK_USE_NONSTD_FUNC_CALLER_PROPERTY */
+
+	duk_remove(ctx, -2);  /* [key result] -> [result] */
+
+	DUK_DDD(DUK_DDDPRINT("-> %!T (found)", (duk_tval *) duk_get_tval(ctx, -1)));
+	return 1;
+}
+
+/*
+ *  HASPROP: Ecmascript property existence check ("in" operator).
+ *
+ *  Interestingly, the 'in' operator does not do any coercion of
+ *  the target object.
+ */
+
+duk_bool_t duk_hobject_hasprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_tval tv_key_copy;
+	duk_hobject *obj;
+	duk_hstring *key;
+	duk_uint32_t arr_idx;
+	duk_bool_t rc;
+	duk_propdesc desc;
+
+	DUK_DDD(DUK_DDDPRINT("hasprop: thr=%p, obj=%p, key=%p (obj -> %!T, key -> %!T)",
+	                     (void *) thr, (void *) tv_obj, (void *) tv_key,
+	                     (duk_tval *) tv_obj, (duk_tval *) tv_key));
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->heap != NULL);
+	DUK_ASSERT(tv_obj != NULL);
+	DUK_ASSERT(tv_key != NULL);
+	DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
+
+	DUK_TVAL_SET_TVAL(&tv_key_copy, tv_key);
+	tv_key = &tv_key_copy;
+
+	if (!DUK_TVAL_IS_OBJECT(tv_obj)) {
+		/* Note: unconditional throw */
+		DUK_DDD(DUK_DDDPRINT("base object is not an object -> reject"));
+		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_INVALID_BASE);
+	}
+	obj = DUK_TVAL_GET_OBJECT(tv_obj);
+	DUK_ASSERT(obj != NULL);
+
+	/* XXX: fast path for arrays? */
+
+	arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
+	DUK_ASSERT(key != NULL);
+	DUK_UNREF(arr_idx);
+
+#if defined(DUK_USE_ES6_PROXY)
+	if (DUK_UNLIKELY(DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(obj))) {
+		duk_hobject *h_target;
+		duk_bool_t tmp_bool;
+
+		/* XXX: the key in 'key in obj' is string coerced before we're called
+		 * (which is the required behavior in E5/E5.1/E6) so the key is a string
+		 * here already.
+		 */
+
+		if (duk__proxy_check_prop(thr, obj, DUK_STRIDX_HAS, tv_key, &h_target)) {
+			/* [ ... key trap handler ] */
+			DUK_DDD(DUK_DDDPRINT("-> proxy object 'has' for key %!T", (duk_tval *) tv_key));
+			duk_push_hobject(ctx, h_target);  /* target */
+			duk_push_tval(ctx, tv_key);       /* P */
+			duk_call_method(ctx, 2 /*nargs*/);
+			tmp_bool = duk_to_boolean(ctx, -1);
+			if (!tmp_bool) {
+				/* Target object must be checked for a conflicting
+				 * non-configurable property.
+				 */
+
+				if (duk__get_own_property_desc_raw(thr, h_target, key, arr_idx, &desc, 0 /*push_value*/)) {
+					DUK_DDD(DUK_DDDPRINT("proxy 'has': target has matching property %!O, check for "
+					                     "conflicting property; desc.flags=0x%08lx, "
+					                     "desc.get=%p, desc.set=%p",
+					                     (duk_heaphdr *) key, (unsigned long) desc.flags,
+					                     (void *) desc.get, (void *) desc.set));
+					/* XXX: Extensibility check for target uses IsExtensible().  If we
+					 * implemented the isExtensible trap and didn't reject proxies as
+					 * proxy targets, it should be respected here.
+					 */
+					if (!((desc.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) &&  /* property is configurable and */
+					      DUK_HOBJECT_HAS_EXTENSIBLE(h_target))) {          /* ... target is extensible */
+						DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_PROXY_REJECTED);
+					}
+				}
+			}
+
+			duk_pop_2(ctx);  /* [ key trap_result ] -> [] */
+			return tmp_bool;
+		}
+
+		obj = h_target;  /* resume check from proxy target */
+	}
+#endif  /* DUK_USE_ES6_PROXY */
+
+	/* XXX: inline into a prototype walking loop? */
+
+	rc = duk__get_property_desc(thr, obj, key, &desc, 0);  /* push_value = 0 */
+
+	duk_pop(ctx);  /* [ key ] -> [] */
+	return rc;
+}
+
+/*
+ *  HASPROP variant used internally.
+ *
+ *  This primitive must never throw an error, callers rely on this.
+ *  Does not implement proxy behavior: if applied to a proxy object,
+ *  returns key existence on the proxy object itself.
+ */
+
+duk_bool_t duk_hobject_hasprop_raw(duk_hthread *thr, duk_hobject *obj, duk_hstring *key) {
+	duk_propdesc dummy;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->heap != NULL);
+	DUK_ASSERT(obj != NULL);
+	DUK_ASSERT(key != NULL);
+
+	DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
+
+	return duk__get_property_desc(thr, obj, key, &dummy, 0 /*push_value*/);
+}
+
+/*
+ *  Helper: handle Array object 'length' write which automatically
+ *  deletes properties, see E5 Section 15.4.5.1, step 3.  This is
+ *  quite tricky to get right.
+ *
+ *  Used by duk_hobject_putprop().
+ */
+
+static duk_uint32_t duk__get_old_array_length(duk_hthread *thr, duk_hobject *obj, duk_propdesc *temp_desc) {
+	duk_bool_t rc;
+	duk_tval *tv;
+	duk_uint32_t res;
+
+	DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
+
+	/* FIXME: this assumption is actually invalid, because e.g. Array.prototype.push()
+	 * can create an array whose length is above 2**32.
+	 */
+
+	/* Call only for objects with array exotic behavior, as we assume
+	 * that the length property always exists, and always contains a
+	 * valid number value (in unsigned 32-bit range).
+	 */
+
+	rc = duk__get_own_property_desc_raw(thr, obj, DUK_HTHREAD_STRING_LENGTH(thr), DUK__NO_ARRAY_INDEX, temp_desc, 0);
+	DUK_UNREF(rc);
+	DUK_ASSERT(rc != 0);  /* arrays MUST have a 'length' property */
+	DUK_ASSERT(temp_desc->e_idx >= 0);
+
+	tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, temp_desc->e_idx);
+	DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));  /* array 'length' is always a number, as we coerce it */
+	DUK_ASSERT(DUK_TVAL_GET_NUMBER(tv) >= 0.0);
+	DUK_ASSERT(DUK_TVAL_GET_NUMBER(tv) <= (double) 0xffffffffUL);
+	res = (duk_uint32_t) DUK_TVAL_GET_NUMBER(tv);
+	DUK_ASSERT((duk_double_t) res == DUK_TVAL_GET_NUMBER(tv));
+
+	return res;
+}
+
+static duk_uint32_t duk__to_new_array_length_checked(duk_hthread *thr) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_uint32_t res;
+
+	/* Input value should be on stack top and will be coerced and
+	 * left on stack top.
+	 */
+
+	/* FIXME: coerce in_val to new_len, check that this is correct */
+	res = ((duk_uint32_t) duk_to_number(ctx, -1)) & 0xffffffffUL;
+	if (res != duk_get_number(ctx, -1)) {
+		DUK_ERROR(thr, DUK_ERR_RANGE_ERROR, DUK_STR_INVALID_ARRAY_LENGTH);
+	}
+	return res;
+}
+
+/* Delete elements required by a smaller length, taking into account
+ * potentially non-configurable elements.  Returns non-zero if all
+ * elements could be deleted, and zero if all or some elements could
+ * not be deleted.  Also writes final "target length" to 'out_result_len'.
+ * This is the length value that should go into the 'length' property
+ * (must be set by the caller).  Never throws an error.
+ */
+static duk_bool_t duk__handle_put_array_length_smaller(duk_hthread *thr,
+                                                       duk_hobject *obj,
+                                                       duk_uint32_t old_len,
+                                                       duk_uint32_t new_len,
+                                                       duk_uint32_t *out_result_len) {
+	duk_uint32_t target_len;
+	duk_uint_fast32_t i;
+	duk_uint32_t arr_idx;
+	duk_hstring *key;
+	duk_tval *tv;
+	duk_tval tv_tmp;
+	duk_bool_t rc;
+
+	DUK_DDD(DUK_DDDPRINT("new array length smaller than old (%ld -> %ld), "
+	                     "probably need to remove elements",
+	                     (long) old_len, (long) new_len));
+
+	/*
+	 *  New length is smaller than old length, need to delete properties above
+	 *  the new length.
+	 *
+	 *  If array part exists, this is straightforward: array entries cannot
+	 *  be non-configurable so this is guaranteed to work.
+	 *
+	 *  If array part does not exist, array-indexed values are scattered
+	 *  in the entry part, and some may not be configurable (preventing length
+	 *  from becoming lower than their index + 1).  To handle the algorithm
+	 *  in E5 Section 15.4.5.1, step l correctly, we scan the entire property
+	 *  set twice.
+	 */
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(obj != NULL);
+	DUK_ASSERT(new_len < old_len);
+	DUK_ASSERT(out_result_len != NULL);
+	DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
+
+	if (DUK_HOBJECT_HAS_ARRAY_PART(obj)) {
+		/*
+		 *  All defined array-indexed properties are in the array part
+		 *  (we assume the array part is comprehensive), and all array
+		 *  entries are writable, configurable, and enumerable.  Thus,
+		 *  nothing can prevent array entries from being deleted.
+		 */
+
+		DUK_DDD(DUK_DDDPRINT("have array part, easy case"));
+
+		if (old_len < obj->a_size) {
+			/* XXX: assertion that entries >= old_len are already unused */
+			i = old_len;
+		} else {
+			i = obj->a_size;
+		}
+		DUK_ASSERT(i <= obj->a_size);
+
+		while (i > new_len) {
+			i--;
+			tv = DUK_HOBJECT_A_GET_VALUE_PTR(obj, i);
+			DUK_TVAL_SET_TVAL(&tv_tmp, tv);
+			DUK_TVAL_SET_UNDEFINED_UNUSED(tv);
+			DUK_TVAL_DECREF(thr, &tv_tmp);
+		}
+
+		*out_result_len = new_len;
+		return 1;
+	} else {
+		/*
+		 *  Entries part is a bit more complex
+		 */
+
+		/* stage 1: find highest preventing non-configurable entry (if any) */
+
+		DUK_DDD(DUK_DDDPRINT("no array part, slow case"));
+
+		DUK_DDD(DUK_DDDPRINT("array length write, no array part, stage 1: find target_len "
+		                     "(highest preventing non-configurable entry (if any))"));
+
+		target_len = new_len;
+		for (i = 0; i < obj->e_used; i++) {
+			key = DUK_HOBJECT_E_GET_KEY(obj, i);
+			if (!key) {
+				DUK_DDD(DUK_DDDPRINT("skip entry index %ld: null key", (long) i));
+				continue;
+			}
+			if (!DUK_HSTRING_HAS_ARRIDX(key)) {
+				DUK_DDD(DUK_DDDPRINT("skip entry index %ld: key not an array index", (long) i));
+				continue;
+			}
+
+			DUK_ASSERT(DUK_HSTRING_HAS_ARRIDX(key));  /* XXX: macro checks for array index flag, which is unnecessary here */
+			arr_idx = DUK_HSTRING_GET_ARRIDX_SLOW(key);
+			DUK_ASSERT(arr_idx != DUK__NO_ARRAY_INDEX);
+			DUK_ASSERT(arr_idx < old_len);  /* consistency requires this */
+
+			if (arr_idx < new_len) {
+				DUK_DDD(DUK_DDDPRINT("skip entry index %ld: key is array index %ld, below new_len",
+				                     (long) i, (long) arr_idx));
+				continue;
+			}
+			if (DUK_HOBJECT_E_SLOT_IS_CONFIGURABLE(obj, i)) {
+				DUK_DDD(DUK_DDDPRINT("skip entry index %ld: key is a relevant array index %ld, but configurable",
+				                     (long) i, (long) arr_idx));
+				continue;
+			}
+
+			/* relevant array index is non-configurable, blocks write */
+			if (arr_idx >= target_len) {
+				DUK_DDD(DUK_DDDPRINT("entry at index %ld has arr_idx %ld, is not configurable, "
+				                     "update target_len %ld -> %ld",
+				                     (long) i, (long) arr_idx, (long) target_len,
+				                     (long) (arr_idx + 1)));
+				target_len = arr_idx + 1;
+			}
+		}
+
+		/* stage 2: delete configurable entries above target length */
+
+		DUK_DDD(DUK_DDDPRINT("old_len=%ld, new_len=%ld, target_len=%ld",
+		                     (long) old_len, (long) new_len, (long) target_len));
+
+		DUK_DDD(DUK_DDDPRINT("array length write, no array part, stage 2: remove "
+		                     "entries >= target_len"));
+
+		for (i = 0; i < obj->e_used; i++) {
+			key = DUK_HOBJECT_E_GET_KEY(obj, i);
+			if (!key) {
+				DUK_DDD(DUK_DDDPRINT("skip entry index %ld: null key", (long) i));
+				continue;
+			}
+			if (!DUK_HSTRING_HAS_ARRIDX(key)) {
+				DUK_DDD(DUK_DDDPRINT("skip entry index %ld: key not an array index", (long) i));
+				continue;
+			}
+
+			DUK_ASSERT(DUK_HSTRING_HAS_ARRIDX(key));  /* XXX: macro checks for array index flag, which is unnecessary here */
+			arr_idx = DUK_HSTRING_GET_ARRIDX_SLOW(key);
+			DUK_ASSERT(arr_idx != DUK__NO_ARRAY_INDEX);
+			DUK_ASSERT(arr_idx < old_len);  /* consistency requires this */
+
+			if (arr_idx < target_len) {
+				DUK_DDD(DUK_DDDPRINT("skip entry index %ld: key is array index %ld, below target_len",
+				                     (long) i, (long) arr_idx));
+				continue;
+			}
+			DUK_ASSERT(DUK_HOBJECT_E_SLOT_IS_CONFIGURABLE(obj, i));  /* stage 1 guarantees */
+
+			DUK_DDD(DUK_DDDPRINT("delete entry index %ld: key is array index %ld",
+			                     (long) i, (long) arr_idx));
+
+			/*
+			 *  Slow delete, but we don't care as we're already in a very slow path.
+			 *  The delete always succeeds: key has no exotic behavior, property
+			 *  is configurable, and no resize occurs.
+			 */
+			rc = duk_hobject_delprop_raw(thr, obj, key, 0);
+			DUK_UNREF(rc);
+			DUK_ASSERT(rc != 0);
+		}
+
+		/* stage 3: update length (done by caller), decide return code */
+
+		DUK_DDD(DUK_DDDPRINT("array length write, no array part, stage 3: update length (done by caller)"));
+
+		*out_result_len = target_len;
+
+		if (target_len == new_len) {
+			DUK_DDD(DUK_DDDPRINT("target_len matches new_len, return success"));
+			return 1;
+		}
+		DUK_DDD(DUK_DDDPRINT("target_len does not match new_len (some entry prevented "
+		                     "full length adjustment), return error"));
+		return 0;
+	}
+
+	DUK_UNREACHABLE();
+}
+
+/* XXX: is valstack top best place for argument? */
+static duk_bool_t duk__handle_put_array_length(duk_hthread *thr, duk_hobject *obj) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_propdesc desc;
+	duk_uint32_t old_len;
+	duk_uint32_t new_len;
+	duk_uint32_t result_len;
+	duk_tval *tv;
+	duk_bool_t rc;
+
+	DUK_DDD(DUK_DDDPRINT("handling a put operation to array 'length' exotic property, "
+	                     "new val: %!T",
+	                     (duk_tval *) duk_get_tval(ctx, -1)));
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(obj != NULL);
+
+	DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
+
+	DUK_ASSERT(duk_is_valid_index(ctx, -1));
+
+	/*
+	 *  Get old and new length
+	 */
+
+	old_len = duk__get_old_array_length(thr, obj, &desc);
+	duk_dup(ctx, -1);  /* [in_val in_val] */
+	new_len = duk__to_new_array_length_checked(thr);
+	duk_pop(ctx);  /* [in_val in_val] -> [in_val] */
+	DUK_DDD(DUK_DDDPRINT("old_len=%ld, new_len=%ld", (long) old_len, (long) new_len));
+
+	/*
+	 *  Writability check
+	 */
+
+	if (!(desc.flags & DUK_PROPDESC_FLAG_WRITABLE)) {
+		DUK_DDD(DUK_DDDPRINT("length is not writable, fail"));
+		return 0;
+	}
+
+	/*
+	 *  New length not lower than old length => no changes needed
+	 *  (not even array allocation).
+	 */
+
+	if (new_len >= old_len) {
+		DUK_DDD(DUK_DDDPRINT("new length is higher than old length, just update length, no deletions"));
+
+		DUK_ASSERT(desc.e_idx >= 0);
+		DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, desc.e_idx));
+		tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, desc.e_idx);
+		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
+		DUK_TVAL_SET_NUMBER(tv, (duk_double_t) new_len);  /* no decref needed for a number */
+		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
+		return 1;
+	}
+
+	DUK_DDD(DUK_DDDPRINT("new length is lower than old length, probably must delete entries"));
+
+	/*
+	 *  New length lower than old length => delete elements, then
+	 *  update length.
+	 *
+	 *  Note: even though a bunch of elements have been deleted, the 'desc' is
+	 *  still valid as properties haven't been resized (and entries compacted).
+	 */
+
+	rc = duk__handle_put_array_length_smaller(thr, obj, old_len, new_len, &result_len);
+	DUK_ASSERT(result_len >= new_len && result_len <= old_len);
+
+	DUK_ASSERT(desc.e_idx >= 0);
+	DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, desc.e_idx));
+	tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, desc.e_idx);
+	DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
+	DUK_TVAL_SET_NUMBER(tv, (duk_double_t) result_len);  /* no decref needed for a number */
+	DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
+
+	/* XXX: shrink array allocation or entries compaction here? */
+
+	return rc;
+}
+
+/*
+ *  PUTPROP: Ecmascript property write.
+ *
+ *  Unlike Ecmascript primitive which returns nothing, returns 1 to indicate
+ *  success and 0 to indicate failure (assuming throw is not set).
+ *
+ *  This is an extremely tricky function.  Some examples:
+ *
+ *    * Currently a decref may trigger a GC, which may compact an object's
+ *      property allocation.  Consequently, any entry indices (e_idx) will
+ *      be potentially invalidated by a decref.
+ *
+ *    * Exotic behaviors (strings, arrays, arguments object) require,
+ *      among other things:
+ *
+ *      - Preprocessing before and postprocessing after an actual property
+ *        write.  For example, array index write requires pre-checking the
+ *        array 'length' property for access control, and may require an
+ *        array 'length' update after the actual write has succeeded (but
+ *        not if it fails).
+ *
+ *      - Deletion of multiple entries, as a result of array 'length' write.
+ *
+ *    * Input values are taken as pointers which may point to the valstack.
+ *      If valstack is resized because of the put (this may happen at least
+ *      when the array part is abandoned), the pointers can be invalidated.
+ *      (We currently make a copy of all of the input values to avoid issues.)
+ */
+
+duk_bool_t duk_hobject_putprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key, duk_tval *tv_val, duk_bool_t throw_flag) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_tval tv_obj_copy;
+	duk_tval tv_key_copy;
+	duk_tval tv_val_copy;
+	duk_hobject *orig = NULL;  /* NULL if tv_obj is primitive */
+	duk_hobject *curr;
+	duk_hstring *key = NULL;
+	duk_propdesc desc;
+	duk_tval *tv;
+	duk_uint32_t arr_idx;
+	duk_bool_t rc;
+	duk_int_t e_idx;
+	duk_uint_t sanity;
+	duk_uint32_t new_array_length = 0;  /* 0 = no update */
+
+	DUK_DDD(DUK_DDDPRINT("putprop: thr=%p, obj=%p, key=%p, val=%p, throw=%ld "
+	                     "(obj -> %!T, key -> %!T, val -> %!T)",
+	                     (void *) thr, (void *) tv_obj, (void *) tv_key, (void *) tv_val,
+	                     (long) throw_flag, (duk_tval *) tv_obj, (duk_tval *) tv_key, (duk_tval *) tv_val));
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->heap != NULL);
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(tv_obj != NULL);
+	DUK_ASSERT(tv_key != NULL);
+	DUK_ASSERT(tv_val != NULL);
+
+	DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
+
+	/*
+	 *  Make a copy of tv_obj, tv_key, and tv_val to avoid any issues of
+	 *  them being invalidated by a valstack resize.
+	 *
+	 *  XXX: this is an overkill for some paths, so optimize this later
+	 *  (or maybe switch to a stack arguments model entirely).
+	 */
+
+	DUK_TVAL_SET_TVAL(&tv_obj_copy, tv_obj);
+	DUK_TVAL_SET_TVAL(&tv_key_copy, tv_key);
+	DUK_TVAL_SET_TVAL(&tv_val_copy, tv_val);
+	tv_obj = &tv_obj_copy;
+	tv_key = &tv_key_copy;
+	tv_val = &tv_val_copy;
+
+	/*
+	 *  Coercion and fast path processing.
+	 */
+
+	switch (DUK_TVAL_GET_TAG(tv_obj)) {
+	case DUK_TAG_UNDEFINED:
+	case DUK_TAG_NULL: {
+		/* Note: unconditional throw */
+		DUK_DDD(DUK_DDDPRINT("base object is undefined or null -> reject (object=%!iT)",
+		                     (duk_tval *) tv_obj));
+		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_INVALID_BASE);
+		return 0;
+	}
+
+	case DUK_TAG_BOOLEAN: {
+		DUK_DDD(DUK_DDDPRINT("base object is a boolean, start lookup from boolean prototype"));
+		curr = thr->builtins[DUK_BIDX_BOOLEAN_PROTOTYPE];
+		break;
+	}
+
+	case DUK_TAG_STRING: {
+		duk_hstring *h = DUK_TVAL_GET_STRING(tv_obj);
+
+		/*
+		 *  Note: currently no fast path for array index writes.
+		 *  They won't be possible anyway as strings are immutable.
+		 */
+
+		DUK_ASSERT(key == NULL);
+		arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
+		DUK_ASSERT(key != NULL);
+
+		if (key == DUK_HTHREAD_STRING_LENGTH(thr)) {
+			goto fail_not_writable;
+		}
+
+		if (arr_idx != DUK__NO_ARRAY_INDEX &&
+		    arr_idx < DUK_HSTRING_GET_CHARLEN(h)) {
+			goto fail_not_writable;
+		}
+
+		DUK_DDD(DUK_DDDPRINT("base object is a string, start lookup from string prototype"));
+		curr = thr->builtins[DUK_BIDX_STRING_PROTOTYPE];
+		goto lookup;  /* avoid double coercion */
+	}
+
+	case DUK_TAG_OBJECT: {
+		/* Note: no fast paths for property put now */
+		orig = DUK_TVAL_GET_OBJECT(tv_obj);
+		DUK_ASSERT(orig != NULL);
+
+#if defined(DUK_USE_ES6_PROXY)
+		if (DUK_UNLIKELY(DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(orig))) {
+			duk_hobject *h_target;
+			duk_bool_t tmp_bool;
+
+			if (duk__proxy_check_prop(thr, orig, DUK_STRIDX_SET, tv_key, &h_target)) {
+				/* -> [ ... trap handler ] */
+				DUK_DDD(DUK_DDDPRINT("-> proxy object 'set' for key %!T", (duk_tval *) tv_key));
+				duk_push_hobject(ctx, h_target);  /* target */
+				duk_push_tval(ctx, tv_key);       /* P */
+				duk_push_tval(ctx, tv_val);       /* V */
+				duk_push_tval(ctx, tv_obj);       /* Receiver: Proxy object */
+				duk_call_method(ctx, 4 /*nargs*/);
+				tmp_bool = duk_to_boolean(ctx, -1);
+				duk_pop(ctx);
+				if (!tmp_bool) {
+					goto fail_proxy_rejected;
+				}
+
+				/* Target object must be checked for a conflicting
+				 * non-configurable property.
+				 */
+				arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
+				DUK_ASSERT(key != NULL);
+
+				if (duk__get_own_property_desc_raw(thr, h_target, key, arr_idx, &desc, 1 /*push_value*/)) {
+					duk_tval *tv_targ = duk_require_tval(ctx, -1);
+					duk_bool_t datadesc_reject;
+					duk_bool_t accdesc_reject;
+
+					DUK_DDD(DUK_DDDPRINT("proxy 'set': target has matching property %!O, check for "
+					                     "conflicting property; tv_val=%!T, tv_targ=%!T, desc.flags=0x%08lx, "
+					                     "desc.get=%p, desc.set=%p",
+					                     (duk_heaphdr *) key, (duk_tval *) tv_val, (duk_tval *) tv_targ,
+					                     (unsigned long) desc.flags,
+					                     (void *) desc.get, (void *) desc.set));
+
+					datadesc_reject = !(desc.flags & DUK_PROPDESC_FLAG_ACCESSOR) &&
+					                  !(desc.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) &&
+					                  !(desc.flags & DUK_PROPDESC_FLAG_WRITABLE) &&
+					                  !duk_js_samevalue(tv_val, tv_targ);
+					accdesc_reject = (desc.flags & DUK_PROPDESC_FLAG_ACCESSOR) &&
+					                 !(desc.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) &&
+					                 (desc.set == NULL);
+					if (datadesc_reject || accdesc_reject) {
+						DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_PROXY_REJECTED);
+					}
+
+					duk_pop_2(ctx);
+				} else {
+					duk_pop(ctx);
+				}
+				return 1;  /* success */
+			}
+
+			orig = h_target;  /* resume write to target */
+			DUK_TVAL_SET_OBJECT(tv_obj, orig);
+		}
+#endif  /* DUK_USE_ES6_PROXY */
+
+		curr = orig;
+		break;
+	}
+
+	case DUK_TAG_BUFFER: {
+		duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv_obj);
+		duk_int_t pop_count = 0;
+
+		/*
+		 *  Because buffer values may be looped over and read/written
+		 *  from, an array index fast path is important.
+		 */
+
+		if (DUK_TVAL_IS_NUMBER(tv_key)) {
+			arr_idx = duk__tval_number_to_arr_idx(tv_key);
+			DUK_DDD(DUK_DDDPRINT("base object buffer, key is a fast-path number; arr_idx %ld", (long) arr_idx));
+			pop_count = 0;
+		} else {
+			arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
+			DUK_ASSERT(key != NULL);
+			DUK_DDD(DUK_DDDPRINT("base object buffer, key is a non-fast-path number; after "
+			                     "coercion key is %!T, arr_idx %ld",
+			                     (duk_tval *) duk_get_tval(ctx, -1), (long) arr_idx));
+			pop_count = 1;
+		}
+
+		if (arr_idx != DUK__NO_ARRAY_INDEX &&
+		    arr_idx < DUK_HBUFFER_GET_SIZE(h)) {
+			duk_uint8_t *data;
+			DUK_DDD(DUK_DDDPRINT("writing to buffer data at index %ld", (long) arr_idx));
+			data = (duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(h);
+			duk_push_tval(ctx, tv_val);
+			data[arr_idx] = (duk_uint8_t) duk_to_number(ctx, -1);
+			pop_count++;
+			duk_pop_n(ctx, pop_count);
+			DUK_DDD(DUK_DDDPRINT("result: success (buffer data write)"));
+			return 1;
+		}
+
+		if (pop_count == 0) {
+			/* This is a pretty awkward control flow, but we need to recheck the
+			 * key coercion here.
+			 */
+			arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
+			DUK_ASSERT(key != NULL);
+			DUK_DDD(DUK_DDDPRINT("base object buffer, key is a non-fast-path number; after "
+			                     "coercion key is %!T, arr_idx %ld",
+			                     (duk_tval *) duk_get_tval(ctx, -1), (long) arr_idx));
+		}
+
+		if (key == DUK_HTHREAD_STRING_LENGTH(thr)) {
+			goto fail_not_writable;
+		}
+
+		DUK_DDD(DUK_DDDPRINT("base object is a buffer, start lookup from buffer prototype"));
+		curr = thr->builtins[DUK_BIDX_BUFFER_PROTOTYPE];
+		goto lookup;  /* avoid double coercion */
+	}
+
+	case DUK_TAG_POINTER: {
+		DUK_DDD(DUK_DDDPRINT("base object is a pointer, start lookup from pointer prototype"));
+		curr = thr->builtins[DUK_BIDX_POINTER_PROTOTYPE];
+		break;
+	}
+
+	default: {
+		/* number */
+		DUK_DDD(DUK_DDDPRINT("base object is a number, start lookup from number prototype"));
+		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_obj));
+		curr = thr->builtins[DUK_BIDX_NUMBER_PROTOTYPE];
+		break;
+	}
+	}
+
+	DUK_ASSERT(key == NULL);
+	arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
+	DUK_ASSERT(key != NULL);
+
+ lookup:
+
+	/*
+	 *  Check whether the property already exists in the prototype chain.
+	 *  Note that the actual write goes into the original base object
+	 *  (except if an accessor property captures the write).
+	 */
+
+	/* [key] */
+
+	DUK_ASSERT(curr != NULL);
+	sanity = DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY;
+	do {
+		/* 0 = don't push current value */
+		if (!duk__get_own_property_desc_raw(thr, curr, key, arr_idx, &desc, 0)) {
+			goto next_in_chain;
+		}
+
+		if (desc.flags & DUK_PROPDESC_FLAG_ACCESSOR) {
+			/*
+			 *  Found existing accessor property (own or inherited).
+			 *  Call setter with 'this' set to orig, and value as the only argument.
+			 *
+			 *  Note: no exotic arguments object behavior, because [[Put]] never
+			 *  calls [[DefineOwnProperty]] (E5 Section 8.12.5, step 5.b).
+			 */
+
+			duk_hobject *setter;
+
+			DUK_DD(DUK_DDPRINT("put to an own or inherited accessor, calling setter"));
+
+			setter = DUK_HOBJECT_E_GET_VALUE_SETTER(curr, desc.e_idx);
+			if (!setter) {
+				goto fail_no_setter;
+			}
+			duk_push_hobject(ctx, setter);
+			duk_push_tval(ctx, tv_obj);  /* note: original, uncoerced base */
+			duk_push_tval(ctx, tv_val);  /* [key setter this val] */
+#ifdef DUK_USE_NONSTD_SETTER_KEY_ARGUMENT
+			duk_dup(ctx, -4);
+			duk_call_method(ctx, 2);     /* [key setter this val key] -> [key retval] */
+#else
+			duk_call_method(ctx, 1);     /* [key setter this val] -> [key retval] */
+#endif
+			duk_pop(ctx);                /* ignore retval -> [key] */
+			goto success_no_arguments_exotic;
+		}
+
+		if (orig == NULL) {
+			/*
+			 *  Found existing own or inherited plain property, but original
+			 *  base is a primitive value.
+			 */
+			DUK_DD(DUK_DDPRINT("attempt to create a new property in a primitive base object"));
+			goto fail_base_primitive;
+		}
+
+		if (curr != orig) {
+			/*
+			 *  Found existing inherited plain property.
+			 *  Do an access control check, and if OK, write
+			 *  new property to 'orig'.
+			 */
+			if (!DUK_HOBJECT_HAS_EXTENSIBLE(orig)) {
+				DUK_DD(DUK_DDPRINT("found existing inherited plain property, but original object is not extensible"));
+				goto fail_not_extensible;
+			}
+			if (!(desc.flags & DUK_PROPDESC_FLAG_WRITABLE)) {
+				DUK_DD(DUK_DDPRINT("found existing inherited plain property, original object is extensible, but inherited property is not writable"));
+				goto fail_not_writable;
+			}
+			DUK_DD(DUK_DDPRINT("put to new property, object extensible, inherited property found and is writable"));
+			goto create_new;
+		} else {
+			/*
+			 *  Found existing own (non-inherited) plain property.
+			 *  Do an access control check and update in place.
+			 */
+
+			if (!(desc.flags & DUK_PROPDESC_FLAG_WRITABLE)) {
+				DUK_DD(DUK_DDPRINT("found existing own (non-inherited) plain property, but property is not writable"));
+				goto fail_not_writable;
+			}
+			if (desc.flags & DUK_PROPDESC_FLAG_VIRTUAL) {
+				DUK_DD(DUK_DDPRINT("found existing own (non-inherited) virtual property, property is writable"));
+				if (DUK_HOBJECT_HAS_EXOTIC_BUFFEROBJ(curr)) {
+					duk_hbuffer *h;
+
+					DUK_DD(DUK_DDPRINT("writable virtual property is in buffer object"));
+					h = duk_hobject_get_internal_value_buffer(thr->heap, curr);
+					DUK_ASSERT(h != NULL);
+
+					if (arr_idx != DUK__NO_ARRAY_INDEX &&
+					    arr_idx < DUK_HBUFFER_GET_SIZE(h)) {
+						duk_uint8_t *data;
+						DUK_DDD(DUK_DDDPRINT("writing to buffer data at index %ld", (long) arr_idx));
+						data = (duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(h);
+						duk_push_tval(ctx, tv_val);
+						data[arr_idx] = (duk_uint8_t) duk_to_number(ctx, -1);
+						duk_pop(ctx);
+						goto success_no_arguments_exotic;
+					}
+				}
+
+				goto fail_internal;  /* should not happen */
+			}
+			DUK_DD(DUK_DDPRINT("put to existing own plain property, property is writable"));
+			goto update_old;
+		}
+		DUK_UNREACHABLE();
+
+	 next_in_chain:
+		if (sanity-- == 0) {
+			DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, DUK_STR_PROTOTYPE_CHAIN_LIMIT);
+		}
+		curr = curr->prototype;
+	} while (curr);
+
+	/*
+	 *  Property not found in prototype chain.
+	 */
+
+	DUK_DDD(DUK_DDDPRINT("property not found in prototype chain"));
+
+	if (orig == NULL) {
+		DUK_DD(DUK_DDPRINT("attempt to create a new property in a primitive base object"));
+		goto fail_base_primitive;
+	}
+
+	if (!DUK_HOBJECT_HAS_EXTENSIBLE(orig)) {
+		DUK_DD(DUK_DDPRINT("put to a new property (not found in prototype chain), but original object not extensible"));
+		goto fail_not_extensible;
+	}
+
+	goto create_new;
+
+ update_old:
+
+	/*
+	 *  Update an existing property of the base object.
+	 */
+
+	/* [key] */
+
+	DUK_DDD(DUK_DDDPRINT("update an existing property of the original object"));
+
+	DUK_ASSERT(orig != NULL);
+
+	/* Although there are writable virtual properties (e.g. plain buffer
+	 * and buffer object number indices), they are handled before we come
+	 * here.
+	 */
+	DUK_ASSERT((desc.flags & DUK_PROPDESC_FLAG_VIRTUAL) == 0);
+	DUK_ASSERT(desc.a_idx >= 0 || desc.e_idx >= 0);
+
+	if (DUK_HOBJECT_HAS_EXOTIC_ARRAY(orig) &&
+	    key == DUK_HTHREAD_STRING_LENGTH(thr)) {
+		/*
+		 *  Write to 'length' of an array is a very complex case
+		 *  handled in a helper which updates both the array elements
+		 *  and writes the new 'length'.  The write may result in an
+		 *  unconditional RangeError or a partial write (indicated
+		 *  by a return code).
+		 *
+		 *  Note: the helper has an unnecessary writability check
+		 *  for 'length', we already know it is writable.
+		 */
+
+		DUK_DDD(DUK_DDDPRINT("writing existing 'length' property to array exotic, invoke complex helper"));
+
+		/* FIXME: the helper currently assumes stack top contains new
+		 * 'length' value and the whole calling convention is not very
+		 * compatible with what we need.
+		 */
+
+		duk_push_tval(ctx, tv_val);  /* [key val] */
+		rc = duk__handle_put_array_length(thr, orig);
+		duk_pop(ctx);  /* [key val] -> [key] */
+		if (!rc) {
+			goto fail_array_length_partial;
+		}
+
+		/* key is 'length', cannot match argument exotic behavior */
+		goto success_no_arguments_exotic;
+	}
+
+	if (desc.e_idx >= 0) {
+		duk_tval tv_tmp;
+
+		tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(orig, desc.e_idx);
+		DUK_DDD(DUK_DDDPRINT("previous entry value: %!iT", (duk_tval *) tv));
+		DUK_TVAL_SET_TVAL(&tv_tmp, tv);
+		DUK_TVAL_SET_TVAL(tv, tv_val);
+		DUK_TVAL_INCREF(thr, tv);
+		DUK_TVAL_DECREF(thr, &tv_tmp);  /* note: may trigger gc and props compaction, must be last */
+		/* don't touch property attributes or hash part */
+		DUK_DD(DUK_DDPRINT("put to an existing entry at index %ld -> new value %!iT",
+		                   (long) desc.e_idx, (duk_tval *) tv));
+	} else {
+		/* Note: array entries are always writable, so the writability check
+		 * above is pointless for them.  The check could be avoided with some
+		 * refactoring but is probably not worth it.
+		 */
+		duk_tval tv_tmp;
+
+		DUK_ASSERT(desc.a_idx >= 0);
+		tv = DUK_HOBJECT_A_GET_VALUE_PTR(orig, desc.a_idx);
+		DUK_DDD(DUK_DDDPRINT("previous array value: %!iT", (duk_tval *) tv));
+		DUK_TVAL_SET_TVAL(&tv_tmp, tv);
+		DUK_TVAL_SET_TVAL(tv, tv_val);
+		DUK_TVAL_INCREF(thr, tv);
+		DUK_TVAL_DECREF(thr, &tv_tmp);  /* note: may trigger gc and props compaction, must be last */
+		DUK_DD(DUK_DDPRINT("put to an existing array entry at index %ld -> new value %!iT",
+		                   (long) desc.a_idx, (duk_tval *) tv));
+	}
+
+	/* Regardless of whether property is found in entry or array part,
+	 * it may have arguments exotic behavior (array indices may reside
+	 * in entry part for abandoned / non-existent array parts).
+	 */
+	goto success_with_arguments_exotic;
+
+ create_new:
+
+	/*
+	 *  Create a new property in the original object.
+	 *
+	 *  Exotic properties need to be reconsidered here from a write
+	 *  perspective (not just property attributes perspective).
+	 *  However, the property does not exist in the object already,
+	 *  so this limits the kind of exotic properties that apply.
+	 */
+
+	/* [key] */
+
+	DUK_DDD(DUK_DDDPRINT("create new property to original object"));
+
+	DUK_ASSERT(orig != NULL);
+
+	/* Not possible because array object 'length' is present
+	 * from its creation and cannot be deleted, and is thus
+	 * caught as an existing property above.
+	 */
+	DUK_ASSERT(!(DUK_HOBJECT_HAS_EXOTIC_ARRAY(orig) &&
+	             key == DUK_HTHREAD_STRING_LENGTH(thr)));
+
+	if (DUK_HOBJECT_HAS_EXOTIC_ARRAY(orig) &&
+	    arr_idx != DUK__NO_ARRAY_INDEX) {
+		/* automatic length update */
+		duk_uint32_t old_len;
+
+		old_len = duk__get_old_array_length(thr, orig, &desc);
+
+		if (arr_idx >= old_len) {
+			DUK_DDD(DUK_DDDPRINT("write new array entry requires length update "
+			                     "(arr_idx=%ld, old_len=%ld)",
+			                     (long) arr_idx, (long) old_len));
+
+			if (!(desc.flags & DUK_PROPDESC_FLAG_WRITABLE)) {
+				DUK_DD(DUK_DDPRINT("attempt to extend array, but array 'length' is not writable"));
+				goto fail_not_writable;
+			}
+
+			/* Note: actual update happens once write has been completed
+			 * without error below.  The write should always succeed
+			 * from a specification viewpoint, but we may e.g. run out
+			 * of memory.  It's safer in this order.
+			 */
+
+			DUK_ASSERT(arr_idx != 0xffffffffUL);
+			new_array_length = arr_idx + 1;  /* flag for later write */
+		} else {
+			DUK_DDD(DUK_DDDPRINT("write new array entry does not require length update "
+			                     "(arr_idx=%ld, old_len=%ld)",
+			                     (long) arr_idx, (long) old_len));
+		}
+	}
+
+ /* write_to_array_part: */
+
+	/*
+	 *  Write to array part?
+	 *
+	 *  Note: array abandonding requires a property resize which uses
+	 *  'rechecks' valstack for temporaries and may cause any existing
+	 *  valstack pointers to be invalidated.  To protect against this,
+	 *  tv_obj, tv_key, and tv_val are copies of the original inputs.
+	 */
+
+	if (arr_idx != DUK__NO_ARRAY_INDEX &&
+	    DUK_HOBJECT_HAS_ARRAY_PART(orig)) {
+		if (arr_idx < orig->a_size) {
+			goto no_array_growth;
+		}
+
+		/*
+		 *  Array needs to grow, but we don't want it becoming too sparse.
+		 *  If it were to become sparse, abandon array part, moving all
+		 *  array entries into the entries part (for good).
+		 *
+		 *  Since we don't keep track of actual density (used vs. size) of
+		 *  the array part, we need to estimate somehow.  The check is made
+		 *  in two parts:
+		 *
+		 *    - Check whether the resize need is small compared to the
+		 *      current size (relatively); if so, resize without further
+		 *      checking (essentially we assume that the original part is
+		 *      "dense" so that the result would be dense enough).
+		 *
+		 *    - Otherwise, compute the resize using an actual density
+		 *      measurement based on counting the used array entries.
+		 */
+
+		DUK_DDD(DUK_DDDPRINT("write to new array requires array resize, decide whether to do a "
+		                     "fast resize without abandon check (arr_idx=%ld, old_size=%ld)",
+		                     (long) arr_idx, (long) orig->a_size));
+
+		if (duk__abandon_array_slow_check_required(arr_idx, orig->a_size)) {
+			duk_uint32_t old_used;
+			duk_uint32_t old_size;
+
+			DUK_DDD(DUK_DDDPRINT("=> fast check is NOT OK, do slow check for array abandon"));
+
+			duk__compute_a_stats(orig, &old_used, &old_size);
+
+			DUK_DDD(DUK_DDDPRINT("abandon check, array stats: old_used=%ld, old_size=%ld, arr_idx=%ld",
+			                     (long) old_used, (long) old_size, (long) arr_idx));
+
+			/* Note: intentionally use approximations to shave a few instructions:
+			 *   a_used = old_used  (accurate: old_used + 1)
+			 *   a_size = arr_idx   (accurate: arr_idx + 1)
+			 */
+			if (duk__abandon_array_density_check(old_used, arr_idx)) {
+				DUK_DD(DUK_DDPRINT("write to new array entry beyond current length, "
+				                   "decided to abandon array part (would become too sparse)"));
+
+				/* abandoning requires a props allocation resize and
+				 * 'rechecks' the valstack, invalidating any existing
+				 * valstack value pointers!
+				 */
+				duk__abandon_array_checked(thr, orig);
+				DUK_ASSERT(!DUK_HOBJECT_HAS_ARRAY_PART(orig));
+
+				goto write_to_entry_part;
+			}
+
+			DUK_DDD(DUK_DDDPRINT("=> decided to keep array part"));
+		} else {
+			DUK_DDD(DUK_DDDPRINT("=> fast resize is OK"));
+		}
+
+		DUK_DD(DUK_DDPRINT("write to new array entry beyond current length, "
+		                   "decided to extend current allocation"));
+
+		duk__grow_props_for_array_item(thr, orig, arr_idx);
+
+	 no_array_growth:
+
+		/* Note: assume array part is comprehensive, so that either
+		 * the write goes to the array part, or we've abandoned the
+		 * array above (and will not come here).
+		 */
+
+		DUK_ASSERT(DUK_HOBJECT_HAS_ARRAY_PART(orig));
+		DUK_ASSERT(arr_idx < orig->a_size);
+
+		tv = DUK_HOBJECT_A_GET_VALUE_PTR(orig, arr_idx);
+		/* prev value must be unused, no decref */
+		DUK_ASSERT(DUK_TVAL_IS_UNDEFINED_UNUSED(tv));
+		DUK_TVAL_SET_TVAL(tv, tv_val);
+		DUK_TVAL_INCREF(thr, tv);
+		DUK_DD(DUK_DDPRINT("put to new array entry: %ld -> %!T",
+		                   (long) arr_idx, (duk_tval *) tv));
+
+		/* Note: array part values are [[Writable]], [[Enumerable]],
+		 * and [[Configurable]] which matches the required attributes
+		 * here.
+		 */
+		goto entry_updated;
+	}
+
+ write_to_entry_part:
+
+	/*
+	 *  Write to entry part
+	 */
+
+	/* entry allocation updates hash part and increases the key
+	 * refcount; may need a props allocation resize but doesn't
+	 * 'recheck' the valstack.
+	 */
+	e_idx = duk__alloc_entry_checked(thr, orig, key);
+	DUK_ASSERT(e_idx >= 0);
+
+	tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(orig, e_idx);
+	/* prev value can be garbage, no decref */
+	DUK_TVAL_SET_TVAL(tv, tv_val);
+	DUK_TVAL_INCREF(thr, tv);
+	DUK_HOBJECT_E_SET_FLAGS(orig, e_idx, DUK_PROPDESC_FLAGS_WEC);
+	goto entry_updated;
+
+ entry_updated:
+
+	/*
+	 *  Possible pending array length update, which must only be done
+	 *  if the actual entry write succeeded.
+	 */	
+
+	if (new_array_length > 0) {
+		/*
+		 *  Note: zero works as a "no update" marker because the new length
+		 *  can never be zero after a new property is written.
+		 *
+		 *  Note: must re-lookup because calls above (e.g. duk__alloc_entry_checked())
+		 *  may realloc and compact properties and hence change e_idx.
+		 */
+
+		DUK_DDD(DUK_DDDPRINT("write successful, pending array length update to: %ld",
+		                     (long) new_array_length));
+
+		rc = duk__get_own_property_desc_raw(thr, orig, DUK_HTHREAD_STRING_LENGTH(thr), DUK__NO_ARRAY_INDEX, &desc, 0);
+		DUK_UNREF(rc);
+		DUK_ASSERT(rc != 0);
+		DUK_ASSERT(desc.e_idx >= 0);
+
+		tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(orig, desc.e_idx);
+		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
+		DUK_TVAL_SET_NUMBER(tv, (duk_double_t) new_array_length);  /* no need for decref/incref because value is a number */
+	}
+
+	/*
+	 *  Arguments exotic behavior not possible for new properties: all
+	 *  magically bound properties are initially present in the arguments
+	 *  object, and if they are deleted, the binding is also removed from
+	 *  parameter map.
+	 */
+
+	goto success_no_arguments_exotic;
+
+ success_with_arguments_exotic:
+
+	/*
+	 *  Arguments objects have exotic [[DefineOwnProperty]] which updates
+	 *  the internal 'map' of arguments for writes to currently mapped
+	 *  arguments.  More conretely, writes to mapped arguments generate
+	 *  a write to a bound variable.
+	 *
+	 *  The [[Put]] algorithm invokes [[DefineOwnProperty]] for existing
+	 *  data properties and new properties, but not for existing accessors.
+	 *  Hence, in E5 Section 10.6 ([[DefinedOwnProperty]] algorithm), we
+	 *  have a Desc with 'Value' (and possibly other properties too), and
+	 *  we end up in step 5.b.i.
+	 */
+
+	if (arr_idx != DUK__NO_ARRAY_INDEX &&
+	    DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(orig)) {
+		/* Note: only numbered indices are relevant, so arr_idx fast reject
+		 * is good (this is valid unless there are more than 4**32-1 arguments).
+		 */
+
+		DUK_DDD(DUK_DDDPRINT("putprop successful, arguments exotic behavior needed"));
+
+		/* Note: we can reuse 'desc' here */
+
+		/* FIXME: top of stack must contain value, which helper doesn't touch,
+		 * rework to use tv_val directly?
+		 */
+
+		duk_push_tval(ctx, tv_val);
+		(void) duk__check_arguments_map_for_put(thr, orig, key, &desc, throw_flag);
+		duk_pop(ctx);
+	}
+	/* fall thru */
+
+ success_no_arguments_exotic:
+	/* shared exit path now */
+	DUK_DDD(DUK_DDDPRINT("result: success"));
+	duk_pop(ctx);  /* remove key */
+	return 1;
+
+ fail_proxy_rejected:
+	DUK_DDD(DUK_DDDPRINT("result: error, proxy rejects"));
+	if (throw_flag) {
+		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_PROXY_REJECTED);
+	}
+	/* Note: no key on stack */
+	return 0;
+
+ fail_base_primitive:
+	DUK_DDD(DUK_DDDPRINT("result: error, base primitive"));
+	if (throw_flag) {
+		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_INVALID_BASE);
+	}
+	duk_pop(ctx);  /* remove key */
+	return 0;
+
+ fail_not_extensible:
+	DUK_DDD(DUK_DDDPRINT("result: error, not extensible"));
+	if (throw_flag) {
+		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_EXTENSIBLE);
+	}
+	duk_pop(ctx);  /* remove key */
+	return 0;
+	
+ fail_not_writable:
+	DUK_DDD(DUK_DDDPRINT("result: error, not writable"));
+	if (throw_flag) {
+		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_WRITABLE);
+	}
+	duk_pop(ctx);  /* remove key */
+	return 0;
+
+ fail_array_length_partial:
+	DUK_DDD(DUK_DDDPRINT("result: error, array length write only partially successful"));
+	if (throw_flag) {
+		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_ARRAY_LENGTH_WRITE_FAILED);
+	}
+	duk_pop(ctx);  /* remove key */
+	return 0;
+
+ fail_no_setter:
+	DUK_DDD(DUK_DDDPRINT("result: error, accessor property without setter"));
+	if (throw_flag) {
+		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_SETTER_UNDEFINED);
+	}
+	duk_pop(ctx);  /* remove key */
+	return 0;
+
+ fail_internal:
+	DUK_DDD(DUK_DDDPRINT("result: error, internal"));
+	if (throw_flag) {
+		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_INTERNAL_ERROR);
+	}
+	duk_pop(ctx);  /* remove key */
+	return 0;
+}
+
+/*
+ *  Ecmascript compliant [[Delete]](P, Throw).
+ */
+
+duk_bool_t duk_hobject_delprop_raw(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_bool_t throw_flag) {
+	duk_propdesc desc;
+	duk_tval *tv;
+	duk_tval tv_tmp;
+	duk_uint32_t arr_idx;
+
+	DUK_DDD(DUK_DDDPRINT("delprop_raw: thr=%p, obj=%p, key=%p, throw=%ld (obj -> %!O, key -> %!O)",
+	                     (void *) thr, (void *) obj, (void *) key, (long) throw_flag,
+	                     (duk_heaphdr *) obj, (duk_heaphdr *) key));
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->heap != NULL);
+	DUK_ASSERT(obj != NULL);
+	DUK_ASSERT(key != NULL);
+
+	DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
+
+	arr_idx = DUK_HSTRING_GET_ARRIDX_FAST(key);
+
+	/* 0 = don't push current value */
+	if (!duk__get_own_property_desc_raw(thr, obj, key, arr_idx, &desc, 0)) {
+		DUK_DDD(DUK_DDDPRINT("property not found, succeed always"));
+		goto success;
+	}
+
+	if ((desc.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) == 0) {
+		goto fail_not_configurable;
+	}
+
+	/* currently there are no deletable virtual properties */
+	DUK_ASSERT(desc.a_idx >= 0 || desc.e_idx >= 0);
+
+	if (desc.a_idx >= 0) {
+		DUK_ASSERT(desc.e_idx < 0);
+
+		tv = DUK_HOBJECT_A_GET_VALUE_PTR(obj, desc.a_idx);
+		DUK_TVAL_SET_TVAL(&tv_tmp, tv);
+		DUK_TVAL_SET_UNDEFINED_UNUSED(tv);
+		DUK_TVAL_DECREF(thr, &tv_tmp);
+		goto success;
+	} else {
+		DUK_ASSERT(desc.a_idx < 0);
+
+		/* remove hash entry (no decref) */
+		if (desc.h_idx >= 0) {
+			duk_uint32_t *h_base = DUK_HOBJECT_H_GET_BASE(obj);
+
+			DUK_DDD(DUK_DDDPRINT("removing hash entry at h_idx %ld", (long) desc.h_idx));
+			DUK_ASSERT(obj->h_size > 0);
+			DUK_ASSERT((duk_uint32_t) desc.h_idx < obj->h_size);
+			h_base[desc.h_idx] = DUK__HASH_DELETED;
+		} else {
+			DUK_ASSERT(obj->h_size == 0);
+		}
+
+		/* remove value */
+		DUK_DDD(DUK_DDDPRINT("before removing value, e_idx %ld, key %p, key at slot %p",
+		                     (long) desc.e_idx, (void *) key, (void *) DUK_HOBJECT_E_GET_KEY(obj, desc.e_idx)));
+		DUK_DDD(DUK_DDDPRINT("removing value at e_idx %ld", (long) desc.e_idx));
+		if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, desc.e_idx)) {
+			duk_hobject *tmp;
+
+			tmp = DUK_HOBJECT_E_GET_VALUE_GETTER(obj, desc.e_idx);
+			DUK_HOBJECT_E_SET_VALUE_GETTER(obj, desc.e_idx, NULL);
+			DUK_UNREF(tmp);
+			DUK_HOBJECT_DECREF(thr, tmp);
+
+			tmp = DUK_HOBJECT_E_GET_VALUE_SETTER(obj, desc.e_idx);
+			DUK_HOBJECT_E_SET_VALUE_SETTER(obj, desc.e_idx, NULL);
+			DUK_UNREF(tmp);
+			DUK_HOBJECT_DECREF(thr, tmp);
+		} else {
+			tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, desc.e_idx);
+			DUK_TVAL_SET_TVAL(&tv_tmp, tv);
+			DUK_TVAL_SET_UNDEFINED_UNUSED(tv);
+			DUK_TVAL_DECREF(thr, &tv_tmp);
+		}
+		/* this is not strictly necessary because if key == NULL, value MUST be ignored */
+		DUK_HOBJECT_E_SET_FLAGS(obj, desc.e_idx, 0);
+		DUK_TVAL_SET_UNDEFINED_UNUSED(DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, desc.e_idx));
+
+		/* remove key */
+		DUK_DDD(DUK_DDDPRINT("before removing key, e_idx %ld, key %p, key at slot %p",
+		                     (long) desc.e_idx, (void *) key, (void *) DUK_HOBJECT_E_GET_KEY(obj, desc.e_idx)));
+		DUK_DDD(DUK_DDDPRINT("removing key at e_idx %ld", (long) desc.e_idx));
+		DUK_ASSERT(key == DUK_HOBJECT_E_GET_KEY(obj, desc.e_idx));
+		DUK_HOBJECT_E_SET_KEY(obj, desc.e_idx, NULL);
+		DUK_HSTRING_DECREF(thr, key);
+		goto success;
+	}
+
+	DUK_UNREACHABLE();
+	
+ success:
+	/*
+	 *  Argument exotic [[Delete]] behavior (E5 Section 10.6) is
+	 *  a post-check, keeping arguments internal 'map' in sync with
+	 *  any successful deletes (note that property does not need to
+	 *  exist for delete to 'succeed').
+	 *
+	 *  Delete key from 'map'.  Since 'map' only contains array index
+	 *  keys, we can use arr_idx for a fast skip.
+	 */
+
+	DUK_DDD(DUK_DDDPRINT("delete successful, check for arguments exotic behavior"));
+
+	if (arr_idx != DUK__NO_ARRAY_INDEX && DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj)) {
+		/* Note: only numbered indices are relevant, so arr_idx fast reject
+		 * is good (this is valid unless there are more than 4**32-1 arguments).
+		 */
+
+		DUK_DDD(DUK_DDDPRINT("delete successful, arguments exotic behavior needed"));
+
+		/* Note: we can reuse 'desc' here */
+		(void) duk__check_arguments_map_for_delete(thr, obj, key, &desc);
+	}
+
+	DUK_DDD(DUK_DDDPRINT("delete successful"));
+	return 1;
+
+ fail_not_configurable:
+	DUK_DDD(DUK_DDDPRINT("delete failed: property found, not configurable"));
+
+	if (throw_flag) {
+		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_CONFIGURABLE);
+	}
+	return 0;
+}
+
+/*
+ *  DELPROP: Ecmascript property deletion.
+ */
+
+duk_bool_t duk_hobject_delprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key, duk_bool_t throw_flag) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_hstring *key = NULL;
+#if defined(DUK_USE_ES6_PROXY)
+	duk_propdesc desc;
+#endif
+	duk_int_t entry_top;
+	duk_uint32_t arr_idx = DUK__NO_ARRAY_INDEX;
+	duk_bool_t rc;
+
+	DUK_DDD(DUK_DDDPRINT("delprop: thr=%p, obj=%p, key=%p (obj -> %!T, key -> %!T)",
+	                     (void *) thr, (void *) tv_obj, (void *) tv_key,
+	                     (duk_tval *) tv_obj, (duk_tval *) tv_key));
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->heap != NULL);
+	DUK_ASSERT(tv_obj != NULL);
+	DUK_ASSERT(tv_key != NULL);
+
+	DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
+
+	/* Storing the entry top is cheaper here to ensure stack is correct at exit,
+	 * as there are several paths out.
+	 */
+	entry_top = duk_get_top(ctx);
+
+	if (DUK_TVAL_IS_UNDEFINED(tv_obj) ||
+	    DUK_TVAL_IS_NULL(tv_obj)) {
+		DUK_DDD(DUK_DDDPRINT("base object is undefined or null -> reject"));
+		goto fail_invalid_base_uncond;
+	}
+
+	duk_push_tval(ctx, tv_obj);
+	duk_push_tval(ctx, tv_key);
+
+	tv_obj = duk_get_tval(ctx, -2);
+	if (DUK_TVAL_IS_OBJECT(tv_obj)) {
+		duk_hobject *obj = DUK_TVAL_GET_OBJECT(tv_obj);
+		DUK_ASSERT(obj != NULL);
+
+#if defined(DUK_USE_ES6_PROXY)
+		if (DUK_UNLIKELY(DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(obj))) {
+			duk_hobject *h_target;
+			duk_bool_t tmp_bool;
+
+			/* Note: proxy handling must happen before key is string coerced. */
+
+			if (duk__proxy_check_prop(thr, obj, DUK_STRIDX_DELETE_PROPERTY, tv_key, &h_target)) {
+				/* -> [ ... trap handler ] */
+				DUK_DDD(DUK_DDDPRINT("-> proxy object 'deleteProperty' for key %!T", (duk_tval *) tv_key));
+				duk_push_hobject(ctx, h_target);  /* target */
+				duk_push_tval(ctx, tv_key);       /* P */
+				duk_call_method(ctx, 2 /*nargs*/);
+				tmp_bool = duk_to_boolean(ctx, -1);
+				duk_pop(ctx);
+				if (!tmp_bool) {
+					goto fail_proxy_rejected;  /* retval indicates delete failed */
+				}
+
+				/* Target object must be checked for a conflicting
+				 * non-configurable property.
+				 */
+				arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
+				DUK_ASSERT(key != NULL);
+
+				if (duk__get_own_property_desc_raw(thr, h_target, key, arr_idx, &desc, 0 /*push_value*/)) {
+					int desc_reject;
+
+					DUK_DDD(DUK_DDDPRINT("proxy 'deleteProperty': target has matching property %!O, check for "
+					                     "conflicting property; desc.flags=0x%08lx, "
+					                     "desc.get=%p, desc.set=%p",
+					                     (duk_heaphdr *) key, (unsigned long) desc.flags,
+					                     (void *) desc.get, (void *) desc.set));
+
+					desc_reject = !(desc.flags & DUK_PROPDESC_FLAG_CONFIGURABLE);
+					if (desc_reject) {
+						/* unconditional */
+						DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_PROXY_REJECTED);
+					}
+				}
+				rc = 1;  /* success */
+				goto done_rc;
+			}
+
+			obj = h_target;  /* resume delete to target */
+		}
+#endif  /* DUK_USE_ES6_PROXY */
+
+		duk_to_string(ctx, -1);
+		key = duk_get_hstring(ctx, -1);
+		DUK_ASSERT(key != NULL);
+
+		rc = duk_hobject_delprop_raw(thr, obj, key, throw_flag);
+		goto done_rc;
+	} else if (DUK_TVAL_IS_STRING(tv_obj)) {
+		duk_hstring *h = DUK_TVAL_GET_STRING(tv_obj);
+		DUK_ASSERT(h != NULL);
+
+		duk_to_string(ctx, -1);
+		key = duk_get_hstring(ctx, -1);
+		DUK_ASSERT(key != NULL);
+
+		if (key == DUK_HTHREAD_STRING_LENGTH(thr)) {
+			goto fail_not_configurable;
+		}
+
+		arr_idx = DUK_HSTRING_GET_ARRIDX_FAST(key);
+
+		if (arr_idx != DUK__NO_ARRAY_INDEX &&
+		    arr_idx < DUK_HSTRING_GET_CHARLEN(h)) {
+			goto fail_not_configurable;
+		}
+	}
+	/* FIXME: buffer virtual properties? */
+
+	/* non-object base, no offending virtual property */
+	rc = 1;
+	goto done_rc;
+
+ done_rc:
+	duk_set_top(ctx, entry_top);
+	return rc;
+
+ fail_invalid_base_uncond:
+	/* Note: unconditional throw */
+	DUK_ASSERT(duk_get_top(ctx) == entry_top);
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_INVALID_BASE);
+	return 0;
+
+ fail_proxy_rejected:
+	if (throw_flag) {
+		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_PROXY_REJECTED);
+	}
+	duk_set_top(ctx, entry_top);
+	return 0;
+
+ fail_not_configurable:
+	if (throw_flag) {
+		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_CONFIGURABLE);
+	}
+	duk_set_top(ctx, entry_top);
+	return 0;
+}
+
+/*
+ *  Internal helper to define a property with specific flags, ignoring
+ *  normal semantics such as extensibility, write protection etc.
+ *  Overwrites any existing value and attributes unless caller requests
+ *  that value only be updated if it doesn't already exists.
+ *
+ *  Does not support:
+ *    - virtual properties (error if write attempted)
+ *    - getter/setter properties (error if write attempted)
+ *    - non-default (!= WEC) attributes for array entries (error if attempted)
+ *    - array abandoning: if array part exists, it is always extended
+ *    - array 'length' updating
+ *
+ *  Stack: [... in_val] -> []
+ *
+ *  Used for e.g. built-in initialization and environment record
+ *  operations.
+ */
+
+void duk_hobject_define_property_internal(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_small_uint_t flags) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_propdesc desc;
+	duk_uint32_t arr_idx;
+	duk_int_t e_idx;
+	duk_tval tv_tmp;
+	duk_tval *tv1 = NULL;
+	duk_tval *tv2 = NULL;
+	duk_small_uint_t propflags = flags & DUK_PROPDESC_FLAGS_MASK;  /* mask out flags not actually stored */
+
+	DUK_DDD(DUK_DDDPRINT("define new property (internal): thr=%p, obj=%!O, key=%!O, flags=0x%02lx, val=%!T",
+	                     (void *) thr, (duk_heaphdr *) obj, (duk_heaphdr *) key,
+	                     (unsigned long) flags, (duk_tval *) duk_get_tval(ctx, -1)));
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->heap != NULL);
+	DUK_ASSERT(obj != NULL);
+	DUK_ASSERT(key != NULL);
+
+	DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
+	DUK_ASSERT(duk_is_valid_index(ctx, -1));  /* contains value */
+
+	arr_idx = DUK_HSTRING_GET_ARRIDX_SLOW(key);
+
+	if (duk__get_own_property_desc_raw(thr, obj, key, arr_idx, &desc, 0)) {  /* push_value = 0 */
+		if (desc.e_idx >= 0) {
+			if (flags & DUK_PROPDESC_FLAG_NO_OVERWRITE) {
+				DUK_DDD(DUK_DDDPRINT("property already exists in the entry part -> skip as requested"));
+				goto pop_exit;
+			}
+			DUK_DDD(DUK_DDDPRINT("property already exists in the entry part -> update value and attributes"));
+			if (DUK_UNLIKELY(DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, desc.e_idx))) {
+				DUK_D(DUK_DPRINT("existing property is an accessor, not supported"));
+				goto error_internal;
+			}
+
+			DUK_HOBJECT_E_SET_FLAGS(obj, desc.e_idx, propflags);
+			tv1 = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, desc.e_idx);
+		} else if (desc.a_idx >= 0) {
+			if (flags & DUK_PROPDESC_FLAG_NO_OVERWRITE) {
+				DUK_DDD(DUK_DDDPRINT("property already exists in the array part -> skip as requested"));
+				goto pop_exit;
+			}
+			DUK_DDD(DUK_DDDPRINT("property already exists in the array part -> update value (assert attributes)"));
+			if (propflags != DUK_PROPDESC_FLAGS_WEC) {
+				DUK_D(DUK_DPRINT("existing property in array part, but propflags not WEC (0x%02lx)",
+				                 (unsigned long) propflags));
+				goto error_internal;
+			}
+
+			tv1 = DUK_HOBJECT_A_GET_VALUE_PTR(obj, desc.a_idx);
+		} else {
+			if (flags & DUK_PROPDESC_FLAG_NO_OVERWRITE) {
+				DUK_DDD(DUK_DDDPRINT("property already exists but is virtual -> skip as requested"));
+				goto pop_exit;
+			}
+			DUK_DDD(DUK_DDDPRINT("property already exists but is virtual -> failure"));
+			goto error_virtual;
+		}
+
+		goto write_value;
+	}
+
+	if (DUK_HOBJECT_HAS_ARRAY_PART(obj)) {
+		if (arr_idx != DUK__NO_ARRAY_INDEX) {
+			DUK_DDD(DUK_DDDPRINT("property does not exist, object has array part -> possibly extend array part and write value (assert attributes)"));
+			DUK_ASSERT(propflags == DUK_PROPDESC_FLAGS_WEC);
+
+			/* always grow the array, no sparse / abandon support here */
+			if (arr_idx >= obj->a_size) {
+				duk__grow_props_for_array_item(thr, obj, arr_idx);
+			}
+
+			DUK_ASSERT(arr_idx < obj->a_size);
+			tv1 = DUK_HOBJECT_A_GET_VALUE_PTR(obj, arr_idx);
+			goto write_value;			
+		}
+	}
+
+	DUK_DDD(DUK_DDDPRINT("property does not exist, object belongs in entry part -> allocate new entry and write value and attributes"));
+	e_idx = duk__alloc_entry_checked(thr, obj, key);  /* increases key refcount */
+	DUK_ASSERT(e_idx >= 0);
+	DUK_HOBJECT_E_SET_FLAGS(obj, e_idx, propflags);
+	tv1 = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, e_idx);
+	/* new entry: previous value is garbage; set to undefined to share write_value */
+	DUK_TVAL_SET_UNDEFINED_ACTUAL(tv1);
+	goto write_value;
+
+ write_value:
+	/* tv1 points to value storage */
+
+	tv2 = duk_require_tval(ctx, -1);  /* late lookup, avoid side effects */
+	DUK_DDD(DUK_DDDPRINT("writing/updating value: %!T -> %!T",
+	                     (duk_tval *) tv1, (duk_tval *) tv2));
+
+	DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
+	DUK_TVAL_SET_TVAL(tv1, tv2);
+	DUK_TVAL_INCREF(thr, tv1);
+	DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+	goto pop_exit;
+
+ pop_exit:
+	duk_pop(ctx);  /* remove in_val */
+	return;
+
+ error_internal:
+	DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, DUK_STR_INTERNAL_ERROR);
+	return;
+
+ error_virtual:
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_REDEFINE_VIRT_PROP);
+	return;
+}
+
+/*
+ *  Fast path for defining array indexed values without interning the key.
+ *  This is used by e.g. code for Array prototype and traceback creation so
+ *  must avoid interning.
+ */
+
+void duk_hobject_define_property_internal_arridx(duk_hthread *thr, duk_hobject *obj, duk_uarridx_t arr_idx, duk_small_uint_t flags) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_hstring *key;
+	duk_tval *tv1, *tv2;
+	duk_tval tv_tmp;
+
+	DUK_DDD(DUK_DDDPRINT("define new property (internal) arr_idx fast path: thr=%p, obj=%!O, "
+	                     "arr_idx=%ld, flags=0x%02lx, val=%!T",
+	                     (void *) thr, obj, (long) arr_idx, (unsigned long) flags,
+	                     (duk_tval *) duk_get_tval(ctx, -1)));
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->heap != NULL);
+	DUK_ASSERT(obj != NULL);
+
+	if (DUK_HOBJECT_HAS_ARRAY_PART(obj) &&
+	    arr_idx != DUK__NO_ARRAY_INDEX &&
+	    flags == DUK_PROPDESC_FLAGS_WEC) {
+		DUK_ASSERT((flags & DUK_PROPDESC_FLAG_NO_OVERWRITE) == 0);  /* covered by comparison */
+
+		DUK_DDD(DUK_DDDPRINT("define property to array part (property may or may not exist yet)"));
+
+		/* always grow the array, no sparse / abandon support here */
+		if (arr_idx >= obj->a_size) {
+			duk__grow_props_for_array_item(thr, obj, arr_idx);
+		}
+
+		DUK_ASSERT(arr_idx < obj->a_size);
+		tv1 = DUK_HOBJECT_A_GET_VALUE_PTR(obj, arr_idx);
+		tv2 = duk_require_tval(ctx, -1);
+
+		DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
+		DUK_TVAL_SET_TVAL(tv1, tv2);
+		DUK_TVAL_INCREF(thr, tv1);
+		DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+
+		duk_pop(ctx);  /* [ ...val ] -> [ ... ] */
+		return;
+	}
+
+	DUK_DDD(DUK_DDDPRINT("define property fast path didn't work, use slow path"));
+
+	duk_push_uint(ctx, (duk_uint_t) arr_idx);
+	key = duk_to_hstring(ctx, -1);
+	DUK_ASSERT(key != NULL);
+	duk_insert(ctx, -2);  /* [ ... val key ] -> [ ... key val ] */
+
+	duk_hobject_define_property_internal(thr, obj, key, flags);
+
+	duk_pop(ctx);  /* [ ... key ] -> [ ... ] */
+}
+
+/*
+ *  Internal helper for defining an accessor property, ignoring
+ *  normal semantics such as extensibility, write protection etc.
+ *  Overwrites any existing value and attributes.  This is called
+ *  very rarely, so the implementation first sets a value to undefined
+ *  and then changes the entry to an accessor (this is to save code space).
+ */
+
+void duk_hobject_define_accessor_internal(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_hobject *getter, duk_hobject *setter, duk_small_uint_t propflags) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_int_t e_idx;
+	duk_int_t h_idx;
+
+	DUK_DDD(DUK_DDDPRINT("define new accessor (internal): thr=%p, obj=%!O, key=%!O, "
+	                     "getter=%!O, setter=%!O, flags=0x%02lx",
+	                     (void *) thr, (duk_heaphdr *) obj, (duk_heaphdr *) key,
+	                     (duk_heaphdr *) getter, (duk_heaphdr *) setter,
+	                     (unsigned long) propflags));
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->heap != NULL);
+	DUK_ASSERT(obj != NULL);
+	DUK_ASSERT(key != NULL);
+	DUK_ASSERT((propflags & ~DUK_PROPDESC_FLAGS_MASK) == 0);
+	/* setter and/or getter may be NULL */
+
+	DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
+
+	/* force the property to 'undefined' to create a slot for it */
+	duk_push_undefined(ctx);
+	duk_hobject_define_property_internal(thr, obj, key, propflags);
+	duk_hobject_find_existing_entry(obj, key, &e_idx, &h_idx);
+	DUK_DDD(DUK_DDDPRINT("accessor slot: e_idx=%ld, h_idx=%ld", (long) e_idx, (long) h_idx));
+	DUK_ASSERT(e_idx >= 0);
+	DUK_ASSERT((duk_uint32_t) e_idx < obj->e_used);
+
+	/* no need to decref, as previous value is 'undefined' */
+	DUK_HOBJECT_E_SLOT_SET_ACCESSOR(obj, e_idx);
+	DUK_HOBJECT_E_SET_VALUE_GETTER(obj, e_idx, getter);
+	DUK_HOBJECT_E_SET_VALUE_SETTER(obj, e_idx, setter);
+	DUK_HOBJECT_INCREF(thr, getter);
+	DUK_HOBJECT_INCREF(thr, setter);
+}
+
+/*
+ *  Internal helpers for managing object 'length'
+ */
+
+/* XXX: awkward helpers */
+
+void duk_hobject_set_length(duk_hthread *thr, duk_hobject *obj, duk_uint32_t length) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_push_hobject(ctx, obj);
+	duk_push_hstring_stridx(ctx, DUK_STRIDX_LENGTH);
+	duk_push_u32(ctx, length);
+	(void) duk_hobject_putprop(thr, duk_get_tval(ctx, -3), duk_get_tval(ctx, -2), duk_get_tval(ctx, -1), 0);
+	duk_pop_n(ctx, 3);
+}
+
+void duk_hobject_set_length_zero(duk_hthread *thr, duk_hobject *obj) {
+	duk_hobject_set_length(thr, obj, 0);
+}
+
+duk_uint32_t duk_hobject_get_length(duk_hthread *thr, duk_hobject *obj) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_double_t val;
+	duk_push_hobject(ctx, obj);
+	duk_push_hstring_stridx(ctx, DUK_STRIDX_LENGTH);
+	(void) duk_hobject_getprop(thr, duk_get_tval(ctx, -2), duk_get_tval(ctx, -1));
+	val = duk_to_number(ctx, -1);
+	duk_pop_n(ctx, 3);
+	if (val >= 0.0 && val < DUK_DOUBLE_2TO32) {
+		return (duk_uint32_t) val;
+	}
+	return 0;
+}
+
+/*
+ *  Object.getOwnPropertyDescriptor()  (E5 Sections 15.2.3.3, 8.10.4)
+ *
+ *  This is an actual function call.
+ */
+
+duk_ret_t duk_hobject_object_get_own_property_descriptor(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hobject *obj;
+	duk_hstring *key;
+	duk_propdesc pd;
+	duk_bool_t rc;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->heap != NULL);
+
+	obj = duk_require_hobject(ctx, 0);
+	(void) duk_to_string(ctx, 1);
+	key = duk_require_hstring(ctx, 1);
+
+	DUK_ASSERT(obj != NULL);
+	DUK_ASSERT(key != NULL);
+
+	DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
+
+	rc = duk__get_own_property_desc(thr, obj, key, &pd, 1);  /* push_value = 1 */
+	if (!rc) {
+		duk_push_undefined(ctx);
+
+		/* [obj key undefined] */
+		return 1;
+	}
+
+	duk_push_object(ctx);
+
+	/* [obj key value desc] */
+
+	if (DUK_PROPDESC_IS_ACCESSOR(&pd)) {
+		/* If a setter/getter is missing (undefined), the descriptor must
+		 * still have the property present with the value 'undefined'.
+		 */
+		if (pd.get) {
+			duk_push_hobject(ctx, pd.get);
+		} else {
+			duk_push_undefined(ctx);
+		}
+		duk_put_prop_stridx(ctx, -2, DUK_STRIDX_GET);
+		if (pd.set) {
+			duk_push_hobject(ctx, pd.set);
+		} else {
+			duk_push_undefined(ctx);
+		}
+		duk_put_prop_stridx(ctx, -2, DUK_STRIDX_SET);
+	} else {
+		duk_dup(ctx, -2);  /* [obj key value desc value] */
+		duk_put_prop_stridx(ctx, -2, DUK_STRIDX_VALUE);
+		duk_push_boolean(ctx, DUK_PROPDESC_IS_WRITABLE(&pd));
+		duk_put_prop_stridx(ctx, -2, DUK_STRIDX_WRITABLE);
+
+		/* [obj key value desc] */
+	}
+	duk_push_boolean(ctx, DUK_PROPDESC_IS_ENUMERABLE(&pd));
+	duk_put_prop_stridx(ctx, -2, DUK_STRIDX_ENUMERABLE);
+	duk_push_boolean(ctx, DUK_PROPDESC_IS_CONFIGURABLE(&pd));
+	duk_put_prop_stridx(ctx, -2, DUK_STRIDX_CONFIGURABLE);
+
+	/* [obj key value desc] */
+	return 1;
+}
+
+/*
+ *  NormalizePropertyDescriptor().
+ *
+ *  Internal helper to convert an external property descriptor on stack top
+ *  to a normalized form with plain, coerced values.  The original descriptor
+ *  object is not altered.
+ */
+
+/* XXX: very basic optimization -> duk_get_prop_stridx_top */
+
+static void duk__normalize_property_descriptor(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_idx_t idx_in;
+	duk_idx_t idx_out;
+	duk_idx_t target_top;
+	duk_bool_t is_data_desc = 0;
+	duk_bool_t is_acc_desc = 0;
+
+	DUK_ASSERT(ctx != NULL);
+
+	/* must be an object, otherwise TypeError (E5.1 Section 8.10.5, step 1) */
+	(void) duk_require_hobject(ctx, -1);
+
+	idx_in = duk_require_normalize_index(ctx, -1);
+	duk_push_object(ctx);  /* [... desc_in desc_out] */
+	idx_out = idx_in + 1;
+
+	/* this approach allows us to be care-free with the "stack policy"
+	 * until the very end.
+	 */
+	target_top = duk_get_top(ctx);
+
+	if (duk_get_prop_stridx(ctx, idx_in, DUK_STRIDX_VALUE)) {
+		is_data_desc = 1;
+		duk_put_prop_stridx(ctx, idx_out, DUK_STRIDX_VALUE);
+	}
+
+	if (duk_get_prop_stridx(ctx, idx_in, DUK_STRIDX_WRITABLE)) {
+		is_data_desc = 1;
+		duk_to_boolean(ctx, -1);
+		duk_put_prop_stridx(ctx, idx_out, DUK_STRIDX_WRITABLE);
+	}
+
+	if (duk_get_prop_stridx(ctx, idx_in, DUK_STRIDX_GET)) {
+		duk_tval *tv = duk_require_tval(ctx, -1);
+		is_acc_desc = 1;
+		if (DUK_TVAL_IS_UNDEFINED(tv) ||
+		    (DUK_TVAL_IS_OBJECT(tv) &&
+		     DUK_HOBJECT_IS_CALLABLE(DUK_TVAL_GET_OBJECT(tv)))) {
+			duk_put_prop_stridx(ctx, idx_out, DUK_STRIDX_GET);
+		} else {
+			goto type_error;
+		}
+	}
+
+	if (duk_get_prop_stridx(ctx, idx_in, DUK_STRIDX_SET)) {
+		duk_tval *tv = duk_require_tval(ctx, -1);
+		is_acc_desc = 1;
+		if (DUK_TVAL_IS_UNDEFINED(tv) ||
+		    (DUK_TVAL_IS_OBJECT(tv) &&
+		     DUK_HOBJECT_IS_CALLABLE(DUK_TVAL_GET_OBJECT(tv)))) {
+			duk_put_prop_stridx(ctx, idx_out, DUK_STRIDX_SET);
+		} else {
+			goto type_error;
+		}
+	}
+
+	if (duk_get_prop_stridx(ctx, idx_in, DUK_STRIDX_ENUMERABLE)) {
+		duk_to_boolean(ctx, -1);
+		duk_put_prop_stridx(ctx, idx_out, DUK_STRIDX_ENUMERABLE);
+	}
+
+	if (duk_get_prop_stridx(ctx, idx_in, DUK_STRIDX_CONFIGURABLE)) {
+		duk_to_boolean(ctx, -1);
+		duk_put_prop_stridx(ctx, idx_out, DUK_STRIDX_CONFIGURABLE);
+	}
+
+	if (is_data_desc && is_acc_desc) {
+		goto type_error;
+	}
+
+	/* pop any crud */
+	duk_set_top(ctx, target_top);
+
+	/* [... desc_in desc_out] */
+
+	duk_remove(ctx, -2);
+
+	/* [... desc_out] */
+
+	return;
+
+ type_error:
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_INVALID_DESCRIPTOR);
+}
+
+/*
+ *  Object.defineProperty()  (E5 Section 15.2.3.6)
+ *
+ *  Inlines ToPropertyDescriptor() and all [[DefineOwnProperty]] exotic
+ *  behaviors.
+ *
+ *  Note: Ecmascript compliant [[DefineOwnProperty]](P, Desc, Throw) is not
+ *  implemented directly, but Object.defineProperty() serves its purpose.
+ *  We don't need the [[DefineOwnProperty]] internally and we don't have a
+ *  property descriptor with 'missing values' so it's easier to avoid it
+ *  entirely.
+ *
+ *  This is a Duktape/C function.
+ */
+
+/* XXX: this is a major target for size optimization */
+
+duk_ret_t duk_hobject_object_define_property(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hobject *obj;
+	duk_hstring *key;
+	duk_hobject *desc;
+	duk_uint32_t arr_idx;
+	duk_idx_t idx_desc;
+	duk_tval tv;
+	duk_bool_t has_enumerable;
+	duk_bool_t has_configurable;
+	duk_bool_t has_writable;
+	duk_bool_t has_value;
+	duk_bool_t has_get;
+	duk_bool_t has_set;
+	duk_bool_t is_enumerable;
+	duk_bool_t is_configurable;
+	duk_bool_t is_writable;
+	duk_idx_t idx_value;
+	duk_hobject *get;
+	duk_hobject *set;
+	duk_small_uint_t new_flags;
+	duk_propdesc curr;
+	duk_uint32_t arridx_new_array_length;  /* != 0 => post-update for array 'length' (used when key is an array index) */
+	duk_uint32_t arrlen_old_len;
+	duk_uint32_t arrlen_new_len;
+	duk_bool_t pending_write_protect;
+	duk_bool_t throw_flag = 1;   /* Object.defineProperty() calls [[DefineOwnProperty]] with Throw=true */
+
+	DUK_DDD(DUK_DDDPRINT("Object.defineProperty(): thr=%p obj=%!T key=%!T desc=%!T",
+	                     (void *) thr,
+	                     (duk_tval *) duk_get_tval(ctx, 0),
+	                     (duk_tval *) duk_get_tval(ctx, 1),
+	                     (duk_tval *) duk_get_tval(ctx, 2)));
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->heap != NULL);
+	DUK_ASSERT(ctx != NULL);
+
+	DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
+
+	obj = duk_require_hobject(ctx, 0);
+	(void) duk_to_string(ctx, 1);
+	key = duk_require_hstring(ctx, 1);
+	desc = duk_require_hobject(ctx, 2);
+	DUK_UNREF(desc);
+	idx_desc = 2;
+
+	DUK_ASSERT(obj != NULL);
+	DUK_ASSERT(key != NULL);
+	DUK_ASSERT(desc != NULL);
+
+	arr_idx = DUK_HSTRING_GET_ARRIDX_SLOW(key);
+
+	DUK_DDD(DUK_DDDPRINT("Object.defineProperty(): thr=%p obj=%!O key=%!O arr_idx=0x%08lx desc=%!O",
+	                     (void *) thr, (duk_heaphdr *) obj, (duk_heaphdr *) key,
+	                     (unsigned long) arr_idx, (duk_heaphdr *) desc));
+
+	/* Many of the above are just assigned over but are given base values to
+	 * avoid warnings with some compilers.  But because the values are unused,
+	 * scan-build will complain about them; silence with DUK_UNREF().
+	 */
+
+	has_enumerable = 0; DUK_UNREF(has_enumerable);
+	has_configurable = 0; DUK_UNREF(has_configurable);
+	has_value = 0; DUK_UNREF(has_value);
+	has_writable = 0; DUK_UNREF(has_writable);
+	has_get = 0; DUK_UNREF(has_get);
+	has_set = 0; DUK_UNREF(has_set);
+	is_enumerable = 0; DUK_UNREF(is_enumerable);
+	is_configurable = 0; DUK_UNREF(is_configurable);
+	is_writable = 0; DUK_UNREF(is_writable);
+	idx_value = -1; DUK_UNREF(idx_value);
+	get = NULL; DUK_UNREF(get);
+	set = NULL; DUK_UNREF(set);
+
+	arridx_new_array_length = 0;
+	pending_write_protect = 0;
+	arrlen_old_len = 0;
+	arrlen_new_len = 0;
+
+	/*
+	 *  Extract property descriptor values as required in ToPropertyDescriptor().
+	 *  However, don't create an explicit property descriptor object: we don't
+	 *  want to create a new Ecmascript object, and the internal property descriptor
+	 *  does not support partial descriptors.
+	 *
+	 *  Note that ToPropertyDescriptor() does coercions with potential errors, so
+	 *  all coercions must be done first.  Boolean conversion of 'undefined' is false.
+	 */
+
+	is_enumerable = duk_get_prop_stridx_boolean(ctx, idx_desc, DUK_STRIDX_ENUMERABLE, &has_enumerable);
+	is_configurable = duk_get_prop_stridx_boolean(ctx, idx_desc, DUK_STRIDX_CONFIGURABLE, &has_configurable);
+
+	has_value = duk_get_prop_stridx(ctx, idx_desc, DUK_STRIDX_VALUE);
+	if (has_value) {
+		/* Note: we don't want to store a pointer to an duk_tval in the
+		 * valstack here, because a valstack resize (which may occur
+		 * on any gc) might invalidate it.
+		 */
+		idx_value = duk_require_top_index(ctx);
+	} else {
+		idx_value = -1;
+	}
+	/* leave value on stack intentionally to ensure we can refer to it later */
+
+	is_writable = duk_get_prop_stridx_boolean(ctx, idx_desc, DUK_STRIDX_WRITABLE, &has_writable);
+
+	has_get = duk_get_prop_stridx(ctx, idx_desc, DUK_STRIDX_GET);
+	get = NULL;
+	if (has_get && !duk_is_undefined(ctx, -1)) {
+		/* XXX: get = duk_require_callable_hobject(ctx, -1)? */
+		get = duk_require_hobject(ctx, -1);
+		DUK_ASSERT(get != NULL);
+		if (!DUK_HOBJECT_IS_CALLABLE(get)) {
+			goto fail_invalid_desc;
+		}
+	}
+	/* leave get on stack */
+
+	has_set = duk_get_prop_stridx(ctx, idx_desc, DUK_STRIDX_SET);
+	set = NULL;
+	if (has_set && !duk_is_undefined(ctx, -1)) {
+		set = duk_require_hobject(ctx, -1);
+		DUK_ASSERT(set != NULL);
+		if (!DUK_HOBJECT_IS_CALLABLE(set)) {
+			goto fail_invalid_desc;
+		}
+	}
+	/* leave set on stack */
+
+	if ((has_set || has_get) && (has_value || has_writable)) {
+		goto fail_invalid_desc;
+	}
+
+	/* [obj key desc value get set] */
+
+	DUK_DDD(DUK_DDDPRINT("has_enumerable=%ld is_enumerable=%ld "
+	                     "has_configurable=%ld is_configurable=%ld "
+	                     "has_writable=%ld is_writable=%ld "
+	                     "has_value=%ld value=%!T "
+	                     "has_get=%ld get=%p=%!O "
+	                     "has_set=%ld set=%p=%!O ",
+	                     (long) has_enumerable, (long) is_enumerable,
+	                     (long) has_configurable, (long) is_configurable,
+	                     (long) has_writable, (long) is_writable,
+	                     (long) has_value, (duk_tval *) duk_get_tval(ctx, idx_value),
+	                     (long) has_get, (void *) get, (duk_heaphdr *) get,
+	                     (long) has_set, (void *) set, (duk_heaphdr *) set));
+
+	/*
+	 *  Array exotic behaviors can be implemented at this point.  The local variables
+	 *  are essentially a 'value copy' of the input descriptor (Desc), which is modified
+	 *  by the Array [[DefineOwnProperty]] (E5 Section 15.4.5.1).
+	 */
+
+	if (!DUK_HOBJECT_HAS_EXOTIC_ARRAY(obj)) {
+		goto skip_array_exotic;
+	}
+
+	if (key == DUK_HTHREAD_STRING_LENGTH(thr)) {
+		/* E5 Section 15.4.5.1, step 3, steps a - i are implemented here, j - n at the end */
+		if (!has_value) {
+			DUK_DDD(DUK_DDDPRINT("exotic array behavior for 'length', but no value in descriptor -> normal behavior"));
+			goto skip_array_exotic;
+		}
+	
+		DUK_DDD(DUK_DDDPRINT("exotic array behavior for 'length', value present in descriptor -> exotic behavior"));
+
+		/*
+		 *  Get old and new length
+		 */
+
+		/* Note: reuse 'curr' as a temp propdesc */
+		arrlen_old_len = duk__get_old_array_length(thr, obj, &curr);
+
+		duk_dup(ctx, idx_value);
+		arrlen_new_len = duk__to_new_array_length_checked(thr);
+		duk_replace(ctx, idx_value);  /* step 3.e: replace 'Desc.[[Value]]' */
+
+		DUK_DDD(DUK_DDDPRINT("old_len=%ld, new_len=%ld", (long) arrlen_old_len, (long) arrlen_new_len));
+
+		if (arrlen_new_len >= arrlen_old_len) {
+			/* standard behavior, step 3.f.i */
+			DUK_DDD(DUK_DDDPRINT("new length is same or higher as previous => standard behavior"));
+			goto skip_array_exotic;
+		}
+		DUK_DDD(DUK_DDDPRINT("new length is smaller than previous => exotic post behavior"));
+
+		/* FIXME: consolidated algorithm step 15.f -> redundant? */
+		if (!(curr.flags & DUK_PROPDESC_FLAG_WRITABLE)) {
+			/* Note: 'curr' refers to 'length' propdesc */
+			goto fail_not_writable_array_length;
+		}
+
+		/* steps 3.h and 3.i */
+		if (has_writable && !is_writable) {
+			DUK_DDD(DUK_DDDPRINT("desc writable is false, force it back to true, and flag pending write protect"));
+			is_writable = 1;
+			pending_write_protect = 1;
+		}
+
+		/* remaining actual steps are carried out if standard DefineOwnProperty succeeds */
+	} else if (arr_idx != DUK__NO_ARRAY_INDEX) {
+		/* FIXME: any chance of unifying this with the 'length' key handling? */
+
+		/* E5 Section 15.4.5.1, step 4 */
+		duk_uint32_t old_len;
+
+		/* Note: use 'curr' as a temp propdesc */
+		old_len = duk__get_old_array_length(thr, obj, &curr);
+
+		if (arr_idx >= old_len) {
+			DUK_DDD(DUK_DDDPRINT("defineProperty requires array length update "
+			                     "(arr_idx=%ld, old_len=%ld)",
+			                     (long) arr_idx, (long) old_len));
+
+			if (!(curr.flags & DUK_PROPDESC_FLAG_WRITABLE)) {
+				/* Note: 'curr' refers to 'length' propdesc */
+				goto fail_not_writable_array_length;
+			}
+
+			/* actual update happens once write has been completed without
+			 * error below.
+			 */
+			DUK_ASSERT(arr_idx != 0xffffffffUL);
+			arridx_new_array_length = arr_idx + 1;
+		} else {
+			DUK_DDD(DUK_DDDPRINT("defineProperty does not require length update "
+			                     "(arr_idx=%ld, old_len=%ld) -> standard behavior",
+			                     (long) arr_idx, (long) old_len));
+		}
+	}
+ skip_array_exotic:
+
+	/*
+	 *  Actual Object.defineProperty() default algorithm.
+	 */
+
+	/*
+	 *  First check whether property exists; if not, simple case.  This covers
+	 *  steps 1-4.
+	 */
+
+	if (!duk__get_own_property_desc_raw(thr, obj, key, arr_idx, &curr, 1)) {
+		DUK_DDD(DUK_DDDPRINT("property does not exist"));
+
+		if (!DUK_HOBJECT_HAS_EXTENSIBLE(obj)) {
+			goto fail_not_extensible;
+		}
+
+		/* FIXME: share final setting code for value and flags?  difficult because
+		 * refcount code is different.  Share entry allocation?  But can't allocate
+		 * until array index checked.
+		 */
+
+		/* steps 4.a and 4.b are tricky */
+		if (has_set || has_get) {
+			duk_int_t e_idx;
+
+			DUK_DDD(DUK_DDDPRINT("create new accessor property"));
+
+			DUK_ASSERT(has_set || set == NULL);
+			DUK_ASSERT(has_get || get == NULL);
+			DUK_ASSERT(!has_value);
+			DUK_ASSERT(!has_writable);
+
+			new_flags = DUK_PROPDESC_FLAG_ACCESSOR;  /* defaults, E5 Section 8.6.1, Table 7 */
+			if (has_enumerable && is_enumerable) {
+				new_flags |= DUK_PROPDESC_FLAG_ENUMERABLE;
+			}
+			if (has_configurable && is_configurable) {
+				new_flags |= DUK_PROPDESC_FLAG_CONFIGURABLE;
+			}
+
+			if (arr_idx != DUK__NO_ARRAY_INDEX && DUK_HOBJECT_HAS_ARRAY_PART(obj)) {
+				DUK_DDD(DUK_DDDPRINT("accessor cannot go to array part, abandon array"));
+				duk__abandon_array_checked(thr, obj);
+			}
+
+			/* write to entry part */
+			e_idx = duk__alloc_entry_checked(thr, obj, key);
+			DUK_ASSERT(e_idx >= 0);
+
+			DUK_HOBJECT_E_SET_VALUE_GETTER(obj, e_idx, get);
+			DUK_HOBJECT_E_SET_VALUE_SETTER(obj, e_idx, set);
+			DUK_HOBJECT_INCREF(thr, get);
+			DUK_HOBJECT_INCREF(thr, set);
+
+			DUK_HOBJECT_E_SET_FLAGS(obj, e_idx, new_flags);
+			goto success_exotics;
+		} else {
+			duk_int_t e_idx;
+			duk_tval *tv2;
+
+			DUK_DDD(DUK_DDDPRINT("create new data property"));
+
+			DUK_ASSERT(!has_set);
+			DUK_ASSERT(!has_get);
+
+			new_flags = 0;  /* defaults, E5 Section 8.6.1, Table 7 */
+			if (has_writable && is_writable) {
+				new_flags |= DUK_PROPDESC_FLAG_WRITABLE;
+			}
+			if (has_enumerable && is_enumerable) {
+				new_flags |= DUK_PROPDESC_FLAG_ENUMERABLE;
+			}
+			if (has_configurable && is_configurable) {
+				new_flags |= DUK_PROPDESC_FLAG_CONFIGURABLE;
+			}
+			if (has_value) {
+				duk_tval *tv_tmp = duk_require_tval(ctx, idx_value);
+				DUK_TVAL_SET_TVAL(&tv, tv_tmp);
+			} else {
+				DUK_TVAL_SET_UNDEFINED_ACTUAL(&tv);  /* default value */
+			}
+
+			if (arr_idx != DUK__NO_ARRAY_INDEX && DUK_HOBJECT_HAS_ARRAY_PART(obj)) {
+				if (new_flags == DUK_PROPDESC_FLAGS_WEC) {
+#if 0
+					DUK_DDD(DUK_DDDPRINT("new data property attributes match array defaults, attempt to write to array part"));
+					/* may become sparse...*/
+#endif
+					/* FIXME: handling for array part missing now; this doesn't affect
+					 * compliance but causes array entry writes using defineProperty()
+					 * to always abandon array part.
+					 */
+				}
+				DUK_DDD(DUK_DDDPRINT("new data property cannot go to array part, abandon array"));
+				duk__abandon_array_checked(thr, obj);
+				/* fall through */
+			}
+
+			/* write to entry part */
+			e_idx = duk__alloc_entry_checked(thr, obj, key);
+			DUK_ASSERT(e_idx >= 0);
+			tv2 = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, e_idx);
+			DUK_TVAL_SET_TVAL(tv2, &tv);
+			DUK_TVAL_INCREF(thr, tv2);
+
+			DUK_HOBJECT_E_SET_FLAGS(obj, e_idx, new_flags);
+			goto success_exotics;
+		}
+		DUK_UNREACHABLE();
+	}
+
+	/* we currently assume virtual properties are not configurable (as none of them are) */
+	DUK_ASSERT((curr.e_idx >= 0 || curr.a_idx >= 0) || !(curr.flags & DUK_PROPDESC_FLAG_CONFIGURABLE));
+
+	/* [obj key desc value get set curr_value] */
+
+	/*
+	 *  Property already exists.  Steps 5-6 detect whether any changes need
+	 *  to be made.
+	 */
+
+	if (has_enumerable) {
+		if (is_enumerable) {
+			if (!(curr.flags & DUK_PROPDESC_FLAG_ENUMERABLE)) {
+				goto need_check;
+			}
+		} else {
+			if (curr.flags & DUK_PROPDESC_FLAG_ENUMERABLE) {
+				goto need_check;
+			}
+		}
+	}
+	if (has_configurable) {
+		if (is_configurable) {
+			if (!(curr.flags & DUK_PROPDESC_FLAG_CONFIGURABLE)) {
+				goto need_check;
+			}
+		} else {
+			if (curr.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) {
+				goto need_check;
+			}
+		}
+	}
+	if (has_value) {
+		duk_tval *tmp1;
+		duk_tval *tmp2;
+	
+		/* attempt to change from accessor to data property */
+		if (curr.flags & DUK_PROPDESC_FLAG_ACCESSOR) {
+			goto need_check;
+		}
+
+		tmp1 = duk_require_tval(ctx, -1);         /* curr value */
+		tmp2 = duk_require_tval(ctx, idx_value);  /* new value */
+		if (!duk_js_samevalue(tmp1, tmp2)) {
+			goto need_check;
+		}
+	}
+	if (has_writable) {
+		/* attempt to change from accessor to data property */
+		if (curr.flags & DUK_PROPDESC_FLAG_ACCESSOR) {
+			goto need_check;
+		}
+
+		if (is_writable) {
+			if (!(curr.flags & DUK_PROPDESC_FLAG_WRITABLE)) {
+				goto need_check;
+			}
+		} else {
+			if (curr.flags & DUK_PROPDESC_FLAG_WRITABLE) {
+				goto need_check;
+			}
+		}
+	}
+	if (has_set) {
+		if (curr.flags & DUK_PROPDESC_FLAG_ACCESSOR) {
+			if (set != curr.set) {
+				goto need_check;
+			}
+		} else {
+			goto need_check;
+		}
+	}
+	if (has_get) {
+		if (curr.flags & DUK_PROPDESC_FLAG_ACCESSOR) {
+			if (get != curr.get) {
+				goto need_check;
+			}
+		} else {
+			goto need_check;
+		}
+	}
+
+	/* property exists, either 'desc' is empty, or all values
+	 * match (SameValue)
+	 */
+	goto success_no_exotics;
+
+ need_check:
+
+	/*
+	 *  Some change(s) need to be made.  Steps 7-11.
+	 */
+
+	/* shared checks for all descriptor types */
+	if (!(curr.flags & DUK_PROPDESC_FLAG_CONFIGURABLE)) {
+		if (has_configurable && is_configurable) {
+			goto fail_not_configurable;
+		}
+		if (has_enumerable) {
+			if (curr.flags & DUK_PROPDESC_FLAG_ENUMERABLE) {
+				if (!is_enumerable) {
+					goto fail_not_configurable;
+				}
+			} else {
+				if (is_enumerable) {
+					goto fail_not_configurable;
+				}
+			}
+		}
+	}
+
+	/* reject attempt to change virtual properties: not part of the
+	 * standard algorithm, applies currently to e.g. virtual index
+	 * properties of buffer objects (which are virtual but writable).
+	 */
+	if (curr.flags & DUK_PROPDESC_FLAG_VIRTUAL) {
+		goto fail_virtual;
+	}
+
+	/* descriptor type specific checks */
+	if (has_set || has_get) {
+		/* IsAccessorDescriptor(desc) == true */
+		DUK_ASSERT(!has_writable);
+		DUK_ASSERT(!has_value);
+
+		if (curr.flags & DUK_PROPDESC_FLAG_ACCESSOR) {
+			/* curr and desc are accessors */
+			if (!(curr.flags & DUK_PROPDESC_FLAG_CONFIGURABLE)) {
+				if (has_set && set != curr.set) {
+					goto fail_not_configurable;
+				}
+				if (has_get && get != curr.get) {
+					goto fail_not_configurable;
+				}
+			}
+		} else {
+			int rc;
+			duk_tval tv_tmp;
+			duk_tval *tv1;
+
+			/* curr is data, desc is accessor */
+			if (!(curr.flags & DUK_PROPDESC_FLAG_CONFIGURABLE)) {
+				goto fail_not_configurable;
+			}
+
+			DUK_DDD(DUK_DDDPRINT("convert property to accessor property"));
+			if (curr.a_idx >= 0) {
+				DUK_DDD(DUK_DDDPRINT("property to convert is stored in an array entry, abandon array and re-lookup"));
+				duk__abandon_array_checked(thr, obj);
+				duk_pop(ctx);  /* remove old value */
+				rc = duk__get_own_property_desc_raw(thr, obj, key, arr_idx, &curr, 1);
+				DUK_UNREF(rc);
+				DUK_ASSERT(rc != 0);
+				DUK_ASSERT(curr.e_idx >= 0 && curr.a_idx < 0);
+			}
+
+			DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, curr.e_idx));
+
+			tv1 = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, curr.e_idx);
+			DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
+			DUK_TVAL_SET_UNDEFINED_UNUSED(tv1);
+			DUK_TVAL_DECREF(thr, &tv_tmp);
+
+			DUK_HOBJECT_E_SET_VALUE_GETTER(obj, curr.e_idx, NULL);
+			DUK_HOBJECT_E_SET_VALUE_SETTER(obj, curr.e_idx, NULL);
+			DUK_HOBJECT_E_SLOT_CLEAR_WRITABLE(obj, curr.e_idx);
+			DUK_HOBJECT_E_SLOT_SET_ACCESSOR(obj, curr.e_idx);
+
+			DUK_DDD(DUK_DDDPRINT("flags after data->accessor conversion: 0x%02lx",
+			                     (unsigned long) DUK_HOBJECT_E_GET_FLAGS(obj, curr.e_idx)));
+
+			/* re-lookup to update curr.flags -- FIXME: faster to update directly */
+			duk_pop(ctx);  /* remove old value */
+			rc = duk__get_own_property_desc_raw(thr, obj, key, arr_idx, &curr, 1);
+			DUK_UNREF(rc);
+			DUK_ASSERT(rc != 0);
+		}
+	} else if (has_value || has_writable) {
+		/* IsDataDescriptor(desc) == true */
+		DUK_ASSERT(!has_set);
+		DUK_ASSERT(!has_get);
+
+		if (curr.flags & DUK_PROPDESC_FLAG_ACCESSOR) {
+			duk_bool_t rc;
+			duk_hobject *tmp;
+
+			/* curr is accessor, desc is data */
+			if (!(curr.flags & DUK_PROPDESC_FLAG_CONFIGURABLE)) {
+				goto fail_not_configurable;
+			}
+
+			/* curr is accessor -> cannot be in array part */
+			DUK_ASSERT(curr.e_idx >= 0 && curr.a_idx < 0);
+
+			DUK_DDD(DUK_DDDPRINT("convert property to data property"));
+
+			DUK_ASSERT(DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, curr.e_idx));
+			tmp = DUK_HOBJECT_E_GET_VALUE_GETTER(obj, curr.e_idx);
+			DUK_UNREF(tmp);
+			DUK_HOBJECT_E_SET_VALUE_GETTER(obj, curr.e_idx, NULL);
+			DUK_HOBJECT_DECREF(thr, tmp);
+			tmp = DUK_HOBJECT_E_GET_VALUE_SETTER(obj, curr.e_idx);
+			DUK_UNREF(tmp);
+			DUK_HOBJECT_E_SET_VALUE_SETTER(obj, curr.e_idx, NULL);
+			DUK_HOBJECT_DECREF(thr, tmp);
+
+			DUK_TVAL_SET_UNDEFINED_ACTUAL(DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, curr.e_idx));
+			DUK_HOBJECT_E_SLOT_CLEAR_WRITABLE(obj, curr.e_idx);
+			DUK_HOBJECT_E_SLOT_CLEAR_ACCESSOR(obj, curr.e_idx);
+
+			DUK_DDD(DUK_DDDPRINT("flags after accessor->data conversion: 0x%02lx",
+			                     (unsigned long) DUK_HOBJECT_E_GET_FLAGS(obj, curr.e_idx)));
+
+			/* re-lookup to update curr.flags -- FIXME: faster to update directly */
+			duk_pop(ctx);  /* remove old value */
+			rc = duk__get_own_property_desc_raw(thr, obj, key, arr_idx, &curr, 1);
+			DUK_UNREF(rc);
+			DUK_ASSERT(rc != 0);
+		} else {
+			/* curr and desc are data */
+			if (!(curr.flags & DUK_PROPDESC_FLAG_CONFIGURABLE)) {
+				if (!(curr.flags & DUK_PROPDESC_FLAG_WRITABLE) && has_writable && is_writable) {
+					goto fail_not_configurable;
+				}
+				/* Note: changing from writable to non-writable is OK */
+				if (!(curr.flags & DUK_PROPDESC_FLAG_WRITABLE) && has_value) {
+					duk_tval *tmp1 = duk_require_tval(ctx, -1);         /* curr value */
+					duk_tval *tmp2 = duk_require_tval(ctx, idx_value);  /* new value */
+					if (!duk_js_samevalue(tmp1, tmp2)) {
+						goto fail_not_configurable;
+					}
+				}
+			}
+		}
+	} else {
+		/* IsGenericDescriptor(desc) == true; this means in practice that 'desc'
+		 * only has [[Enumerable]] or [[Configurable]] flag updates, which are
+		 * allowed at this point.
+		 */
+
+		DUK_ASSERT(!has_value && !has_writable && !has_get && !has_set);
+	}
+
+	/*
+	 *  Start doing property attributes updates.  Steps 12-13.
+	 *
+	 *  Start by computing new attribute flags without writing yet.
+	 *  Property type conversion is done above if necessary.
+	 */
+
+	new_flags = curr.flags;
+
+	if (has_enumerable) {
+		if (is_enumerable) {
+			new_flags |= DUK_PROPDESC_FLAG_ENUMERABLE;
+		} else {
+			new_flags &= ~DUK_PROPDESC_FLAG_ENUMERABLE;
+		}
+	}
+	if (has_configurable) {
+		if (is_configurable) {
+			new_flags |= DUK_PROPDESC_FLAG_CONFIGURABLE;
+		} else {
+			new_flags &= ~DUK_PROPDESC_FLAG_CONFIGURABLE;
+		}
+	}
+	if (has_writable) {
+		if (is_writable) {
+			new_flags |= DUK_PROPDESC_FLAG_WRITABLE;
+		} else {
+			new_flags &= ~DUK_PROPDESC_FLAG_WRITABLE;
+		}
+	}
+
+	/* FIXME: write protect after flag? -> any chance of handling it here? */
+
+	DUK_DDD(DUK_DDDPRINT("new flags that we want to write: 0x%02lx",
+	                     (unsigned long) new_flags));
+
+	/*
+	 *  Check whether we need to abandon an array part (if it exists)
+	 */
+
+	if (curr.a_idx >= 0) {
+		duk_bool_t rc;
+
+		DUK_ASSERT(curr.e_idx < 0);
+
+		if (new_flags == DUK_PROPDESC_FLAGS_WEC) {
+			duk_tval *tv1, *tv2;
+			duk_tval tv_tmp;
+
+			DUK_DDD(DUK_DDDPRINT("array index, new property attributes match array defaults, update in-place"));
+
+			DUK_ASSERT(curr.flags == DUK_PROPDESC_FLAGS_WEC);  /* must have been, since in array part */
+			DUK_ASSERT(!has_set);
+			DUK_ASSERT(!has_get);
+
+			tv2 = duk_require_tval(ctx, idx_value);
+			tv1 = DUK_HOBJECT_A_GET_VALUE_PTR(obj, curr.a_idx);
+			DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
+			DUK_TVAL_SET_TVAL(tv1, tv2);
+			DUK_TVAL_INCREF(thr, tv1);
+			DUK_TVAL_DECREF(thr, &tv_tmp);
+			goto success_exotics;
+		}
+
+		DUK_DDD(DUK_DDDPRINT("array index, new property attributes do not match array defaults, abandon array and re-lookup"));
+		duk__abandon_array_checked(thr, obj);
+		duk_pop(ctx);  /* remove old value */
+		rc = duk__get_own_property_desc_raw(thr, obj, key, arr_idx, &curr, 1);
+		DUK_UNREF(rc);
+		DUK_ASSERT(rc != 0);
+		DUK_ASSERT(curr.e_idx >= 0 && curr.a_idx < 0);
+	}
+
+	DUK_DDD(DUK_DDDPRINT("updating existing property in entry part"));
+
+	/* array case is handled comprehensively above */
+	DUK_ASSERT(curr.e_idx >= 0 && curr.a_idx < 0);
+
+	DUK_DDD(DUK_DDDPRINT("update existing property attributes"));
+	DUK_HOBJECT_E_SET_FLAGS(obj, curr.e_idx, new_flags);
+
+	if (has_set) {
+		duk_hobject *tmp;
+
+		DUK_DDD(DUK_DDDPRINT("update existing property setter"));
+		DUK_ASSERT(DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, curr.e_idx));
+
+		tmp = DUK_HOBJECT_E_GET_VALUE_SETTER(obj, curr.e_idx);
+		DUK_UNREF(tmp);
+		DUK_HOBJECT_E_SET_VALUE_SETTER(obj, curr.e_idx, set);
+		DUK_HOBJECT_INCREF(thr, set);
+		DUK_HOBJECT_DECREF(thr, tmp);
+	}
+	if (has_get) {
+		duk_hobject *tmp;
+
+		DUK_DDD(DUK_DDDPRINT("update existing property getter"));
+		DUK_ASSERT(DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, curr.e_idx));
+
+		tmp = DUK_HOBJECT_E_GET_VALUE_GETTER(obj, curr.e_idx);
+		DUK_UNREF(tmp);
+		DUK_HOBJECT_E_SET_VALUE_GETTER(obj, curr.e_idx, get);
+		DUK_HOBJECT_INCREF(thr, get);
+		DUK_HOBJECT_DECREF(thr, tmp);
+	}
+	if (has_value) {
+		duk_tval *tv1, *tv2;
+		duk_tval tv_tmp;
+
+		DUK_DDD(DUK_DDDPRINT("update existing property value"));
+		DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, curr.e_idx));
+
+		tv2 = duk_require_tval(ctx, idx_value);
+		tv1 = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, curr.e_idx);
+		DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
+		DUK_TVAL_SET_TVAL(tv1, tv2);
+		DUK_TVAL_INCREF(thr, tv1);
+		DUK_TVAL_DECREF(thr, &tv_tmp);
+	}
+
+	/*
+	 *  Standard algorithm succeeded without errors, check for exotic post-behaviors.
+	 *
+	 *  Arguments exotic behavior in E5 Section 10.6 occurs after the standard
+	 *  [[DefineOwnProperty]] has completed successfully.
+	 *
+	 *  Array exotic behavior in E5 Section 15.4.5.1 is implemented partly
+	 *  prior to the default [[DefineOwnProperty]], but:
+	 *    - for an array index key (e.g. "10") the final 'length' update occurs here
+	 *    - for 'length' key the element deletion and 'length' update occurs here
+	 */
+
+ success_exotics:
+
+	/* [obj key desc value get set curr_value] */
+
+	if (DUK_HOBJECT_HAS_EXOTIC_ARRAY(obj)) {
+		if (arridx_new_array_length > 0) {
+			duk_tval *tmp;
+			duk_bool_t rc;
+
+			/*
+			 *  Note: zero works as a "no update" marker because the new length
+			 *  can never be zero after a new property is written.
+			 */
+
+			/* E5 Section 15.4.5.1, steps 4.e.i - 4.e.ii */
+
+			DUK_DDD(DUK_DDDPRINT("defineProperty successful, pending array length update to: %ld",
+			                     (long) arridx_new_array_length));
+
+			/* Note: reuse 'curr' */
+			rc = duk__get_own_property_desc_raw(thr, obj, DUK_HTHREAD_STRING_LENGTH(thr), DUK__NO_ARRAY_INDEX, &curr, 0);
+			DUK_UNREF(rc);
+			DUK_ASSERT(rc != 0);
+			DUK_ASSERT(curr.e_idx >= 0);
+
+			tmp = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, curr.e_idx);
+			DUK_ASSERT(DUK_TVAL_IS_NUMBER(tmp));
+			DUK_TVAL_SET_NUMBER(tmp, (duk_double_t) arridx_new_array_length);  /* no need for decref/incref because value is a number */
+		}
+		if (key == DUK_HTHREAD_STRING_LENGTH(thr) && arrlen_new_len < arrlen_old_len) {
+			/*
+			 *  E5 Section 15.4.5.1, steps 3.k - 3.n.  The order at the end combines
+			 *  the error case 3.l.iii and the success case 3.m-3.n.
+			 *
+			 *  Note: 'length' is always in entries part, so no array abandon issues for
+			 *  'writable' update.
+			 */
+
+			/* FIXME: investigate whether write protect can be handled above, if we
+			 * just update length here while ignoring its protected status
+			 */
+
+			duk_tval *tmp;
+			duk_uint32_t result_len;
+			int rc;
+
+			DUK_DDD(DUK_DDDPRINT("defineProperty successful, key is 'length', exotic array behavior, "
+			                     "doing array element deletion and length update"));
+
+			rc = duk__handle_put_array_length_smaller(thr, obj, arrlen_old_len, arrlen_new_len, &result_len);
+
+			/* update length (curr points to length, and we assume it's still valid) */
+			DUK_ASSERT(result_len >= arrlen_new_len && result_len <= arrlen_old_len);
+
+			DUK_ASSERT(curr.e_idx >= 0);
+			DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, curr.e_idx));
+			tmp = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, curr.e_idx);
+			DUK_ASSERT(DUK_TVAL_IS_NUMBER(tmp));
+			DUK_TVAL_SET_NUMBER(tmp, (duk_double_t) result_len);  /* no decref needed for a number */
+			DUK_ASSERT(DUK_TVAL_IS_NUMBER(tmp));
+
+			if (pending_write_protect) {
+				DUK_DDD(DUK_DDDPRINT("setting array length non-writable (pending writability update)"));
+				DUK_HOBJECT_E_SLOT_CLEAR_WRITABLE(obj, curr.e_idx);
+			}
+
+			/*
+			 *  FIXME: shrink array allocation or entries compaction here?
+			 */
+
+			if (!rc) {
+				goto fail_array_length_partial;
+			}
+		}
+	} else if (arr_idx != DUK__NO_ARRAY_INDEX && DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj)) {
+		duk_hobject *map;
+		duk_hobject *varenv;
+
+		DUK_ASSERT(arridx_new_array_length == 0);
+		DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARRAY(obj));  /* traits are separate; in particular, arguments not an array */
+
+		map = NULL;
+		varenv = NULL;
+		if (!duk__lookup_arguments_map(thr, obj, key, &curr, &map, &varenv)) {
+			goto success_no_exotics;
+		}
+		DUK_ASSERT(map != NULL);
+		DUK_ASSERT(varenv != NULL);
+
+		/* [obj key desc value get set curr_value varname] */
+
+		if (has_set || has_get) {
+			/* = IsAccessorDescriptor(Desc) */
+			DUK_DDD(DUK_DDDPRINT("defineProperty successful, key mapped to arguments 'map' "
+			                     "changed to an accessor, delete arguments binding"));
+
+			(void) duk_hobject_delprop_raw(thr, map, key, 0);  /* ignore result */
+		} else {
+			/* Note: this order matters (final value before deleting map entry must be done) */
+			DUK_DDD(DUK_DDDPRINT("defineProperty successful, key mapped to arguments 'map', "
+			                     "check for value update / binding deletion"));
+
+			if (has_value) {
+				duk_hstring *varname;
+
+				DUK_DDD(DUK_DDDPRINT("defineProperty successful, key mapped to arguments 'map', "
+				                     "update bound value (variable/argument)"));
+
+				varname = duk_require_hstring(ctx, -1);
+				DUK_ASSERT(varname != NULL);
+
+				DUK_DDD(DUK_DDDPRINT("arguments object automatic putvar for a bound variable; "
+				                     "key=%!O, varname=%!O, value=%!T",
+				                     (duk_heaphdr *) key,
+				                     (duk_heaphdr *) varname,
+				                     (duk_tval *) duk_require_tval(ctx, idx_value)));
+
+				/* strict flag for putvar comes from our caller (currently: fixed) */
+				duk_js_putvar_envrec(thr, varenv, varname, duk_require_tval(ctx, idx_value), throw_flag);
+			}
+			if (has_writable && !is_writable) {
+				DUK_DDD(DUK_DDDPRINT("defineProperty successful, key mapped to arguments 'map', "
+				                     "changed to non-writable, delete arguments binding"));
+
+				(void) duk_hobject_delprop_raw(thr, map, key, 0);  /* ignore result */
+			}
+		}
+
+		/* 'varname' is in stack in this else branch, leaving an unbalanced stack below,
+		 * but this doesn't matter now.
+		 */
+	}
+
+ success_no_exotics:
+	/* no need to unwind stack (rewound automatically) */
+	duk_set_top(ctx, 1);  /* -> [ obj ] */
+	return 1;
+
+ fail_virtual:
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_PROPERTY_IS_VIRTUAL);
+	return 0;
+
+ fail_invalid_desc:
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_INVALID_DESCRIPTOR);
+	return 0;
+
+ fail_not_writable_array_length:
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_ARRAY_LENGTH_NOT_WRITABLE);
+	return 0;
+
+ fail_not_extensible:
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_EXTENSIBLE);
+	return 0;
+
+ fail_not_configurable:
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_CONFIGURABLE);
+	return 0;
+
+ fail_array_length_partial:
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_ARRAY_LENGTH_WRITE_FAILED);
+	return 0;
+}
+
+/*
+ *  Object.defineProperties()  (E5 Section 15.2.3.7)
+ *
+ *  This is an actual function call.
+ */
+
+duk_ret_t duk_hobject_object_define_properties(duk_context *ctx) {
+	duk_require_hobject(ctx, 0);  /* target */
+	duk_to_object(ctx, 1);        /* properties object */
+
+	DUK_DDD(DUK_DDDPRINT("target=%!iT, properties=%!iT",
+	                     (duk_tval *) duk_get_tval(ctx, 0),
+	                     (duk_tval *) duk_get_tval(ctx, 1)));
+
+	duk_push_object(ctx);
+	duk_enum(ctx, 1, DUK_ENUM_OWN_PROPERTIES_ONLY /*enum_flags*/);
+
+	/* [hobject props descriptors enum(props)] */
+
+	DUK_DDD(DUK_DDDPRINT("enum(properties)=%!iT",
+	                     (duk_tval *) duk_get_tval(ctx, 3)));
+
+	for (;;) {
+		if (!duk_next(ctx, 3, 1 /*get_value*/)) {
+			break;
+		}
+
+		DUK_DDD(DUK_DDDPRINT("-> key=%!iT, desc=%!iT",
+		                     (duk_tval *) duk_get_tval(ctx, -2),
+		                     (duk_tval *) duk_get_tval(ctx, -1)));
+
+		/* [hobject props descriptors enum(props) key desc] */
+
+		duk__normalize_property_descriptor(ctx);
+		
+		/* [hobject props descriptors enum(props) key desc_norm] */
+
+		duk_put_prop(ctx, 2);
+
+		/* [hobject props descriptors enum(props)] */
+	}
+
+	DUK_DDD(DUK_DDDPRINT("-> descriptors=%!iT",
+	                     (duk_tval *) duk_get_tval(ctx, 2)));
+
+	/* We rely on 'descriptors' having the same key order as 'props'
+	 * to match the array semantics of E5 Section 15.2.3.7.
+	 */
+
+	duk_pop(ctx);
+	duk_enum(ctx, 2, 0 /*enum_flags*/);
+
+	/* [hobject props descriptors enum(descriptors)] */
+
+	DUK_DDD(DUK_DDDPRINT("enum(descriptors)=%!iT",
+	                     (duk_tval *) duk_get_tval(ctx, 3)));
+
+	for (;;) {
+		if (!duk_next(ctx, 3, 1 /*get_value*/)) {
+			break;
+		}
+
+		DUK_DDD(DUK_DDDPRINT("-> key=%!iT, desc=%!iT",
+		                     (duk_tval *) duk_get_tval(ctx, -2),
+		                     (duk_tval *) duk_get_tval(ctx, -1)));
+
+		/* [hobject props descriptors enum(descriptors) key desc_norm] */
+
+		duk_dup(ctx, 0);
+		duk_insert(ctx, -3);
+
+		/* [hobject props descriptors enum(descriptors) hobject key desc_norm] */
+
+		/* FIXME: need access to the -original- Object.defineProperty function
+		 * object here (the property is configurable so a caller may have changed
+		 * it).  This is not a good approach as a new Ecmascript function is created
+		 * for every loop.
+		 */
+		duk_push_c_function(ctx, duk_hobject_object_define_property, 3);
+		duk_insert(ctx, -4);
+
+		/* [hobject props descriptors enum(descriptors) Object.defineProperty hobject key desc_norm] */
+
+		duk_call(ctx, 3);
+
+		/* [hobject props descriptors enum(descriptors) retval] */
+
+		/* FIXME: call which ignores result would be nice */
+
+		duk_pop(ctx);
+	}
+
+	/* [hobject props descriptors enum(descriptors)] */
+
+	duk_dup(ctx, 0);
+	
+	/* [hobject props descriptors enum(descriptors) hobject] */
+
+	return 1;
+}
+
+/*
+ *  Object.prototype.hasOwnProperty() and Object.prototype.propertyIsEnumerable().
+ */
+
+duk_bool_t duk_hobject_object_ownprop_helper(duk_context *ctx, duk_small_uint_t required_desc_flags) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hstring *h_v;
+	duk_hobject *h_obj;
+	duk_propdesc desc;
+	duk_bool_t ret;
+
+	/* coercion order matters */
+	h_v = duk_to_hstring(ctx, 0);
+	DUK_ASSERT(h_v != NULL);
+
+	h_obj = duk_push_this_coercible_to_object(ctx);
+	DUK_ASSERT(h_obj != NULL);
+
+	ret = duk__get_own_property_desc(thr, h_obj, h_v, &desc, 0 /*push_value*/);
+
+	duk_push_boolean(ctx, ret && ((desc.flags & required_desc_flags) == required_desc_flags));
+	return 1;
+}
+
+/*
+ *  Object.seal() and Object.freeze()  (E5 Sections 15.2.3.8 and 15.2.3.9)
+ * 
+ *  Since the algorithms are similar, a helper provides both functions.
+ *  Freezing is essentially sealing + making plain properties non-writable.
+ *
+ *  Note: virtual (non-concrete) properties which are non-configurable but
+ *  writable would pose some problems, but such properties do not currently
+ *  exist (all virtual properties are non-configurable and non-writable).
+ *  If they did exist, the non-configurability does NOT prevent them from
+ *  becoming non-writable.  However, this change should be recorded somehow
+ *  so that it would turn up (e.g. when getting the property descriptor),
+ *  requiring some additional flags in the object.
+ */
+
+void duk_hobject_object_seal_freeze_helper(duk_hthread *thr, duk_hobject *obj, duk_bool_t is_freeze) {
+	duk_uint_fast32_t i;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->heap != NULL);
+	DUK_ASSERT(obj != NULL);
+
+	DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
+
+	/*
+	 *  Abandon array part because all properties must become non-configurable.
+	 *  Note that this is now done regardless of whether this is always the case
+	 *  (skips check, but performance problem if caller would do this many times
+	 *  for the same object; not likely).
+	 */
+
+	duk__abandon_array_checked(thr, obj);
+	DUK_ASSERT(obj->a_size == 0);
+
+	for (i = 0; i < obj->e_used; i++) {
+		duk_uint8_t *fp;
+
+		/* since duk__abandon_array_checked() causes a resize, there should be no gaps in keys */
+		DUK_ASSERT(DUK_HOBJECT_E_GET_KEY(obj, i) != NULL);
+
+		/* avoid multiple computations of flags address; bypasses macros */
+		fp = DUK_HOBJECT_E_GET_FLAGS_PTR(obj, i);
+		if (is_freeze && !((*fp) & DUK_PROPDESC_FLAG_ACCESSOR)) {
+			*fp &= ~(DUK_PROPDESC_FLAG_WRITABLE | DUK_PROPDESC_FLAG_CONFIGURABLE);
+		} else {
+			*fp &= ~DUK_PROPDESC_FLAG_CONFIGURABLE;
+		}
+	}
+
+	DUK_HOBJECT_CLEAR_EXTENSIBLE(obj);
+
+	/* no need to compact since we already did that in duk__abandon_array_checked()
+	 * (regardless of whether an array part existed or not.
+	 */
+
+	return;
+}
+
+/*
+ *  Object.isSealed() and Object.isFrozen()  (E5 Sections 15.2.3.11, 15.2.3.13)
+ *
+ *  Since the algorithms are similar, a helper provides both functions.
+ *  Freezing is essentially sealing + making plain properties non-writable.
+ *
+ *  Note: all virtual (non-concrete) properties are currently non-configurable
+ *  and non-writable (and there are no accessor virtual properties), so they don't
+ *  need to be considered here now.
+ */
+
+duk_bool_t duk_hobject_object_is_sealed_frozen_helper(duk_hobject *obj, duk_bool_t is_frozen) {
+	duk_uint_fast32_t i;
+
+	DUK_ASSERT(obj != NULL);
+
+	/* Note: no allocation pressure, no need to check refcounts etc */
+
+	/* must not be extensible */
+	if (DUK_HOBJECT_HAS_EXTENSIBLE(obj)) {
+		return 0;
+	}
+
+	/* all virtual properties are non-configurable and non-writable */
+
+	/* entry part must not contain any configurable properties, or
+	 * writable properties (if is_frozen).
+	 */
+	for (i = 0; i < obj->e_used; i++) {
+		duk_small_uint_t flags;
+
+		if (!DUK_HOBJECT_E_GET_KEY(obj, i)) {
+			continue;
+		}
+
+		/* avoid multiple computations of flags address; bypasses macros */
+		flags = (duk_small_uint_t) DUK_HOBJECT_E_GET_FLAGS(obj, i);
+
+		if (flags & DUK_PROPDESC_FLAG_CONFIGURABLE) {
+			return 0;
+		}
+		if (is_frozen &&
+		    !(flags & DUK_PROPDESC_FLAG_ACCESSOR) &&
+		    (flags & DUK_PROPDESC_FLAG_WRITABLE)) {
+			return 0;
+		}
+	}
+
+	/* array part must not contain any non-unused properties, as they would
+	 * be configurable and writable.
+	 */
+	for (i = 0; i < obj->a_size; i++) {
+		duk_tval *tv = DUK_HOBJECT_A_GET_VALUE_PTR(obj, i);
+		if (!DUK_TVAL_IS_UNDEFINED_UNUSED(tv)) {
+			return 0;
+		}
+	}
+
+	return 1;
+}
+
+/*
+ *  Object.preventExtensions() and Object.isExtensible()  (E5 Sections 15.2.3.10, 15.2.3.13)
+ *
+ *  Implemented directly in macros:
+ *
+ *    DUK_HOBJECT_OBJECT_PREVENT_EXTENSIONS()
+ *    DUK_HOBJECT_OBJECT_IS_EXTENSIBLE()
+ */
+
+/* Undefine local defines */
+
+#undef DUK__NO_ARRAY_INDEX
+#undef DUK__HASH_INITIAL
+#undef DUK__HASH_PROBE_STEP
+#undef DUK__HASH_UNUSED
+#undef DUK__HASH_DELETED
+#undef DUK__VALSTACK_SPACE
+#line 1 "duk_hstring_misc.c"
+/*
+ *  Misc support functions
+ */
+
+/* include removed: duk_internal.h */
+
+duk_ucodepoint_t duk_hstring_char_code_at_raw(duk_hthread *thr, duk_hstring *h, duk_uint_t pos) {
+	duk_uint32_t boff;
+	duk_uint8_t *p, *p_start, *p_end;
+	duk_ucodepoint_t cp;
+
+	/* Caller must check character offset to be inside the string. */
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(h != NULL);
+	DUK_ASSERT_DISABLE(pos >= 0);  /* unsigned */
+	DUK_ASSERT(pos < (duk_uint_t) DUK_HSTRING_GET_CHARLEN(h));
+
+	boff = duk_heap_strcache_offset_char2byte(thr, h, (duk_uint32_t) pos);
+	DUK_DDD(DUK_DDDPRINT("charCodeAt: pos=%ld -> boff=%ld, str=%!O",
+	                     (long) pos, (long) boff, (duk_heaphdr *) h));
+	DUK_ASSERT_DISABLE(boff >= 0);
+	DUK_ASSERT(boff < DUK_HSTRING_GET_BYTELEN(h));
+
+	p_start = DUK_HSTRING_GET_DATA(h);
+	p_end = p_start + DUK_HSTRING_GET_BYTELEN(h);
+	p = p_start + boff;
+	DUK_DDD(DUK_DDDPRINT("p_start=%p, p_end=%p, p=%p",
+	                     (void *) p_start, (void *) p_end, (void *) p));
+
+	/* This may throw an error though not for valid E5 strings. */
+	cp = duk_unicode_decode_xutf8_checked(thr, &p, p_start, p_end);
+	return cp;
+}
+#line 1 "duk_hthread_alloc.c"
+/*
+ *  duk_hthread allocation and freeing.
+ */
+
+/* include removed: duk_internal.h */
+
+/*
+ *  Allocate initial stacks for a thread.  Note that 'thr' must be reachable
+ *  as a garbage collection may be triggered by the allocation attempts.
+ *  Returns zero (without leaking memory) if init fails.
+ */
+
+duk_bool_t duk_hthread_init_stacks(duk_heap *heap, duk_hthread *thr) {
+	duk_size_t alloc_size;
+	duk_size_t i;
+
+	DUK_ASSERT(heap != NULL);
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->valstack == NULL);
+	DUK_ASSERT(thr->valstack_end == NULL);
+	DUK_ASSERT(thr->valstack_bottom == NULL);
+	DUK_ASSERT(thr->valstack_top == NULL);
+	DUK_ASSERT(thr->callstack == NULL);
+	DUK_ASSERT(thr->catchstack == NULL);
+
+	/* valstack */
+	alloc_size = sizeof(duk_tval) * DUK_VALSTACK_INITIAL_SIZE;
+	thr->valstack = (duk_tval *) DUK_ALLOC(heap, alloc_size);
+	if (!thr->valstack) {
+		goto fail;
+	}
+	DUK_MEMZERO(thr->valstack, alloc_size);
+	thr->valstack_end = thr->valstack + DUK_VALSTACK_INITIAL_SIZE;
+	thr->valstack_bottom = thr->valstack;
+	thr->valstack_top = thr->valstack;
+
+	for (i = 0; i < DUK_VALSTACK_INITIAL_SIZE; i++) {
+		DUK_TVAL_SET_UNDEFINED_UNUSED(&thr->valstack[i]);
+	}
+
+	/* callstack */
+	alloc_size = sizeof(duk_activation) * DUK_CALLSTACK_INITIAL_SIZE;
+	thr->callstack = (duk_activation *) DUK_ALLOC(heap, alloc_size);
+	if (!thr->callstack) {
+		goto fail;
+	}
+	DUK_MEMZERO(thr->callstack, alloc_size);
+	thr->callstack_size = DUK_CALLSTACK_INITIAL_SIZE;
+	DUK_ASSERT(thr->callstack_top == 0);
+
+	/* catchstack */
+	alloc_size = sizeof(duk_catcher) * DUK_CATCHSTACK_INITIAL_SIZE;
+	thr->catchstack = (duk_catcher *) DUK_ALLOC(heap, alloc_size);
+	if (!thr->catchstack) {
+		goto fail;
+	}
+	DUK_MEMZERO(thr->catchstack, alloc_size);
+	thr->catchstack_size = DUK_CATCHSTACK_INITIAL_SIZE;
+	DUK_ASSERT(thr->catchstack_top == 0);
+
+	return 1;
+
+ fail:
+	DUK_FREE(heap, thr->valstack);
+	DUK_FREE(heap, thr->callstack);
+	DUK_FREE(heap, thr->catchstack);
+
+	thr->valstack = NULL;
+	thr->callstack = NULL;
+	thr->catchstack = NULL;
+	return 0;
+}
+
+/* For indirect allocs. */
+
+void *duk_hthread_get_valstack_ptr(void *ud) {
+	duk_hthread *thr = (duk_hthread *) ud;
+	return (void *) thr->valstack;
+}
+
+void *duk_hthread_get_callstack_ptr(void *ud) {
+	duk_hthread *thr = (duk_hthread *) ud;
+	return (void *) thr->callstack;
+}
+
+void *duk_hthread_get_catchstack_ptr(void *ud) {
+	duk_hthread *thr = (duk_hthread *) ud;
+	return (void *) thr->catchstack;
+}
+#line 1 "duk_hthread_builtins.c"
+/*
+ *  Initialize built-in objects.  Current thread must have a valstack
+ *  and initialization errors may longjmp, so a setjmp() catch point
+ *  must exist.
+ */
+
+/* include removed: duk_internal.h */
+
+/*
+ *  Encoding constants, must match genbuiltins.py
+ */
+
+#define DUK__CLASS_BITS                  5
+#define DUK__BIDX_BITS                   6
+#define DUK__STRIDX_BITS                 9  /* XXX: try to optimize to 8 */
+#define DUK__NATIDX_BITS                 8
+#define DUK__NUM_NORMAL_PROPS_BITS       6
+#define DUK__NUM_FUNC_PROPS_BITS         6
+#define DUK__PROP_FLAGS_BITS             3
+#define DUK__STRING_LENGTH_BITS          8
+#define DUK__STRING_CHAR_BITS            7
+#define DUK__LENGTH_PROP_BITS            3
+#define DUK__NARGS_BITS                  3
+#define DUK__PROP_TYPE_BITS              3
+#define DUK__MAGIC_BITS                  16
+
+#define DUK__NARGS_VARARGS_MARKER        0x07
+#define DUK__NO_CLASS_MARKER             0x00   /* 0 = DUK_HOBJECT_CLASS_UNUSED */
+#define DUK__NO_BIDX_MARKER              0x3f
+#define DUK__NO_STRIDX_MARKER            0xff
+
+#define DUK__PROP_TYPE_DOUBLE            0
+#define DUK__PROP_TYPE_STRING            1
+#define DUK__PROP_TYPE_STRIDX            2
+#define DUK__PROP_TYPE_BUILTIN           3
+#define DUK__PROP_TYPE_UNDEFINED         4
+#define DUK__PROP_TYPE_BOOLEAN_TRUE      5
+#define DUK__PROP_TYPE_BOOLEAN_FALSE     6
+#define DUK__PROP_TYPE_ACCESSOR          7
+
+/*
+ *  Create built-in objects by parsing an init bitstream generated
+ *  by genbuiltins.py.
+ */
+
+void duk_hthread_create_builtin_objects(duk_hthread *thr) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_bitdecoder_ctx bd_ctx;
+	duk_bitdecoder_ctx *bd = &bd_ctx;  /* convenience */
+	duk_hobject *h;
+	duk_small_uint_t i, j;
+
+	DUK_D(DUK_DPRINT("INITBUILTINS BEGIN"));
+
+	DUK_MEMZERO(&bd_ctx, sizeof(bd_ctx));
+	bd->data = (const duk_uint8_t *) duk_builtins_data;
+	bd->length = (duk_size_t) DUK_BUILTINS_DATA_LENGTH;
+
+	/*
+	 *  First create all built-in bare objects on the empty valstack.
+	 *  During init, their indices will correspond to built-in indices.
+	 *
+	 *  Built-ins will be reachable from both valstack and thr->builtins.
+	 */
+
+	/* XXX: there is no need to resize valstack because builtin count
+	 * is much less than the default space; assert for it.
+	 */
+
+	DUK_DD(DUK_DDPRINT("create empty built-ins"));
+	DUK_ASSERT_TOP(ctx, 0);
+	for (i = 0; i < DUK_NUM_BUILTINS; i++) {
+		duk_small_uint_t class_num;
+		duk_small_int_t len = -1;  /* must be signed */
+
+		class_num = (duk_small_uint_t) duk_bd_decode(bd, DUK__CLASS_BITS);
+		len = (duk_small_int_t) duk_bd_decode_flagged(bd, DUK__LENGTH_PROP_BITS, (duk_int32_t) -1 /*def_value*/);
+
+		if (class_num == DUK_HOBJECT_CLASS_FUNCTION) {
+			duk_small_uint_t natidx;
+			duk_small_uint_t stridx;
+			duk_int_t c_nargs;  /* must hold DUK_VARARGS */
+			duk_c_function c_func;
+			duk_int16_t magic;
+
+			DUK_DDD(DUK_DDDPRINT("len=%ld", (long) len));
+			DUK_ASSERT(len >= 0);
+
+			natidx = (duk_small_uint_t) duk_bd_decode(bd, DUK__NATIDX_BITS);
+			stridx = (duk_small_uint_t) duk_bd_decode(bd, DUK__STRIDX_BITS);
+			c_func = duk_bi_native_functions[natidx];
+
+			c_nargs = (duk_small_uint_t) duk_bd_decode_flagged(bd, DUK__NARGS_BITS, len /*def_value*/);
+			if (c_nargs == DUK__NARGS_VARARGS_MARKER) {
+				c_nargs = DUK_VARARGS;
+			}
+
+			/* XXX: set magic directly here? (it could share the c_nargs arg) */
+			duk_push_c_function_noexotic(ctx, c_func, c_nargs);
+
+			h = duk_require_hobject(ctx, -1);
+			DUK_ASSERT(h != NULL);
+
+			/* Currently all built-in native functions are strict.
+			 * duk_push_c_function() now sets strict flag, so
+			 * assert for it.
+			 */
+			DUK_ASSERT(DUK_HOBJECT_HAS_STRICT(h));
+
+			/* XXX: function properties */
+
+			duk_push_hstring_stridx(ctx, stridx);
+			duk_def_prop_stridx(ctx, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_NONE);
+
+			/* Almost all global level Function objects are constructable
+			 * but not all: Function.prototype is a non-constructable,
+			 * callable Function.
+			 */
+			if (duk_bd_decode_flag(bd)) {
+				DUK_ASSERT(DUK_HOBJECT_HAS_CONSTRUCTABLE(h));
+			} else {
+				DUK_HOBJECT_CLEAR_CONSTRUCTABLE(h);
+			}
+
+			/* Cast converts magic to 16-bit signed value */
+			magic = (duk_int16_t) duk_bd_decode_flagged(bd, DUK__MAGIC_BITS, 0 /*def_value*/);
+			((duk_hnativefunction *) h)->magic = magic;
+		} else {
+			/* XXX: ARRAY_PART for Array prototype? */
+
+			duk_push_object_helper(ctx,
+			                       DUK_HOBJECT_FLAG_EXTENSIBLE,
+			                       -1);  /* no prototype or class yet */
+
+			h = duk_require_hobject(ctx, -1);
+			DUK_ASSERT(h != NULL);
+		}
+
+		DUK_HOBJECT_SET_CLASS_NUMBER(h, class_num);
+
+		thr->builtins[i] = h;
+		DUK_HOBJECT_INCREF(thr, &h->hdr);
+
+		if (len >= 0) {
+			/*
+			 *  For top-level objects, 'length' property has the following
+			 *  default attributes: non-writable, non-enumerable, non-configurable
+			 *  (E5 Section 15).
+			 *
+			 *  However, 'length' property for Array.prototype has attributes
+			 *  expected of an Array instance which are different: writable,
+			 *  non-enumerable, non-configurable (E5 Section 15.4.5.2).
+			 *
+			 *  This is currently determined implicitly based on class; there are
+			 *  no attribute flags in the init data.
+			 */
+
+			duk_push_int(ctx, len);
+			duk_def_prop_stridx(ctx,
+			                    -2,
+			                    DUK_STRIDX_LENGTH,
+			                    (class_num == DUK_HOBJECT_CLASS_ARRAY ?  /* only Array.prototype matches */
+			                     DUK_PROPDESC_FLAGS_W : DUK_PROPDESC_FLAGS_NONE));
+		}
+
+		/* enable exotic behaviors last */
+
+		if (class_num == DUK_HOBJECT_CLASS_ARRAY) {
+			DUK_HOBJECT_SET_EXOTIC_ARRAY(h);
+		}
+		if (class_num == DUK_HOBJECT_CLASS_STRING) {
+			DUK_HOBJECT_SET_EXOTIC_STRINGOBJ(h);
+		}
+
+		/* some assertions */
+
+		DUK_ASSERT(DUK_HOBJECT_HAS_EXTENSIBLE(h));
+		/* DUK_HOBJECT_FLAG_CONSTRUCTABLE varies */
+		DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(h));
+		DUK_ASSERT(!DUK_HOBJECT_HAS_COMPILEDFUNCTION(h));
+		/* DUK_HOBJECT_FLAG_NATIVEFUNCTION varies */
+		DUK_ASSERT(!DUK_HOBJECT_HAS_THREAD(h));
+		DUK_ASSERT(!DUK_HOBJECT_HAS_ARRAY_PART(h));       /* currently, even for Array.prototype */
+		/* DUK_HOBJECT_FLAG_STRICT varies */
+		DUK_ASSERT(!DUK_HOBJECT_HAS_NATIVEFUNCTION(h) ||  /* all native functions have NEWENV */
+		           DUK_HOBJECT_HAS_NEWENV(h));
+		DUK_ASSERT(!DUK_HOBJECT_HAS_NAMEBINDING(h));
+		DUK_ASSERT(!DUK_HOBJECT_HAS_CREATEARGS(h));
+		DUK_ASSERT(!DUK_HOBJECT_HAS_ENVRECCLOSED(h));
+		/* DUK_HOBJECT_FLAG_EXOTIC_ARRAY varies */
+		/* DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ varies */
+		DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(h));
+
+		DUK_DDD(DUK_DDDPRINT("created built-in %ld, class=%ld, length=%ld", (long) i, (long) class_num, (long) len));
+	}
+
+	/*
+	 *  Then decode the builtins init data (see genbuiltins.py) to
+	 *  init objects
+	 */
+
+	DUK_DD(DUK_DDPRINT("initialize built-in object properties"));
+	for (i = 0; i < DUK_NUM_BUILTINS; i++) {
+		duk_small_uint_t t;
+		duk_small_uint_t num;
+
+		DUK_DDD(DUK_DDDPRINT("initializing built-in object at index %ld", (long) i));
+		h = thr->builtins[i];
+
+		t = (duk_small_uint_t) duk_bd_decode(bd, DUK__BIDX_BITS);
+		if (t != DUK__NO_BIDX_MARKER) {
+			DUK_DDD(DUK_DDDPRINT("set internal prototype: built-in %ld", (long) t));
+			DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, h, thr->builtins[t]);
+		}
+
+		t = (duk_small_uint_t) duk_bd_decode(bd, DUK__BIDX_BITS);
+		if (t != DUK__NO_BIDX_MARKER) {
+			/* 'prototype' property for all built-in objects (which have it) has attributes:
+			 *  [[Writable]] = false,
+			 *  [[Enumerable]] = false,
+			 *  [[Configurable]] = false
+			 */
+			DUK_DDD(DUK_DDDPRINT("set external prototype: built-in %ld", (long) t));
+			duk_def_prop_stridx_builtin(ctx, i, DUK_STRIDX_PROTOTYPE, t, DUK_PROPDESC_FLAGS_NONE);
+		}
+
+		t = (duk_small_uint_t) duk_bd_decode(bd, DUK__BIDX_BITS);
+		if (t != DUK__NO_BIDX_MARKER) {
+			/* 'constructor' property for all built-in objects (which have it) has attributes:
+			 *  [[Writable]] = true,
+			 *  [[Enumerable]] = false,	
+			 *  [[Configurable]] = true
+			 */
+			DUK_DDD(DUK_DDDPRINT("set external constructor: built-in %ld", (long) t));
+			duk_def_prop_stridx_builtin(ctx, i, DUK_STRIDX_CONSTRUCTOR, t, DUK_PROPDESC_FLAGS_WC);
+		}
+
+		/* normal valued properties */
+		num = (duk_small_uint_t) duk_bd_decode(bd, DUK__NUM_NORMAL_PROPS_BITS);
+		DUK_DDD(DUK_DDDPRINT("built-in object %ld, %ld normal valued properties", (long) i, (long) num));
+		for (j = 0; j < num; j++) {
+			duk_small_uint_t stridx;
+			duk_small_uint_t prop_flags;
+
+			stridx = (duk_small_uint_t) duk_bd_decode(bd, DUK__STRIDX_BITS);
+
+			/*
+			 *  Property attribute defaults are defined in E5 Section 15 (first
+			 *  few pages); there is a default for all properties and a special
+			 *  default for 'length' properties.  Variation from the defaults is
+			 *  signaled using a single flag bit in the bitstream.
+			 */
+
+			if (duk_bd_decode_flag(bd)) {
+				prop_flags = (duk_small_uint_t) duk_bd_decode(bd, DUK__PROP_FLAGS_BITS);
+			} else {
+				if (stridx == DUK_STRIDX_LENGTH) {
+					prop_flags = DUK_PROPDESC_FLAGS_NONE;
+				} else {
+					prop_flags = DUK_PROPDESC_FLAGS_WC;
+				}
+			}
+
+			t = (duk_small_uint_t) duk_bd_decode(bd, DUK__PROP_TYPE_BITS);
+
+			DUK_DDD(DUK_DDDPRINT("built-in %ld, normal-valued property %ld, stridx %ld, flags 0x%02lx, type %ld",
+			                     (long) i, (long) j, (long) stridx, (unsigned long) prop_flags, (long) t));
+
+			switch (t) {
+			case DUK__PROP_TYPE_DOUBLE: {
+				duk_double_union du;
+				duk_small_uint_t k;
+
+				for (k = 0; k < 8; k++) {
+					/* Encoding endianness must match target memory layout,
+					 * build scripts and genbuiltins.py must ensure this.
+					 */
+					du.uc[k] = (duk_uint8_t) duk_bd_decode(bd, 8);
+				}
+
+				duk_push_number(ctx, du.d);  /* push operation normalizes NaNs */
+				break;
+			}
+			case DUK__PROP_TYPE_STRING: {
+				duk_small_uint_t n;
+				duk_small_uint_t k;
+				duk_uint8_t *p;
+
+				n = (duk_small_uint_t) duk_bd_decode(bd, DUK__STRING_LENGTH_BITS);
+				p = (duk_uint8_t *) duk_push_fixed_buffer(ctx, n);
+				for (k = 0; k < n; k++) {
+					*p++ = (duk_uint8_t) duk_bd_decode(bd, DUK__STRING_CHAR_BITS);
+				}
+
+				duk_to_string(ctx, -1);
+				break;
+			}
+			case DUK__PROP_TYPE_STRIDX: {
+				duk_small_uint_t n;
+
+				n = (duk_small_uint_t) duk_bd_decode(bd, DUK__STRIDX_BITS);
+				DUK_ASSERT_DISABLE(n >= 0);  /* unsigned */
+				DUK_ASSERT(n < DUK_HEAP_NUM_STRINGS);
+				duk_push_hstring_stridx(ctx, n);
+				break;
+			}
+			case DUK__PROP_TYPE_BUILTIN: {
+				duk_small_uint_t bidx;
+
+				bidx = (duk_small_uint_t) duk_bd_decode(bd, DUK__BIDX_BITS);
+				DUK_ASSERT(bidx != DUK__NO_BIDX_MARKER);
+				duk_dup(ctx, (duk_idx_t) bidx);
+				break;
+			}
+			case DUK__PROP_TYPE_UNDEFINED: {
+				duk_push_undefined(ctx);
+				break;
+			}
+			case DUK__PROP_TYPE_BOOLEAN_TRUE: {
+				duk_push_true(ctx);
+				break;
+			}
+			case DUK__PROP_TYPE_BOOLEAN_FALSE: {
+				duk_push_false(ctx);
+				break;
+			}
+			case DUK__PROP_TYPE_ACCESSOR: {
+				duk_small_uint_t natidx_getter = (duk_small_uint_t) duk_bd_decode(bd, DUK__NATIDX_BITS);
+				duk_small_uint_t natidx_setter = (duk_small_uint_t) duk_bd_decode(bd, DUK__NATIDX_BITS);
+				duk_c_function c_func_getter;
+				duk_c_function c_func_setter;
+
+				/* XXX: this is a bit awkward because there is no exposed helper
+				 * in the API style, only this internal helper.
+				 */
+				DUK_DDD(DUK_DDDPRINT("built-in accessor property: objidx=%ld, stridx=%ld, getteridx=%ld, setteridx=%ld, flags=0x%04lx",
+				                     (long) i, (long) stridx, (long) natidx_getter, (long) natidx_setter, (unsigned long) prop_flags));
+
+				c_func_getter = duk_bi_native_functions[natidx_getter];
+				c_func_setter = duk_bi_native_functions[natidx_setter];
+				duk_push_c_function_noconstruct_noexotic(ctx, c_func_getter, 0);  /* always 0 args */
+				duk_push_c_function_noconstruct_noexotic(ctx, c_func_setter, 1);  /* always 1 arg */
+
+				/* XXX: magic for getter/setter? */
+
+				prop_flags |= DUK_PROPDESC_FLAG_ACCESSOR;  /* accessor flag not encoded explicitly */
+				duk_hobject_define_accessor_internal(thr,
+				                                     duk_require_hobject(ctx, i),
+				                                     DUK_HTHREAD_GET_STRING(thr, stridx),
+				                                     duk_require_hobject(ctx, -2),
+				                                     duk_require_hobject(ctx, -1),
+				                                     prop_flags);
+				duk_pop_2(ctx);  /* getter and setter, now reachable through object */
+				goto skip_value;
+			}
+			default: {
+				/* exhaustive */
+				DUK_UNREACHABLE();
+			}
+			}
+
+			DUK_ASSERT((prop_flags & DUK_PROPDESC_FLAG_ACCESSOR) == 0);
+			duk_def_prop_stridx(ctx, i, stridx, prop_flags);
+
+		 skip_value:
+			continue;  /* avoid empty label at the end of a compound statement */
+		}
+
+		/* native function properties */
+		num = (duk_small_uint_t) duk_bd_decode(bd, DUK__NUM_FUNC_PROPS_BITS);
+		DUK_DDD(DUK_DDDPRINT("built-in object %ld, %ld function valued properties", (long) i, (long) num));
+		for (j = 0; j < num; j++) {
+			duk_small_uint_t stridx;
+			duk_small_uint_t natidx;
+			duk_int_t c_nargs;  /* must hold DUK_VARARGS */
+			duk_small_uint_t c_length;
+			duk_int16_t magic;
+			duk_c_function c_func;
+			duk_hnativefunction *h_func;
+
+			stridx = (duk_small_uint_t) duk_bd_decode(bd, DUK__STRIDX_BITS);
+			natidx = (duk_small_uint_t) duk_bd_decode(bd, DUK__NATIDX_BITS);
+
+			c_length = (duk_small_uint_t) duk_bd_decode(bd, DUK__LENGTH_PROP_BITS);
+			c_nargs = (duk_int_t) duk_bd_decode_flagged(bd, DUK__NARGS_BITS, (duk_int32_t) c_length /*def_value*/);
+			if (c_nargs == DUK__NARGS_VARARGS_MARKER) {
+				c_nargs = DUK_VARARGS;
+			}
+
+			c_func = duk_bi_native_functions[natidx];
+
+			DUK_DDD(DUK_DDDPRINT("built-in %ld, function-valued property %ld, stridx %ld, natidx %ld, length %ld, nargs %ld",
+			                     (long) i, (long) j, (long) stridx, (long) natidx, (long) c_length,
+			                     (c_nargs == DUK_VARARGS ? (long) -1 : (long) c_nargs)));
+
+			/* [ (builtin objects) ] */
+
+			duk_push_c_function_noconstruct_noexotic(ctx, c_func, c_nargs);
+			h_func = duk_require_hnativefunction(ctx, -1);
+			DUK_UNREF(h_func);
+
+			/* Currently all built-in native functions are strict.
+			 * This doesn't matter for many functions, but e.g.
+			 * String.prototype.charAt (and other string functions)
+			 * rely on being strict so that their 'this' binding is
+			 * not automatically coerced.
+			 */
+			DUK_HOBJECT_SET_STRICT((duk_hobject *) h_func);
+
+			/* No built-in functions are constructable except the top
+			 * level ones (Number, etc).
+			 */
+			DUK_ASSERT(!DUK_HOBJECT_HAS_CONSTRUCTABLE((duk_hobject *) h_func));
+
+			/* XXX: any way to avoid decoding magic bit; there are quite
+			 * many function properties and relatively few with magic values.
+			 */
+			/* Cast converts magic to 16-bit signed value */
+			magic = (duk_int16_t) duk_bd_decode_flagged(bd, DUK__MAGIC_BITS, 0);
+			h_func->magic = magic;
+
+			/* [ (builtin objects) func ] */
+
+			duk_push_int(ctx, c_length);
+			duk_def_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_NONE);
+
+			duk_push_hstring_stridx(ctx, stridx);
+			duk_def_prop_stridx(ctx, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_NONE);
+
+			/* XXX: other properties of function instances; 'arguments', 'caller'. */
+
+			DUK_DD(DUK_DDPRINT("built-in object %ld, function property %ld -> %!T",
+			                   (long) i, (long) j, (duk_tval *) duk_get_tval(ctx, -1)));
+
+			/* [ (builtin objects) func ] */
+
+			/*
+			 *  The default property attributes are correct for all
+			 *  function valued properties of built-in objects now.
+			 */
+
+			duk_def_prop_stridx(ctx, i, stridx, DUK_PROPDESC_FLAGS_WC);
+
+			/* [ (builtin objects) ] */
+		}
+	}
+
+	/*
+	 *  Special post-tweaks, for cases not covered by the init data format.
+	 *
+	 *  - Set Date.prototype.toGMTString to Date.prototype.toUTCString.
+	 *    toGMTString is required to have the same Function object as
+	 *    toUTCString in E5 Section B.2.6.  Note that while Smjs respects
+	 *    this, V8 does not (the Function objects are distinct).
+	 *
+	 *  - Make DoubleError non-extensible.
+	 *
+	 *  - Add info about most important effective compile options to Duktape.
+	 *
+	 *  - Possibly remove some properties (values or methods) which are not
+	 *    desirable with current feature options but are not currently
+	 *    conditional in init data.
+	 */
+
+	duk_get_prop_stridx(ctx, DUK_BIDX_DATE_PROTOTYPE, DUK_STRIDX_TO_UTC_STRING);
+	duk_def_prop_stridx(ctx, DUK_BIDX_DATE_PROTOTYPE, DUK_STRIDX_TO_GMT_STRING, DUK_PROPDESC_FLAGS_WC);
+
+	h = duk_require_hobject(ctx, DUK_BIDX_DOUBLE_ERROR);
+	DUK_ASSERT(h != NULL);
+	DUK_HOBJECT_CLEAR_EXTENSIBLE(h);
+
+#if !defined(DUK_USE_ES6_OBJECT_PROTO_PROPERTY)
+	DUK_DD(DUK_DDPRINT("delete Object.prototype.__proto__ built-in which is not enabled in features"));
+	(void) duk_hobject_delprop_raw(thr, thr->builtins[DUK_BIDX_OBJECT_PROTOTYPE], DUK_HTHREAD_STRING___PROTO__(thr), 1 /*throw_flag*/);
+#endif
+
+#if !defined(DUK_USE_ES6_OBJECT_SETPROTOTYPEOF)
+	DUK_DD(DUK_DDPRINT("delete Object.setPrototypeOf built-in which is not enabled in features"));
+	(void) duk_hobject_delprop_raw(thr, thr->builtins[DUK_BIDX_OBJECT_CONSTRUCTOR], DUK_HTHREAD_STRING_SET_PROTOTYPE_OF(thr), 1 /*throw_flag*/);
+#endif
+
+	duk_push_string(ctx,
+#if defined(DUK_USE_INTEGER_LE)
+	                "l"
+#elif defined(DUK_USE_INTEGER_BE)
+	                "b"
+#elif defined(DUK_USE_INTEGER_ME)  /* integer mixed endian not really used now */
+	                "m"
+#else
+	                "?"
+#endif
+#if defined(DUK_USE_DOUBLE_LE)
+	                "l"
+#elif defined(DUK_USE_DOUBLE_BE)
+	                "b"
+#elif defined(DUK_USE_DOUBLE_ME)
+	                "m"
+#else
+	                "?"
+#endif
+#if defined(DUK_USE_BYTEORDER_FORCED)
+			"f"
+#endif
+	                " "
+#if defined(DUK_USE_PACKED_TVAL)
+	                "p"
+#else
+	                "u"
+#endif
+	                " "
+#if defined(DUK_USE_HOBJECT_LAYOUT_1)
+			"p1"
+#elif defined(DUK_USE_HOBJECT_LAYOUT_2)
+			"p2"
+#elif defined(DUK_USE_HOBJECT_LAYOUT_3)
+			"p3"
+#else
+			"p?"
+#endif
+			" "
+#if defined(DUK_USE_ALIGN_4)
+			"a4"
+#elif defined(DUK_USE_ALIGN_8)
+			"a8"
+#else
+			"a1"
+#endif
+			" "
+	                DUK_USE_ARCH_STRING);
+	duk_def_prop_stridx(ctx, DUK_BIDX_DUKTAPE, DUK_STRIDX_ENV, DUK_PROPDESC_FLAGS_WC);
+
+	/*
+	 *  InitJS code - Ecmascript code evaluated from a built-in source
+	 *  which provides e.g. backward compatibility.  User can also provide
+	 *  JS code to be evaluated at startup.
+	 */
+
+#ifdef DUK_USE_BUILTIN_INITJS
+	/* XXX: compression */
+	DUK_DD(DUK_DDPRINT("running built-in initjs"));
+	duk_eval_string(ctx, (const char *) duk_initjs_data);  /* initjs data is NUL terminated */
+	duk_pop(ctx);
+#endif  /* DUK_USE_BUILTIN_INITJS */
+
+#ifdef DUK_USE_USER_INITJS
+	/* XXX: compression (as an option) */
+	DUK_DD(DUK_DDPRINT("running user initjs"));
+	duk_eval_string_noresult(ctx, (const char *) DUK_USE_USER_INITJS);
+#endif  /* DUK_USE_USER_INITJS */
+
+	/*
+	 *  Since built-ins are not often extended, compact them.
+	 */
+
+	DUK_DD(DUK_DDPRINT("compact built-ins"));
+	for (i = 0; i < DUK_NUM_BUILTINS; i++) {
+		duk_hobject_compact_props(thr, thr->builtins[i]);
+	}
+
+	DUK_D(DUK_DPRINT("INITBUILTINS END"));
+
+#ifdef DUK_USE_DDPRINT
+	for (i = 0; i < DUK_NUM_BUILTINS; i++) {
+		DUK_DD(DUK_DDPRINT("built-in object %ld after initialization and compacting: %!@iO",
+		                   (long) i, (duk_heaphdr *) thr->builtins[i]));
+	}
+#endif
+	
+#ifdef DUK_USE_DDDPRINT /*XXX:incorrect*/
+	for (i = 0; i < DUK_NUM_BUILTINS; i++) {
+		DUK_DDD(DUK_DDDPRINT("built-in object %ld after initialization and compacting", (long) i));
+		DUK_DEBUG_DUMP_HOBJECT(thr->builtins[i]);
+	}
+#endif
+
+	/*
+	 *  Pop built-ins from stack: they are now INCREF'd and
+	 *  reachable from the builtins[] array.
+	 */
+
+	duk_pop_n(ctx, DUK_NUM_BUILTINS);
+	DUK_ASSERT_TOP(ctx, 0);
+}
+
+void duk_hthread_copy_builtin_objects(duk_hthread *thr_from, duk_hthread *thr_to) {
+	duk_small_uint_t i;
+
+	for (i = 0; i < DUK_NUM_BUILTINS; i++) {
+		thr_to->builtins[i] = thr_from->builtins[i];
+		DUK_HOBJECT_INCREF(thr_to, thr_to->builtins[i]);  /* side effect free */
+	}
+}
+
+#line 1 "duk_hthread_misc.c"
+/*
+ *  Thread support.
+ */
+
+/* include removed: duk_internal.h */
+
+void duk_hthread_terminate(duk_hthread *thr) {
+	DUK_ASSERT(thr != NULL);
+
+	/* Order of unwinding is important */
+
+	duk_hthread_catchstack_unwind(thr, 0);
+
+	duk_hthread_callstack_unwind(thr, 0);  /* side effects, possibly errors */
+
+	thr->valstack_bottom = thr->valstack;
+	duk_set_top((duk_context *) thr, 0);  /* unwinds valstack, updating refcounts */
+
+	thr->state = DUK_HTHREAD_STATE_TERMINATED;
+
+	/* Here we could remove references to built-ins, but it may not be
+	 * worth the effort because built-ins are quite likely to be shared
+	 * with another (unterminated) thread, and terminated threads are also
+	 * usually garbage collected quite quickly.  Also, doing DECREFs
+	 * could trigger finalization, which would run on the current thread
+	 * and have access to only some of the built-ins.  Garbage collection
+	 * deals with this correctly already.
+	 */
+
+	/* XXX: Shrink the stacks to minimize memory usage?  May not
+	 * be worth the effort because terminated threads are usually
+	 * garbage collected quite soon.
+	 */
+}
+
+duk_activation *duk_hthread_get_current_activation(duk_hthread *thr) {
+	DUK_ASSERT(thr != NULL);
+
+	if (thr->callstack_top > 0) {
+		return thr->callstack + thr->callstack_top - 1;
+	} else {
+		return NULL;
+	}
+}
+#line 1 "duk_hthread_stacks.c"
+/*
+ *  Manipulation of thread stacks (valstack, callstack, catchstack).
+ *
+ *  Ideally unwinding of stacks should have no side effects, which would
+ *  then favor separate unwinding and shrink check primitives for each
+ *  stack type.  A shrink check may realloc and thus have side effects.
+ *
+ *  However, currently callstack unwinding itself has side effects, as it
+ *  needs to DECREF multiple objects, close environment records, etc.
+ *  Stacks must thus be unwound in the correct order by the caller.
+ *
+ *  (FIXME: This should be probably reworked so that there is a shared
+ *  unwind primitive which handles all stacks as requested, and knows
+ *  the proper order for unwinding.)
+ *
+ *  Valstack entries above 'top' are always kept initialized to
+ *  "undefined unused".  Callstack and catchstack entries above 'top'
+ *  are not zeroed and are left as garbage.
+ *
+ *  Value stack handling is mostly a part of the API implementation.
+ */
+
+/* include removed: duk_internal.h */
+
+/* check that there is space for at least one new entry */
+void duk_hthread_callstack_grow(duk_hthread *thr) {
+	duk_size_t old_size;
+	duk_size_t new_size;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT_DISABLE(thr->callstack_top >= 0);   /* avoid warning (unsigned) */
+	DUK_ASSERT(thr->callstack_size >= thr->callstack_top);
+
+	if (thr->callstack_top < thr->callstack_size) {
+		return;
+	}
+
+	old_size = thr->callstack_size;
+	new_size = old_size + DUK_CALLSTACK_GROW_STEP;
+
+	/* this is a bit approximate (errors out before max is reached); this is OK */
+	if (new_size >= thr->callstack_max) {
+		DUK_ERROR(thr, DUK_ERR_RANGE_ERROR, "callstack limit");
+	}
+
+	DUK_DD(DUK_DDPRINT("growing callstack %ld -> %ld", (long) old_size, (long) new_size));
+
+	/*
+	 *  Note: must use indirect variant of DUK_REALLOC() because underlying
+	 *  pointer may be changed by mark-and-sweep.
+	 */
+
+	thr->callstack = (duk_activation *) DUK_REALLOC_INDIRECT_CHECKED(thr, duk_hthread_get_callstack_ptr, (void *) thr, sizeof(duk_activation) * new_size);
+	thr->callstack_size = new_size;
+
+	/* note: any entries above the callstack top are garbage and not zeroed */
+}
+
+void duk_hthread_callstack_shrink_check(duk_hthread *thr) {
+	duk_size_t new_size;
+	duk_activation *p;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT_DISABLE(thr->callstack_top >= 0);  /* avoid warning (unsigned) */
+	DUK_ASSERT(thr->callstack_size >= thr->callstack_top);
+
+	if (thr->callstack_size - thr->callstack_top < DUK_CALLSTACK_SHRINK_THRESHOLD) {
+		return;
+	}
+
+	new_size = thr->callstack_top + DUK_CALLSTACK_SHRINK_SPARE;
+	DUK_ASSERT(new_size >= thr->callstack_top);
+
+	DUK_DD(DUK_DDPRINT("shrinking callstack %ld -> %ld", (long) thr->callstack_size, (long) new_size));
+
+	/*
+	 *  Note: must use indirect variant of DUK_REALLOC() because underlying
+	 *  pointer may be changed by mark-and-sweep.
+	 */
+
+	/* shrink failure is not fatal */
+	p = (duk_activation *) DUK_REALLOC_INDIRECT(thr->heap, duk_hthread_get_callstack_ptr, (void *) thr, sizeof(duk_activation) * new_size);
+	if (p) {
+		thr->callstack = p;
+		thr->callstack_size = new_size;
+	} else {
+		DUK_D(DUK_DPRINT("callstack shrink failed, ignoring"));
+	}
+
+	/* note: any entries above the callstack top are garbage and not zeroed */
+}
+
+void duk_hthread_callstack_unwind(duk_hthread *thr, duk_size_t new_top) {
+	duk_size_t idx;
+
+	DUK_DDD(DUK_DDDPRINT("unwind callstack top of thread %p from %ld to %ld",
+	                     (void *) thr,
+	                     (thr != NULL ? (long) thr->callstack_top : (long) -1),
+	                     (long) new_top));
+
+	DUK_ASSERT(thr);
+	DUK_ASSERT(thr->heap);
+	DUK_ASSERT_DISABLE(new_top >= 0);  /* unsigned */
+	DUK_ASSERT((duk_size_t) new_top <= thr->callstack_top);  /* cannot grow */
+
+	/*
+	 *  The loop below must avoid issues with potential callstack
+	 *  reallocations.  A resize (and other side effects) may happen
+	 *  e.g. due to finalizer/errhandler calls caused by a refzero or
+	 *  mark-and-sweep.  Arbitrary finalizers may run, because when
+	 *  an environment record is refzero'd, it may refer to arbitrary
+	 *  values which also become refzero'd.
+	 *
+	 *  So, the pointer 'p' is re-looked-up below whenever a side effect
+	 *  might have changed it.
+	 */
+
+	idx = thr->callstack_top;
+	while (idx > new_top) {
+		duk_activation *p;
+#ifdef DUK_USE_REFERENCE_COUNTING
+		duk_hobject *tmp;
+#endif
+
+		idx--;
+		DUK_ASSERT_DISABLE(idx >= 0);  /* unsigned */
+		DUK_ASSERT((duk_size_t) idx < thr->callstack_size);  /* true, despite side effect resizes */
+
+		p = thr->callstack + idx;
+		DUK_ASSERT(p->func != NULL);
+
+#ifdef DUK_USE_NONSTD_FUNC_CALLER_PROPERTY
+		/*
+		 *  Restore 'caller' property for non-strict callee functions.
+		 */
+
+		if (!DUK_HOBJECT_HAS_STRICT(p->func)) {
+			duk_tval *tv_caller;
+			duk_tval tv_tmp;
+			duk_hobject *h_tmp;
+
+			tv_caller = duk_hobject_find_existing_entry_tval_ptr(p->func, DUK_HTHREAD_STRING_CALLER(thr));
+
+			/* The p->prev_caller should only be set if the entry for 'caller'
+			 * exists (as it is only set in that case, and the property is not
+			 * configurable), but handle all the cases anyway.
+			 */
+
+			if (tv_caller) {
+				DUK_TVAL_SET_TVAL(&tv_tmp, tv_caller);
+				if (p->prev_caller) {
+					/* Just transfer the refcount from p->prev_caller to tv_caller,
+					 * so no need for a refcount update.  This is the expected case.
+					 */
+					DUK_TVAL_SET_OBJECT(tv_caller, p->prev_caller);
+					p->prev_caller = NULL;
+				} else {
+					DUK_TVAL_SET_NULL(tv_caller);   /* no incref needed */
+					DUK_ASSERT(p->prev_caller == NULL);
+				}
+				DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+			} else {
+				h_tmp = p->prev_caller;
+				if (h_tmp) {
+					p->prev_caller = NULL;
+					DUK_HOBJECT_DECREF(thr, h_tmp);  /* side effects */
+				}
+			}
+			p = thr->callstack + idx;  /* avoid side effects */
+			DUK_ASSERT(p->prev_caller == NULL);
+		}
+#endif
+
+		/*
+		 *  Close environment record(s) if they exist.
+		 *
+		 *  Only variable environments are closed.  If lex_env != var_env, it
+		 *  cannot currently contain any register bound declarations.
+		 *
+		 *  Only environments created for a NEWENV function are closed.  If an
+		 *  environment is created for e.g. an eval call, it must not be closed.
+		 */
+
+		if (!DUK_HOBJECT_HAS_NEWENV(p->func)) {
+			DUK_DDD(DUK_DDDPRINT("skip closing environments, envs not owned by this activation"));
+			goto skip_env_close;
+		}
+
+		DUK_ASSERT(p->lex_env == p->var_env);
+		if (p->var_env != NULL) {
+			DUK_DDD(DUK_DDDPRINT("closing var_env record %p -> %!O",
+			                     (void *) p->var_env, (duk_heaphdr *) p->var_env));
+			duk_js_close_environment_record(thr, p->var_env, p->func, p->idx_bottom);
+			p = thr->callstack + idx;  /* avoid side effect issues */
+		}
+
+#if 0
+		if (p->lex_env != NULL) {
+			if (p->lex_env == p->var_env) {
+				/* common case, already closed, so skip */
+				DUK_DD(DUK_DDPRINT("lex_env and var_env are the same and lex_env "
+				                   "already closed -> skip closing lex_env"));
+				;
+			} else {
+				DUK_DD(DUK_DDPRINT("closing lex_env record %p -> %!O",
+				                   (void *) p->lex_env, (duk_heaphdr *) p->lex_env));
+				duk_js_close_environment_record(thr, p->lex_env, p->func, p->idx_bottom);
+				p = thr->callstack + idx;  /* avoid side effect issues */
+			}
+		}
+#endif
+
+		DUK_ASSERT((p->lex_env == NULL) ||
+		           ((duk_hobject_find_existing_entry_tval_ptr(p->lex_env, DUK_HTHREAD_STRING_INT_CALLEE(thr)) == NULL) &&
+		            (duk_hobject_find_existing_entry_tval_ptr(p->lex_env, DUK_HTHREAD_STRING_INT_VARMAP(thr)) == NULL) &&
+		            (duk_hobject_find_existing_entry_tval_ptr(p->lex_env, DUK_HTHREAD_STRING_INT_THREAD(thr)) == NULL) &&
+		            (duk_hobject_find_existing_entry_tval_ptr(p->lex_env, DUK_HTHREAD_STRING_INT_REGBASE(thr)) == NULL)));
+
+		DUK_ASSERT((p->var_env == NULL) ||
+		           ((duk_hobject_find_existing_entry_tval_ptr(p->var_env, DUK_HTHREAD_STRING_INT_CALLEE(thr)) == NULL) &&
+		            (duk_hobject_find_existing_entry_tval_ptr(p->var_env, DUK_HTHREAD_STRING_INT_VARMAP(thr)) == NULL) &&
+		            (duk_hobject_find_existing_entry_tval_ptr(p->var_env, DUK_HTHREAD_STRING_INT_THREAD(thr)) == NULL) &&
+		            (duk_hobject_find_existing_entry_tval_ptr(p->var_env, DUK_HTHREAD_STRING_INT_REGBASE(thr)) == NULL)));
+
+	 skip_env_close:
+
+		/*
+		 *  Update preventcount
+		 */
+
+		if (p->flags & DUK_ACT_FLAG_PREVENT_YIELD) {
+			DUK_ASSERT(thr->callstack_preventcount >= 1);
+			thr->callstack_preventcount--;
+		}
+
+		/*
+		 *  Reference count updates
+		 *
+		 *  Note: careful manipulation of refcounts.  The top is
+		 *  not updated yet, so all the activations are reachable
+		 *  for mark-and-sweep (which may be triggered by decref).
+		 *  However, the pointers are NULL so this is not an issue.
+		 */
+
+#ifdef DUK_USE_REFERENCE_COUNTING
+		tmp = p->var_env;
+#endif
+		p->var_env = NULL;
+#ifdef DUK_USE_REFERENCE_COUNTING
+		DUK_HOBJECT_DECREF(thr, tmp);
+		p = thr->callstack + idx;  /* avoid side effect issues */
+#endif
+
+#ifdef DUK_USE_REFERENCE_COUNTING
+		tmp = p->lex_env;
+#endif
+		p->lex_env = NULL;
+#ifdef DUK_USE_REFERENCE_COUNTING
+		DUK_HOBJECT_DECREF(thr, tmp);
+		p = thr->callstack + idx;  /* avoid side effect issues */
+#endif
+
+		/* Note: this may cause a corner case situation where a finalizer
+		 * may see a currently reachable activation whose 'func' is NULL.
+		 */
+#ifdef DUK_USE_REFERENCE_COUNTING
+		tmp = p->func;
+#endif
+		p->func = NULL;
+#ifdef DUK_USE_REFERENCE_COUNTING
+		DUK_HOBJECT_DECREF(thr, tmp);
+		p = thr->callstack + idx;  /* avoid side effect issues */
+		DUK_UNREF(p);
+#endif
+	}
+
+	thr->callstack_top = new_top;
+
+	/*
+	 *  We could clear the book-keeping variables for the topmost activation,
+	 *  but don't do so now.
+	 */
+#if 0
+	if (thr->callstack_top > 0) {
+		duk_activation *p = thr->callstack + thr->callstack_top - 1;
+		p->idx_retval = 0;
+	}
+#endif
+
+	/* Note: any entries above the callstack top are garbage and not zeroed.
+	 * Also topmost activation idx_retval is garbage (not zeroed), and must
+	 * be ignored.
+	 */
+}
+
+void duk_hthread_catchstack_grow(duk_hthread *thr) {
+	duk_size_t old_size;
+	duk_size_t new_size;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT_DISABLE(thr->catchstack_top);  /* avoid warning (unsigned) */
+	DUK_ASSERT(thr->catchstack_size >= thr->catchstack_top);
+
+	if (thr->catchstack_top < thr->catchstack_size) {
+		return;
+	}
+
+	old_size = thr->catchstack_size;
+	new_size = old_size + DUK_CATCHSTACK_GROW_STEP;
+
+	/* this is a bit approximate (errors out before max is reached); this is OK */
+	if (new_size >= thr->catchstack_max) {
+		DUK_ERROR(thr, DUK_ERR_RANGE_ERROR, "catchstack limit");
+	}
+
+	DUK_DD(DUK_DDPRINT("growing catchstack %ld -> %ld", (long) old_size, (long) new_size));
+
+	/*
+	 *  Note: must use indirect variant of DUK_REALLOC() because underlying
+	 *  pointer may be changed by mark-and-sweep.
+	 */
+
+	thr->catchstack = (duk_catcher *) DUK_REALLOC_INDIRECT_CHECKED(thr, duk_hthread_get_catchstack_ptr, (void *) thr, sizeof(duk_catcher) * new_size);
+	thr->catchstack_size = new_size;
+
+	/* note: any entries above the catchstack top are garbage and not zeroed */
+}
+
+void duk_hthread_catchstack_shrink_check(duk_hthread *thr) {
+	duk_size_t new_size;
+	duk_catcher *p;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT_DISABLE(thr->catchstack_top >= 0);  /* avoid warning (unsigned) */
+	DUK_ASSERT(thr->catchstack_size >= thr->catchstack_top);
+
+	if (thr->catchstack_size - thr->catchstack_top < DUK_CATCHSTACK_SHRINK_THRESHOLD) {
+		return;
+	}
+
+	new_size = thr->catchstack_top + DUK_CATCHSTACK_SHRINK_SPARE;
+	DUK_ASSERT(new_size >= thr->catchstack_top);
+
+	DUK_DD(DUK_DDPRINT("shrinking catchstack %ld -> %ld", (long) thr->catchstack_size, (long) new_size));
+
+	/*
+	 *  Note: must use indirect variant of DUK_REALLOC() because underlying
+	 *  pointer may be changed by mark-and-sweep.
+	 */
+
+	/* shrink failure is not fatal */
+	p = (duk_catcher *) DUK_REALLOC_INDIRECT(thr->heap, duk_hthread_get_catchstack_ptr, (void *) thr, sizeof(duk_catcher) * new_size);
+	if (p) {
+		thr->catchstack = p;
+		thr->catchstack_size = new_size;
+	} else {
+		DUK_D(DUK_DPRINT("catchstack shrink failed, ignoring"));
+	}
+
+	/* note: any entries above the catchstack top are garbage and not zeroed */
+}
+
+void duk_hthread_catchstack_unwind(duk_hthread *thr, duk_size_t new_top) {
+	duk_size_t idx;
+
+	DUK_DDD(DUK_DDDPRINT("unwind catchstack top of thread %p from %ld to %ld",
+	                     (void *) thr,
+	                     (thr != NULL ? (long) thr->catchstack_top : (long) -1),
+	                     (long) new_top));
+
+	DUK_ASSERT(thr);
+	DUK_ASSERT(thr->heap);
+	DUK_ASSERT_DISABLE(new_top >= 0);  /* unsigned */
+	DUK_ASSERT((duk_size_t) new_top <= thr->catchstack_top);  /* cannot grow */
+
+	/*
+	 *  Since there are no references in the catcher structure,
+	 *  unwinding is quite simple.  The only thing we need to
+	 *  look out for is popping a possible lexical environment
+	 *  established for an active catch clause.
+	 */
+
+	idx = thr->catchstack_top;
+	while (idx > new_top) {
+		duk_catcher *p;
+		duk_activation *act;
+		duk_hobject *env;
+
+		idx--;
+		DUK_ASSERT_DISABLE(idx >= 0);  /* unsigned */
+		DUK_ASSERT((duk_size_t) idx < thr->catchstack_size);
+
+		p = thr->catchstack + idx;
+
+		if (DUK_CAT_HAS_LEXENV_ACTIVE(p)) {
+			DUK_DDD(DUK_DDDPRINT("unwinding catchstack idx %ld, callstack idx %ld, callstack top %ld: lexical environment active",
+			                     (long) idx, (long) p->callstack_index, (long) thr->callstack_top));
+
+			/* FIXME: Here we have a nasty dependency: the need to manipulate
+			 * the callstack means that catchstack must always be unwound by
+			 * the caller before unwinding the callstack.  This should be fixed
+			 * later.
+			 */
+
+			/* Note that multiple catchstack entries may refer to the same
+			 * callstack entry.
+			 */
+			act = thr->callstack + p->callstack_index;
+			DUK_ASSERT(act >= thr->callstack);
+			DUK_ASSERT(act < thr->callstack + thr->callstack_top);
+
+			DUK_DDD(DUK_DDDPRINT("catchstack_index=%ld, callstack_index=%ld, lex_env=%!iO",
+			                     (long) idx, (long) p->callstack_index,
+			                     (duk_heaphdr *) act->lex_env));
+
+			env = act->lex_env;             /* current lex_env of the activation (created for catcher) */
+			DUK_ASSERT(env != NULL);        /* must be, since env was created when catcher was created */
+			act->lex_env = env->prototype;  /* prototype is lex_env before catcher created */
+			DUK_HOBJECT_DECREF(thr, env);
+
+			/* There is no need to decref anything else than 'env': if 'env'
+			 * becomes unreachable, refzero will handle decref'ing its prototype.
+			 */
+		}
+	}
+
+	thr->catchstack_top = new_top;
+
+	/* note: any entries above the catchstack top are garbage and not zeroed */
+}
+#line 1 "duk_js_call.c"
+/*
+ *  Call handling.
+ *
+ *  The main work horse functions are:
+ *    - duk_handle_call(): call to a C/Ecmascript functions
+ *    - duk_handle_safe_call(): make a protected C call within current activation
+ *    - duk_handle_ecma_call_setup(): Ecmascript-to-Ecmascript calls, including
+ *      tail calls and coroutine resume
+ */
+
+/* include removed: duk_internal.h */
+
+/*
+ *  Arguments object creation.
+ *
+ *  Creating arguments objects is a bit finicky, see E5 Section 10.6 for the
+ *  specific requirements.  Much of the arguments object exotic behavior is
+ *  implemented in duk_hobject_props.c, and is enabled by the object flag
+ *  DUK_HOBJECT_FLAG_EXOTIC_ARGUMENTS.
+ */
+
+static void duk__create_arguments_object(duk_hthread *thr,
+                                         duk_hobject *func,
+                                         duk_hobject *varenv,
+                                         duk_idx_t idx_argbase,        /* idx of first argument on stack */
+                                         duk_idx_t num_stack_args) {   /* num args starting from idx_argbase */
+	duk_context *ctx = (duk_context *) thr;
+	duk_hobject *arg;          /* 'arguments' */
+	duk_hobject *formals;      /* formals for 'func' (may be NULL if func is a C function) */
+	duk_idx_t i_arg;
+	duk_idx_t i_map;
+	duk_idx_t i_mappednames;
+	duk_idx_t i_formals;
+	duk_idx_t i_argbase;
+	duk_idx_t n_formals;
+	duk_idx_t idx;
+	duk_bool_t need_map;
+
+	DUK_DDD(DUK_DDDPRINT("creating arguments object for func=%!iO, varenv=%!iO, "
+	                     "idx_argbase=%ld, num_stack_args=%ld",
+	                     (duk_heaphdr *) func, (duk_heaphdr *) varenv,
+	                     (long) idx_argbase, (long) num_stack_args));
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(func != NULL);
+	DUK_ASSERT(DUK_HOBJECT_IS_NONBOUND_FUNCTION(func));
+	DUK_ASSERT(varenv != NULL);
+	DUK_ASSERT(idx_argbase >= 0);  /* assumed to bottom relative */
+	DUK_ASSERT(num_stack_args >= 0);
+
+	need_map = 0;
+
+	i_argbase = idx_argbase;
+	DUK_ASSERT(i_argbase >= 0);
+
+	duk_push_hobject(ctx, func);
+	duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_FORMALS);
+	formals = duk_get_hobject(ctx, -1);
+	n_formals = 0;
+	if (formals) {
+		duk_get_prop_stridx(ctx, -1, DUK_STRIDX_LENGTH);
+		n_formals = (duk_idx_t) duk_require_int(ctx, -1);
+		duk_pop(ctx);
+	}
+	duk_remove(ctx, -2);  /* leave formals on stack for later use */
+	i_formals = duk_require_top_index(ctx);
+
+	DUK_ASSERT(n_formals >= 0);
+	DUK_ASSERT(formals != NULL || n_formals == 0);
+
+	DUK_DDD(DUK_DDDPRINT("func=%!O, formals=%!O, n_formals=%ld",
+	                     (duk_heaphdr *) func, (duk_heaphdr *) formals,
+	                     (long) n_formals));
+
+	/* [ ... formals ] */
+
+	/*
+	 *  Create required objects:
+	 *    - 'arguments' object: array-like, but not an array
+	 *    - 'map' object: internal object, tied to 'arguments'
+	 *    - 'mappedNames' object: temporary value used during construction
+	 */
+
+	i_arg = duk_push_object_helper(ctx,
+	                               DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                               DUK_HOBJECT_FLAG_ARRAY_PART |
+	                               DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ARGUMENTS),
+	                               DUK_BIDX_OBJECT_PROTOTYPE);
+	DUK_ASSERT(i_arg >= 0);
+	arg = duk_require_hobject(ctx, -1);
+	DUK_ASSERT(arg != NULL);
+
+	i_map = duk_push_object_helper(ctx,
+	                               DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                               DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT),
+	                               -1);  /* no prototype */
+	DUK_ASSERT(i_map >= 0);
+
+	i_mappednames = duk_push_object_helper(ctx,
+	                                       DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                                       DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT),
+	                                       -1);  /* no prototype */
+	DUK_ASSERT(i_mappednames >= 0);
+
+	/* [... formals arguments map mappedNames] */
+
+	DUK_DDD(DUK_DDDPRINT("created arguments related objects: "
+	                     "arguments at index %ld -> %!O "
+	                     "map at index %ld -> %!O "
+	                     "mappednames at index %ld -> %!O",
+	                     (long) i_arg, (duk_heaphdr *) duk_get_hobject(ctx, i_arg),
+	                     (long) i_map, (duk_heaphdr *) duk_get_hobject(ctx, i_map),
+	                     (long) i_mappednames, (duk_heaphdr *) duk_get_hobject(ctx, i_mappednames)));
+
+	/*
+	 *  Init arguments properties, map, etc.
+	 */
+
+	duk_push_int(ctx, num_stack_args);
+	duk_def_prop_stridx(ctx, i_arg, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_WC);
+
+	/*
+	 *  Init argument related properties
+	 */
+
+	/* step 11 */
+	idx = num_stack_args - 1;
+	while (idx >= 0) {
+		DUK_DDD(DUK_DDDPRINT("arg idx %ld, argbase=%ld, argidx=%ld",
+		                     (long) idx, (long) i_argbase, (long) (i_argbase + idx)));
+
+		DUK_DDD(DUK_DDDPRINT("define arguments[%ld]=arg", (long) idx));
+		duk_dup(ctx, i_argbase + idx);
+		duk_def_prop_index_wec(ctx, i_arg, (duk_uarridx_t) idx);
+		DUK_DDD(DUK_DDDPRINT("defined arguments[%ld]=arg", (long) idx));
+
+		/* step 11.c is relevant only if non-strict (checked in 11.c.ii) */
+		if (!DUK_HOBJECT_HAS_STRICT(func) && idx < n_formals) {
+			DUK_ASSERT(formals != NULL);
+
+			DUK_DDD(DUK_DDDPRINT("strict function, index within formals (%ld < %ld)",
+			                     (long) idx, (long) n_formals));
+
+			duk_get_prop_index(ctx, i_formals, idx);
+			DUK_ASSERT(duk_is_string(ctx, -1));
+
+			duk_dup(ctx, -1);  /* [... name name] */
+
+			if (!duk_has_prop(ctx, i_mappednames)) {
+				/* steps 11.c.ii.1 - 11.c.ii.4, but our internal book-keeping
+				 * differs from the reference model
+				 */
+
+				/* [... name] */
+
+				need_map = 1;
+
+				DUK_DDD(DUK_DDDPRINT("set mappednames[%s]=%ld",
+				                     (const char *) duk_get_string(ctx, -1),
+				                     (long) idx));
+				duk_dup(ctx, -1);                      /* name */
+				duk_push_uint(ctx, (duk_uint_t) idx);  /* index */
+				duk_to_string(ctx, -1);
+				duk_def_prop_wec(ctx, i_mappednames);  /* out of spec, must be configurable */
+
+				DUK_DDD(DUK_DDDPRINT("set map[%ld]=%s",
+				                     (long) idx,
+				                     duk_get_string(ctx, -1)));
+				duk_dup(ctx, -1);         /* name */
+				duk_def_prop_index_wec(ctx, i_map, (duk_uarridx_t) idx);  /* out of spec, must be configurable */
+			} else {
+				/* duk_has_prop() popped the second 'name' */
+			}
+
+			/* [... name] */
+			duk_pop(ctx);  /* pop 'name' */
+		}
+
+		idx--;
+	}
+
+	DUK_DDD(DUK_DDDPRINT("actual arguments processed"));
+
+	/* step 12 */
+	if (need_map) {
+		DUK_DDD(DUK_DDDPRINT("adding 'map' and 'varenv' to arguments object"));
+
+		/* should never happen for a strict callee */
+		DUK_ASSERT(!DUK_HOBJECT_HAS_STRICT(func));
+
+		duk_dup(ctx, i_map);
+		duk_def_prop_stridx(ctx, i_arg, DUK_STRIDX_INT_MAP, DUK_PROPDESC_FLAGS_NONE);  /* out of spec, don't care */
+
+		/* The variable environment for magic variable bindings needs to be
+		 * given by the caller and recorded in the arguments object.
+		 *
+		 * See E5 Section 10.6, the creation of setters/getters.
+		 *
+		 * The variable environment also provides access to the callee, so
+		 * an explicit (internal) callee property is not needed.
+		 */
+
+		duk_push_hobject(ctx, varenv);
+		duk_def_prop_stridx(ctx, i_arg, DUK_STRIDX_INT_VARENV, DUK_PROPDESC_FLAGS_NONE);  /* out of spec, don't care */
+	}
+
+	/* steps 13-14 */
+	if (DUK_HOBJECT_HAS_STRICT(func)) {
+		/*
+		 *  Note: callee/caller are throwers and are not deletable etc.
+		 *  They could be implemented as virtual properties, but currently
+		 *  there is no support for virtual properties which are accessors
+		 *  (only plain virtual properties).  This would not be difficult
+		 *  to change in duk_hobject_props, but we can make the throwers
+		 *  normal, concrete properties just as easily.
+		 *
+		 *  Note that the specification requires that the *same* thrower
+		 *  built-in object is used here!  See E5 Section 10.6 main
+		 *  algoritm, step 14, and Section 13.2.3 which describes the
+		 *  thrower.  See test case test-arguments-throwers.js.
+		 */
+
+		DUK_DDD(DUK_DDDPRINT("strict function, setting caller/callee to throwers"));
+
+		duk_def_prop_stridx_thrower(ctx, i_arg, DUK_STRIDX_CALLER, DUK_PROPDESC_FLAGS_NONE);
+		duk_def_prop_stridx_thrower(ctx, i_arg, DUK_STRIDX_CALLEE, DUK_PROPDESC_FLAGS_NONE);
+	} else {
+		DUK_DDD(DUK_DDDPRINT("non-strict function, setting callee to actual value"));
+		duk_push_hobject(ctx, func);
+		duk_def_prop_stridx(ctx, i_arg, DUK_STRIDX_CALLEE, DUK_PROPDESC_FLAGS_WC);
+	}
+
+	/* set exotic behavior only after we're done */
+	if (need_map) {
+		/*
+		 *  Note: exotic behaviors are only enabled for arguments
+		 *  objects which have a parameter map (see E5 Section 10.6
+		 *  main algorithm, step 12).
+		 *
+		 *  In particular, a non-strict arguments object with no
+		 *  mapped formals does *NOT* get exotic behavior, even
+		 *  for e.g. "caller" property.  This seems counterintuitive
+		 *  but seems to be the case.
+		 */
+
+		/* cannot be strict (never mapped variables) */
+		DUK_ASSERT(!DUK_HOBJECT_HAS_STRICT(func));
+
+		DUK_DDD(DUK_DDDPRINT("enabling exotic behavior for arguments object"));
+		DUK_HOBJECT_SET_EXOTIC_ARGUMENTS(arg);
+	} else {
+		DUK_DDD(DUK_DDDPRINT("not enabling exotic behavior for arguments object"));
+	}
+
+	/* nice log */
+	DUK_DDD(DUK_DDDPRINT("final arguments related objects: "
+	                     "arguments at index %ld -> %!O "
+	                     "map at index %ld -> %!O "
+	                     "mappednames at index %ld -> %!O",
+	                     (long) i_arg, (duk_heaphdr *) duk_get_hobject(ctx, i_arg),
+	                     (long) i_map, (duk_heaphdr *) duk_get_hobject(ctx, i_map),
+	                     (long) i_mappednames, (duk_heaphdr *) duk_get_hobject(ctx, i_mappednames)));
+
+	/* [args(n) [crud] formals arguments map mappednames] -> [args [crud] arguments] */
+	duk_pop_2(ctx);
+	duk_remove(ctx, -2);
+}
+
+/* Helper for creating the arguments object and adding it to the env record
+ * on top of the value stack.  This helper has a very strict dependency on
+ * the shape of the input stack.
+ */
+static void duk__handle_createargs_for_call(duk_hthread *thr,
+                                            duk_hobject *func,
+                                            duk_hobject *env,
+                                            duk_idx_t num_stack_args) {
+	duk_context *ctx = (duk_context *) thr;
+
+	DUK_DDD(DUK_DDDPRINT("creating arguments object for function call"));
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(func != NULL);
+	DUK_ASSERT(env != NULL);
+	DUK_ASSERT(DUK_HOBJECT_HAS_CREATEARGS(func));
+	DUK_ASSERT(duk_get_top(ctx) >= num_stack_args + 1);
+
+	/* [... arg1 ... argN envobj] */
+
+	duk__create_arguments_object(thr,
+	                             func,
+	                             env,
+	                             duk_get_top(ctx) - num_stack_args - 1,    /* idx_argbase */
+	                             num_stack_args);
+
+	/* [... arg1 ... argN envobj argobj] */
+
+	duk_def_prop_stridx(ctx,
+	                    -2,
+	                    DUK_STRIDX_LC_ARGUMENTS,
+	                    DUK_HOBJECT_HAS_STRICT(func) ? DUK_PROPDESC_FLAGS_E :   /* strict: non-deletable, non-writable */
+	                                                   DUK_PROPDESC_FLAGS_WE);  /* non-strict: non-deletable, writable */
+	/* [... arg1 ... argN envobj] */
+}
+
+/*
+ *  Helper for handling a "bound function" chain when a call is being made.
+ *
+ *  Follows the bound function chain until a non-bound function is found.
+ *  Prepends the bound arguments to the value stack (at idx_func + 2),
+ *  updating 'num_stack_args' in the process.  The 'this' binding is also
+ *  updated if necessary (at idx_func + 1).  Note that for constructor calls
+ *  the 'this' binding is never updated by [[BoundThis]].
+ *
+ *  XXX: bound function chains could be collapsed at bound function creation
+ *  time so that each bound function would point directly to a non-bound
+ *  function.  This would make call time handling much easier.
+ */
+
+static void duk__handle_bound_chain_for_call(duk_hthread *thr,
+                                             duk_idx_t idx_func,
+                                             duk_idx_t *p_num_stack_args,   /* may be changed by call */
+                                             duk_hobject **p_func,    /* changed by call */
+                                             duk_bool_t is_constructor_call) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_idx_t num_stack_args;
+	duk_hobject *func;
+	duk_uint_t sanity;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(p_num_stack_args != NULL);
+	DUK_ASSERT(p_func != NULL);
+	DUK_ASSERT(*p_func != NULL);
+	DUK_ASSERT(DUK_HOBJECT_HAS_BOUND(*p_func));
+
+	num_stack_args = *p_num_stack_args;
+	func = *p_func;
+
+	sanity = DUK_HOBJECT_BOUND_CHAIN_SANITY;
+	do {	
+		duk_idx_t i, len;
+
+		if (!DUK_HOBJECT_HAS_BOUND(func)) {
+			break;
+		}
+
+		DUK_DDD(DUK_DDDPRINT("bound function encountered, ptr=%p", (void *) func));
+
+		/* XXX: this could be more compact by accessing the internal properties
+		 * directly as own properties (they cannot be inherited, and are not
+		 * externally visible).
+		 */
+
+		DUK_DDD(DUK_DDDPRINT("bound function encountered, ptr=%p, num_stack_args=%ld",
+		                     (void *) func, (long) num_stack_args));
+
+		/* [ ... func this arg1 ... argN ] */
+
+		if (is_constructor_call) {
+			/* See: ecmascript-testcases/test-spec-bound-constructor.js */
+			DUK_DDD(DUK_DDDPRINT("constructor call: don't update this binding"));
+		} else {
+			duk_get_prop_stridx(ctx, idx_func, DUK_STRIDX_INT_THIS);
+			duk_replace(ctx, idx_func + 1);  /* idx_this = idx_func + 1 */
+		}
+
+		/* [ ... func this arg1 ... argN ] */
+
+		/* XXX: duk_get_length? */
+		duk_get_prop_stridx(ctx, idx_func, DUK_STRIDX_INT_ARGS);  /* -> [ ... func this arg1 ... argN _args ] */
+		duk_get_prop_stridx(ctx, -1, DUK_STRIDX_LENGTH);          /* -> [ ... func this arg1 ... argN _args length ] */
+		len = (duk_idx_t) duk_require_int(ctx, -1);
+		duk_pop(ctx);
+		for (i = 0; i < len; i++) {
+			/* XXX: very slow - better to bulk allocate a gap, and copy
+			 * from args_array directly (we know it has a compact array
+			 * part, etc).
+			 */
+
+			/* [ ... func this <some bound args> arg1 ... argN _args ] */
+			duk_get_prop_index(ctx, -1, i);
+			duk_insert(ctx, idx_func + 2 + i);  /* idx_args = idx_func + 2 */
+		}
+		num_stack_args += len;  /* must be updated to work properly (e.g. creation of 'arguments') */
+		duk_pop(ctx);
+
+		/* [ ... func this <bound args> arg1 ... argN ] */
+
+		duk_get_prop_stridx(ctx, idx_func, DUK_STRIDX_INT_TARGET);
+		duk_replace(ctx, idx_func);  /* replace also in stack; not strictly necessary */
+		func = duk_require_hobject(ctx, idx_func);
+
+		DUK_DDD(DUK_DDDPRINT("bound function handled, num_stack_args=%ld, idx_func=%ld",
+		                     (long) num_stack_args, (long) idx_func));
+	} while (--sanity > 0);
+
+	if (sanity == 0) {
+		DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, DUK_STR_BOUND_CHAIN_LIMIT);
+	}
+
+	DUK_DDD(DUK_DDDPRINT("final non-bound function is: %p", (void *) func));
+
+	DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));
+	DUK_ASSERT(DUK_HOBJECT_HAS_COMPILEDFUNCTION(func) || DUK_HOBJECT_HAS_NATIVEFUNCTION(func));
+
+	/* write back */
+	*p_num_stack_args = num_stack_args;
+	*p_func = func;
+}
+
+/*
+ *  Helper for setting up var_env and lex_env of an activation,
+ *  assuming it does NOT have the DUK_HOBJECT_FLAG_NEWENV flag.
+ */
+
+static void duk__handle_oldenv_for_call(duk_hthread *thr,
+                                        duk_hobject *func,
+                                        duk_activation *act) {
+	duk_tval *tv;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(func != NULL);
+	DUK_ASSERT(act != NULL);
+	DUK_ASSERT(!DUK_HOBJECT_HAS_NEWENV(func));
+	DUK_ASSERT(!DUK_HOBJECT_HAS_CREATEARGS(func));
+
+	tv = duk_hobject_find_existing_entry_tval_ptr(func, DUK_HTHREAD_STRING_INT_LEXENV(thr));
+	if (tv) {
+		DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
+		DUK_ASSERT(DUK_HOBJECT_IS_ENV(DUK_TVAL_GET_OBJECT(tv)));
+		act->lex_env = DUK_TVAL_GET_OBJECT(tv);
+
+		tv = duk_hobject_find_existing_entry_tval_ptr(func, DUK_HTHREAD_STRING_INT_VARENV(thr));
+		if (tv) {
+			DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
+			DUK_ASSERT(DUK_HOBJECT_IS_ENV(DUK_TVAL_GET_OBJECT(tv)));
+			act->var_env = DUK_TVAL_GET_OBJECT(tv);
+		} else {
+			act->var_env = act->lex_env;
+		}
+	} else {
+		act->lex_env = thr->builtins[DUK_BIDX_GLOBAL_ENV];
+		act->var_env = act->lex_env;
+	}
+
+	DUK_HOBJECT_INCREF(thr, act->lex_env);
+	DUK_HOBJECT_INCREF(thr, act->var_env);
+}
+
+/*
+ *  Helper for updating callee 'caller' property.
+ */
+
+#ifdef DUK_USE_NONSTD_FUNC_CALLER_PROPERTY
+static void duk__update_func_caller_prop(duk_hthread *thr, duk_hobject *func) {
+	duk_tval *tv_caller;
+	duk_hobject *h_tmp;
+	duk_activation *act_callee;
+	duk_activation *act_caller;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(func != NULL);
+	DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));  /* bound chain resolved */
+	DUK_ASSERT(thr->callstack_top >= 1);
+
+	if (DUK_HOBJECT_HAS_STRICT(func)) {
+		/* Strict functions don't get their 'caller' updated. */
+		return;
+	}
+
+	act_callee = thr->callstack + thr->callstack_top - 1;
+	act_caller = (thr->callstack_top >= 2 ? act_callee - 1 : NULL);
+
+	/* Backup 'caller' property and update its value. */
+	tv_caller = duk_hobject_find_existing_entry_tval_ptr(func, DUK_HTHREAD_STRING_CALLER(thr));
+	if (tv_caller) {
+		/* If caller is global/eval code, 'caller' should be set to
+		 * 'null'.
+		 *
+		 * XXX: there is no exotic flag to infer this correctly now.
+		 * The NEWENV flag is used now which works as intended for
+		 * everything (global code, non-strict eval code, and functions)
+		 * except strict eval code.  Bound functions are never an issue
+		 * because 'func' has been resolved to a non-bound function.
+		 */
+
+		if (act_caller) {
+			/* act_caller->func may be NULL in some finalization cases,
+			 * just treat like we don't know the caller.
+			 */
+			if (act_caller->func && !DUK_HOBJECT_HAS_NEWENV(act_caller->func)) {
+				/* Setting to NULL causes 'caller' to be set to
+				 * 'null' as desired.
+				 */
+				act_caller = NULL;
+			}
+		}
+
+		if (DUK_TVAL_IS_OBJECT(tv_caller)) {
+			h_tmp = DUK_TVAL_GET_OBJECT(tv_caller);
+			DUK_ASSERT(h_tmp != NULL);
+			act_callee->prev_caller = h_tmp;
+
+			/* Previous value doesn't need refcount changes because its ownership
+			 * is transferred to prev_caller.
+			 */
+
+			if (act_caller) {
+				DUK_ASSERT(act_caller->func != NULL);
+				DUK_TVAL_SET_OBJECT(tv_caller, act_caller->func);
+				DUK_TVAL_INCREF(thr, tv_caller);
+			} else {
+				DUK_TVAL_SET_NULL(tv_caller);  /* no incref */
+			}
+		} else {
+			/* 'caller' must only take on 'null' or function value */
+			DUK_ASSERT(!DUK_TVAL_IS_HEAP_ALLOCATED(tv_caller));
+			DUK_ASSERT(act_callee->prev_caller == NULL);
+			if (act_caller && act_caller->func) {
+				/* Tolerate act_caller->func == NULL which happens in
+				 * some finalization cases; treat like unknown caller.
+				 */
+				DUK_TVAL_SET_OBJECT(tv_caller, act_caller->func);
+				DUK_TVAL_INCREF(thr, tv_caller);
+			} else {
+				DUK_TVAL_SET_NULL(tv_caller);  /* no incref */
+			}
+		}
+	}
+}
+#endif  /* DUK_USE_NONSTD_FUNC_CALLER_PROPERTY */
+
+/*
+ *  Determine the effective 'this' binding and coerce the current value
+ *  on the valstack to the effective one (in-place, at idx_this).
+ *
+ *  The current this value in the valstack (at idx_this) represents either:
+ *    - the caller's requested 'this' binding; or
+ *    - a 'this' binding accumulated from the bound function chain
+ *
+ *  The final 'this' binding for the target function may still be
+ *  different, and is determined as described in E5 Section 10.4.3.
+ *
+ *  For global and eval code (E5 Sections 10.4.1 and 10.4.2), we assume
+ *  that the caller has provided the correct 'this' binding explicitly
+ *  when calling, i.e.:
+ *
+ *    - global code: this=global object
+ *    - direct eval: this=copy from eval() caller's this binding
+ *    - other eval:  this=global object
+ *
+ *  Note: this function may cause a recursive function call with arbitrary
+ *  side effects, because ToObject() may be called.
+ */
+
+static void duk__coerce_effective_this_binding(duk_hthread *thr,
+                                               duk_hobject *func,
+                                               duk_idx_t idx_this) {
+	duk_context *ctx = (duk_context *) thr;
+
+	if (DUK_HOBJECT_HAS_STRICT(func)) {
+		DUK_DDD(DUK_DDDPRINT("this binding: strict -> use directly"));
+	} else {
+		duk_tval *tv_this = duk_require_tval(ctx, idx_this);
+		duk_hobject *obj_global;
+
+		if (DUK_TVAL_IS_OBJECT(tv_this)) {
+			DUK_DDD(DUK_DDDPRINT("this binding: non-strict, object -> use directly"));
+		} else if (DUK_TVAL_IS_UNDEFINED(tv_this) || DUK_TVAL_IS_NULL(tv_this)) {
+			DUK_DDD(DUK_DDDPRINT("this binding: non-strict, undefined/null -> use global object"));
+			obj_global = thr->builtins[DUK_BIDX_GLOBAL];
+			if (obj_global) {
+				duk_push_hobject(ctx, obj_global);
+			} else {
+				/*
+				 *  This may only happen if built-ins are being "torn down".
+				 *  This behavior is out of specification scope.
+				 */
+				DUK_D(DUK_DPRINT("this binding: wanted to use global object, but it is NULL -> using undefined instead"));
+				duk_push_undefined(ctx);
+			}
+			duk_replace(ctx, idx_this);
+		} else {
+			DUK_DDD(DUK_DDDPRINT("this binding: non-strict, not object/undefined/null -> use ToObject(value)"));
+			duk_to_object(ctx, idx_this);  /* may have side effects */
+		}
+	}
+}
+
+/*
+ *  Helper for making various kinds of calls.
+ *
+ *  Call flags:
+ *
+ *    DUK_CALL_FLAG_PROTECTED        <-->  protected call
+ *    DUK_CALL_FLAG_IGNORE_RECLIMIT  <-->  ignore C recursion limit,
+ *                                         for errhandler calls
+ *    DUK_CALL_FLAG_CONSTRUCTOR_CALL <-->  for 'new Foo()' calls
+ *
+ *  Input stack:
+ *
+ *    [ func this arg1 ... argN ]
+ *
+ *  Output stack:
+ *
+ *    [ retval ]         (DUK_EXEC_SUCCESS)
+ *    [ errobj ]         (DUK_EXEC_ERROR (normal error), protected call)
+ *
+ *  Even when executing a protected call an error may be thrown in rare cases.
+ *  For instance, if we run out of memory when setting up the return stack
+ *  after a caught error, the out of memory is propagated to the caller.
+ *  Similarly, API errors (such as invalid input stack shape and invalid
+ *  indices) cause an error to propagate out of this function.  If there is
+ *  no catchpoint for this error, the fatal error handler is called.
+ *
+ *  See 'execution.txt'.
+ *
+ *  The allowed thread states for making a call are:
+ *    - thr matches heap->curr_thread, and thr is already RUNNING
+ *    - thr does not match heap->curr_thread (may be NULL or other),
+ *      and thr is INACTIVE (in this case, a setjmp() catchpoint is
+ *      always used for thread book-keeping to work properly)
+ *
+ *  Like elsewhere, gotos are used to keep indent level minimal and
+ *  avoiding a dozen helpers with awkward plumbing.
+ *
+ *  Note: setjmp() and local variables have a nasty interaction,
+ *  see execution.txt; non-volatile locals modified after setjmp()
+ *  call are not guaranteed to keep their value.
+ */
+
+duk_int_t duk_handle_call(duk_hthread *thr,
+                          duk_idx_t num_stack_args,
+                          duk_small_uint_t call_flags) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_size_t entry_valstack_bottom_index;
+	duk_size_t entry_callstack_top;
+	duk_size_t entry_catchstack_top;
+	duk_int_t entry_call_recursion_depth;
+	duk_hthread *entry_curr_thread;
+	duk_uint_fast8_t entry_thread_state;
+	volatile duk_bool_t need_setjmp;
+	duk_jmpbuf * volatile old_jmpbuf_ptr = NULL;    /* ptr is volatile (not the target) */
+	duk_idx_t idx_func;         /* valstack index of 'func' and retval (relative to entry valstack_bottom) */
+	duk_idx_t idx_args;         /* valstack index of start of args (arg1) (relative to entry valstack_bottom) */
+	duk_idx_t nargs;            /* # argument registers target function wants (< 0 => "as is") */
+	duk_idx_t nregs;            /* # total registers target function wants on entry (< 0 => "as is") */
+	duk_size_t vs_min_size;
+	duk_hobject *func;    /* 'func' on stack (borrowed reference) */
+	duk_activation *act;
+	duk_hobject *env;
+	duk_jmpbuf our_jmpbuf;
+	duk_tval tv_tmp;
+	duk_int_t retval = DUK_EXEC_ERROR;
+	duk_ret_t rc;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(num_stack_args >= 0);
+
+	/* XXX: currently NULL allocations are not supported; remove if later allowed */
+	DUK_ASSERT(thr->valstack != NULL);
+	DUK_ASSERT(thr->callstack != NULL);
+	DUK_ASSERT(thr->catchstack != NULL);
+
+	/*
+	 *  Preliminaries, required by setjmp() handler.
+	 *
+	 *  Must be careful not to throw an unintended error here.
+	 *
+	 *  Note: careful with indices like '-x'; if 'x' is zero, it
+	 *  refers to valstack_bottom.
+	 */
+
+	entry_valstack_bottom_index = (duk_size_t) (thr->valstack_bottom - thr->valstack);
+	entry_callstack_top = thr->callstack_top;
+	entry_catchstack_top = thr->catchstack_top;
+	entry_call_recursion_depth = thr->heap->call_recursion_depth;
+	entry_curr_thread = thr->heap->curr_thread;  /* Note: may be NULL if first call */
+	entry_thread_state = thr->state;
+	idx_func = duk_normalize_index(ctx, -num_stack_args - 2);  /* idx_func must be valid, note: non-throwing! */
+	idx_args = idx_func + 2;                                   /* idx_args is not necessarily valid if num_stack_args == 0 (idx_args then equals top) */
+
+	/* Need a setjmp() catchpoint if a protected call OR if we need to
+	 * do mandatory cleanup.
+	 */
+	need_setjmp = ((call_flags & DUK_CALL_FLAG_PROTECTED) != 0) || (thr->heap->curr_thread != thr);
+
+	DUK_DD(DUK_DDPRINT("duk_handle_call: thr=%p, num_stack_args=%ld, "
+	                   "call_flags=0x%08lx (protected=%ld, ignorerec=%ld, constructor=%ld), need_setjmp=%ld, "
+	                   "valstack_top=%ld, idx_func=%ld, idx_args=%ld, rec_depth=%ld/%ld, "
+	                   "entry_valstack_bottom_index=%ld, entry_callstack_top=%ld, entry_catchstack_top=%ld, "
+	                   "entry_call_recursion_depth=%ld, entry_curr_thread=%p, entry_thread_state=%ld",
+	                   (void *) thr,
+	                   (long) num_stack_args,
+	                   (unsigned long) call_flags,
+	                   (long) ((call_flags & DUK_CALL_FLAG_PROTECTED) != 0 ? 1 : 0),
+	                   (long) ((call_flags & DUK_CALL_FLAG_IGNORE_RECLIMIT) != 0 ? 1 : 0),
+	                   (long) ((call_flags & DUK_CALL_FLAG_CONSTRUCTOR_CALL) != 0 ? 1 : 0),
+	                   (long) need_setjmp,
+	                   (long) duk_get_top(ctx),
+	                   (long) idx_func,
+	                   (long) idx_args,
+	                   (long) thr->heap->call_recursion_depth,
+	                   (long) thr->heap->call_recursion_limit,
+	                   (long) entry_valstack_bottom_index,
+	                   (long) entry_callstack_top,
+	                   (long) entry_catchstack_top,
+	                   (long) entry_call_recursion_depth,
+	                   (void *) entry_curr_thread,
+	                   (long) entry_thread_state));
+
+#ifdef DUK_USE_DDDPRINT /*XXX:incorrect*/
+	DUK_D(DUK_DPRINT("callstack before call setup:"));
+	DUK_DEBUG_DUMP_CALLSTACK(thr);
+#endif
+
+	if (idx_func < 0 || idx_args < 0) {
+		/*
+		 *  Since stack indices are not reliable, we can't do anything useful
+		 *  here.  Invoke the existing setjmp catcher, or if it doesn't exist,
+		 *  call the fatal error handler.
+		 */
+
+		DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_CALL_ARGS);
+	}
+
+	/*
+	 *  Setup a setjmp() catchpoint first because even the call setup
+	 *  may fail.
+	 */
+
+	if (!need_setjmp) {
+		DUK_DDD(DUK_DDDPRINT("don't need a setjmp catchpoint"));
+		goto handle_call;
+	}
+
+	old_jmpbuf_ptr = thr->heap->lj.jmpbuf_ptr;
+	thr->heap->lj.jmpbuf_ptr = &our_jmpbuf;
+
+	if (DUK_SETJMP(thr->heap->lj.jmpbuf_ptr->jb) == 0) {
+		DUK_DDD(DUK_DDDPRINT("setjmp catchpoint setup complete"));
+		goto handle_call;
+	}
+
+	/*
+	 *  Error during setup, call, or postprocessing of the call.
+	 *  The error value is in heap->lj.value1.
+	 *
+	 *  Note: any local variables accessed here must have their value
+	 *  assigned *before* the setjmp() call, OR they must be declared
+	 *  volatile.  Otherwise their value is not guaranteed to be correct.
+	 *
+	 *  The following are such variables:
+	 *    - duk_handle_call() parameters
+	 *    - entry_*
+	 *    - idx_func
+	 *    - idx_args
+	 *
+	 *  The very first thing we do is restore the previous setjmp catcher.
+	 *  This means that any error in error handling will propagate outwards
+	 *  instead of causing a setjmp() re-entry above.  The *only* actual
+	 *  errors that should happen here are allocation errors.
+	 */
+
+	DUK_DDD(DUK_DDDPRINT("error caught during protected duk_handle_call(): %!T",
+	                     (duk_tval *) &thr->heap->lj.value1));
+
+	DUK_ASSERT(thr->heap->lj.type == DUK_LJ_TYPE_THROW);
+	DUK_ASSERT(thr->callstack_top >= entry_callstack_top);
+	DUK_ASSERT(thr->catchstack_top >= entry_catchstack_top);
+
+	/*
+	 *  Restore previous setjmp catchpoint
+	 */
+
+	/* Note: either pointer may be NULL (at entry), so don't assert */
+	DUK_DDD(DUK_DDDPRINT("restore jmpbuf_ptr: %p -> %p",
+	                     (void *) (thr && thr->heap ? thr->heap->lj.jmpbuf_ptr : NULL),
+	                     (void *) old_jmpbuf_ptr));
+
+	thr->heap->lj.jmpbuf_ptr = old_jmpbuf_ptr;
+
+	if (!(call_flags & DUK_CALL_FLAG_PROTECTED)) {
+		/*
+		 *  Caller did not request a protected call but a setjmp
+		 *  catchpoint was set up to allow cleanup.  So, clean up
+		 *  and rethrow.
+		 *
+		 *  We must restore curr_thread here to ensure that its
+		 *  current value doesn't end up pointing to a thread object
+		 *  which has been freed.  This is now a problem because some
+		 *  call sites (namely duk_safe_call()) *first* unwind stacks
+		 *  and only then deal with curr_thread.  If those call sites
+		 *  were fixed, this wouldn't matter here.
+		 *
+		 *  Note: this case happens e.g. when heap->curr_thread is
+		 *  NULL on entry.
+		 */
+
+		DUK_DDD(DUK_DDDPRINT("call is not protected -> clean up and rethrow"));
+
+		DUK_HEAP_SWITCH_THREAD(thr->heap, entry_curr_thread);  /* may be NULL */
+		thr->state = entry_thread_state;
+		DUK_ASSERT((thr->state == DUK_HTHREAD_STATE_INACTIVE && thr->heap->curr_thread == NULL) ||  /* first call */
+		           (thr->state == DUK_HTHREAD_STATE_INACTIVE && thr->heap->curr_thread != NULL) ||  /* other call */
+		           (thr->state == DUK_HTHREAD_STATE_RUNNING && thr->heap->curr_thread == thr));     /* current thread */
+
+		/* XXX: should setjmp catcher be responsible for this instead? */
+		thr->heap->call_recursion_depth = entry_call_recursion_depth;
+		duk_err_longjmp(thr);
+		DUK_UNREACHABLE();
+	}
+
+	duk_hthread_catchstack_unwind(thr, entry_catchstack_top);
+	duk_hthread_callstack_unwind(thr, entry_callstack_top);
+	thr->valstack_bottom = thr->valstack + entry_valstack_bottom_index;
+
+	/* [ ... func this (crud) errobj ] */
+
+	/* FIXME: is there space?  better implementation: write directly over
+	 * 'func' slot to avoid valstack grow issues.
+	 */
+	duk_push_tval(ctx, &thr->heap->lj.value1);
+
+	/* [ ... func this (crud) errobj ] */
+
+	duk_replace(ctx, idx_func);
+	duk_set_top(ctx, idx_func + 1);
+
+	/* [ ... errobj ] */
+
+	/* ensure there is internal valstack spare before we exit; this may
+	 * throw an alloc error
+	 */
+
+	duk_require_valstack_resize((duk_context *) thr,
+	                            (thr->valstack_top - thr->valstack) +            /* top of current func */
+	                                DUK_VALSTACK_INTERNAL_EXTRA,                 /* + spare => min_new_size */
+	                            1);                                              /* allow_shrink */
+
+	/* Note: currently a second setjmp restoration is done at the target;
+	 * this is OK, but could be refactored away.
+	 */
+	retval = DUK_EXEC_ERROR;
+	goto shrink_and_finished;
+
+ handle_call:
+	/*
+	 *  Thread state check and book-keeping.
+	 */
+
+	if (thr == thr->heap->curr_thread) {
+		/* same thread */
+		if (thr->state != DUK_HTHREAD_STATE_RUNNING) {
+			/* should actually never happen, but check anyway */
+			goto thread_state_error;
+		}
+	} else {
+		/* different thread */
+		DUK_ASSERT(thr->heap->curr_thread == NULL ||
+		           thr->heap->curr_thread->state == DUK_HTHREAD_STATE_RUNNING);
+		if (thr->state != DUK_HTHREAD_STATE_INACTIVE) {
+			goto thread_state_error;
+		}
+		DUK_HEAP_SWITCH_THREAD(thr->heap, thr);
+		thr->state = DUK_HTHREAD_STATE_RUNNING;
+
+		/* Note: multiple threads may be simultaneously in the RUNNING
+		 * state, but not in the same "resume chain".
+		 */
+	}
+
+	DUK_ASSERT(thr->heap->curr_thread == thr);
+	DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING);
+
+	/*
+	 *  C call recursion depth check, which provides a reasonable upper
+	 *  bound on maximum C stack size (arbitrary C stack growth is only
+	 *  possible by recursive handle_call / handle_safe_call calls).
+	 */
+
+	DUK_ASSERT(thr->heap->call_recursion_depth >= 0);
+	DUK_ASSERT(thr->heap->call_recursion_depth <= thr->heap->call_recursion_limit);
+
+	if (call_flags & DUK_CALL_FLAG_IGNORE_RECLIMIT) {
+		DUK_DD(DUK_DDPRINT("ignoring reclimit for this call (probably an errhandler call)"));
+	} else {	
+		if (thr->heap->call_recursion_depth >= thr->heap->call_recursion_limit) {
+			/* XXX: error message is a bit misleading: we reached a recursion
+			 * limit which is also essentially the same as a C callstack limit
+			 * (except perhaps with some relaxed threading assumptions).
+			 */
+			DUK_ERROR(thr, DUK_ERR_RANGE_ERROR, DUK_STR_C_CALLSTACK_LIMIT);
+		}
+		thr->heap->call_recursion_depth++;
+	}
+
+	/*
+	 *  Check the function type, handle bound function chains,
+	 *  and prepare parameters for the rest of the call handling.
+	 *  Also figure out the effective 'this' binding, which
+	 *  replaces the current value at idx_func + 1.
+	 *
+	 *  If the target function is a 'bound' one, follow the chain
+	 *  of 'bound' functions until a non-bound function is found.
+	 *  During this process, bound arguments are 'prepended' to
+	 *  existing ones, and the "this" binding is overridden.
+	 *  See E5 Section 15.3.4.5.1.
+	 */
+
+	if (!duk_is_callable(thr, idx_func)) {
+		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_CALLABLE);
+	}
+	func = duk_get_hobject(thr, idx_func);
+	DUK_ASSERT(func != NULL);
+
+	if (DUK_HOBJECT_HAS_BOUND(func)) {
+		/* slow path for bound functions */
+		duk__handle_bound_chain_for_call(thr, idx_func, &num_stack_args, &func, call_flags & DUK_CALL_FLAG_CONSTRUCTOR_CALL);
+	}
+	DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));
+	DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(func) ||
+	           DUK_HOBJECT_IS_NATIVEFUNCTION(func));
+
+	duk__coerce_effective_this_binding(thr, func, idx_func + 1);
+	DUK_DDD(DUK_DDDPRINT("effective 'this' binding is: %!T",
+	                     (duk_tval *) duk_get_tval(ctx, idx_func + 1)));
+
+	/* These base values are never used, but if the compiler doesn't know
+	 * that DUK_ERROR() won't return, these are needed to silence warnings.
+	 * On the other hand, scan-build will warn about the values not being
+	 * used, so add a DUK_UNREF.
+	 */
+	nargs = 0; DUK_UNREF(nargs);
+	nregs = 0; DUK_UNREF(nregs);
+
+	if (DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) {
+		nargs = ((duk_hcompiledfunction *) func)->nargs;
+		nregs = ((duk_hcompiledfunction *) func)->nregs;
+		DUK_ASSERT(nregs >= nargs);
+	} else if (DUK_HOBJECT_IS_NATIVEFUNCTION(func)) {
+		/* Note: nargs (and nregs) may be negative for a native,
+		 * function, which indicates that the function wants the
+		 * input stack "as is" (i.e. handles "vararg" arguments).
+		 */
+		nargs = ((duk_hnativefunction *) func)->nargs;
+		nregs = nargs;
+	} else {
+		/* XXX: this should be an assert */
+		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_CALLABLE);
+	}
+
+	/* [ ... func this arg1 ... argN ] */
+
+	/*
+	 *  Check stack sizes and resize if necessary.
+	 *
+	 *  Call stack is grown by one, catch stack doesn't grow here.
+	 *  Value stack may either grow or shrink, depending on the number
+	 *  of func registers and the number of actual arguments.
+	 */
+
+	duk_hthread_callstack_grow(thr);
+
+	/* if nregs >= 0, func wants args clamped to 'nargs'; else it wants
+	 * all args (= 'num_stack_args')
+	 */
+
+	vs_min_size = (thr->valstack_bottom - thr->valstack) +         /* bottom of current func */
+	              idx_args;                                        /* bottom of new func */
+	vs_min_size += (nregs >= 0 ? nregs : num_stack_args);          /* num entries of new func at entry */
+	if (DUK_HOBJECT_IS_NATIVEFUNCTION(func)) {
+		vs_min_size += DUK_VALSTACK_API_ENTRY_MINIMUM;         /* Duktape/C API guaranteed entries (on top of args) */
+	}
+	vs_min_size += DUK_VALSTACK_INTERNAL_EXTRA,                    /* + spare */
+
+	duk_require_valstack_resize((duk_context *) thr, vs_min_size, 1 /*allow_shrink*/);
+
+	/*
+	 *  Update idx_retval of current activation.
+	 *
+	 *  Although it might seem this is not necessary (bytecode executor
+	 *  does this for Ecmascript-to-Ecmascript calls; other calls are
+	 *  handled here), this turns out to be necessary for handling yield
+	 *  and resume.  For them, an Ecmascript-to-native call happens, and
+	 *  the Ecmascript call's idx_retval must be set for things to work.
+	 */
+
+	if (thr->callstack_top > 0) {
+		/* now set unconditionally, regardless of whether current activation
+		 * is native or not.
+	 	 */
+		(thr->callstack + thr->callstack_top - 1)->idx_retval = entry_valstack_bottom_index + idx_func;
+	}
+
+	/*
+	 *  Setup a preliminary activation.
+	 *
+	 *  Don't touch valstack_bottom or valstack_top yet so that Duktape API
+	 *  calls work normally.
+	 */
+
+	/* [ ... func this arg1 ... argN ] */
+
+	DUK_ASSERT(thr->callstack_top < thr->callstack_size);
+	act = thr->callstack + thr->callstack_top;
+	thr->callstack_top++;
+	DUK_ASSERT(thr->callstack_top <= thr->callstack_size);
+	DUK_ASSERT(thr->valstack_top > thr->valstack_bottom);  /* at least effective 'this' */
+	DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));
+
+	act->flags = 0;
+	if (DUK_HOBJECT_HAS_STRICT(func)) {
+		act->flags |= DUK_ACT_FLAG_STRICT;
+	}
+	if (call_flags & DUK_CALL_FLAG_CONSTRUCTOR_CALL) {
+		act->flags |= DUK_ACT_FLAG_CONSTRUCT;
+		/*act->flags |= DUK_ACT_FLAG_PREVENT_YIELD;*/
+	}
+	if (DUK_HOBJECT_IS_NATIVEFUNCTION(func)) {
+		/*act->flags |= DUK_ACT_FLAG_PREVENT_YIELD;*/
+	}
+	if (call_flags & DUK_CALL_FLAG_DIRECT_EVAL) {
+		act->flags |= DUK_ACT_FLAG_DIRECT_EVAL;
+	}
+
+	/* As a first approximation, all calls except Ecmascript-to-Ecmascript
+	 * calls prevent a yield.
+	 */
+	act->flags |= DUK_ACT_FLAG_PREVENT_YIELD;
+
+	act->func = func;
+	act->var_env = NULL;
+	act->lex_env = NULL;
+#ifdef DUK_USE_NONSTD_FUNC_CALLER_PROPERTY
+	act->prev_caller = NULL;
+#endif
+	act->pc = 0;
+	act->idx_bottom = entry_valstack_bottom_index + idx_args;
+#if 0  /* topmost activation idx_retval is considered garbage, no need to init */
+	act->idx_retval = 0;
+#endif
+
+	if (act->flags & DUK_ACT_FLAG_PREVENT_YIELD) {
+		/* duk_hthread_callstack_unwind() will decrease this on unwind */
+		thr->callstack_preventcount++;
+	}
+
+	DUK_HOBJECT_INCREF(thr, func);  /* act->func */
+
+#ifdef DUK_USE_NONSTD_FUNC_CALLER_PROPERTY
+	duk__update_func_caller_prop(thr, func);
+	act = thr->callstack + thr->callstack_top - 1;
+#endif
+
+	/* [... func this arg1 ... argN] */
+
+#ifdef DUK_USE_DDDPRINT /*XXX:incorrect*/
+	DUK_D(DUK_DPRINT("pushed new activation:"));
+	DUK_DEBUG_DUMP_ACTIVATION(thr, thr->callstack + thr->callstack_top - 1);
+#endif
+
+	/*
+	 *  Environment record creation and 'arguments' object creation.
+	 *  Named function expression name binding is handled by the
+	 *  compiler; the compiled function's parent env will contain
+	 *  the (immutable) binding already.
+	 *
+	 *  This handling is now identical for C and Ecmascript functions.
+	 *  C functions always have the 'NEWENV' flag set, so their
+	 *  environment record initialization is delayed (which is good).
+	 *
+	 *  Delayed creation (on demand) is handled in duk_js_var.c.
+	 */
+
+	DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));  /* bound function chain has already been resolved */
+
+	if (!DUK_HOBJECT_HAS_NEWENV(func)) {
+		/* use existing env (e.g. for non-strict eval); cannot have
+		 * an own 'arguments' object (but can refer to the existing one)
+		 */
+
+		DUK_ASSERT(!DUK_HOBJECT_HAS_CREATEARGS(func));
+
+		duk__handle_oldenv_for_call(thr, func, act);
+
+		DUK_ASSERT(act->lex_env != NULL);
+		DUK_ASSERT(act->var_env != NULL);
+		goto env_done;
+	}
+
+	DUK_ASSERT(DUK_HOBJECT_HAS_NEWENV(func));
+
+	if (!DUK_HOBJECT_HAS_CREATEARGS(func)) {
+		/* no need to create environment record now; leave as NULL */
+		DUK_ASSERT(act->lex_env == NULL);
+		DUK_ASSERT(act->var_env == NULL);
+		goto env_done;
+	}
+
+	/* third arg: absolute index (to entire valstack) of idx_bottom of new activation */
+	env = duk_create_activation_environment_record(thr, func, act->idx_bottom);
+	DUK_ASSERT(env != NULL);
+	
+	/* [... func this arg1 ... argN envobj] */
+
+	DUK_ASSERT(DUK_HOBJECT_HAS_CREATEARGS(func));
+	duk__handle_createargs_for_call(thr, func, env, num_stack_args);
+
+	/* [... func this arg1 ... argN envobj] */
+
+	act->lex_env = env;
+	act->var_env = env;
+	DUK_HOBJECT_INCREF(thr, env);
+	DUK_HOBJECT_INCREF(thr, env);  /* XXX: incref by count (2) directly */
+	duk_pop(ctx);
+
+ env_done:
+	/* [... func this arg1 ... argN] */
+
+	/*
+	 *  Setup value stack: clamp to 'nargs', fill up to 'nregs'
+	 */
+
+	/* XXX: replace with a single operation */
+
+	if (nregs >= 0) {
+		duk_set_top(ctx, idx_args + nargs);  /* clamp anything above nargs */
+		duk_set_top(ctx, idx_args + nregs);  /* extend with undefined */
+	} else {
+		/* 'func' wants stack "as is" */
+	}
+
+#ifdef DUK_USE_DDDPRINT /*XXX:incorrect*/
+	DUK_D(DUK_DPRINT("callstack after call setup:"));
+	DUK_DEBUG_DUMP_CALLSTACK(thr);
+#endif
+
+	/*
+	 *  Determine call type; then setup activation and call
+	 */
+
+	if (DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) {
+		goto ecmascript_call;
+	} else {
+		goto native_call;
+	}
+	DUK_UNREACHABLE();
+
+	/*
+	 *  Native (C) call
+	 */
+
+ native_call:
+	/*
+	 *  Shift to new valstack_bottom.
+	 */
+
+	thr->valstack_bottom = thr->valstack_bottom + idx_args;
+	/* keep current valstack_top */
+	DUK_ASSERT(thr->valstack_bottom >= thr->valstack);
+	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
+	DUK_ASSERT(thr->valstack_end >= thr->valstack_top);
+	DUK_ASSERT(((duk_hnativefunction *) func)->func != NULL);
+
+	/* [... func this | arg1 ... argN] ('this' must precede new bottom) */
+
+	/*
+	 *  Actual function call and return value check.
+	 *
+	 *  Return values:
+	 *    0    success, no return value (default to 'undefined')
+	 *    1    success, one return value on top of stack
+	 *  < 0    error, throw a "magic" error
+	 *  other  invalid
+	 */
+
+	rc = ((duk_hnativefunction *) func)->func((duk_context *) thr);
+
+	if (rc < 0) {
+		duk_error_throw_from_negative_rc(thr, rc);
+		DUK_UNREACHABLE();
+	} else if (rc > 1) {
+		DUK_ERROR(thr, DUK_ERR_API_ERROR, "c function returned invalid rc");
+	}
+	DUK_ASSERT(rc == 0 || rc == 1);
+
+	/*
+	 *  Unwind stack(s) and shift back to old valstack_bottom.
+	 */
+
+	DUK_ASSERT(thr->catchstack_top == entry_catchstack_top);
+	DUK_ASSERT(thr->callstack_top == entry_callstack_top + 1);
+
+#if 0  /* should be no need to unwind */
+	duk_hthread_catchstack_unwind(thr, entry_catchstack_top);
+#endif
+	duk_hthread_callstack_unwind(thr, entry_callstack_top);
+
+	thr->valstack_bottom = thr->valstack + entry_valstack_bottom_index;
+	/* keep current valstack_top */
+
+	DUK_ASSERT(thr->valstack_bottom >= thr->valstack);
+	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
+	DUK_ASSERT(thr->valstack_end >= thr->valstack_top);
+	DUK_ASSERT(thr->valstack_top - thr->valstack_bottom >= idx_func + 1);
+
+	/*
+	 *  Manipulate value stack so that return value is on top
+	 *  (pushing an 'undefined' if necessary).
+	 */
+
+	/* XXX: should this happen in the callee's activation or after unwinding? */
+	if (rc == 0) {
+		duk_require_stack(ctx, 1);
+		duk_push_undefined(ctx);
+	}
+	/* [... func this (crud) retval] */
+
+	DUK_DDD(DUK_DDDPRINT("native call retval -> %!T (rc=%ld)",
+	                     (duk_tval *) duk_get_tval(ctx, -1), (long) rc));
+
+	duk_replace(ctx, idx_func);
+	duk_set_top(ctx, idx_func + 1);
+
+	/* [... retval] */
+
+	/* ensure there is internal valstack spare before we exit */
+
+	duk_require_valstack_resize((duk_context *) thr,
+	                            (thr->valstack_top - thr->valstack) +            /* top of current func */
+	                                DUK_VALSTACK_INTERNAL_EXTRA,                 /* + spare => min_new_size */
+	                            1);                                              /* allow_shrink */
+
+
+	/*
+	 *  Shrink checks and return with success.
+	 */
+
+	retval = DUK_EXEC_SUCCESS;
+	goto shrink_and_finished;	
+
+	/*
+	 *  Ecmascript call
+	 */
+
+ ecmascript_call:
+
+	/*
+	 *  Shift to new valstack_bottom.
+	 */
+
+	thr->valstack_bottom = thr->valstack_bottom + idx_args;
+	/* keep current valstack_top */
+	DUK_ASSERT(thr->valstack_bottom >= thr->valstack);
+	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
+	DUK_ASSERT(thr->valstack_end >= thr->valstack_top);
+
+	/* [... func this | arg1 ... argN] ('this' must precede new bottom) */
+
+	/*
+	 *  Bytecode executor call.
+	 *
+	 *  Execute bytecode, handling any recursive function calls and
+	 *  thread resumptions.  Returns when execution would return from
+	 *  the entry level activation.  When the executor returns, a
+	 *  single return value is left on the stack top.
+	 *
+	 *  The only possible longjmp() is an error (DUK_LJ_TYPE_THROW),
+	 *  other types are handled internally by the executor.
+	 *
+	 */
+
+	DUK_DDD(DUK_DDDPRINT("entering bytecode execution"));
+	duk_js_execute_bytecode(thr);
+	DUK_DDD(DUK_DDDPRINT("returned from bytecode execution"));
+
+	/*
+	 *  Unwind stack(s) and shift back to old valstack_bottom.
+	 */
+
+	DUK_ASSERT(thr->callstack_top == entry_callstack_top + 1);
+
+	duk_hthread_catchstack_unwind(thr, entry_catchstack_top);
+	duk_hthread_callstack_unwind(thr, entry_callstack_top);
+
+	thr->valstack_bottom = thr->valstack + entry_valstack_bottom_index;
+	/* keep current valstack_top */
+
+	DUK_ASSERT(thr->valstack_bottom >= thr->valstack);
+	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
+	DUK_ASSERT(thr->valstack_end >= thr->valstack_top);
+	DUK_ASSERT(thr->valstack_top - thr->valstack_bottom >= idx_func + 1);
+
+	/*
+	 *  Manipulate value stack so that return value is on top.
+	 */
+
+	/* [... func this (crud) retval] */
+
+	duk_replace(ctx, idx_func);
+	duk_set_top(ctx, idx_func + 1);
+
+	/* [... retval] */
+
+	/* ensure there is internal valstack spare before we exit */
+
+	duk_require_valstack_resize((duk_context *) thr,
+	                            (thr->valstack_top - thr->valstack) +            /* top of current func */
+	                                DUK_VALSTACK_INTERNAL_EXTRA,                 /* + spare => min_new_size */
+	                            1);                                              /* allow_shrink */
+
+	/*
+	 *  Shrink checks and return with success.
+	 */
+
+	retval = DUK_EXEC_SUCCESS;
+	goto shrink_and_finished;	
+
+ shrink_and_finished:
+	/* these are "soft" shrink checks, whose failures are ignored */
+	/* XXX: would be nice if fast path was inlined */
+	duk_hthread_catchstack_shrink_check(thr);
+	duk_hthread_callstack_shrink_check(thr);
+	goto finished;
+
+ finished:
+	if (need_setjmp) {
+		/* Note: either pointer may be NULL (at entry), so don't assert;
+		 * this is now done potentially twice, which is OK
+		 */
+		DUK_DDD(DUK_DDDPRINT("restore jmpbuf_ptr: %p -> %p (possibly already done)",
+		                     (void *) (thr && thr->heap ? thr->heap->lj.jmpbuf_ptr : NULL),
+		                     (void *) old_jmpbuf_ptr));
+		thr->heap->lj.jmpbuf_ptr = old_jmpbuf_ptr;
+
+		/* These are just convenience "wiping" of state */
+		thr->heap->lj.type = DUK_LJ_TYPE_UNKNOWN;
+		thr->heap->lj.iserror = 0;
+
+		/* Side effects should not be an issue here: tv_tmp is local and
+		 * thr->heap (and thr->heap->lj) have a stable pointer.  Finalizer
+		 * runs etc capture even out-of-memory errors so nothing should
+		 * throw here.
+		 */
+		DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value1);
+		DUK_TVAL_SET_UNDEFINED_UNUSED(&thr->heap->lj.value1);
+		DUK_TVAL_DECREF(thr, &tv_tmp);
+
+		DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value2);
+		DUK_TVAL_SET_UNDEFINED_UNUSED(&thr->heap->lj.value2);
+		DUK_TVAL_DECREF(thr, &tv_tmp);
+
+		DUK_DDD(DUK_DDDPRINT("setjmp catchpoint torn down"));
+	}
+
+	DUK_HEAP_SWITCH_THREAD(thr->heap, entry_curr_thread);  /* may be NULL */
+	thr->state = (duk_uint8_t) entry_thread_state;
+
+	DUK_ASSERT((thr->state == DUK_HTHREAD_STATE_INACTIVE && thr->heap->curr_thread == NULL) ||  /* first call */
+	           (thr->state == DUK_HTHREAD_STATE_INACTIVE && thr->heap->curr_thread != NULL) ||  /* other call */
+	           (thr->state == DUK_HTHREAD_STATE_RUNNING && thr->heap->curr_thread == thr));     /* current thread */
+	
+	thr->heap->call_recursion_depth = entry_call_recursion_depth;
+
+	return retval;
+
+ thread_state_error:
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid thread state for call (%ld)", (long) thr->state);
+	DUK_UNREACHABLE();
+	return DUK_EXEC_ERROR;  /* never executed */
+}
+
+/*
+ *  Manipulate value stack so that exactly 'num_stack_rets' return
+ *  values are at 'idx_retbase' in every case, assuming there are
+ *  'rc' return values on top of stack.
+ *
+ *  This is a bit tricky, because the called C function operates in
+ *  the same activation record and may have e.g. popped the stack
+ *  empty (below idx_retbase).
+ */
+
+static void duk__safe_call_adjust_valstack(duk_hthread *thr, duk_idx_t idx_retbase, duk_idx_t num_stack_rets, duk_idx_t num_actual_rets) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_idx_t idx_rcbase;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(idx_retbase >= 0);
+	DUK_ASSERT(num_stack_rets >= 0);
+	DUK_ASSERT(num_actual_rets >= 0);
+
+	idx_rcbase = duk_get_top(ctx) - num_actual_rets;  /* base of known return values */
+
+	DUK_DDD(DUK_DDDPRINT("adjust valstack after func call: "
+	                     "num_stack_rets=%ld, num_actual_rets=%ld, stack_top=%ld, idx_retbase=%ld, idx_rcbase=%ld",
+	                     (long) num_stack_rets, (long) num_actual_rets, (long) duk_get_top(ctx),
+	                     (long) idx_retbase, (long) idx_rcbase));
+
+	DUK_ASSERT(idx_rcbase >= 0);  /* caller must check */
+
+	/* ensure space for final configuration (idx_retbase + num_stack_rets) and
+	 * intermediate configurations
+	 */
+	duk_require_stack_top(ctx,
+	                      (idx_rcbase > idx_retbase ? idx_rcbase : idx_retbase) +
+	                      num_stack_rets);
+
+	/* chop extra retvals away / extend with undefined */
+	duk_set_top(ctx, idx_rcbase + num_stack_rets);
+
+	if (idx_rcbase >= idx_retbase) {
+		duk_idx_t count = idx_rcbase - idx_retbase;
+		duk_idx_t i;
+
+		DUK_DDD(DUK_DDDPRINT("elements at/after idx_retbase have enough to cover func retvals "
+		                     "(idx_retbase=%ld, idx_rcbase=%ld)", (long) idx_retbase, (long) idx_rcbase));
+
+		/* nuke values at idx_retbase to get the first retval (initially
+		 * at idx_rcbase) to idx_retbase
+		 */
+
+		DUK_ASSERT(count >= 0);
+
+		for (i = 0; i < count; i++) {
+			/* XXX: inefficient; block remove primitive */
+			duk_remove(ctx, idx_retbase);
+		}
+	} else {
+		duk_idx_t count = idx_retbase - idx_rcbase;
+		duk_idx_t i;
+
+		DUK_DDD(DUK_DDDPRINT("not enough elements at/after idx_retbase to cover func retvals "
+		                     "(idx_retbase=%ld, idx_rcbase=%ld)", (long) idx_retbase, (long) idx_rcbase));
+
+		/* insert 'undefined' values at idx_rcbase to get the
+		 * return values to idx_retbase
+		 */
+
+		DUK_ASSERT(count > 0);
+
+		for (i = 0; i < count; i++) {
+			/* XXX: inefficient; block insert primitive */
+			duk_push_undefined(ctx);
+			duk_insert(ctx, idx_rcbase);
+		}
+	}
+}
+
+/*
+ *  Make a "C protected call" within the current activation.
+ *
+ *  The allowed thread states for making a call are the same as for
+ *  duk_handle_call().
+ *
+ *  Note that like duk_handle_call(), even if this call is protected,
+ *  there are a few situations where the current (pre-entry) setjmp
+ *  catcher (or a fatal error handler if no such catcher exists) is
+ *  invoked:
+ *
+ *    - Blatant API argument errors (e.g. num_stack_args is invalid,
+ *      so we can't form a reasonable return stack)
+ *
+ *    - Errors during error handling, e.g. failure to reallocate
+ *      space in the value stack due to an alloc error
+ *
+ *  Such errors propagate outwards, ultimately to the fatal error
+ *  handler if nothing else.
+ */
+
+/* FIXME: bump preventcount by one for the duration of this call? */
+
+duk_int_t duk_handle_safe_call(duk_hthread *thr,
+                               duk_safe_call_function func,
+                               duk_idx_t num_stack_args,
+                               duk_idx_t num_stack_rets) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_size_t entry_valstack_bottom_index;
+	duk_size_t entry_callstack_top;
+	duk_size_t entry_catchstack_top;
+	duk_int_t entry_call_recursion_depth;
+	duk_hthread *entry_curr_thread;
+	duk_uint_fast8_t entry_thread_state;
+	duk_jmpbuf *old_jmpbuf_ptr = NULL;
+	duk_jmpbuf our_jmpbuf;
+	duk_tval tv_tmp;
+	duk_idx_t idx_retbase;
+	duk_int_t retval;
+	duk_ret_t rc;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(ctx != NULL);
+
+	/* Note: careful with indices like '-x'; if 'x' is zero, it refers to bottom */
+	entry_valstack_bottom_index = (duk_size_t) (thr->valstack_bottom - thr->valstack);
+	entry_callstack_top = thr->callstack_top;
+	entry_catchstack_top = thr->catchstack_top;
+	entry_call_recursion_depth = thr->heap->call_recursion_depth;
+	entry_curr_thread = thr->heap->curr_thread;  /* Note: may be NULL if first call */
+	entry_thread_state = thr->state;
+	idx_retbase = duk_get_top(ctx) - num_stack_args;  /* Note: not a valid stack index if num_stack_args == 0 */
+
+	/* Note: cannot portably debug print a function pointer, hence 'func' not printed! */
+	DUK_DD(DUK_DDPRINT("duk_handle_safe_call: thr=%p, num_stack_args=%ld, num_stack_rets=%ld, "
+	                   "valstack_top=%ld, idx_retbase=%ld, rec_depth=%ld/%ld, "
+	                   "entry_valstack_bottom_index=%ld, entry_callstack_top=%ld, entry_catchstack_top=%ld, "
+	                   "entry_call_recursion_depth=%ld, entry_curr_thread=%p, entry_thread_state=%ld",
+	                   (void *) thr,
+	                   (long) num_stack_args,
+	                   (long) num_stack_rets,
+	                   (long) duk_get_top(ctx),
+	                   (long) idx_retbase,
+	                   (long) thr->heap->call_recursion_depth,
+	                   (long) thr->heap->call_recursion_limit,
+	                   (long) entry_valstack_bottom_index,
+	                   (long) entry_callstack_top,
+	                   (long) entry_catchstack_top,
+	                   (long) entry_call_recursion_depth,
+	                   (void *) entry_curr_thread,
+	                   (long) entry_thread_state));
+
+	if (idx_retbase < 0) {
+		/*
+		 *  Since stack indices are not reliable, we can't do anything useful
+		 *  here.  Invoke the existing setjmp catcher, or if it doesn't exist,
+		 *  call the fatal error handler.
+		 */
+
+		DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_CALL_ARGS);
+	}
+
+	/* setjmp catchpoint setup */
+
+	old_jmpbuf_ptr = thr->heap->lj.jmpbuf_ptr;
+	thr->heap->lj.jmpbuf_ptr = &our_jmpbuf;
+
+	if (DUK_SETJMP(thr->heap->lj.jmpbuf_ptr->jb) == 0) {
+		goto handle_call;
+	}
+
+	/*
+	 *  Error during call.  The error value is at heap->lj.value1.
+	 *
+	 *  Careful with variable accesses here; must be assigned to before
+	 *  setjmp() or be declared volatile.  See duk_handle_call().
+	 *
+	 *  The following are such variables:
+	 *    - duk_handle_safe_call() parameters
+	 *    - entry_*
+	 *    - idx_retbase
+	 *
+	 *  The very first thing we do is restore the previous setjmp catcher.
+	 *  This means that any error in error handling will propagate outwards
+	 *  instead of causing a setjmp() re-entry above.  The *only* actual
+	 *  errors that should happen here are allocation errors.
+	 */
+
+	DUK_DDD(DUK_DDDPRINT("error caught during protected duk_handle_safe_call()"));
+
+	DUK_ASSERT(thr->heap->lj.type == DUK_LJ_TYPE_THROW);
+	DUK_ASSERT(thr->callstack_top >= entry_callstack_top);
+	DUK_ASSERT(thr->catchstack_top >= entry_catchstack_top);
+
+	/* Note: either pointer may be NULL (at entry), so don't assert;
+	 * these are now restored twice which is OK.
+	 */
+	thr->heap->lj.jmpbuf_ptr = old_jmpbuf_ptr;
+
+	duk_hthread_catchstack_unwind(thr, entry_catchstack_top);
+	duk_hthread_callstack_unwind(thr, entry_callstack_top);
+	thr->valstack_bottom = thr->valstack + entry_valstack_bottom_index;
+
+	/* [ ... | (crud) ] */
+
+	/* FIXME: space in valstack?  see discussion in duk_handle_call. */
+	duk_push_tval(ctx, &thr->heap->lj.value1);
+
+	/* [ ... | (crud) errobj ] */
+
+	DUK_ASSERT(duk_get_top(ctx) >= 1);  /* at least errobj must be on stack */
+
+	/* check that the valstack has space for the final amount and any
+	 * intermediate space needed; this is unoptimal but should be safe
+	 */
+	duk_require_stack_top(ctx, idx_retbase + num_stack_rets);  /* final configuration */
+	duk_require_stack(ctx, num_stack_rets);
+
+	duk__safe_call_adjust_valstack(thr, idx_retbase, num_stack_rets, 1);  /* 1 = num actual 'return values' */
+
+	/* [ ... | ] or [ ... | errobj (M * undefined)] where M = num_stack_rets - 1 */
+
+#ifdef DUK_USE_DDDPRINT /*XXX:incorrect*/
+	DUK_DD(DUK_DDPRINT("protected safe_call error handling finished, thread dump:"));
+	DUK_DEBUG_DUMP_HTHREAD(thr);
+#endif
+
+	retval = DUK_EXEC_ERROR;
+	goto shrink_and_finished;
+
+	/*
+	 *  Handle call (inside setjmp)
+	 */
+
+ handle_call:
+
+	DUK_DDD(DUK_DDDPRINT("safe_call setjmp catchpoint setup complete"));
+
+	/*
+	 *  Thread state check and book-keeping.
+	 */
+
+	if (thr == thr->heap->curr_thread) {
+		/* same thread */
+		if (thr->state != DUK_HTHREAD_STATE_RUNNING) {
+			/* should actually never happen, but check anyway */
+			goto thread_state_error;
+		}
+	} else {
+		/* different thread */
+		DUK_ASSERT(thr->heap->curr_thread == NULL ||
+		           thr->heap->curr_thread->state == DUK_HTHREAD_STATE_RUNNING);
+		if (thr->state != DUK_HTHREAD_STATE_INACTIVE) {
+			goto thread_state_error;
+		}
+		DUK_HEAP_SWITCH_THREAD(thr->heap, thr);
+		thr->state = DUK_HTHREAD_STATE_RUNNING;
+
+		/* Note: multiple threads may be simultaneously in the RUNNING
+		 * state, but not in the same "resume chain".
+		 */
+	}
+
+	DUK_ASSERT(thr->heap->curr_thread == thr);
+	DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING);
+
+	/*
+	 *  Recursion limit check.
+	 *
+	 *  Note: there is no need for an "ignore recursion limit" flag
+	 *  for duk_handle_safe_call now.
+	 */
+
+	DUK_ASSERT(thr->heap->call_recursion_depth >= 0);
+	DUK_ASSERT(thr->heap->call_recursion_depth <= thr->heap->call_recursion_limit);
+	if (thr->heap->call_recursion_depth >= thr->heap->call_recursion_limit) {
+		/* XXX: error message is a bit misleading: we reached a recursion
+		 * limit which is also essentially the same as a C callstack limit
+		 * (except perhaps with some relaxed threading assumptions).
+		 */
+		DUK_ERROR(thr, DUK_ERR_RANGE_ERROR, DUK_STR_C_CALLSTACK_LIMIT);
+	}
+	thr->heap->call_recursion_depth++;
+
+	/*
+	 *  Valstack spare check
+	 */
+
+	duk_require_stack(ctx, 0);  /* internal spare */
+
+	/*
+	 *  Make the C call
+	 */
+
+	rc = func(ctx);
+
+	DUK_DDD(DUK_DDDPRINT("safe_call, func rc=%ld", (long) rc));
+
+	/*
+	 *  Valstack manipulation for results
+	 */
+
+	/* we're running inside the caller's activation, so no change in call/catch stack or valstack bottom */
+	DUK_ASSERT(thr->callstack_top == entry_callstack_top);
+	DUK_ASSERT(thr->catchstack_top == entry_catchstack_top);
+	DUK_ASSERT(thr->valstack_bottom >= thr->valstack);
+	DUK_ASSERT((duk_size_t) (thr->valstack_bottom - thr->valstack) == entry_valstack_bottom_index);
+	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
+	DUK_ASSERT(thr->valstack_end >= thr->valstack_top);
+
+	if (rc < 0) {
+		duk_error_throw_from_negative_rc(thr, rc);
+	}
+	DUK_ASSERT(rc >= 0);
+
+	if (duk_get_top(ctx) < rc) {
+		DUK_ERROR(thr, DUK_ERR_API_ERROR, "not enough stack values for safe_call rc");
+	}
+
+	duk__safe_call_adjust_valstack(thr, idx_retbase, num_stack_rets, rc);
+
+	/* Note: no need from callstack / catchstack shrink check */
+	retval = DUK_EXEC_SUCCESS;
+	goto finished;
+
+ shrink_and_finished:
+	/* these are "soft" shrink checks, whose failures are ignored */
+	/* XXX: would be nice if fast path was inlined */
+	duk_hthread_catchstack_shrink_check(thr);
+	duk_hthread_callstack_shrink_check(thr);
+	goto finished;
+
+ finished:
+	/* Note: either pointer may be NULL (at entry), so don't assert */
+	thr->heap->lj.jmpbuf_ptr = old_jmpbuf_ptr;
+
+	/* These are just convenience "wiping" of state */
+	thr->heap->lj.type = DUK_LJ_TYPE_UNKNOWN;
+	thr->heap->lj.iserror = 0;
+
+	/* Side effects should not be an issue here: tv_tmp is local and
+	 * thr->heap (and thr->heap->lj) have a stable pointer.  Finalizer
+	 * runs etc capture even out-of-memory errors so nothing should
+	 * throw here.
+	 */
+	DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value1);
+	DUK_TVAL_SET_UNDEFINED_UNUSED(&thr->heap->lj.value1);
+	DUK_TVAL_DECREF(thr, &tv_tmp);
+
+	DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value2);
+	DUK_TVAL_SET_UNDEFINED_UNUSED(&thr->heap->lj.value2);
+	DUK_TVAL_DECREF(thr, &tv_tmp);
+
+	DUK_DDD(DUK_DDDPRINT("setjmp catchpoint torn down"));
+
+	/* XXX: because we unwind stacks above, thr->heap->curr_thread is at
+	 * risk of pointing to an already freed thread.  This was indeed the
+	 * case in test-bug-multithread-valgrind.c, until duk_handle_call()
+	 * was fixed to restore thr->heap->curr_thread before rethrowing an
+	 * uncaught error.
+	 */
+	DUK_HEAP_SWITCH_THREAD(thr->heap, entry_curr_thread);  /* may be NULL */
+	thr->state = (duk_uint8_t) entry_thread_state;
+
+	DUK_ASSERT((thr->state == DUK_HTHREAD_STATE_INACTIVE && thr->heap->curr_thread == NULL) ||  /* first call */
+	           (thr->state == DUK_HTHREAD_STATE_INACTIVE && thr->heap->curr_thread != NULL) ||  /* other call */
+	           (thr->state == DUK_HTHREAD_STATE_RUNNING && thr->heap->curr_thread == thr));     /* current thread */
+
+	thr->heap->call_recursion_depth = entry_call_recursion_depth;
+
+	/* stack discipline consistency check */
+	DUK_ASSERT(duk_get_top(ctx) == idx_retbase + num_stack_rets);
+
+	return retval;
+
+ thread_state_error:
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid thread state for safe_call (%ld)", (long) thr->state);
+	DUK_UNREACHABLE();
+	return DUK_EXEC_ERROR;  /* never executed */
+}
+
+/*
+ *  Helper for handling an Ecmascript-to-Ecmascript call or an Ecmascript
+ *  function (initial) Duktape.Thread.resume().
+ *
+ *  Compared to normal calls handled by duk_handle_call(), there are a
+ *  bunch of differences:
+ *
+ *    - the call is never protected
+ *    - there is no C recursion depth increase (hence an "ignore recursion
+ *      limit" flag is not applicable)
+ *    - instead of making the call, this helper just performs the thread
+ *      setup and returns; the bytecode executor then restarts execution
+ *      internally
+ *    - ecmascript functions are never 'vararg' functions (they access
+ *      varargs through the 'arguments' object)
+ *
+ *  The callstack of the target contains an earlier Ecmascript call in case
+ *  of an Ecmascript-to-Ecmascript call (whose idx_retval is updated), or
+ *  is empty in case of an initial Duktape.Thread.resume().
+ */
+
+void duk_handle_ecma_call_setup(duk_hthread *thr,
+                                duk_idx_t num_stack_args,
+                                duk_small_uint_t call_flags) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_size_t entry_valstack_bottom_index;
+	duk_idx_t idx_func;     /* valstack index of 'func' and retval (relative to entry valstack_bottom) */
+	duk_idx_t idx_args;     /* valstack index of start of args (arg1) (relative to entry valstack_bottom) */
+	duk_idx_t nargs;        /* # argument registers target function wants (< 0 => never for ecma calls) */
+	duk_idx_t nregs;        /* # total registers target function wants on entry (< 0 => never for ecma calls) */
+	duk_hobject *func;      /* 'func' on stack (borrowed reference) */
+	duk_activation *act;
+	duk_hobject *env;
+	duk_bool_t use_tailcall;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(!((call_flags & DUK_CALL_FLAG_IS_RESUME) != 0 && (call_flags & DUK_CALL_FLAG_IS_TAILCALL) != 0));
+
+	/* XXX: assume these? */
+	DUK_ASSERT(thr->valstack != NULL);
+	DUK_ASSERT(thr->callstack != NULL);
+	DUK_ASSERT(thr->catchstack != NULL);
+
+	/* no need to handle thread state book-keeping here */
+	DUK_ASSERT((call_flags & DUK_CALL_FLAG_IS_RESUME) != 0 ||
+	           (thr->state == DUK_HTHREAD_STATE_RUNNING &&
+	            thr->heap->curr_thread == thr));
+
+	/* if a tailcall:
+	 *   - an Ecmascript activation must be on top of the callstack
+	 *   - there cannot be any active catchstack entries
+	 */
+#ifdef DUK_USE_ASSERTIONS
+	if (call_flags & DUK_CALL_FLAG_IS_TAILCALL) {
+		duk_size_t our_callstack_index;
+		duk_size_t i;
+
+		DUK_ASSERT(thr->callstack_top >= 1);
+		our_callstack_index = thr->callstack_top - 1;
+		DUK_ASSERT_DISABLE(our_callstack_index >= 0);
+		DUK_ASSERT(our_callstack_index < thr->callstack_size);
+		DUK_ASSERT(thr->callstack[our_callstack_index].func != NULL);
+		DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(thr->callstack[our_callstack_index].func));
+
+		/* No entry in the catchstack which would actually catch a
+		 * throw can refer to the callstack entry being reused.
+		 * There *can* be catchstack entries referring to the current
+		 * callstack entry as long as they don't catch (e.g. label sites).
+		 */
+
+		for (i = 0; i < thr->catchstack_top; i++) {
+			DUK_ASSERT(thr->catchstack[i].callstack_index < our_callstack_index ||  /* refer to callstack entries below current */
+			           DUK_CAT_GET_TYPE(thr->catchstack + i) == DUK_CAT_TYPE_LABEL); /* or a non-catching entry */
+		}
+	}
+#endif  /* DUK_USE_ASSERTIONS */
+
+	entry_valstack_bottom_index = (duk_size_t) (thr->valstack_bottom - thr->valstack);
+	idx_func = duk_normalize_index(thr, -num_stack_args - 2);
+	idx_args = idx_func + 2;
+
+	DUK_DD(DUK_DDPRINT("handle_ecma_call_setup: thr=%p, "
+	                   "num_stack_args=%ld, call_flags=0x%08lx (resume=%ld, tailcall=%ld), "
+	                   "idx_func=%ld, idx_args=%ld, entry_valstack_bottom_index=%ld",
+	                   (void *) thr,
+	                   (long) num_stack_args,
+	                   (unsigned long) call_flags,
+	                   (long) ((call_flags & DUK_CALL_FLAG_IS_RESUME) != 0 ? 1 : 0),
+	                   (long) ((call_flags & DUK_CALL_FLAG_IS_TAILCALL) != 0 ? 1 : 0),
+	                   (long) idx_func,
+	                   (long) idx_args,
+	                   (long) entry_valstack_bottom_index));
+
+#ifdef DUK_USE_DDDPRINT /*XXX:incorrect*/
+	DUK_D(DUK_DPRINT("callstack before call setup:"));
+	DUK_DEBUG_DUMP_CALLSTACK(thr);
+#endif
+
+	if (idx_func < 0 || idx_args < 0) {
+		/* XXX: assert? compiler is responsible for this never happening */
+		DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_CALL_ARGS);
+	}
+
+	/*
+	 *  Check the function type, handle bound function chains,
+	 *  and prepare parameters for the rest of the call handling.
+	 *  Also figure out the effective 'this' binding, which replaces
+	 *  the current value at idx_func + 1.
+	 *
+	 *  If the target function is a 'bound' one, follow the chain
+	 *  of 'bound' functions until a non-bound function is found.
+	 *  During this process, bound arguments are 'prepended' to
+	 *  existing ones, and the "this" binding is overridden.
+	 *  See E5 Section 15.3.4.5.1.
+	 */
+
+	if (!duk_is_callable(thr, idx_func)) {
+		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_CALLABLE);
+	}
+	func = duk_get_hobject(thr, idx_func);
+	DUK_ASSERT(func != NULL);
+
+	if (DUK_HOBJECT_HAS_BOUND(func)) {
+		/* slow path for bound functions */
+		duk__handle_bound_chain_for_call(thr, idx_func, &num_stack_args, &func, call_flags & DUK_CALL_FLAG_CONSTRUCTOR_CALL);
+	}
+	DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));
+	DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(func));  /* caller must ensure this */
+
+	duk__coerce_effective_this_binding(thr, func, idx_func + 1);
+	DUK_DDD(DUK_DDDPRINT("effective 'this' binding is: %!T",
+	                     duk_get_tval(ctx, idx_func + 1)));
+
+	nargs = ((duk_hcompiledfunction *) func)->nargs;
+	nregs = ((duk_hcompiledfunction *) func)->nregs;
+	DUK_ASSERT(nregs >= nargs);
+
+	/* [ ... func this arg1 ... argN ] */
+
+	/*
+	 *  Preliminary activation record and valstack manipulation.
+	 *  The concrete actions depend on whether the we're dealing
+	 *  with a tailcall (reuse an existing activation), a resume,
+	 *  or a normal call.
+	 *
+	 *  The basic actions, in varying order, are:
+	 *
+	 *    - Check stack size for call handling
+	 *    - Grow call stack if necessary (non-tail-calls)
+	 *    - Update current activation (idx_retval) if necessary
+	 *      (non-tail, non-resume calls)
+	 *    - Move start of args (idx_args) to valstack bottom
+	 *      (tail calls)
+	 *
+	 *  Don't touch valstack_bottom or valstack_top yet so that Duktape API
+	 *  calls work normally.
+	 */
+
+	/* XXX: some overlapping code; cleanup */
+	use_tailcall = call_flags & DUK_CALL_FLAG_IS_TAILCALL;
+#if !defined(DUK_USE_TAILCALL)
+	DUK_ASSERT(use_tailcall == 0);  /* compiler ensures this */
+#endif
+	if (use_tailcall) {
+		/* tailcall cannot be flagged to resume calls, and a
+		 * previous frame must exist
+		 */
+		DUK_ASSERT(thr->callstack_top >= 1);
+		DUK_ASSERT((call_flags & DUK_CALL_FLAG_IS_RESUME) == 0);
+
+		act = thr->callstack + thr->callstack_top - 1;
+		if (act->flags & DUK_ACT_FLAG_PREVENT_YIELD) {
+			/* See: test-bug-tailcall-preventyield-assert.c. */
+			DUK_DDD(DUK_DDDPRINT("tailcall prevented by current activation having DUK_ACT_FLAG_PREVENTYIELD"));
+			use_tailcall = 0;
+		} else if (DUK_HOBJECT_HAS_NOTAIL(func)) {
+			DUK_D(DUK_DPRINT("tailcall prevented by function having a notail flag"));
+			use_tailcall = 0;
+		}
+	}
+
+	if (use_tailcall) {
+		duk_tval *tv1, *tv2;
+		duk_tval tv_tmp;
+		duk_size_t cs_index;
+		duk_int_t i_stk;  /* must be signed for loop structure */
+		duk_idx_t i_arg;
+
+		/*
+		 *  Tailcall handling
+		 *
+		 *  Although the callstack entry is reused, we need to explicitly unwind
+		 *  the current activation (or simulate an unwind).  In particular, the
+		 *  current activation must be closed, otherwise something like
+		 *  test-bug-reduce-judofyr.js results.  Also catchstack needs be unwound
+		 *  because there may be non-error-catching label entries in valid tailcalls.
+		 */
+
+		DUK_DDD(DUK_DDDPRINT("is tailcall, reusing activation at callstack top, at index %ld",
+		                     (long) (thr->callstack_top - 1)));
+
+		/* 'act' already set above */
+
+		DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));
+		DUK_ASSERT(!DUK_HOBJECT_HAS_NATIVEFUNCTION(func));
+		DUK_ASSERT(DUK_HOBJECT_HAS_COMPILEDFUNCTION(func));
+		DUK_ASSERT((act->flags & DUK_ACT_FLAG_PREVENT_YIELD) == 0);
+
+		/* Unwind catchstack entries referring to the callstack entry we're reusing */
+		cs_index = thr->callstack_top - 1;
+		DUK_ASSERT(thr->catchstack_top <= DUK_INT_MAX);  /* catchstack limits */
+		for (i_stk = (duk_int_t) (thr->catchstack_top - 1); i_stk >= 0; i_stk--) {
+			duk_catcher *cat = thr->catchstack + i_stk;
+			if (cat->callstack_index != cs_index) {
+				/* 'i' is the first entry we'll keep */
+				break;
+			}
+		}
+		duk_hthread_catchstack_unwind(thr, i_stk + 1);
+
+		/* Unwind the topmost callstack entry before reusing it */
+		DUK_ASSERT(thr->callstack_top > 0);
+		duk_hthread_callstack_unwind(thr, thr->callstack_top - 1);
+
+		/* Then reuse the unwound activation; callstack was not shrunk so there is always space */
+		thr->callstack_top++;
+		DUK_ASSERT(thr->callstack_top <= thr->callstack_size);
+		act = thr->callstack + thr->callstack_top - 1;
+
+		/* Start filling in the activation */
+		act->func = func;  /* don't want an intermediate exposed state with func == NULL */
+#ifdef DUK_USE_NONSTD_FUNC_CALLER_PROPERTY
+		act->prev_caller = NULL;
+#endif
+		act->pc = 0;       /* don't want an intermediate exposed state with invalid pc */
+#ifdef DUK_USE_REFERENCE_COUNTING
+		DUK_HOBJECT_INCREF(thr, func);
+		act = thr->callstack + thr->callstack_top - 1;  /* side effects (currently none though) */
+#endif
+
+#ifdef DUK_USE_NONSTD_FUNC_CALLER_PROPERTY
+#ifdef DUK_USE_TAILCALL
+#error incorrect options: tailcalls enabled with function caller property
+#endif
+		/* XXX: this doesn't actually work properly for tail calls, so
+		 * tail calls are disabled when DUK_USE_NONSTD_FUNC_CALLER_PROPERTY
+		 * is in use.
+		 */
+		duk__update_func_caller_prop(thr, func);
+		act = thr->callstack + thr->callstack_top - 1;
+#endif
+
+		act->flags = (DUK_HOBJECT_HAS_STRICT(func) ?
+		              DUK_ACT_FLAG_STRICT | DUK_ACT_FLAG_TAILCALLED :
+	        	      DUK_ACT_FLAG_TAILCALLED);
+
+		DUK_ASSERT(act->func == func);      /* already updated */
+		DUK_ASSERT(act->var_env == NULL);   /* already NULLed (by unwind) */
+		DUK_ASSERT(act->lex_env == NULL);   /* already NULLed (by unwind) */
+		DUK_ASSERT(act->pc == 0);           /* already zeroed */
+		act->idx_bottom = entry_valstack_bottom_index;  /* tail call -> reuse current "frame" */
+		DUK_ASSERT(nregs >= 0);
+#if 0  /* topmost activation idx_retval is considered garbage, no need to init */
+		act->idx_retval = 0;
+#endif
+
+		/*
+		 *  Manipulate valstack so that args are on the current bottom and the
+		 *  previous caller's 'this' binding (which is the value preceding the
+		 *  current bottom) is replaced with the new 'this' binding:
+		 *
+		 *       [ ... this_old | (crud) func this_new arg1 ... argN ]
+		 *  -->  [ ... this_new | arg1 ... argN ]
+		 *
+		 *  For tailcalling to work properly, the valstack bottom must not grow
+		 *  here; otherwise crud would accumulate on the valstack.
+		 */
+
+		tv1 = thr->valstack_bottom - 1;
+		tv2 = thr->valstack_bottom + idx_func + 1;
+		DUK_ASSERT(tv1 >= thr->valstack && tv1 < thr->valstack_top);  /* tv1 is -below- valstack_bottom */
+		DUK_ASSERT(tv2 >= thr->valstack_bottom && tv2 < thr->valstack_top);
+		DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
+		DUK_TVAL_SET_TVAL(tv1, tv2);
+		DUK_TVAL_INCREF(thr, tv1);
+		DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+		
+		for (i_arg = 0; i_arg < idx_args; i_arg++) {
+			/* XXX: block removal API primitive */
+			/* Note: 'func' is popped from valstack here, but it is
+			 * already reachable from the activation.
+			 */
+			duk_remove(ctx, 0);
+		}
+		idx_func = 0; DUK_UNREF(idx_func);  /* really 'not applicable' anymore, should not be referenced after this */
+		idx_args = 0;
+
+		/* [ ... this_new | arg1 ... argN ] */
+
+		/* now we can also do the valstack resize check */
+
+		duk_require_valstack_resize((duk_context *) thr,
+		                            (thr->valstack_bottom - thr->valstack) +         /* bottom of current func */
+		                                idx_args +                                   /* bottom of new func (always 0 here) */
+		                                nregs +                                      /* num entries of new func at entry */
+		                                DUK_VALSTACK_INTERNAL_EXTRA,                 /* + spare => min_new_size */
+		                            1);                                              /* allow_shrink */
+	} else {
+		DUK_DDD(DUK_DDDPRINT("not a tailcall, pushing a new activation to callstack, to index %ld",
+		                     (long) (thr->callstack_top)));
+
+		duk_hthread_callstack_grow(thr);
+
+		/* func wants args clamped to 'nargs' */
+
+		duk_require_valstack_resize((duk_context *) thr,
+		                            (thr->valstack_bottom - thr->valstack) +         /* bottom of current func */
+		                                idx_args +                                   /* bottom of new func */
+		                                nregs +                                      /* num entries of new func at entry */
+		                                DUK_VALSTACK_INTERNAL_EXTRA,                 /* + spare => min_new_size */
+		                            1);                                              /* allow_shrink */
+
+		if (call_flags & DUK_CALL_FLAG_IS_RESUME) {
+			DUK_DDD(DUK_DDDPRINT("is resume -> no update to current activation (may not even exist)"));
+		} else {
+			DUK_DDD(DUK_DDDPRINT("update to current activation idx_retval"));
+			DUK_ASSERT(thr->callstack_top < thr->callstack_size);
+			DUK_ASSERT(thr->callstack_top >= 1);
+			act = thr->callstack + thr->callstack_top - 1;
+			DUK_ASSERT(act->func != NULL);
+			DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(act->func));
+			act->idx_retval = entry_valstack_bottom_index + idx_func;
+		}
+
+		DUK_ASSERT(thr->callstack_top < thr->callstack_size);
+		act = thr->callstack + thr->callstack_top;
+		thr->callstack_top++;
+		DUK_ASSERT(thr->callstack_top <= thr->callstack_size);
+
+		DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));
+		DUK_ASSERT(!DUK_HOBJECT_HAS_NATIVEFUNCTION(func));
+		DUK_ASSERT(DUK_HOBJECT_HAS_COMPILEDFUNCTION(func));
+
+		act->flags = (DUK_HOBJECT_HAS_STRICT(func) ?
+		              DUK_ACT_FLAG_STRICT :
+	        	      0);
+		act->func = func;
+		act->var_env = NULL;
+		act->lex_env = NULL;
+#ifdef DUK_USE_NONSTD_FUNC_CALLER_PROPERTY
+		act->prev_caller = NULL;
+#endif
+		act->pc = 0;
+		act->idx_bottom = entry_valstack_bottom_index + idx_args;
+		DUK_ASSERT(nregs >= 0);
+#if 0  /* topmost activation idx_retval is considered garbage, no need to init */
+		act->idx_retval = 0;
+#endif
+
+		DUK_HOBJECT_INCREF(thr, func);  /* act->func */
+
+#ifdef DUK_USE_NONSTD_FUNC_CALLER_PROPERTY
+		duk__update_func_caller_prop(thr, func);
+		act = thr->callstack + thr->callstack_top - 1;
+#endif
+	}
+
+	/* [... func this arg1 ... argN]  (not tail call)
+	 * [this | arg1 ... argN]         (tail call)
+	 *
+	 * idx_args updated to match
+	 */
+
+#ifdef DUK_USE_DDDPRINT /*XXX:incorrect*/
+	DUK_D(DUK_DPRINT("pushed new activation:"));
+	DUK_DEBUG_DUMP_ACTIVATION(thr, thr->callstack + thr->callstack_top - 1);
+#endif
+
+	/*
+	 *  Environment record creation and 'arguments' object creation.
+	 *  Named function expression name binding is handled by the
+	 *  compiler; the compiled function's parent env will contain
+	 *  the (immutable) binding already.
+	 *
+	 *  Delayed creation (on demand) is handled in duk_js_var.c.
+	 */
+
+	DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));  /* bound function chain has already been resolved */
+
+	if (!DUK_HOBJECT_HAS_NEWENV(func)) {
+		/* use existing env (e.g. for non-strict eval); cannot have
+		 * an own 'arguments' object (but can refer to the existing one)
+		 */
+
+		duk__handle_oldenv_for_call(thr, func, act);
+
+		DUK_ASSERT(act->lex_env != NULL);
+		DUK_ASSERT(act->var_env != NULL);
+		goto env_done;
+	}
+
+	DUK_ASSERT(DUK_HOBJECT_HAS_NEWENV(func));
+
+	if (!DUK_HOBJECT_HAS_CREATEARGS(func)) {
+		/* no need to create environment record now; leave as NULL */
+		DUK_ASSERT(act->lex_env == NULL);
+		DUK_ASSERT(act->var_env == NULL);
+		goto env_done;
+	}
+
+	/* third arg: absolute index (to entire valstack) of idx_bottom of new activation */
+	env = duk_create_activation_environment_record(thr, func, act->idx_bottom);
+	DUK_ASSERT(env != NULL);
+
+	/* [... arg1 ... argN envobj] */
+
+	DUK_ASSERT(DUK_HOBJECT_HAS_CREATEARGS(func));
+	duk__handle_createargs_for_call(thr, func, env, num_stack_args);
+
+	/* [... arg1 ... argN envobj] */
+
+	act->lex_env = env;
+	act->var_env = env;
+	DUK_HOBJECT_INCREF(thr, act->lex_env);
+	DUK_HOBJECT_INCREF(thr, act->var_env);
+	duk_pop(ctx);
+
+ env_done:
+	/* [... arg1 ... argN] */
+
+	/*
+	 *  Setup value stack: clamp to 'nargs', fill up to 'nregs'
+	 */
+
+	/* XXX: replace with a single operation */
+
+	DUK_ASSERT(nregs >= 0);
+	duk_set_top(ctx, idx_args + nargs);  /* clamp anything above nargs */
+	duk_set_top(ctx, idx_args + nregs);  /* extend with undefined */
+
+#ifdef DUK_USE_DDDPRINT /*XXX:incorrect*/
+	DUK_D(DUK_DPRINT("callstack after call setup:"));
+	DUK_DEBUG_DUMP_CALLSTACK(thr);
+#endif
+
+	/*
+	 *  Shift to new valstack_bottom.
+	 */
+
+	thr->valstack_bottom = thr->valstack_bottom + idx_args;
+	/* keep current valstack_top */
+	DUK_ASSERT(thr->valstack_bottom >= thr->valstack);
+	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
+	DUK_ASSERT(thr->valstack_end >= thr->valstack_top);
+
+	/*
+	 *  Return to bytecode executor, which will resume execution from
+	 *  the topmost activation.
+	 */
+}
+#line 1 "duk_js_compiler.c"
+/*
+ *  Ecmascript compiler.
+ *
+ *  Parses an input string and generates a function template result.
+ *  Compilation may happen in multiple contexts (global code, eval
+ *  code, function code).
+ *
+ *  The parser uses a traditional top-down recursive parsing for the
+ *  statement level, and an operator precedence based top-down approach
+ *  for the expression level.  The attempt is to minimize the C stack
+ *  depth.  Bytecode is generated directly without an intermediate
+ *  representation (tree), at the cost of needing two passes over each
+ *  function.
+ *
+ *  The top-down recursive parser functions are named "duk__parse_XXX".
+ *
+ *  Recursion limits are in key functions to prevent arbitrary C recursion:
+ *  function body parsing, statement parsing, and expression parsing.
+ *
+ *  See doc/compiler.txt for discussion on the design.
+ *
+ *  A few typing notes:
+ *
+ *    - duk_regconst_t: unsigned, no marker value for "none"
+ *    - duk_reg_t: signed, < 0 = none
+ *    - PC values: duk_int_t, negative values used as markers
+ */
+
+/* include removed: duk_internal.h */
+
+/* if highest bit of a register number is set, it refers to a constant instead */
+#define DUK__CONST_MARKER                 DUK_JS_CONST_MARKER
+
+/* for array and object literals */
+#define DUK__MAX_ARRAY_INIT_VALUES        20
+#define DUK__MAX_OBJECT_INIT_PAIRS        10
+
+/* XXX: hack, remove when const lookup is not O(n) */
+#define DUK__GETCONST_MAX_CONSTS_CHECK    256
+
+/* these limits are based on bytecode limits */
+#define DUK__MAX_CONSTS                   (DUK_BC_BC_MAX + 1)
+#define DUK__MAX_FUNCS                    (DUK_BC_BC_MAX + 1)
+#define DUK__MAX_TEMPS                    (DUK_BC_BC_MAX + 1)
+
+#define DUK__RECURSION_INCREASE(comp_ctx,thr)  do { \
+		DUK_DDD(DUK_DDDPRINT("RECURSION INCREASE: %s:%ld", (const char *) DUK_FILE_MACRO, (long) DUK_LINE_MACRO)); \
+		duk__recursion_increase((comp_ctx)); \
+	} while (0)
+
+#define DUK__RECURSION_DECREASE(comp_ctx,thr)  do { \
+		DUK_DDD(DUK_DDDPRINT("RECURSION DECREASE: %s:%ld", (const char *) DUK_FILE_MACRO, (long) DUK_LINE_MACRO)); \
+		duk__recursion_decrease((comp_ctx)); \
+	} while (0)
+
+/* Value stack slot limits: these are quite approximate right now, and
+ * because they overlap in control flow, some could be eliminated.
+ */
+#define DUK__COMPILE_ENTRY_SLOTS          8
+#define DUK__FUNCTION_INIT_REQUIRE_SLOTS  16
+#define DUK__FUNCTION_BODY_REQUIRE_SLOTS  16
+#define DUK__PARSE_STATEMENTS_SLOTS       16
+#define DUK__PARSE_EXPR_SLOTS             16
+
+/* Temporary structure used to pass a stack allocated region through
+ * duk_safe_call().
+ */
+typedef struct {
+	duk_small_uint_t flags;
+	duk_compiler_ctx comp_ctx_alloc;
+	duk_lexer_point lex_pt_alloc;
+} duk__compiler_stkstate;
+
+/*
+ *  Prototypes
+ */
+
+/* lexing */
+static void duk__advance_helper(duk_compiler_ctx *comp_ctx, duk_small_int_t expect);
+static void duk__advance_expect(duk_compiler_ctx *comp_ctx, duk_small_int_t expect);
+static void duk__advance(duk_compiler_ctx *ctx);
+
+/* function helpers */
+static void duk__init_func_valstack_slots(duk_compiler_ctx *comp_ctx);
+static void duk__reset_func_for_pass2(duk_compiler_ctx *comp_ctx);
+static void duk__init_varmap_and_prologue_for_pass2(duk_compiler_ctx *comp_ctx, duk_reg_t *out_stmt_value_reg);
+static void duk__convert_to_func_template(duk_compiler_ctx *comp_ctx);
+static duk_int_t duk__cleanup_varmap(duk_compiler_ctx *comp_ctx);
+
+/* code emission */
+static duk_int_t duk__get_current_pc(duk_compiler_ctx *comp_ctx);
+static duk_compiler_instr *duk__get_instr_ptr(duk_compiler_ctx *comp_ctx, duk_int_t pc);
+static void duk__emit(duk_compiler_ctx *comp_ctx, duk_instr_t ins);
+#if 0  /* unused */
+static void duk__emit_op_only(duk_compiler_ctx *comp_ctx, duk_small_uint_t op);
+#endif
+static void duk__emit_a_b_c(duk_compiler_ctx *comp_ctx, duk_small_uint_t op_flags, duk_regconst_t a, duk_regconst_t b, duk_regconst_t c);
+static void duk__emit_a_b(duk_compiler_ctx *comp_ctx, duk_small_uint_t op_flags, duk_regconst_t a, duk_regconst_t b);
+#if 0  /* unused */
+static void duk__emit_a(duk_compiler_ctx *comp_ctx, duk_small_uint_t op_flags, duk_regconst_t a);
+#endif
+static void duk__emit_a_bc(duk_compiler_ctx *comp_ctx, duk_small_uint_t op_flags, duk_regconst_t a, duk_regconst_t bc);
+static void duk__emit_abc(duk_compiler_ctx *comp_ctx, duk_small_uint_t op, duk_regconst_t abc);
+static void duk__emit_extraop_b_c(duk_compiler_ctx *comp_ctx, duk_small_uint_t extraop_flags, duk_regconst_t b, duk_regconst_t c);
+static void duk__emit_extraop_b(duk_compiler_ctx *comp_ctx, duk_small_uint_t extraop_flags, duk_regconst_t b);
+static void duk__emit_extraop_bc(duk_compiler_ctx *comp_ctx, duk_small_uint_t extraop, duk_regconst_t bc);
+static void duk__emit_extraop_only(duk_compiler_ctx *comp_ctx, duk_small_uint_t extraop_flags);
+static void duk__emit_load_int32(duk_compiler_ctx *comp_ctx, duk_reg_t reg, duk_int32_t val);
+static void duk__emit_jump(duk_compiler_ctx *comp_ctx, duk_int_t target_pc);
+static duk_int_t duk__emit_jump_empty(duk_compiler_ctx *comp_ctx);
+static void duk__insert_jump_entry(duk_compiler_ctx *comp_ctx, duk_int_t jump_pc);
+static void duk__patch_jump(duk_compiler_ctx *comp_ctx, duk_int_t jump_pc, duk_int_t target_pc);
+static void duk__patch_jump_here(duk_compiler_ctx *comp_ctx, duk_int_t jump_pc);
+static void duk__patch_trycatch(duk_compiler_ctx *comp_ctx, duk_int_t trycatch_pc, duk_regconst_t reg_catch, duk_regconst_t const_varname, duk_small_uint_t flags);
+static void duk__emit_if_false_skip(duk_compiler_ctx *comp_ctx, duk_regconst_t regconst);
+static void duk__emit_if_true_skip(duk_compiler_ctx *comp_ctx, duk_regconst_t regconst);
+static void duk__emit_invalid(duk_compiler_ctx *comp_ctx);
+
+/* ivalue/ispec helpers */
+static void duk__copy_ispec(duk_compiler_ctx *comp_ctx, duk_ispec *src, duk_ispec *dst);
+static void duk__copy_ivalue(duk_compiler_ctx *comp_ctx, duk_ivalue *src, duk_ivalue *dst);
+static duk_bool_t duk__is_whole_get_int32(duk_double_t x, duk_int32_t *ival);
+static duk_reg_t duk__alloctemps(duk_compiler_ctx *comp_ctx, duk_small_int_t num);
+static duk_reg_t duk__alloctemp(duk_compiler_ctx *comp_ctx);
+static void duk__settemp_checkmax(duk_compiler_ctx *comp_ctx, duk_reg_t temp_next);
+static duk_regconst_t duk__getconst(duk_compiler_ctx *comp_ctx);
+static duk_regconst_t duk__ispec_toregconst_raw(duk_compiler_ctx *comp_ctx,
+                                                duk_ispec *x,
+                                                duk_reg_t forced_reg,
+                                                duk_small_uint_t flags);
+static void duk__ispec_toforcedreg(duk_compiler_ctx *comp_ctx, duk_ispec *x, duk_reg_t forced_reg);
+static void duk__ivalue_toplain_raw(duk_compiler_ctx *comp_ctx, duk_ivalue *x, duk_reg_t forced_reg);
+static void duk__ivalue_toplain(duk_compiler_ctx *comp_ctx, duk_ivalue *x);
+static void duk__ivalue_toplain_ignore(duk_compiler_ctx *comp_ctx, duk_ivalue *x);
+static duk_regconst_t duk__ivalue_toregconst_raw(duk_compiler_ctx *comp_ctx,
+                                                 duk_ivalue *x,
+                                                 duk_reg_t forced_reg,
+                                                 duk_small_uint_t flags);
+static duk_reg_t duk__ivalue_toreg(duk_compiler_ctx *comp_ctx, duk_ivalue *x);
+#if 0  /* unused */
+static duk_reg_t duk__ivalue_totempreg(duk_compiler_ctx *comp_ctx, duk_ivalue *x);
+#endif
+static void duk__ivalue_toforcedreg(duk_compiler_ctx *comp_ctx, duk_ivalue *x, duk_int_t forced_reg);
+static duk_regconst_t duk__ivalue_toregconst(duk_compiler_ctx *comp_ctx, duk_ivalue *x);
+
+/* identifier handling */
+static duk_reg_t duk__lookup_active_register_binding(duk_compiler_ctx *comp_ctx);
+static duk_bool_t duk__lookup_lhs(duk_compiler_ctx *ctx, duk_reg_t *out_reg_varbind, duk_regconst_t *out_rc_varname);
+
+/* label handling */
+static void duk__add_label(duk_compiler_ctx *comp_ctx, duk_hstring *h_label, duk_int_t pc_label, duk_int_t label_id);
+static void duk__update_label_flags(duk_compiler_ctx *comp_ctx, duk_int_t label_id, duk_small_uint_t flags);
+static void duk__lookup_active_label(duk_compiler_ctx *comp_ctx, duk_hstring *h_label, duk_bool_t is_break, duk_int_t *out_label_id, duk_int_t *out_label_catch_depth, duk_int_t *out_label_pc, duk_bool_t *out_is_closest);
+static void duk__reset_labels_to_length(duk_compiler_ctx *comp_ctx, duk_int_t len);
+
+/* top-down expression parser */
+static void duk__expr_nud(duk_compiler_ctx *comp_ctx, duk_ivalue *res);
+static void duk__expr_led(duk_compiler_ctx *comp_ctx, duk_ivalue *left, duk_ivalue *res);
+static duk_small_uint_t duk__expr_lbp(duk_compiler_ctx *comp_ctx);
+static duk_bool_t duk__expr_is_empty(duk_compiler_ctx *comp_ctx);
+
+/* exprtop is the top level variant which resets nud/led counts */
+static void duk__expr(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags);
+static void duk__exprtop(duk_compiler_ctx *ctx, duk_ivalue *res, duk_small_uint_t rbp_flags);
+
+/* convenience helpers */
+static duk_reg_t duk__expr_toreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags);
+#if 0  /* unused */
+static duk_reg_t duk__expr_totempreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags);
+#endif
+static void duk__expr_toforcedreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags, duk_reg_t forced_reg);
+static duk_regconst_t duk__expr_toregconst(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags);
+static void duk__expr_toplain(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags);
+static void duk__expr_toplain_ignore(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags);
+static duk_reg_t duk__exprtop_toreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags);
+#if 0  /* unused */
+static duk_reg_t duk__exprtop_totempreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags);
+static void duk__exprtop_toforcedreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags, duk_reg_t forced_reg);
+#endif
+static duk_regconst_t duk__exprtop_toregconst(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags);
+#if 0  /* unused */
+static void duk__exprtop_toplain_ignore(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags);
+#endif
+
+/* expression parsing helpers */
+static duk_int_t duk__parse_arguments(duk_compiler_ctx *comp_ctx, duk_ivalue *res);
+static void duk__nud_array_literal(duk_compiler_ctx *comp_ctx, duk_ivalue *res);
+static void duk__nud_object_literal(duk_compiler_ctx *comp_ctx, duk_ivalue *res);
+static duk_bool_t duk__nud_object_literal_key_check(duk_compiler_ctx *comp_ctx, duk_small_uint_t new_key_flags);
+
+/* statement parsing */
+static void duk__parse_var_decl(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t expr_flags, duk_reg_t *out_reg_varbind, duk_regconst_t *out_rc_varname);
+static void duk__parse_var_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res);
+static void duk__parse_for_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_int_t pc_label_site);
+static void duk__parse_switch_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_int_t pc_label_site);
+static void duk__parse_if_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res);
+static void duk__parse_do_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_int_t pc_label_site);
+static void duk__parse_while_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_int_t pc_label_site);
+static void duk__parse_break_or_continue_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res);
+static void duk__parse_return_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res);
+static void duk__parse_throw_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res);
+static void duk__parse_try_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res);
+static void duk__parse_with_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res);
+static void duk__parse_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_bool_t allow_source_elem);
+static duk_int_t duk__stmt_label_site(duk_compiler_ctx *comp_ctx, duk_int_t label_id);
+static void duk__parse_stmts(duk_compiler_ctx *comp_ctx, duk_bool_t allow_source_elem, duk_bool_t expect_eof);
+
+static void duk__parse_func_body(duk_compiler_ctx *comp_ctx, duk_bool_t expect_eof, duk_bool_t implicit_return_value);
+static void duk__parse_func_formals(duk_compiler_ctx *comp_ctx);
+static void duk__parse_func_like_raw(duk_compiler_ctx *comp_ctx, duk_bool_t is_decl, duk_bool_t is_setget);
+static duk_int_t duk__parse_func_like_fnum(duk_compiler_ctx *comp_ctx, duk_bool_t is_decl, duk_bool_t is_setget);
+
+/*
+ *  Parser control values for tokens.  The token table is ordered by the
+ *  DUK_TOK_XXX defines.
+ *
+ *  The binding powers are for lbp() use (i.e. for use in led() context).
+ *  Binding powers are positive for typing convenience, and bits at the
+ *  top should be reserved for flags.  Binding power step must be higher
+ *  than 1 so that binding power "lbp - 1" can be used for right associative
+ *  operators.  Currently a step of 2 is used (which frees one more bit for
+ *  flags).
+ */
+
+/* XXX: actually single step levels would work just fine, clean up */
+
+/* binding power "levels" (see doc/compiler.txt) */
+#define DUK__BP_INVALID                0             /* always terminates led() */
+#define DUK__BP_EOF                    2
+#define DUK__BP_CLOSING                4             /* token closes expression, e.g. ')', ']' */
+#define DUK__BP_FOR_EXPR               DUK__BP_CLOSING    /* bp to use when parsing a top level Expression */
+#define DUK__BP_COMMA                  6
+#define DUK__BP_ASSIGNMENT             8
+#define DUK__BP_CONDITIONAL            10
+#define DUK__BP_LOR                    12
+#define DUK__BP_LAND                   14
+#define DUK__BP_BOR                    16
+#define DUK__BP_BXOR                   18
+#define DUK__BP_BAND                   20
+#define DUK__BP_EQUALITY               22
+#define DUK__BP_RELATIONAL             24
+#define DUK__BP_SHIFT                  26
+#define DUK__BP_ADDITIVE               28
+#define DUK__BP_MULTIPLICATIVE         30
+#define DUK__BP_POSTFIX                32
+#define DUK__BP_CALL                   34
+#define DUK__BP_MEMBER                 36
+
+#define DUK__TOKEN_LBP_BP_MASK         0x1f
+#define DUK__TOKEN_LBP_FLAG_NO_REGEXP  (1 << 5)   /* regexp literal must not follow this token */
+#define DUK__TOKEN_LBP_FLAG_TERMINATES (1 << 6)   /* terminates expression; e.g. post-increment/-decrement */
+#define DUK__TOKEN_LBP_FLAG_UNUSED     (1 << 7)   /* spare */
+
+#define DUK__TOKEN_LBP_GET_BP(x)       ((duk_small_uint_t) (((x) & DUK__TOKEN_LBP_BP_MASK) * 2))
+
+#define DUK__MK_LBP(bp)                ((bp) >> 1)    /* bp is assumed to be even */
+#define DUK__MK_LBP_FLAGS(bp,flags)    (((bp) >> 1) | (flags))
+
+static const duk_uint8_t duk__token_lbp[] = {
+	DUK__MK_LBP(DUK__BP_EOF),                                 /* DUK_TOK_EOF */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_LINETERM */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_COMMENT */
+	DUK__MK_LBP_FLAGS(DUK__BP_INVALID, DUK__TOKEN_LBP_FLAG_NO_REGEXP),  /* DUK_TOK_IDENTIFIER */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_BREAK */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_CASE */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_CATCH */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_CONTINUE */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_DEBUGGER */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_DEFAULT */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_DELETE */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_DO */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_ELSE */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_FINALLY */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_FOR */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_FUNCTION */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_IF */
+	DUK__MK_LBP(DUK__BP_RELATIONAL),                          /* DUK_TOK_IN */
+	DUK__MK_LBP(DUK__BP_RELATIONAL),                          /* DUK_TOK_INSTANCEOF */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_NEW */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_RETURN */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_SWITCH */
+	DUK__MK_LBP_FLAGS(DUK__BP_INVALID, DUK__TOKEN_LBP_FLAG_NO_REGEXP),  /* DUK_TOK_THIS */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_THROW */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_TRY */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_TYPEOF */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_VAR */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_VOID */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_WHILE */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_WITH */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_CLASS */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_CONST */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_ENUM */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_EXPORT */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_EXTENDS */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_IMPORT */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_SUPER */
+	DUK__MK_LBP_FLAGS(DUK__BP_INVALID, DUK__TOKEN_LBP_FLAG_NO_REGEXP),  /* DUK_TOK_NULL */
+	DUK__MK_LBP_FLAGS(DUK__BP_INVALID, DUK__TOKEN_LBP_FLAG_NO_REGEXP),  /* DUK_TOK_TRUE */
+	DUK__MK_LBP_FLAGS(DUK__BP_INVALID, DUK__TOKEN_LBP_FLAG_NO_REGEXP),  /* DUK_TOK_FALSE */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_GET */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_SET */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_IMPLEMENTS */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_INTERFACE */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_LET */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_PACKAGE */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_PRIVATE */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_PROTECTED */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_PUBLIC */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_STATIC */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_YIELD */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_LCURLY */
+	DUK__MK_LBP_FLAGS(DUK__BP_INVALID, DUK__TOKEN_LBP_FLAG_NO_REGEXP),  /* DUK_TOK_RCURLY */
+	DUK__MK_LBP(DUK__BP_MEMBER),                              /* DUK_TOK_LBRACKET */
+	DUK__MK_LBP_FLAGS(DUK__BP_CLOSING, DUK__TOKEN_LBP_FLAG_NO_REGEXP),  /* DUK_TOK_RBRACKET */
+	DUK__MK_LBP(DUK__BP_CALL),                                /* DUK_TOK_LPAREN */
+	DUK__MK_LBP_FLAGS(DUK__BP_CLOSING, DUK__TOKEN_LBP_FLAG_NO_REGEXP),  /* DUK_TOK_RPAREN */
+	DUK__MK_LBP(DUK__BP_MEMBER),                              /* DUK_TOK_PERIOD */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_SEMICOLON */
+	DUK__MK_LBP(DUK__BP_COMMA),                               /* DUK_TOK_COMMA */
+	DUK__MK_LBP(DUK__BP_RELATIONAL),                          /* DUK_TOK_LT */
+	DUK__MK_LBP(DUK__BP_RELATIONAL),                          /* DUK_TOK_GT */
+	DUK__MK_LBP(DUK__BP_RELATIONAL),                          /* DUK_TOK_LE */
+	DUK__MK_LBP(DUK__BP_RELATIONAL),                          /* DUK_TOK_GE */
+	DUK__MK_LBP(DUK__BP_EQUALITY),                            /* DUK_TOK_EQ */
+	DUK__MK_LBP(DUK__BP_EQUALITY),                            /* DUK_TOK_NEQ */
+	DUK__MK_LBP(DUK__BP_EQUALITY),                            /* DUK_TOK_SEQ */
+	DUK__MK_LBP(DUK__BP_EQUALITY),                            /* DUK_TOK_SNEQ */
+	DUK__MK_LBP(DUK__BP_ADDITIVE),                            /* DUK_TOK_ADD */
+	DUK__MK_LBP(DUK__BP_ADDITIVE),                            /* DUK_TOK_SUB */
+	DUK__MK_LBP(DUK__BP_MULTIPLICATIVE),                      /* DUK_TOK_MUL */
+	DUK__MK_LBP(DUK__BP_MULTIPLICATIVE),                      /* DUK_TOK_DIV */
+	DUK__MK_LBP(DUK__BP_MULTIPLICATIVE),                      /* DUK_TOK_MOD */
+	DUK__MK_LBP(DUK__BP_POSTFIX),                             /* DUK_TOK_INCREMENT */
+	DUK__MK_LBP(DUK__BP_POSTFIX),                             /* DUK_TOK_DECREMENT */
+	DUK__MK_LBP(DUK__BP_SHIFT),                               /* DUK_TOK_ALSHIFT */
+	DUK__MK_LBP(DUK__BP_SHIFT),                               /* DUK_TOK_ARSHIFT */
+	DUK__MK_LBP(DUK__BP_SHIFT),                               /* DUK_TOK_RSHIFT */
+	DUK__MK_LBP(DUK__BP_BAND),                                /* DUK_TOK_BAND */
+	DUK__MK_LBP(DUK__BP_BOR),                                 /* DUK_TOK_BOR */
+	DUK__MK_LBP(DUK__BP_BXOR),                                /* DUK_TOK_BXOR */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_LNOT */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_BNOT */
+	DUK__MK_LBP(DUK__BP_LAND),                                /* DUK_TOK_LAND */
+	DUK__MK_LBP(DUK__BP_LOR),                                 /* DUK_TOK_LOR */
+	DUK__MK_LBP(DUK__BP_CONDITIONAL),                         /* DUK_TOK_QUESTION */
+	DUK__MK_LBP(DUK__BP_INVALID),                             /* DUK_TOK_COLON */
+	DUK__MK_LBP(DUK__BP_ASSIGNMENT),                          /* DUK_TOK_EQUALSIGN */
+	DUK__MK_LBP(DUK__BP_ASSIGNMENT),                          /* DUK_TOK_ADD_EQ */
+	DUK__MK_LBP(DUK__BP_ASSIGNMENT),                          /* DUK_TOK_SUB_EQ */
+	DUK__MK_LBP(DUK__BP_ASSIGNMENT),                          /* DUK_TOK_MUL_EQ */
+	DUK__MK_LBP(DUK__BP_ASSIGNMENT),                          /* DUK_TOK_DIV_EQ */
+	DUK__MK_LBP(DUK__BP_ASSIGNMENT),                          /* DUK_TOK_MOD_EQ */
+	DUK__MK_LBP(DUK__BP_ASSIGNMENT),                          /* DUK_TOK_ALSHIFT_EQ */
+	DUK__MK_LBP(DUK__BP_ASSIGNMENT),                          /* DUK_TOK_ARSHIFT_EQ */
+	DUK__MK_LBP(DUK__BP_ASSIGNMENT),                          /* DUK_TOK_RSHIFT_EQ */
+	DUK__MK_LBP(DUK__BP_ASSIGNMENT),                          /* DUK_TOK_BAND_EQ */
+	DUK__MK_LBP(DUK__BP_ASSIGNMENT),                          /* DUK_TOK_BOR_EQ */
+	DUK__MK_LBP(DUK__BP_ASSIGNMENT),                          /* DUK_TOK_BXOR_EQ */
+	DUK__MK_LBP_FLAGS(DUK__BP_INVALID, DUK__TOKEN_LBP_FLAG_NO_REGEXP),  /* DUK_TOK_NUMBER */
+	DUK__MK_LBP_FLAGS(DUK__BP_INVALID, DUK__TOKEN_LBP_FLAG_NO_REGEXP),  /* DUK_TOK_STRING */
+	DUK__MK_LBP_FLAGS(DUK__BP_INVALID, DUK__TOKEN_LBP_FLAG_NO_REGEXP),  /* DUK_TOK_REGEXP */
+};
+
+/*
+ *  Misc helpers
+ */
+
+static void duk__recursion_increase(duk_compiler_ctx *comp_ctx) {
+	DUK_ASSERT(comp_ctx != NULL);
+	DUK_ASSERT(comp_ctx->recursion_depth >= 0);
+	if (comp_ctx->recursion_depth >= comp_ctx->recursion_limit) {
+		DUK_ERROR(comp_ctx->thr, DUK_ERR_RANGE_ERROR, DUK_STR_COMPILER_RECURSION_LIMIT);
+	}
+	comp_ctx->recursion_depth++;
+}
+
+static void duk__recursion_decrease(duk_compiler_ctx *comp_ctx) {
+	DUK_ASSERT(comp_ctx != NULL);
+	DUK_ASSERT(comp_ctx->recursion_depth > 0);
+	comp_ctx->recursion_depth--;
+}
+
+static duk_bool_t duk__hstring_is_eval_or_arguments(duk_compiler_ctx *comp_ctx, duk_hstring *h) {
+	DUK_UNREF(comp_ctx);
+	DUK_ASSERT(h != NULL);
+	return DUK_HSTRING_HAS_EVAL_OR_ARGUMENTS(h);
+}
+
+static duk_bool_t duk__hstring_is_eval_or_arguments_in_strict_mode(duk_compiler_ctx *comp_ctx, duk_hstring *h) {
+	DUK_ASSERT(h != NULL);
+	return (comp_ctx->curr_func.is_strict &&
+	        DUK_HSTRING_HAS_EVAL_OR_ARGUMENTS(h));
+}
+
+/*
+ *  Parser duk__advance() token eating functions
+ */
+
+/* XXX: valstack handling is awkward.  Add a valstack helper which
+ * avoids dup():ing; valstack_copy(src, dst)?
+ */
+
+static void duk__advance_helper(duk_compiler_ctx *comp_ctx, duk_small_int_t expect) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_bool_t regexp;
+
+	DUK_ASSERT(comp_ctx->curr_token.t >= 0 && comp_ctx->curr_token.t <= DUK_TOK_MAXVAL);  /* MAXVAL is inclusive */
+
+	/*
+	 *  Use current token to decide whether a RegExp can follow.
+	 *
+	 *  We can use either 't' or 't_nores'; the latter would not
+	 *  recognize keywords.  Some keywords can be followed by a
+	 *  RegExp (e.g. "return"), so using 't' is better.  This is
+	 *  not trivial, see doc/compiler.txt.
+	 */
+
+	regexp = 1;
+	if (duk__token_lbp[comp_ctx->curr_token.t] & DUK__TOKEN_LBP_FLAG_NO_REGEXP) {
+		regexp = 0;
+	}
+	if (comp_ctx->curr_func.reject_regexp_in_adv) {
+		comp_ctx->curr_func.reject_regexp_in_adv = 0;
+		regexp = 0;
+	}
+
+	if (expect >= 0 && comp_ctx->curr_token.t != expect) {
+		DUK_D(DUK_DPRINT("parse error: expect=%ld, got=%ld",
+		                 (long) expect, (long) comp_ctx->curr_token.t));
+		DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_PARSE_ERROR);
+	}
+
+	/* make current token the previous; need to fiddle with valstack "backing store" */
+	DUK_MEMCPY(&comp_ctx->prev_token, &comp_ctx->curr_token, sizeof(duk_token));
+	duk_copy(ctx, comp_ctx->tok11_idx, comp_ctx->tok21_idx);
+	duk_copy(ctx, comp_ctx->tok12_idx, comp_ctx->tok22_idx);
+
+	/* parse new token */
+	duk_lexer_parse_js_input_element(&comp_ctx->lex,
+	                                 &comp_ctx->curr_token,
+	                                 comp_ctx->curr_func.is_strict,
+	                                 regexp);
+
+	DUK_DDD(DUK_DDDPRINT("advance: curr: tok=%ld/%ld,%ld-%ld,term=%ld,%!T,%!T "
+	                     "prev: tok=%ld/%ld,%ld-%ld,term=%ld,%!T,%!T",
+	                     (long) comp_ctx->curr_token.t,
+	                     (long) comp_ctx->curr_token.t_nores,
+	                     (long) comp_ctx->curr_token.start_line,
+	                     (long) comp_ctx->curr_token.end_line,
+	                     (long) comp_ctx->curr_token.lineterm,
+	                     (duk_tval *) duk_get_tval(ctx, comp_ctx->tok11_idx),
+	                     (duk_tval *) duk_get_tval(ctx, comp_ctx->tok12_idx),
+	                     (long) comp_ctx->prev_token.t,
+	                     (long) comp_ctx->prev_token.t_nores,
+	                     (long) comp_ctx->prev_token.start_line,
+	                     (long) comp_ctx->prev_token.end_line,
+	                     (long) comp_ctx->prev_token.lineterm,
+	                     (duk_tval *) duk_get_tval(ctx, comp_ctx->tok21_idx),
+	                     (duk_tval *) duk_get_tval(ctx, comp_ctx->tok22_idx)));
+}
+
+/* advance, expecting current token to be a specific token; parse next token in regexp context */
+static void duk__advance_expect(duk_compiler_ctx *comp_ctx, duk_small_int_t expect) {
+	duk__advance_helper(comp_ctx, expect);
+}
+
+/* advance, whatever the current token is; parse next token in regexp context */
+static void duk__advance(duk_compiler_ctx *comp_ctx) {
+	duk__advance_helper(comp_ctx, -1);
+}
+
+/*
+ *  Helpers for duk_compiler_func.
+ */
+
+/* init function state: inits valstack allocations */
+static void duk__init_func_valstack_slots(duk_compiler_ctx *comp_ctx) {
+	duk_compiler_func *func = &comp_ctx->curr_func;
+	duk_hthread *thr = comp_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_idx_t entry_top;
+
+	entry_top = duk_get_top(ctx);
+
+	DUK_MEMZERO(func, sizeof(*func));  /* intentional overlap with earlier memzero */
+#ifdef DUK_USE_EXPLICIT_NULL_INIT
+	func->h_name = NULL;
+	func->h_code = NULL;
+	func->h_consts = NULL;
+	func->h_funcs = NULL;
+	func->h_decls = NULL;
+	func->h_labelnames = NULL;
+	func->h_labelinfos = NULL;
+	func->h_argnames = NULL;
+	func->h_varmap = NULL;
+#endif
+
+	duk_require_stack(ctx, DUK__FUNCTION_INIT_REQUIRE_SLOTS);
+
+	/* XXX: getter for dynamic buffer */
+
+	duk_push_dynamic_buffer(ctx, 0);
+	func->code_idx = entry_top + 0;
+	func->h_code = (duk_hbuffer_dynamic *) duk_get_hbuffer(ctx, entry_top + 0);
+	DUK_ASSERT(func->h_code != NULL);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(func->h_code));
+
+	duk_push_array(ctx);
+	func->consts_idx = entry_top + 1;
+	func->h_consts = duk_get_hobject(ctx, entry_top + 1);
+	DUK_ASSERT(func->h_consts != NULL);
+
+	duk_push_array(ctx);
+	func->funcs_idx = entry_top + 2;
+	func->h_funcs = duk_get_hobject(ctx, entry_top + 2);
+	DUK_ASSERT(func->h_funcs != NULL);
+	DUK_ASSERT(func->fnum_next == 0);
+
+	duk_push_array(ctx);
+	func->decls_idx = entry_top + 3;
+	func->h_decls = duk_get_hobject(ctx, entry_top + 3);
+	DUK_ASSERT(func->h_decls != NULL);
+
+	duk_push_array(ctx);
+	func->labelnames_idx = entry_top + 4;
+	func->h_labelnames = duk_get_hobject(ctx, entry_top + 4);
+	DUK_ASSERT(func->h_labelnames != NULL);
+
+	duk_push_dynamic_buffer(ctx, 0);
+	func->labelinfos_idx = entry_top + 5;
+	func->h_labelinfos = (duk_hbuffer_dynamic *) duk_get_hbuffer(ctx, entry_top + 5);
+	DUK_ASSERT(func->h_labelinfos != NULL);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(func->h_labelinfos));
+
+	duk_push_array(ctx);
+	func->argnames_idx = entry_top + 6;
+	func->h_argnames = duk_get_hobject(ctx, entry_top + 6);
+	DUK_ASSERT(func->h_argnames != NULL);
+
+	duk_push_object_internal(ctx);
+	func->varmap_idx = entry_top + 7;
+	func->h_varmap = duk_get_hobject(ctx, entry_top + 7);
+	DUK_ASSERT(func->h_varmap != NULL);
+}
+
+/* reset function state (prepare for pass 2) */
+static void duk__reset_func_for_pass2(duk_compiler_ctx *comp_ctx) {
+	duk_compiler_func *func = &comp_ctx->curr_func;
+	duk_hthread *thr = comp_ctx->thr;
+
+	/* XXX: reset buffers while keeping existing spare */
+
+	duk_hbuffer_reset(thr, func->h_code);
+	duk_hobject_set_length_zero(thr, func->h_consts);
+	/* keep func->h_funcs; inner functions are not reparsed to avoid O(depth^2) parsing */
+	func->fnum_next = 0;
+	/* duk_hobject_set_length_zero(thr, func->h_funcs); */
+	duk_hobject_set_length_zero(thr, func->h_labelnames);
+	duk_hbuffer_reset(thr, func->h_labelinfos);
+	/* keep func->h_argnames; it is fixed for all passes */
+}
+
+/* cleanup varmap from any null entries, compact it, etc; returns number
+ * of final entries after cleanup.
+ */
+static duk_int_t duk__cleanup_varmap(duk_compiler_ctx *comp_ctx) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_hobject *h_varmap;
+	duk_hstring *h_key;
+	duk_tval *tv;
+	duk_uint32_t i, e_used;
+	duk_int_t ret;
+
+	/* [ ... varmap ] */
+
+	h_varmap = duk_get_hobject(ctx, -1);
+	DUK_ASSERT(h_varmap != NULL);
+
+	ret = 0;
+	e_used = h_varmap->e_used;
+	for (i = 0; i < e_used; i++) {
+		h_key = DUK_HOBJECT_E_GET_KEY(h_varmap, i);
+		if (!h_key) {
+			continue;
+		}
+
+		DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(h_varmap, i));
+
+		/* The entries can either be register numbers or 'null' values.
+		 * Thus, no need to DECREF them and get side effects.  DECREF'ing
+		 * the keys (strings) can cause memory to be freed but no side
+		 * effects as strings don't have finalizers.  This is why we can
+		 * rely on the object properties not changing from underneath us.
+		 */
+
+		tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(h_varmap, i);
+		if (!DUK_TVAL_IS_NUMBER(tv)) {
+			DUK_ASSERT(!DUK_TVAL_IS_HEAP_ALLOCATED(tv));
+			DUK_TVAL_SET_UNDEFINED_UNUSED(tv);
+			DUK_HOBJECT_E_SET_KEY(h_varmap, i, NULL);
+			DUK_HSTRING_DECREF(thr, h_key);
+		} else {
+			ret++;
+		}
+	}
+
+	duk_compact(ctx, -1);
+
+	return ret;
+}
+
+/* convert duk_compiler_func into a function template, leaving the result
+ * on top of stack.
+ */
+/* XXX: awkward and bloated asm -- use faster internal accesses */
+static void duk__convert_to_func_template(duk_compiler_ctx *comp_ctx) {
+	duk_compiler_func *func = &comp_ctx->curr_func;
+	duk_hthread *thr = comp_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_hcompiledfunction *h_res;
+	duk_hbuffer_fixed *h_data;
+	duk_size_t consts_count;
+	duk_size_t funcs_count;
+	duk_size_t code_count;
+	duk_size_t code_size;
+	duk_size_t data_size;
+	duk_size_t i;
+	duk_tval *p_const;
+	duk_hobject **p_func;
+	duk_instr_t *p_instr;
+	duk_compiler_instr *q_instr;
+	duk_tval *tv;
+
+	DUK_DDD(DUK_DDDPRINT("converting duk_compiler_func to function/template"));
+	DUK_DD(DUK_DDPRINT("code=%!xO consts=%!O funcs=%!O",
+	                   (duk_heaphdr *) func->h_code,
+	                   (duk_heaphdr *) func->h_consts,
+	                   (duk_heaphdr *) func->h_funcs));
+
+	/*
+	 *  Push result object and init its flags
+	 */
+
+	/* Valstack should suffice here, required on function valstack init */
+
+	(void) duk_push_compiledfunction(ctx);
+	h_res = (duk_hcompiledfunction *) duk_get_hobject(ctx, -1);  /* XXX: specific getter */
+
+	if (func->is_function) {
+		DUK_DDD(DUK_DDDPRINT("function -> set NEWENV"));
+		DUK_HOBJECT_SET_NEWENV((duk_hobject *) h_res);
+
+		if (!func->is_arguments_shadowed) {
+			/* arguments object would be accessible; note that shadowing
+			 * bindings are arguments or function declarations, neither
+			 * of which are deletable, so this is safe.
+			 */
+
+			if (func->id_access_arguments || func->may_direct_eval) {
+				DUK_DDD(DUK_DDDPRINT("function may access 'arguments' object directly or "
+				                     "indirectly -> set CREATEARGS"));
+				DUK_HOBJECT_SET_CREATEARGS((duk_hobject *) h_res);
+			}
+		}
+	} else if (func->is_eval && func->is_strict) {
+		DUK_DDD(DUK_DDDPRINT("strict eval code -> set NEWENV"));
+		DUK_HOBJECT_SET_NEWENV((duk_hobject *) h_res);
+	} else {
+		/* non-strict eval: env is caller's env or global env (direct vs. indirect call)
+		 * global code: env is is global env
+		 */
+		DUK_DDD(DUK_DDDPRINT("non-strict eval code or global code -> no NEWENV"));
+		DUK_ASSERT(!DUK_HOBJECT_HAS_NEWENV((duk_hobject *) h_res));
+	}
+
+	if (func->is_function && !func->is_decl && func->h_name != NULL) {
+		DUK_DDD(DUK_DDDPRINT("function expression with a name -> set NAMEBINDING"));
+		DUK_HOBJECT_SET_NAMEBINDING((duk_hobject *) h_res);
+	}
+
+	if (func->is_strict) {
+		DUK_DDD(DUK_DDDPRINT("function is strict -> set STRICT"));
+		DUK_HOBJECT_SET_STRICT((duk_hobject *) h_res);
+	}
+
+	if (func->is_notail) {
+		DUK_DDD(DUK_DDDPRINT("function is notail -> set NOTAIL"));
+		DUK_HOBJECT_SET_NOTAIL((duk_hobject *) h_res);
+	}
+
+	/*
+	 *  Build function fixed size 'data' buffer, which contains bytecode,
+	 *  constants, and inner function references.
+	 *
+	 *  During the building phase 'data' is reachable but incomplete.
+	 *  Only incref's occur during building (no refzero or GC happens),
+	 *  so the building process is atomic.
+	 */
+
+	consts_count = duk_hobject_get_length(comp_ctx->thr, func->h_consts);
+	funcs_count = duk_hobject_get_length(comp_ctx->thr, func->h_funcs) / 3;
+	code_count = DUK_HBUFFER_GET_SIZE(func->h_code) / sizeof(duk_compiler_instr);
+	code_size = code_count * sizeof(duk_instr_t);
+
+	data_size = consts_count * sizeof(duk_tval) +
+	            funcs_count * sizeof(duk_hobject *) +
+	            code_size;
+
+	DUK_DDD(DUK_DDDPRINT("consts_count=%ld, funcs_count=%ld, code_size=%ld -> "
+	                     "data_size=%ld*%ld + %ld*%ld + %ld = %ld",
+	                     (long) consts_count, (long) funcs_count, (long) code_size,
+	                     (long) consts_count, (long) sizeof(duk_tval),
+	                     (long) funcs_count, (long) sizeof(duk_hobject *),
+	                     (long) code_size, (long) data_size));
+
+	duk_push_fixed_buffer(ctx, data_size);
+	h_data = (duk_hbuffer_fixed *) duk_get_hbuffer(ctx, -1);
+	DUK_ASSERT(h_data != NULL);
+
+	h_res->data = (duk_hbuffer *) h_data;
+	DUK_HEAPHDR_INCREF(thr, h_data);
+
+	p_const = (duk_tval *) DUK_HBUFFER_FIXED_GET_DATA_PTR(h_data);
+	for (i = 0; i < consts_count; i++) {
+		DUK_ASSERT(i <= DUK_UARRIDX_MAX);  /* const limits */
+		tv = duk_hobject_find_existing_array_entry_tval_ptr(func->h_consts, (duk_uarridx_t) i);
+		DUK_ASSERT(tv != NULL);
+		DUK_TVAL_SET_TVAL(p_const, tv);
+		p_const++;
+		DUK_TVAL_INCREF(thr, tv);  /* may be a string constant */
+
+		DUK_DDD(DUK_DDDPRINT("constant: %!T", (duk_tval *) tv));
+	}
+
+	p_func = (duk_hobject **) p_const;
+	h_res->funcs = p_func;
+	for (i = 0; i < funcs_count; i++) {
+		duk_hobject *h;
+		DUK_ASSERT(i * 3 <= DUK_UARRIDX_MAX);  /* func limits */
+		tv = duk_hobject_find_existing_array_entry_tval_ptr(func->h_funcs, (duk_uarridx_t) (i * 3));
+		DUK_ASSERT(tv != NULL);
+		DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
+		h = DUK_TVAL_GET_OBJECT(tv);
+		DUK_ASSERT(h != NULL);
+		DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(h));
+		*p_func++ = h;
+		DUK_HOBJECT_INCREF(thr, h);
+
+		DUK_DDD(DUK_DDDPRINT("inner function: %p -> %!iO",
+		                     (void *) h, (duk_heaphdr *) h));
+	}
+
+	p_instr = (duk_instr_t *) p_func;
+	h_res->bytecode = p_instr;
+
+	/* copy bytecode instructions one at a time */
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(func->h_code));
+	q_instr = (duk_compiler_instr *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(func->h_code);
+	for (i = 0; i < code_count; i++) {
+		p_instr[i] = q_instr[i].ins;
+	}
+	/* Note: 'q_instr' is still used below */
+
+	duk_pop(ctx);  /* 'data' (and everything in it) is reachable through h_res now */
+
+	/*
+	 *  Init object properties
+	 *
+	 *  Properties should be added in decreasing order of access frequency.
+	 *  (Not very critical for function templates.)
+	 */
+
+	DUK_DDD(DUK_DDDPRINT("init function properties"));
+
+	/* [ ... res ] */
+
+	/* _varmap: omitted if function is guaranteed not to do slow path identifier
+	 * accesses or if it would turn out to be empty of actual register mappings
+	 * after a cleanup.
+	 */
+	if (func->id_access_slow ||     /* directly uses slow accesses */
+	    func->may_direct_eval ||    /* may indirectly slow access through a direct eval */
+	    funcs_count > 0) {          /* has inner functions which may slow access (XXX: this can be optimized by looking at the inner functions) */
+		duk_int_t num_used;
+		duk_dup(ctx, func->varmap_idx);
+		num_used = duk__cleanup_varmap(comp_ctx);
+		DUK_DDD(DUK_DDDPRINT("cleaned up varmap: %!T (num_used=%ld)",
+		                     (duk_tval *) duk_get_tval(ctx, -1), (long) num_used));
+
+		if (num_used > 0) {
+			duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_VARMAP, DUK_PROPDESC_FLAGS_NONE);
+		} else {
+			DUK_DDD(DUK_DDDPRINT("varmap is empty after cleanup -> no need to add"));
+			duk_pop(ctx);
+		}
+	}
+
+	/* _formals: omitted if function is guaranteed not to need a (non-strict) arguments object */
+	if (1) {  /* FIXME: condition */
+		/* FIXME: if omitted, recheck handling for 'length' in duk_js_push_closure();
+		 * it currently relies on _formals being set.
+		 */
+		duk_dup(ctx, func->argnames_idx);
+		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_FORMALS, DUK_PROPDESC_FLAGS_NONE);
+	}
+
+	/* name */
+	if (func->h_name) {
+		duk_push_hstring(ctx, func->h_name);
+		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_NONE);
+	}
+
+	/* _source */
+#if defined(DUK_USE_NONSTD_FUNC_SOURCE_PROPERTY)
+	if (0) {
+		/* FIXME: Currently function source code is not stored, as it is not
+		 * required by the standard.  Source code should not be stored by
+		 * default (user should enable it explicitly), and the source should
+		 * probably be compressed with a trivial text compressor; average
+		 * compression of 20-30% is quite easy to achieve even with a trivial
+		 * compressor (RLE + backwards lookup).
+		 */
+		/* FIXME: Debugging needs source code to be useful: sometimes input
+		 * code is not found in files as it may be generated and then eval()'d,
+		 * given by dynamic C code, etc.
+		 */
+
+		/*
+		 *  For global or eval code this is straightforward.  For functions
+		 *  created with the Function constructor we only get the source for
+		 *  the body and must manufacture the "function ..." part.
+		 *
+		 *  For instance, for constructed functions (v8):
+		 *
+		 *    > a = new Function("foo", "bar", "print(foo)");
+		 *    [Function]
+		 *    > a.toString()
+		 *    'function anonymous(foo,bar) {\nprint(foo)\n}'
+		 *
+		 *  Similarly for e.g. getters (v8):
+		 *
+		 *    > x = { get a(foo,bar) { print(foo); } }
+		 *    { a: [Getter] }
+		 *    > Object.getOwnPropertyDescriptor(x, 'a').get.toString()
+		 *    'function a(foo,bar) { print(foo); }'
+		 */
+
+		/* FIXME: need tokenizer indices for start and end to substring */
+		/* FIXME: always normalize function declaration part? */
+		/* FIXME: if we keep _formals, only need to store body */
+#if 0
+		duk_push_string(ctx, "FIXME");
+		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_SOURCE, DUK_PROPDESC_FLAGS_NONE);
+#endif
+	}
+#endif  /* DUK_USE_NONSTD_FUNC_SOURCE_PROPERTY */
+
+	/* _pc2line */
+#if defined(DUK_USE_PC2LINE)
+	if (1) {
+		/*
+		 *  Size-optimized pc->line mapping.
+		 */
+
+		DUK_ASSERT(code_count <= DUK_COMPILER_MAX_BYTECODE_LENGTH);
+		duk_hobject_pc2line_pack(thr, q_instr, (duk_uint_fast32_t) code_count);  /* -> pushes fixed buffer */
+		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_PC2LINE, DUK_PROPDESC_FLAGS_NONE);
+
+		/* XXX: if assertions enabled, walk through all valid PCs
+		 * and check line mapping.
+		 */
+	}
+#endif  /* DUK_USE_PC2LINE */
+
+	/* fileName */
+	if (comp_ctx->h_filename) {
+		/*
+		 *  Source filename (or equivalent), for identifying thrown errors.
+		 */
+
+		duk_push_hstring(ctx, comp_ctx->h_filename);
+		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_NONE);
+	}
+
+	/*
+	 *  Init remaining result fields
+	 *
+	 *  'nregs' controls how large a register frame is allocated.
+	 *
+	 *  'nargs' controls how many formal arguments are written to registers:
+	 *  r0, ... r(nargs-1).  The remaining registers are initialized to
+	 *  undefined.
+	 */
+
+	DUK_ASSERT(func->temp_max >= 0);
+	h_res->nregs = func->temp_max;
+	h_res->nargs = duk_hobject_get_length(thr, func->h_argnames);
+	DUK_ASSERT(h_res->nregs >= h_res->nargs);  /* pass2 allocation handles this */
+
+	DUK_DD(DUK_DDPRINT("converted function: %!ixT",
+	                   (duk_tval *) duk_get_tval(ctx, -1)));
+
+	/*
+	 *  Compact the function template.
+	 */
+
+	duk_compact(ctx, -1);
+
+	/*
+	 *  Debug dumping
+	 */
+
+#ifdef DUK_USE_DDDPRINT
+	{
+		duk_hcompiledfunction *h;
+		duk_instr_t *p, *p_start, *p_end;
+
+		h = (duk_hcompiledfunction *) duk_get_hobject(ctx, -1);
+		p_start = (duk_instr_t *) DUK_HCOMPILEDFUNCTION_GET_CODE_BASE(h);
+		p_end = (duk_instr_t *) DUK_HCOMPILEDFUNCTION_GET_CODE_END(h);
+
+		p = p_start;
+		while (p < p_end) {
+			DUK_DDD(DUK_DDDPRINT("BC %04ld: %!I        ; 0x%08lx op=%ld (%!C) a=%ld b=%ld c=%ld",
+			                     (long) (p - p_start),
+			                     (duk_instr_t) (*p),
+			                     (unsigned long) (*p),
+			                     (long) DUK_DEC_OP(*p),
+			                     (long) DUK_DEC_OP(*p),
+			                     (long) DUK_DEC_A(*p),
+			                     (long) DUK_DEC_B(*p),
+			                     (long) DUK_DEC_C(*p)));
+			p++;
+		}
+	}
+#endif
+}
+
+/*
+ *  Code emission helpers
+ *
+ *  Some emission helpers understand the range of target and source reg/const
+ *  values and automatically emit shuffling code if necessary.  This is the
+ *  case when the slot in question (A, B, C) is used in the standard way and
+ *  for opcodes the emission helpers explicitly understand (like DUK_OP_CALL).
+ *
+ *  The standard way is that:
+ *    - slot A is a target register
+ *    - slot B is a source register/constant
+ *    - slot C is a source register/constant
+ *
+ *  If a slot is used in a non-standard way the caller must indicate this
+ *  somehow.  If a slot is used as a target instead of a source (or vice
+ *  versa), this can be indicated with a flag to trigger proper shuffling
+ *  (e.g. DUK__EMIT_FLAG_B_IS_TARGET).  If the value in the slot is not
+ *  register/const related at all, the caller must ensure that the raw value
+ *  fits into the corresponding slot so as to not trigger shuffling.  The
+ *  caller must set a "no shuffle" flag to ensure compilation fails if
+ *  shuffling were to be triggered because of an internal error.
+ *
+ *  For slots B and C the raw slot size is 9 bits but one bit is reserved for
+ *  the reg/const indicator.  To use the full 9-bit range for a raw value,
+ *  shuffling must be disabled with the DUK__EMIT_FLAG_NO_SHUFFLE_{B,C} flag.
+ *  Shuffling is only done for A, B, and C slots, not the larger BC or ABC slots.
+ *
+ *  There is call handling specific understanding in the A-B-C emitter to
+ *  convert call setup and call instructions into indirect ones if necessary.
+ */
+
+/* Code emission flags, passed in the 'opcode' field.  Opcode + flags
+ * fit into 16 bits for now, so use duk_small_uint.t.
+ */
+#define DUK__EMIT_FLAG_NO_SHUFFLE_A  (1 << 8)
+#define DUK__EMIT_FLAG_NO_SHUFFLE_B  (1 << 9)
+#define DUK__EMIT_FLAG_NO_SHUFFLE_C  (1 << 10)
+#define DUK__EMIT_FLAG_A_IS_SOURCE   (1 << 11)  /* slot A is a source (default: target) */
+#define DUK__EMIT_FLAG_B_IS_TARGET   (1 << 12)  /* slot B is a target (default: source) */
+#define DUK__EMIT_FLAG_C_IS_TARGET   (1 << 13)  /* slot C is a target (default: source) */
+
+/* XXX: clarify on when and where DUK__CONST_MARKER is allowed */
+/* XXX: opcode specific assertions on when consts are allowed */
+
+/* XXX: macro smaller than call? */
+static duk_int_t duk__get_current_pc(duk_compiler_ctx *comp_ctx) {
+	return (duk_int_t) (DUK_HBUFFER_GET_SIZE(comp_ctx->curr_func.h_code) / sizeof(duk_compiler_instr));
+}
+
+static duk_compiler_instr *duk__get_instr_ptr(duk_compiler_ctx *comp_ctx, duk_int_t pc) {
+	duk_compiler_func *f = &comp_ctx->curr_func;
+	duk_uint8_t *p;
+	duk_compiler_instr *code_begin, *code_end;
+
+	p = (duk_uint8_t *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(f->h_code);
+	code_begin = (duk_compiler_instr *) p;
+	code_end = (duk_compiler_instr *) (p + DUK_HBUFFER_GET_SIZE(f->h_code));
+	DUK_UNREF(code_end);
+
+	DUK_ASSERT(pc >= 0);
+	DUK_ASSERT((duk_size_t) pc < (duk_size_t) (code_end - code_begin));
+
+	return code_begin + pc;
+}
+
+/* emit instruction; could return PC but that's not needed in the majority
+ * of cases.
+ */
+static void duk__emit(duk_compiler_ctx *comp_ctx, duk_instr_t ins) {
+	duk_hbuffer_dynamic *h;
+#if defined(DUK_USE_PC2LINE)
+	duk_int_t line;
+#endif
+	duk_compiler_instr instr;
+
+	DUK_DDD(DUK_DDDPRINT("duk__emit: 0x%08lx line=%ld pc=%ld --> %!I",
+	                     (unsigned long) ins, (long) comp_ctx->curr_token.start_line,
+	                     (long) duk__get_current_pc(comp_ctx), (duk_instr_t) ins));
+
+	h = comp_ctx->curr_func.h_code;
+#if defined(DUK_USE_PC2LINE)
+	line = comp_ctx->curr_token.start_line;  /* approximation, close enough */
+#endif
+
+	instr.ins = ins;
+#if defined(DUK_USE_PC2LINE)
+	instr.line = line;
+#endif
+
+	/* Limit checks for bytecode byte size and line number. */
+#if defined(DUK_USE_ESBC_LIMITS)
+	if (DUK_UNLIKELY(line > DUK_USE_ESBC_MAX_LINENUMBER ||
+	                 DUK_HBUFFER_GET_SIZE((duk_hbuffer *) h) > DUK_USE_ESBC_MAX_BYTES)) {
+		DUK_ERROR(comp_ctx->thr, DUK_ERR_RANGE_ERROR, DUK_STR_BYTECODE_LIMIT);
+	}
+#endif
+
+	duk_hbuffer_append_bytes(comp_ctx->thr, h, (duk_uint8_t *) &instr, sizeof(instr));
+}
+
+#if 0 /* unused */
+static void duk__emit_op_only(duk_compiler_ctx *comp_ctx, duk_small_uint_t op) {
+	duk__emit(comp_ctx, DUK_ENC_OP_ABC(op, 0));
+}
+#endif
+
+/* Important main primitive. */
+static void duk__emit_a_b_c(duk_compiler_ctx *comp_ctx, duk_small_uint_t op_flags, duk_regconst_t a, duk_regconst_t b, duk_regconst_t c) {
+	duk_instr_t ins = 0;
+	duk_int_t a_out = 0;
+	duk_int_t b_out = 0;
+	duk_int_t c_out = 0;
+	duk_int_t tmp;
+
+	DUK_DDD(DUK_DDDPRINT("emit: op_flags=%04lx, a=%ld, b=%ld, c=%ld",
+	                     (unsigned long) op_flags, (long) a, (long) b, (long) c));
+	
+	/* We could rely on max temp/const checks: if they don't exceed BC
+	 * limit, nothing here can either (just asserts would be enough).
+	 * Currently we check for the limits, which provides additional
+	 * protection against creating invalid bytecode due to compiler
+	 * bugs.
+	 */
+
+	DUK_ASSERT_DISABLE((op_flags & 0xff) >= DUK_BC_OP_MIN);  /* unsigned */
+	DUK_ASSERT((op_flags & 0xff) <= DUK_BC_OP_MAX);
+
+	/* Input shuffling happens before the actual operation, while output
+	 * shuffling happens afterwards.  Output shuffling decisions are still
+	 * made at the same time to reduce branch clutter; output shuffle decisions
+	 * are recorded into X_out variables.
+	 */
+
+	/* Slot A */
+
+	if (a <= DUK_BC_A_MAX) {
+		;
+	} else if (op_flags & DUK__EMIT_FLAG_NO_SHUFFLE_A) {
+		DUK_D(DUK_DPRINT("out of regs: 'a' (reg) needs shuffling but shuffle prohibited, a: %ld", (long) a));
+		goto error_outofregs;
+	} else if (a <= DUK_BC_BC_MAX) {
+		comp_ctx->curr_func.needs_shuffle = 1;
+		tmp = comp_ctx->curr_func.shuffle1;
+		if (op_flags & DUK__EMIT_FLAG_A_IS_SOURCE) {
+			duk__emit(comp_ctx, DUK_ENC_OP_A_BC(DUK_OP_LDREG, tmp, a));
+		} else {
+			duk_small_int_t op = op_flags & 0xff;
+			if (op == DUK_OP_CSVAR || op == DUK_OP_CSREG || op == DUK_OP_CSPROP) {
+				/* Special handling for call setup instructions.  The target
+				 * is expressed indirectly, but there is no output shuffling.
+				 */
+				DUK_ASSERT((op_flags & DUK__EMIT_FLAG_A_IS_SOURCE) == 0);
+				duk__emit_load_int32(comp_ctx, tmp, a);
+				DUK_ASSERT(DUK_OP_CSVARI == DUK_OP_CSVAR + 1);
+				DUK_ASSERT(DUK_OP_CSREGI == DUK_OP_CSREG + 1);
+				DUK_ASSERT(DUK_OP_CSPROPI == DUK_OP_CSPROP + 1);
+				op_flags++;  /* indirect opcode follows direct */
+			} else {
+				/* Output shuffle needed after main operation */
+				a_out = a;
+			}
+		}
+		a = tmp;
+	} else {
+		DUK_D(DUK_DPRINT("out of regs: 'a' (reg) needs shuffling but does not fit into BC, a: %ld", (long) a));
+		goto error_outofregs;
+	}
+
+	/* Slot B */
+
+	if (b & DUK__CONST_MARKER) {
+		DUK_ASSERT((op_flags & DUK__EMIT_FLAG_NO_SHUFFLE_B) == 0);
+		DUK_ASSERT((op_flags & DUK__EMIT_FLAG_B_IS_TARGET) == 0);
+		DUK_ASSERT((op_flags & 0xff) != DUK_OP_CALL);
+		DUK_ASSERT((op_flags & 0xff) != DUK_OP_NEW);
+		b = b & ~DUK__CONST_MARKER;
+		if (b <= 0xff) {
+			ins |= DUK_ENC_OP_A_B_C(0, 0, 0x100, 0);  /* const flag for B */
+		} else if (b <= DUK_BC_BC_MAX) {
+			comp_ctx->curr_func.needs_shuffle = 1;
+			tmp = comp_ctx->curr_func.shuffle2;
+			duk__emit(comp_ctx, DUK_ENC_OP_A_BC(DUK_OP_LDCONST, tmp, b));
+			b = tmp;
+		} else {
+			DUK_D(DUK_DPRINT("out of regs: 'b' (const) needs shuffling but does not fit into BC, b: %ld", (long) b));
+			goto error_outofregs;
+		}
+	} else {
+		if (b <= 0xff) {
+			;
+		} else if (op_flags & DUK__EMIT_FLAG_NO_SHUFFLE_B) {
+			if (b > DUK_BC_B_MAX) {
+				/* Note: 0xff != DUK_BC_B_MAX */
+				DUK_D(DUK_DPRINT("out of regs: 'b' (reg) needs shuffling but shuffle prohibited, b: %ld", (long) b));
+				goto error_outofregs;
+			}
+		} else if (b <= DUK_BC_BC_MAX) {
+			comp_ctx->curr_func.needs_shuffle = 1;
+			tmp = comp_ctx->curr_func.shuffle2;
+			if (op_flags & DUK__EMIT_FLAG_B_IS_TARGET) {
+				/* Output shuffle needed after main operation */
+				b_out = b;
+			} else {
+				duk_small_int_t op = op_flags & 0xff;
+				if (op == DUK_OP_CALL || op == DUK_OP_NEW ||
+				    op == DUK_OP_MPUTOBJ || op == DUK_OP_MPUTARR) {
+					/* Special handling for CALL/NEW/MPUTOBJ/MPUTARR shuffling.
+					 * For each, slot B identifies the first register of a range
+					 * of registers, so normal shuffling won't work.  Instead,
+					 * an indirect version of the opcode is used.
+					 */
+					DUK_ASSERT((op_flags & DUK__EMIT_FLAG_B_IS_TARGET) == 0);
+					duk__emit_load_int32(comp_ctx, tmp, b);
+					DUK_ASSERT(DUK_OP_CALLI == DUK_OP_CALL + 1);
+					DUK_ASSERT(DUK_OP_NEWI == DUK_OP_NEW + 1);
+					DUK_ASSERT(DUK_OP_MPUTOBJI == DUK_OP_MPUTOBJ + 1);
+					DUK_ASSERT(DUK_OP_MPUTARRI == DUK_OP_MPUTARR + 1);
+					op_flags++;  /* indirect opcode follows direct */
+				} else {
+					duk__emit(comp_ctx, DUK_ENC_OP_A_BC(DUK_OP_LDREG, tmp, b));
+				}
+			}
+			b = tmp;
+		} else {
+			DUK_D(DUK_DPRINT("out of regs: 'b' (reg) needs shuffling but does not fit into BC, b: %ld", (long) b));
+			goto error_outofregs;
+		}
+	}
+
+	/* Slot C */
+
+	if (c & DUK__CONST_MARKER) {
+		DUK_ASSERT((op_flags & DUK__EMIT_FLAG_NO_SHUFFLE_C) == 0);
+		DUK_ASSERT((op_flags & DUK__EMIT_FLAG_C_IS_TARGET) == 0);
+		c = c & ~DUK__CONST_MARKER;
+		if (c <= 0xff) {
+			ins |= DUK_ENC_OP_A_B_C(0, 0, 0, 0x100);  /* const flag for C */
+		} else if (c <= DUK_BC_BC_MAX) {
+			comp_ctx->curr_func.needs_shuffle = 1;
+			tmp = comp_ctx->curr_func.shuffle3;
+			duk__emit(comp_ctx, DUK_ENC_OP_A_BC(DUK_OP_LDCONST, tmp, c));
+			c = tmp;
+		} else {
+			DUK_D(DUK_DPRINT("out of regs: 'c' (const) needs shuffling but does not fit into BC, c: %ld", (long) c));
+			goto error_outofregs;
+		}
+	} else {
+		if (c <= 0xff) {
+			;
+		} else if (op_flags & DUK__EMIT_FLAG_NO_SHUFFLE_C) {
+			if (c > DUK_BC_C_MAX) {
+				/* Note: 0xff != DUK_BC_C_MAX */
+				DUK_D(DUK_DPRINT("out of regs: 'c' (reg) needs shuffling but shuffle prohibited, c: %ld", (long) c));
+				goto error_outofregs;
+			}
+		} else if (c <= DUK_BC_BC_MAX) {
+			comp_ctx->curr_func.needs_shuffle = 1;
+			tmp = comp_ctx->curr_func.shuffle3;
+			if (op_flags & DUK__EMIT_FLAG_C_IS_TARGET) {
+				/* Output shuffle needed after main operation */
+				c_out = c;
+			} else {
+				duk_small_int_t op = op_flags & 0xff;
+				if (op == DUK_OP_EXTRA &&
+				    (a == DUK_EXTRAOP_INITGET || a == DUK_EXTRAOP_INITSET)) {
+					/* Special shuffling for INITGET/INITSET, where slot C
+					 * identifies a register pair and cannot be shuffled
+					 * normally.  Use an indirect variant instead.
+					 */
+					DUK_ASSERT((op_flags & DUK__EMIT_FLAG_C_IS_TARGET) == 0);
+					duk__emit_load_int32(comp_ctx, tmp, c);
+					DUK_ASSERT(DUK_EXTRAOP_INITGETI == DUK_EXTRAOP_INITGET + 1);
+					DUK_ASSERT(DUK_EXTRAOP_INITSETI == DUK_EXTRAOP_INITSET + 1);
+					a++;  /* indirect opcode follows direct */
+				} else {
+					duk__emit(comp_ctx, DUK_ENC_OP_A_BC(DUK_OP_LDREG, tmp, c));
+				}
+			}
+			c = tmp;
+		} else {
+			DUK_D(DUK_DPRINT("out of regs: 'c' (reg) needs shuffling but does not fit into BC, c: %ld", (long) c));
+			goto error_outofregs;
+		}
+	}
+
+	/* Main operation */
+
+	DUK_ASSERT_DISABLE(a >= DUK_BC_A_MIN);  /* unsigned */
+	DUK_ASSERT(a <= DUK_BC_A_MAX);
+	DUK_ASSERT_DISABLE(b >= DUK_BC_B_MIN);  /* unsigned */
+	DUK_ASSERT(b <= DUK_BC_B_MAX);
+	DUK_ASSERT_DISABLE(c >= DUK_BC_C_MIN);  /* unsigned */
+	DUK_ASSERT(c <= DUK_BC_C_MAX);
+
+	ins |= DUK_ENC_OP_A_B_C(op_flags & 0xff, a, b, c);
+	duk__emit(comp_ctx, ins);
+
+	/* Output shuffling: only one output register is realistically possible.
+	 * Zero is OK to check against: if the target register was zero, it is
+	 * never shuffled.
+	 */
+
+	if (a_out != 0) {
+		DUK_ASSERT(b_out == 0);
+		DUK_ASSERT(c_out == 0);
+		duk__emit(comp_ctx, DUK_ENC_OP_A_BC(DUK_OP_STREG, a, a_out));
+	} else if (b_out != 0) {
+		DUK_ASSERT(a_out == 0);
+		DUK_ASSERT(c_out == 0);
+		duk__emit(comp_ctx, DUK_ENC_OP_A_BC(DUK_OP_STREG, b, b_out));
+	} else if (c_out != 0) {
+		DUK_ASSERT(b_out == 0);
+		DUK_ASSERT(c_out == 0);
+		duk__emit(comp_ctx, DUK_ENC_OP_A_BC(DUK_OP_STREG, c, c_out));
+	}
+
+	return;
+
+ error_outofregs:
+	DUK_ERROR(comp_ctx->thr, DUK_ERR_RANGE_ERROR, DUK_STR_REG_LIMIT);
+}
+
+static void duk__emit_a_b(duk_compiler_ctx *comp_ctx, duk_small_uint_t op_flags, duk_regconst_t a, duk_regconst_t b) {
+	duk__emit_a_b_c(comp_ctx, op_flags, a, b, 0);
+}
+
+#if 0  /* unused */
+static void duk__emit_a(duk_compiler_ctx *comp_ctx, int op_flags, int a) {
+	duk__emit_a_b_c(comp_ctx, op_flags, a, 0, 0);
+}
+#endif
+
+static void duk__emit_a_bc(duk_compiler_ctx *comp_ctx, duk_small_uint_t op_flags, duk_regconst_t a, duk_regconst_t bc) {
+	duk_instr_t ins;
+	duk_int_t tmp;
+
+	/* allow caller to give a const number with the DUK__CONST_MARKER */
+	bc = bc & (~DUK__CONST_MARKER);
+
+	DUK_ASSERT_DISABLE((op_flags & 0xff) >= DUK_BC_OP_MIN);  /* unsigned */
+	DUK_ASSERT((op_flags & 0xff) <= DUK_BC_OP_MAX);
+	DUK_ASSERT_DISABLE(bc >= DUK_BC_BC_MIN);  /* unsigned */
+	DUK_ASSERT(bc <= DUK_BC_BC_MAX);
+	DUK_ASSERT((bc & DUK__CONST_MARKER) == 0);
+
+	if (bc <= DUK_BC_BC_MAX) {
+		;
+	} else {
+		/* No BC shuffling now. */
+		goto error_outofregs;
+	}
+
+	if (a <= DUK_BC_A_MAX) {
+		ins = DUK_ENC_OP_A_BC(op_flags & 0xff, a, bc);
+		duk__emit(comp_ctx, ins);
+	} else if (op_flags & DUK__EMIT_FLAG_NO_SHUFFLE_A) {
+		goto error_outofregs;
+	} else if (a <= DUK_BC_BC_MAX) {
+		comp_ctx->curr_func.needs_shuffle = 1;
+		tmp = comp_ctx->curr_func.shuffle1;
+		ins = DUK_ENC_OP_A_BC(op_flags & 0xff, tmp, bc);
+		if (op_flags & DUK__EMIT_FLAG_A_IS_SOURCE) {
+			duk__emit(comp_ctx, DUK_ENC_OP_A_BC(DUK_OP_LDREG, tmp, a));
+			duk__emit(comp_ctx, ins);
+		} else {
+			duk__emit(comp_ctx, ins);
+			duk__emit(comp_ctx, DUK_ENC_OP_A_BC(DUK_OP_STREG, tmp, a));
+		}
+	} else {
+		goto error_outofregs;
+	}
+	return;
+
+ error_outofregs:
+	DUK_ERROR(comp_ctx->thr, DUK_ERR_RANGE_ERROR, DUK_STR_REG_LIMIT);
+}
+
+static void duk__emit_abc(duk_compiler_ctx *comp_ctx, duk_small_uint_t op, duk_regconst_t abc) {
+	duk_instr_t ins;
+
+	DUK_ASSERT_DISABLE(op >= DUK_BC_OP_MIN);  /* unsigned */
+	DUK_ASSERT(op <= DUK_BC_OP_MAX);
+	DUK_ASSERT_DISABLE(abc >= DUK_BC_ABC_MIN);  /* unsigned */
+	DUK_ASSERT(abc <= DUK_BC_ABC_MAX);
+	DUK_ASSERT((abc & DUK__CONST_MARKER) == 0);
+
+	ins = DUK_ENC_OP_ABC(op, abc);
+	DUK_DDD(DUK_DDDPRINT("duk__emit_abc: 0x%08lx line=%ld pc=%ld op=%ld (%!C) abc=%ld (%!I)",
+	                     (unsigned long) ins, (long) comp_ctx->curr_token.start_line,
+	                     (long) duk__get_current_pc(comp_ctx), (long) op, (long) op,
+	                     (long) abc, (duk_instr_t) ins));
+	duk__emit(comp_ctx, ins);
+}
+
+static void duk__emit_extraop_b_c(duk_compiler_ctx *comp_ctx, duk_small_uint_t extraop_flags, duk_regconst_t b, duk_regconst_t c) {
+	DUK_ASSERT_DISABLE((extraop_flags & 0xff) >= DUK_BC_EXTRAOP_MIN);  /* unsigned */
+	DUK_ASSERT((extraop_flags & 0xff) <= DUK_BC_EXTRAOP_MAX);
+	/* Setting "no shuffle A" would be prudent but not necessary, assert covers it. */
+	duk__emit_a_b_c(comp_ctx,
+	                DUK_OP_EXTRA | (extraop_flags & ~0xff),  /* transfer flags */
+	                extraop_flags & 0xff,
+	                b,
+	                c);
+}
+
+static void duk__emit_extraop_b(duk_compiler_ctx *comp_ctx, duk_small_uint_t extraop_flags, duk_regconst_t b) {
+	DUK_ASSERT_DISABLE((extraop_flags & 0xff) >= DUK_BC_EXTRAOP_MIN);  /* unsigned */
+	DUK_ASSERT((extraop_flags & 0xff) <= DUK_BC_EXTRAOP_MAX);
+	/* Setting "no shuffle A" would be prudent but not necessary, assert covers it. */
+	duk__emit_a_b_c(comp_ctx,
+	                DUK_OP_EXTRA | (extraop_flags & ~0xff),  /* transfer flags */
+	                extraop_flags & 0xff,
+	                b,
+	                0);
+}
+
+static void duk__emit_extraop_bc(duk_compiler_ctx *comp_ctx, duk_small_uint_t extraop, duk_regconst_t bc) {
+	DUK_ASSERT_DISABLE(extraop >= DUK_BC_EXTRAOP_MIN);  /* unsigned */
+	DUK_ASSERT(extraop <= DUK_BC_EXTRAOP_MAX);
+	/* Setting "no shuffle A" would be prudent but not necessary, assert covers it. */
+	duk__emit_a_bc(comp_ctx,
+	               DUK_OP_EXTRA,
+	               extraop,
+	               bc);
+}
+
+static void duk__emit_extraop_only(duk_compiler_ctx *comp_ctx, duk_small_uint_t extraop_flags) {
+	DUK_ASSERT_DISABLE((extraop_flags & 0xff) >= DUK_BC_EXTRAOP_MIN);  /* unsigned */
+	DUK_ASSERT((extraop_flags & 0xff) <= DUK_BC_EXTRAOP_MAX);
+	/* Setting "no shuffle A" would be prudent but not necessary, assert covers it. */
+	duk__emit_a_b_c(comp_ctx,
+	                DUK_OP_EXTRA | (extraop_flags & ~0xff),  /* transfer flags */
+	                extraop_flags & 0xff,
+	                0,
+	                0);
+}
+
+static void duk__emit_load_int32(duk_compiler_ctx *comp_ctx, duk_reg_t reg, duk_int32_t val) {
+	/* XXX: Shuffling support could be implemented here so that LDINT+LDINTX
+	 * would only shuffle once (instead of twice).  The current code works
+	 * though, and has a smaller compiler footprint.
+	 */
+
+	if ((val >= (duk_int32_t) DUK_BC_BC_MIN - (duk_int32_t) DUK_BC_LDINT_BIAS) &&
+	    (val <= (duk_int32_t) DUK_BC_BC_MAX - (duk_int32_t) DUK_BC_LDINT_BIAS)) {
+		DUK_DDD(DUK_DDDPRINT("emit LDINT to reg %ld for %ld", (long) reg, (long) val));
+		duk__emit_a_bc(comp_ctx, DUK_OP_LDINT, reg, (duk_regconst_t) (val + (duk_int32_t) DUK_BC_LDINT_BIAS));
+	} else {
+		duk_int32_t hi = val >> DUK_BC_LDINTX_SHIFT;
+		duk_int32_t lo = val & ((((duk_int32_t) 1) << DUK_BC_LDINTX_SHIFT) - 1);
+		DUK_ASSERT(lo >= 0);
+		DUK_DDD(DUK_DDDPRINT("emit LDINT+LDINTX to reg %ld for %ld -> hi %ld, lo %ld",
+		                     (long) reg, (long) val, (long) hi, (long) lo));
+		duk__emit_a_bc(comp_ctx, DUK_OP_LDINT, reg, (duk_regconst_t) (hi + (duk_int32_t) DUK_BC_LDINT_BIAS));
+		duk__emit_a_bc(comp_ctx, DUK_OP_LDINTX, reg, (duk_regconst_t) lo);
+	}
+}
+
+static void duk__emit_jump(duk_compiler_ctx *comp_ctx, duk_int_t target_pc) {
+	duk_hbuffer_dynamic *h;
+	duk_int_t curr_pc;
+	duk_int_t offset;
+
+	h = comp_ctx->curr_func.h_code;
+	curr_pc = (duk_int_t) (DUK_HBUFFER_GET_SIZE(h) / sizeof(duk_compiler_instr));
+	offset = (duk_int_t) target_pc - (duk_int_t) curr_pc - 1;
+	DUK_ASSERT(offset + DUK_BC_JUMP_BIAS >= DUK_BC_ABC_MIN);
+	DUK_ASSERT(offset + DUK_BC_JUMP_BIAS <= DUK_BC_ABC_MAX);
+	duk__emit_abc(comp_ctx, DUK_OP_JUMP, (duk_regconst_t) (offset + DUK_BC_JUMP_BIAS));
+}
+
+static duk_int_t duk__emit_jump_empty(duk_compiler_ctx *comp_ctx) {
+	duk_int_t ret;
+
+	ret = duk__get_current_pc(comp_ctx);  /* useful for patching jumps later */
+	duk__emit_abc(comp_ctx, DUK_OP_JUMP, 0);
+	return ret;
+}
+
+/* Insert an empty jump in the middle of code emitted earlier.  This is
+ * currently needed for compiling for-in.
+ */
+static void duk__insert_jump_entry(duk_compiler_ctx *comp_ctx, duk_int_t jump_pc) {
+	duk_hbuffer_dynamic *h;
+#if defined(DUK_USE_PC2LINE)
+	duk_int_t line;
+#endif
+	duk_compiler_instr instr;
+	duk_size_t offset;
+
+	h = comp_ctx->curr_func.h_code;
+#if defined(DUK_USE_PC2LINE)
+	line = comp_ctx->curr_token.start_line;  /* approximation, close enough */
+#endif
+
+	instr.ins = DUK_ENC_OP_ABC(DUK_OP_JUMP, 0);
+#if defined(DUK_USE_PC2LINE)
+	instr.line = line;
+#endif
+
+	offset = jump_pc * sizeof(duk_compiler_instr);
+
+	duk_hbuffer_insert_bytes(comp_ctx->thr, h, offset, (duk_uint8_t *) &instr, sizeof(instr));
+}
+
+/* Does not assume that jump_pc contains a DUK_OP_JUMP previously; this is intentional
+ * to allow e.g. an INVALID opcode be overwritten with a JUMP (label management uses this).
+ */
+static void duk__patch_jump(duk_compiler_ctx *comp_ctx, duk_int_t jump_pc, duk_int_t target_pc) {
+	duk_compiler_instr *instr;
+	duk_int_t offset;
+
+	/* allow negative PCs, behave as a no-op */
+	if (jump_pc < 0) {
+		DUK_DDD(DUK_DDDPRINT("duk__patch_jump(): nop call, jump_pc=%ld (<0), target_pc=%ld",
+		                     (long) jump_pc, (long) target_pc));
+		return;
+	}
+	DUK_ASSERT(jump_pc >= 0);
+
+	/* XXX: range assert */
+	instr = duk__get_instr_ptr(comp_ctx, jump_pc);
+	DUK_ASSERT(instr != NULL);
+
+	/* XXX: range assert */
+	offset = target_pc - jump_pc - 1;
+
+	instr->ins = DUK_ENC_OP_ABC(DUK_OP_JUMP, offset + DUK_BC_JUMP_BIAS);
+	DUK_DDD(DUK_DDDPRINT("duk__patch_jump(): jump_pc=%ld, target_pc=%ld, offset=%ld",
+	                     (long) jump_pc, (long) target_pc, (long) offset));
+}
+
+static void duk__patch_jump_here(duk_compiler_ctx *comp_ctx, duk_int_t jump_pc) {
+	duk__patch_jump(comp_ctx, jump_pc, duk__get_current_pc(comp_ctx));
+}
+
+static void duk__patch_trycatch(duk_compiler_ctx *comp_ctx, duk_int_t trycatch_pc, duk_regconst_t reg_catch, duk_regconst_t const_varname, duk_small_uint_t flags) {
+	duk_compiler_instr *instr;
+
+	instr = duk__get_instr_ptr(comp_ctx, trycatch_pc);
+	DUK_ASSERT(instr != NULL);
+
+	instr->ins = DUK_ENC_OP_A_B_C(DUK_OP_TRYCATCH, flags, reg_catch, const_varname);
+}
+
+static void duk__emit_if_false_skip(duk_compiler_ctx *comp_ctx, duk_regconst_t regconst) {
+	duk__emit_a_b_c(comp_ctx, DUK_OP_IF, 0 /*false*/, regconst, 0);
+}
+
+static void duk__emit_if_true_skip(duk_compiler_ctx *comp_ctx, duk_regconst_t regconst) {
+	duk__emit_a_b_c(comp_ctx, DUK_OP_IF, 1 /*true*/, regconst, 0);
+}
+
+static void duk__emit_invalid(duk_compiler_ctx *comp_ctx) {
+	duk__emit_abc(comp_ctx, DUK_OP_INVALID, 0);
+}
+
+/*
+ *  Peephole optimizer for finished bytecode.
+ *
+ *  Does not remove opcodes; currently only straightens out unconditional
+ *  jump chains which are generated by several control structures.
+ */
+
+static void duk__peephole_optimize_bytecode(duk_compiler_ctx *comp_ctx) {
+	duk_hbuffer_dynamic *h;
+	duk_compiler_instr *bc;
+	duk_small_uint_t iter;
+	duk_int_t i, n;
+	duk_int_t count_opt;
+
+	h = comp_ctx->curr_func.h_code;
+	DUK_ASSERT(h != NULL);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(h));
+
+	bc = (duk_compiler_instr *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(h);
+	DUK_ASSERT(DUK_HBUFFER_GET_SIZE(h) / sizeof(duk_compiler_instr) <= DUK_INT_MAX);  /* bytecode limits */
+	n = (duk_int_t) (DUK_HBUFFER_GET_SIZE(h) / sizeof(duk_compiler_instr));
+
+	for (iter = 0; iter < DUK_COMPILER_PEEPHOLE_MAXITER; iter++) {
+		count_opt = 0;
+
+		for (i = 0; i < n; i++) {
+			duk_instr_t ins;
+			duk_int_t target_pc1;
+			duk_int_t target_pc2;
+
+			ins = bc[i].ins;
+			if (DUK_DEC_OP(ins) != DUK_OP_JUMP) {
+				continue;
+			}
+	
+			target_pc1 = i + 1 + DUK_DEC_ABC(ins) - DUK_BC_JUMP_BIAS;
+			DUK_DDD(DUK_DDDPRINT("consider jump at pc %ld; target_pc=%ld", (long) i, (long) target_pc1));
+			DUK_ASSERT(target_pc1 >= 0);
+			DUK_ASSERT(target_pc1 < n);
+
+			/* Note: if target_pc1 == i, we'll optimize a jump to itself.
+			 * This does not need to be checked for explicitly; the case
+			 * is rare and max iter breaks us out.
+			 */
+
+			ins = bc[target_pc1].ins;
+			if (DUK_DEC_OP(ins) != DUK_OP_JUMP) {
+				continue;
+			}
+
+			target_pc2 = target_pc1 + 1 + DUK_DEC_ABC(ins) - DUK_BC_JUMP_BIAS;
+
+			DUK_DDD(DUK_DDDPRINT("optimizing jump at pc %ld; old target is %ld -> new target is %ld",
+			                     (long) i, (long) target_pc1, (long) target_pc2));
+
+			bc[i].ins = DUK_ENC_OP_ABC(DUK_OP_JUMP, target_pc2 - (i + 1) + DUK_BC_JUMP_BIAS);
+
+			count_opt++;
+		}
+
+		DUK_DD(DUK_DDPRINT("optimized %ld jumps on peephole round %ld", (long) count_opt, (long) (iter + 1)));
+
+		if (count_opt == 0) {
+			break;
+		}
+	}
+}
+
+/*
+ *  Intermediate value helpers
+ */
+
+#define DUK__ISREG(comp_ctx,x)              (((x) & DUK__CONST_MARKER) == 0)
+#define DUK__ISCONST(comp_ctx,x)            (((x) & DUK__CONST_MARKER) != 0)
+#define DUK__ISTEMP(comp_ctx,x)             (DUK__ISREG((comp_ctx), (x)) && (duk_regconst_t) (x) >= (duk_regconst_t) ((comp_ctx)->curr_func.temp_first))
+#define DUK__GETTEMP(comp_ctx)              ((comp_ctx)->curr_func.temp_next)
+#define DUK__SETTEMP(comp_ctx,x)            ((comp_ctx)->curr_func.temp_next = (x))  /* dangerous: must only lower (temp_max not updated) */
+#define DUK__SETTEMP_CHECKMAX(comp_ctx,x)   duk__settemp_checkmax((comp_ctx),(x))
+#define DUK__ALLOCTEMP(comp_ctx)            duk__alloctemp((comp_ctx))
+#define DUK__ALLOCTEMPS(comp_ctx,count)     duk__alloctemps((comp_ctx),(count))
+
+/* Flags for intermediate value coercions.  A flag for using a forced reg
+ * is not needed, the forced_reg argument suffices and generates better
+ * code (it is checked as it is used).
+ */
+#define DUK__IVAL_FLAG_ALLOW_CONST          (1 << 0)  /* allow a constant to be returned */
+#define DUK__IVAL_FLAG_REQUIRE_TEMP         (1 << 1)  /* require a (mutable) temporary as a result */
+#define DUK__IVAL_FLAG_REQUIRE_SHORT        (1 << 2)  /* require a short (8-bit) reg/const which fits into bytecode B/C slot */
+
+/* XXX: some code might benefit from DUK__SETTEMP_IFTEMP(ctx,x) */
+
+static void duk__copy_ispec(duk_compiler_ctx *comp_ctx, duk_ispec *src, duk_ispec *dst) {
+	duk_context *ctx = (duk_context *) comp_ctx->thr;
+
+	dst->t = src->t;
+	dst->regconst = src->regconst;
+	duk_copy(ctx, src->valstack_idx, dst->valstack_idx);
+}
+
+static void duk__copy_ivalue(duk_compiler_ctx *comp_ctx, duk_ivalue *src, duk_ivalue *dst) {
+	duk_context *ctx = (duk_context *) comp_ctx->thr;
+
+	dst->t = src->t;
+	dst->op = src->op;
+	dst->x1.t = src->x1.t;
+	dst->x1.regconst = src->x1.regconst;
+	dst->x2.t = src->x2.t;
+	dst->x2.regconst = src->x2.regconst;
+	duk_copy(ctx, src->x1.valstack_idx, dst->x1.valstack_idx);
+	duk_copy(ctx, src->x2.valstack_idx, dst->x2.valstack_idx);
+}
+
+/* XXX: to util */
+static duk_bool_t duk__is_whole_get_int32(duk_double_t x, duk_int32_t *ival) {
+	duk_small_int_t c;
+	duk_int32_t t;
+
+	c = DUK_FPCLASSIFY(x);
+	if (c == DUK_FP_NORMAL || (c == DUK_FP_ZERO && !DUK_SIGNBIT(x))) {
+		/* Don't allow negative zero as it will cause trouble with
+		 * LDINT+LDINTX.  But positive zero is OK.
+		 */
+		t = (duk_int32_t) x;
+		if ((duk_double_t) t == x) {
+			*ival = t;
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+static duk_reg_t duk__alloctemps(duk_compiler_ctx *comp_ctx, duk_small_int_t num) {
+	duk_reg_t res;
+
+	res = comp_ctx->curr_func.temp_next;
+	comp_ctx->curr_func.temp_next += num;
+
+	if (comp_ctx->curr_func.temp_next > DUK__MAX_TEMPS) {  /* == DUK__MAX_TEMPS is OK */
+		DUK_ERROR(comp_ctx->thr, DUK_ERR_INTERNAL_ERROR, DUK_STR_TEMP_LIMIT);
+	}
+
+	/* maintain highest 'used' temporary, needed to figure out nregs of function */
+	if (comp_ctx->curr_func.temp_next > comp_ctx->curr_func.temp_max) {
+		comp_ctx->curr_func.temp_max = comp_ctx->curr_func.temp_next;
+	}
+
+	return res;
+}
+
+static duk_reg_t duk__alloctemp(duk_compiler_ctx *comp_ctx) {
+	return duk__alloctemps(comp_ctx, 1);
+}
+
+static void duk__settemp_checkmax(duk_compiler_ctx *comp_ctx, duk_reg_t temp_next) {
+	comp_ctx->curr_func.temp_next = temp_next;
+	if (temp_next > comp_ctx->curr_func.temp_max) {
+		comp_ctx->curr_func.temp_max = temp_next;
+	}
+}
+
+/* get const for value at valstack top */
+static duk_regconst_t duk__getconst(duk_compiler_ctx *comp_ctx) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_compiler_func *f = &comp_ctx->curr_func;
+	duk_tval *tv1;
+	duk_int_t i, n, n_check;
+
+	n = (duk_int_t) duk_get_length(ctx, f->consts_idx);
+
+	tv1 = duk_get_tval(ctx, -1);
+	DUK_ASSERT(tv1 != NULL);
+
+	/* Sanity workaround for handling functions with a large number of
+	 * constants at least somewhat reasonably.  Otherwise checking whether
+	 * we already have the constant would grow very slow (as it is O(N^2)).
+	 */
+	n_check = (n > DUK__GETCONST_MAX_CONSTS_CHECK ? DUK__GETCONST_MAX_CONSTS_CHECK : n);
+	for (i = 0; i < n_check; i++) {
+		duk_tval *tv2 = DUK_HOBJECT_A_GET_VALUE_PTR(f->h_consts, i);
+
+		/* Strict equality is NOT enough, because we cannot use the same
+		 * constant for e.g. +0 and -0.
+		 */
+		if (duk_js_samevalue(tv1, tv2)) {
+			DUK_DDD(DUK_DDDPRINT("reused existing constant for %!T -> const index %ld",
+			                     (duk_tval *) tv1, (long) i));
+			duk_pop(ctx);
+			return (duk_regconst_t) (i | DUK__CONST_MARKER);
+		}
+	}
+
+	if (n >= DUK__MAX_CONSTS) {
+		DUK_ERROR(comp_ctx->thr, DUK_ERR_INTERNAL_ERROR, DUK_STR_CONST_LIMIT);
+	}
+
+	DUK_DDD(DUK_DDDPRINT("allocating new constant for %!T -> const index %ld",
+	                     (duk_tval *) tv1, (long) n));
+	(void) duk_put_prop_index(ctx, f->consts_idx, n);  /* invalidates tv1, tv2 */
+	return (duk_regconst_t) (n | DUK__CONST_MARKER);
+}
+
+/* Get the value represented by an duk_ispec to a register or constant.
+ * The caller can control the result by indicating whether or not:
+ *
+ *   (1) a constant is allowed (sometimes the caller needs the result to
+ *       be in a register)
+ *
+ *   (2) a temporary register is required (usually when caller requires
+ *       the register to be safely mutable; normally either a bound
+ *       register or a temporary register are both OK)
+ *
+ *   (3) a forced register target needs to be used
+ *
+ * Bytecode may be emitted to generate the necessary value.  The return
+ * value is either a register or a constant.
+ */
+
+static duk_regconst_t duk__ispec_toregconst_raw(duk_compiler_ctx *comp_ctx,
+                                                duk_ispec *x,
+                                                duk_reg_t forced_reg,
+                                                duk_small_uint_t flags) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+
+	DUK_DDD(DUK_DDDPRINT("duk__ispec_toregconst_raw(): x={%ld:%ld:%!T}, "
+	                     "forced_reg=%ld, flags 0x%08lx: allow_const=%ld require_temp=%ld require_short=%ld",
+	                     (long) x->t,
+	                     (long) x->regconst,
+	                     (duk_tval *) duk_get_tval(ctx, x->valstack_idx),
+	                     (long) forced_reg,
+	                     (unsigned long) flags,
+	                     (long) ((flags & DUK__IVAL_FLAG_ALLOW_CONST) ? 1 : 0),
+	                     (long) ((flags & DUK__IVAL_FLAG_REQUIRE_TEMP) ? 1 : 0),
+	                     (long) ((flags & DUK__IVAL_FLAG_REQUIRE_SHORT) ? 1 : 0)));
+
+	switch (x->t) {
+	case DUK_ISPEC_VALUE: {
+		duk_tval *tv;
+
+		tv = duk_get_tval(ctx, x->valstack_idx);
+		DUK_ASSERT(tv != NULL);
+
+		switch (DUK_TVAL_GET_TAG(tv)) {
+		case DUK_TAG_UNDEFINED: {
+			/* Note: although there is no 'undefined' literal, undefined
+			 * values can occur during compilation as a result of e.g.
+			 * the 'void' operator.
+			 */
+			duk_reg_t dest = (forced_reg >= 0 ? forced_reg : DUK__ALLOCTEMP(comp_ctx));
+			duk__emit_extraop_bc(comp_ctx, DUK_EXTRAOP_LDUNDEF, (duk_regconst_t) dest);
+			return (duk_regconst_t) dest;
+		}
+		case DUK_TAG_NULL: {
+			duk_reg_t dest = (forced_reg >= 0 ? forced_reg : DUK__ALLOCTEMP(comp_ctx));
+			duk__emit_extraop_bc(comp_ctx, DUK_EXTRAOP_LDNULL, (duk_regconst_t) dest);
+			return (duk_regconst_t) dest;
+		}
+		case DUK_TAG_BOOLEAN: {
+			duk_reg_t dest = (forced_reg >= 0 ? forced_reg : DUK__ALLOCTEMP(comp_ctx));
+			duk__emit_extraop_bc(comp_ctx,
+			                     (DUK_TVAL_GET_BOOLEAN(tv) ? DUK_EXTRAOP_LDTRUE : DUK_EXTRAOP_LDFALSE),
+			                     (duk_regconst_t) dest);
+			return (duk_regconst_t) dest;
+		}
+		case DUK_TAG_POINTER: {
+			DUK_UNREACHABLE();
+			break;
+		}
+		case DUK_TAG_STRING: {
+			duk_hstring *h;
+			duk_reg_t dest;
+			duk_regconst_t constidx;
+
+			h = DUK_TVAL_GET_STRING(tv);
+			DUK_UNREF(h);
+			DUK_ASSERT(h != NULL);
+
+#if 0  /* XXX: to be implemented? */
+			/* Use special opcodes to load short strings */
+			if (DUK_HSTRING_GET_BYTELEN(h) <= 2) {
+				/* Encode into a single opcode (18 bits can encode 1-2 bytes + length indicator) */
+			} else if (DUK_HSTRING_GET_BYTELEN(h) <= 6) {
+				/* Encode into a double constant (53 bits can encode 6*8 = 48 bits + 3-bit length */
+			}
+#endif
+			duk_dup(ctx, x->valstack_idx);
+			constidx = duk__getconst(comp_ctx);
+
+			if (flags & DUK__IVAL_FLAG_ALLOW_CONST) {
+				return constidx;
+			}
+
+			dest = (forced_reg >= 0 ? forced_reg : DUK__ALLOCTEMP(comp_ctx));
+			duk__emit_a_bc(comp_ctx, DUK_OP_LDCONST, (duk_regconst_t) dest, constidx);
+			return (duk_regconst_t) dest;
+		}
+		case DUK_TAG_OBJECT: {
+			DUK_UNREACHABLE();
+			break;
+		}
+		case DUK_TAG_BUFFER: {
+			DUK_UNREACHABLE();
+			break;
+		}
+		default: {
+			/* number */
+			duk_reg_t dest;
+			duk_regconst_t constidx;
+			duk_double_t dval;
+			duk_int32_t ival;
+
+			DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
+			dval = DUK_TVAL_GET_NUMBER(tv);
+
+			if (!(flags & DUK__IVAL_FLAG_ALLOW_CONST)) {
+				/* A number can be loaded either through a constant, using
+				 * LDINT, or using LDINT+LDINTX.  LDINT is always a size win,
+				 * LDINT+LDINTX is not if the constant is used multiple times.
+				 * Currently always prefer LDINT+LDINTX over a double constant.
+				 */
+
+				if (duk__is_whole_get_int32(dval, &ival)) {
+					dest = (forced_reg >= 0 ? forced_reg : DUK__ALLOCTEMP(comp_ctx));
+					duk__emit_load_int32(comp_ctx, dest, ival);
+					return (duk_regconst_t) dest;
+				}
+			}
+
+			duk_dup(ctx, x->valstack_idx);
+			constidx = duk__getconst(comp_ctx);
+
+			if (flags & DUK__IVAL_FLAG_ALLOW_CONST) {
+				return constidx;
+			} else {
+				dest = (forced_reg >= 0 ? forced_reg : DUK__ALLOCTEMP(comp_ctx));
+				duk__emit_a_bc(comp_ctx, DUK_OP_LDCONST, (duk_regconst_t) dest, constidx);
+				return (duk_regconst_t) dest;
+			}
+		}
+		}  /* end switch */
+	}
+	case DUK_ISPEC_REGCONST: {
+		if ((x->regconst & DUK__CONST_MARKER) && !(flags & DUK__IVAL_FLAG_ALLOW_CONST)) {
+			duk_reg_t dest = (forced_reg >= 0 ? forced_reg : DUK__ALLOCTEMP(comp_ctx));
+			duk__emit_a_bc(comp_ctx, DUK_OP_LDCONST, (duk_regconst_t) dest, x->regconst);
+			return (duk_regconst_t) dest;
+		} else {
+			if (forced_reg >= 0) {
+				if (x->regconst != (duk_regconst_t) forced_reg) {
+					duk__emit_a_bc(comp_ctx, DUK_OP_LDREG, forced_reg, x->regconst);
+				}
+				return (duk_regconst_t) forced_reg;
+			} else {
+				if ((flags & DUK__IVAL_FLAG_REQUIRE_TEMP) && !DUK__ISTEMP(comp_ctx, x->regconst)) {
+					duk_reg_t dest = DUK__ALLOCTEMP(comp_ctx);
+					duk__emit_a_bc(comp_ctx, DUK_OP_LDREG, (duk_regconst_t) dest, x->regconst);
+					return (duk_regconst_t) dest;
+				} else {
+					return x->regconst;
+				}
+			}
+		}
+	}
+	default: {
+		break;
+	}
+	}
+
+	DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, DUK_STR_INTERNAL_ERROR);
+	return 0;
+}
+
+static void duk__ispec_toforcedreg(duk_compiler_ctx *comp_ctx, duk_ispec *x, duk_reg_t forced_reg) {
+	DUK_ASSERT(forced_reg >= 0);
+	(void) duk__ispec_toregconst_raw(comp_ctx, x, forced_reg, 0 /*flags*/);
+}
+
+/* Coerce an duk_ivalue to a 'plain' value by generating the necessary
+ * arithmetic operations, property access, or variable access bytecode.
+ * The duk_ivalue argument ('x') is converted into a plain value as a
+ * side effect.
+ */
+static void duk__ivalue_toplain_raw(duk_compiler_ctx *comp_ctx, duk_ivalue *x, duk_reg_t forced_reg) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+
+	DUK_DDD(DUK_DDDPRINT("duk__ivalue_toplain_raw(): x={t=%ld,op=%ld,x1={%ld:%ld:%!T},x2={%ld:%ld:%!T}}, "
+	                     "forced_reg=%ld",
+	                     (long) x->t, (long) x->op,
+	                     (long) x->x1.t, (long) x->x1.regconst,
+	                     (duk_tval *) duk_get_tval(ctx, x->x1.valstack_idx),
+	                     (long) x->x2.t, (long) x->x2.regconst,
+	                     (duk_tval *) duk_get_tval(ctx, x->x2.valstack_idx),
+	                     (long) forced_reg));
+
+	switch (x->t) {
+	case DUK_IVAL_PLAIN: {
+		return;
+	}
+	/* XXX: support unary arithmetic ivalues (useful?) */
+	case DUK_IVAL_ARITH: {
+		duk_regconst_t arg1;
+		duk_regconst_t arg2;
+		duk_reg_t dest;
+		duk_tval *tv1;
+		duk_tval *tv2;
+
+		DUK_DDD(DUK_DDDPRINT("arith to plain conversion"));
+
+		/* inline arithmetic check for constant values */
+		/* XXX: use the exactly same arithmetic function here as in executor */
+		if (x->x1.t == DUK_ISPEC_VALUE && x->x2.t == DUK_ISPEC_VALUE) {
+			tv1 = duk_get_tval(ctx, x->x1.valstack_idx);
+			tv2 = duk_get_tval(ctx, x->x2.valstack_idx);
+			DUK_ASSERT(tv1 != NULL);
+			DUK_ASSERT(tv2 != NULL);
+
+			DUK_DDD(DUK_DDDPRINT("arith: tv1=%!T, tv2=%!T",
+			                     (duk_tval *) tv1,
+			                     (duk_tval *) tv2));
+
+			if (DUK_TVAL_IS_NUMBER(tv1) && DUK_TVAL_IS_NUMBER(tv2)) {
+				duk_double_t d1 = DUK_TVAL_GET_NUMBER(tv1);
+				duk_double_t d2 = DUK_TVAL_GET_NUMBER(tv2);
+				duk_double_t d3;
+				duk_bool_t accept = 1;
+
+				DUK_DDD(DUK_DDDPRINT("arith inline check: d1=%lf, d2=%lf, op=%ld",
+				                     (double) d1, (double) d2, (long) x->op));
+				switch (x->op) {
+				case DUK_OP_ADD:	d3 = d1 + d2; break;
+				case DUK_OP_SUB:	d3 = d1 - d2; break;
+				case DUK_OP_MUL:	d3 = d1 * d2; break;
+				case DUK_OP_DIV:	d3 = d1 / d2; break;
+				default:		accept = 0; break;
+				}
+
+				if (accept) {
+					duk_double_union du;
+					du.d = d3;
+					DUK_DBLUNION_NORMALIZE_NAN_CHECK(&du);
+					d3 = du.d;
+
+					x->t = DUK_IVAL_PLAIN;
+					DUK_ASSERT(x->x1.t == DUK_ISPEC_VALUE);
+					DUK_TVAL_SET_NUMBER(tv1, d3);  /* old value is number: no refcount */
+					return;
+				}
+			} else if (x->op == DUK_OP_ADD && DUK_TVAL_IS_STRING(tv1) && DUK_TVAL_IS_STRING(tv2)) {
+				/* inline string concatenation */
+				duk_dup(ctx, x->x1.valstack_idx);
+				duk_dup(ctx, x->x2.valstack_idx);
+				duk_concat(ctx, 2);
+				duk_replace(ctx, x->x1.valstack_idx);
+				x->t = DUK_IVAL_PLAIN;
+				DUK_ASSERT(x->x1.t == DUK_ISPEC_VALUE);
+				return;
+			}
+		}
+
+		arg1 = duk__ispec_toregconst_raw(comp_ctx, &x->x1, -1, DUK__IVAL_FLAG_ALLOW_CONST | DUK__IVAL_FLAG_REQUIRE_SHORT /*flags*/);
+		arg2 = duk__ispec_toregconst_raw(comp_ctx, &x->x2, -1, DUK__IVAL_FLAG_ALLOW_CONST | DUK__IVAL_FLAG_REQUIRE_SHORT /*flags*/);
+
+		/* If forced reg, use it as destination.  Otherwise try to
+		 * use either coerced ispec if it is a temporary.
+		 */
+		if (forced_reg >= 0) {
+			dest = forced_reg;
+		} else if (DUK__ISTEMP(comp_ctx, arg1)) {
+			dest = (duk_reg_t) arg1;
+		} else if (DUK__ISTEMP(comp_ctx, arg2)) {
+			dest = (duk_reg_t) arg2;
+		} else {
+			dest = DUK__ALLOCTEMP(comp_ctx);
+		}
+
+		duk__emit_a_b_c(comp_ctx, x->op, (duk_regconst_t) dest, arg1, arg2);
+
+		x->t = DUK_IVAL_PLAIN;
+		x->x1.t = DUK_ISPEC_REGCONST;
+		x->x1.regconst = (duk_regconst_t) dest;
+		return;
+	}
+	case DUK_IVAL_PROP: {
+		/* XXX: very similar to DUK_IVAL_ARITH - merge? */
+		duk_regconst_t arg1;
+		duk_regconst_t arg2;
+		duk_reg_t dest;
+
+		/* need a short reg/const, does not have to be a mutable temp */
+		arg1 = duk__ispec_toregconst_raw(comp_ctx, &x->x1, -1, DUK__IVAL_FLAG_ALLOW_CONST | DUK__IVAL_FLAG_REQUIRE_SHORT /*flags*/);
+		arg2 = duk__ispec_toregconst_raw(comp_ctx, &x->x2, -1, DUK__IVAL_FLAG_ALLOW_CONST | DUK__IVAL_FLAG_REQUIRE_SHORT /*flags*/);
+
+		if (forced_reg >= 0) {
+			dest = forced_reg;
+		} else if (DUK__ISTEMP(comp_ctx, arg1)) {
+			/* FIXME: arg1 being used as a mutable temp? */
+			dest = (duk_reg_t) arg1;
+		} else if (DUK__ISTEMP(comp_ctx, arg2)) {
+			/* FIXME: arg1 being used as a mutable temp? */
+			dest = (duk_reg_t) arg2;
+		} else {
+			dest = DUK__ALLOCTEMP(comp_ctx);
+		}
+
+		duk__emit_a_b_c(comp_ctx, DUK_OP_GETPROP, (duk_regconst_t) dest, arg1, arg2);
+
+		x->t = DUK_IVAL_PLAIN;
+		x->x1.t = DUK_ISPEC_REGCONST;
+		x->x1.regconst = (duk_regconst_t) dest;
+		return;
+	}
+	case DUK_IVAL_VAR: {
+		/* x1 must be a string */
+		duk_reg_t dest;
+		duk_reg_t reg_varbind;
+		duk_regconst_t rc_varname;
+
+		DUK_ASSERT(x->x1.t == DUK_ISPEC_VALUE);
+
+		duk_dup(ctx, x->x1.valstack_idx);
+		if (duk__lookup_lhs(comp_ctx, &reg_varbind, &rc_varname)) {
+			x->t = DUK_IVAL_PLAIN;
+			x->x1.t = DUK_ISPEC_REGCONST;
+			x->x1.regconst = (duk_regconst_t) reg_varbind;
+		} else {
+			dest = (forced_reg >= 0 ? forced_reg : DUK__ALLOCTEMP(comp_ctx));
+			duk__emit_a_bc(comp_ctx, DUK_OP_GETVAR, (duk_regconst_t) dest, rc_varname);
+			x->t = DUK_IVAL_PLAIN;
+			x->x1.t = DUK_ISPEC_REGCONST;
+			x->x1.regconst = (duk_regconst_t) dest;
+		}
+		return;
+	}
+	case DUK_IVAL_NONE:
+	default: {
+		break;
+	}
+	}
+
+	DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, DUK_STR_INTERNAL_ERROR);
+	return;
+}
+
+/* evaluate to plain value, no forced register (temp/bound reg both ok) */
+static void duk__ivalue_toplain(duk_compiler_ctx *comp_ctx, duk_ivalue *x) {
+	duk__ivalue_toplain_raw(comp_ctx, x, -1 /*forced_reg*/);
+}
+
+/* evaluate to final form (e.g. coerce GETPROP to code), throw away temp */
+static void duk__ivalue_toplain_ignore(duk_compiler_ctx *comp_ctx, duk_ivalue *x) {
+	duk_reg_t temp;
+
+	/* If duk__ivalue_toplain_raw() allocates a temp, forget it and
+	 * restore next temp state.
+	 */
+	temp = DUK__GETTEMP(comp_ctx);
+	duk__ivalue_toplain_raw(comp_ctx, x, -1 /*forced_reg*/);
+	DUK__SETTEMP(comp_ctx, temp);
+}
+
+/* Coerce an duk_ivalue to a register or constant; result register may
+ * be a temp or a bound register.
+ *
+ * The duk_ivalue argument ('x') is converted into a regconst as a
+ * side effect.
+ */
+static duk_regconst_t duk__ivalue_toregconst_raw(duk_compiler_ctx *comp_ctx,
+                                                 duk_ivalue *x,
+                                                 duk_reg_t forced_reg,
+                                                 duk_small_uint_t flags) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_regconst_t reg;
+	DUK_UNREF(thr);
+	DUK_UNREF(ctx);
+
+	DUK_DDD(DUK_DDDPRINT("duk__ivalue_toregconst_raw(): x={t=%ld,op=%ld,x1={%ld:%ld:%!T},x2={%ld:%ld:%!T}}, "
+	                     "forced_reg=%ld, flags 0x%08lx: allow_const=%ld require_temp=%ld require_short=%ld",
+	                     (long) x->t, (long) x->op,
+	                     (long) x->x1.t, (long) x->x1.regconst,
+	                     (duk_tval *) duk_get_tval(ctx, x->x1.valstack_idx),
+	                     (long) x->x2.t, (long) x->x2.regconst,
+	                     (duk_tval *) duk_get_tval(ctx, x->x2.valstack_idx),
+	                     (long) forced_reg,
+	                     (unsigned long) flags,
+	                     (long) ((flags & DUK__IVAL_FLAG_ALLOW_CONST) ? 1 : 0),
+	                     (long) ((flags & DUK__IVAL_FLAG_REQUIRE_TEMP) ? 1 : 0),
+	                     (long) ((flags & DUK__IVAL_FLAG_REQUIRE_SHORT) ? 1 : 0)));
+
+	/* first coerce to a plain value */
+	duk__ivalue_toplain_raw(comp_ctx, x, forced_reg);
+	DUK_ASSERT(x->t == DUK_IVAL_PLAIN);
+
+	/* then to a register */
+	reg = duk__ispec_toregconst_raw(comp_ctx, &x->x1, forced_reg, flags);
+	x->x1.t = DUK_ISPEC_REGCONST;
+	x->x1.regconst = reg;
+
+	return reg;
+}
+
+static duk_reg_t duk__ivalue_toreg(duk_compiler_ctx *comp_ctx, duk_ivalue *x) {
+	return duk__ivalue_toregconst_raw(comp_ctx, x, -1, 0 /*flags*/);
+}
+
+#if 0  /* unused */
+static duk_reg_t duk__ivalue_totempreg(duk_compiler_ctx *comp_ctx, duk_ivalue *x) {
+	return duk__ivalue_toregconst_raw(comp_ctx, x, -1, DUK__IVAL_FLAG_REQUIRE_TEMP /*flags*/);
+}
+#endif
+
+static void duk__ivalue_toforcedreg(duk_compiler_ctx *comp_ctx, duk_ivalue *x, duk_int_t forced_reg) {
+	DUK_ASSERT(forced_reg >= 0);
+	(void) duk__ivalue_toregconst_raw(comp_ctx, x, forced_reg, 0 /*flags*/);
+}
+
+static duk_regconst_t duk__ivalue_toregconst(duk_compiler_ctx *comp_ctx, duk_ivalue *x) {
+	return duk__ivalue_toregconst_raw(comp_ctx, x, -1, DUK__IVAL_FLAG_ALLOW_CONST /*flags*/);
+}
+
+/* The issues below can be solved with better flags */
+
+/* XXX: many operations actually want toforcedtemp() -- brand new temp? */
+/* XXX: need a toplain_ignore() which will only coerce a value to a temp
+ * register if it might have a side effect.  Side-effect free values do not
+ * need to be coerced.
+ */
+
+/*
+ *  Identifier handling
+ */
+
+static duk_reg_t duk__lookup_active_register_binding(duk_compiler_ctx *comp_ctx) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_hstring *h_varname;
+	duk_reg_t ret;
+
+	DUK_DDD(DUK_DDDPRINT("resolving identifier reference to '%!T'",
+	                     (duk_tval *) duk_get_tval(ctx, -1)));
+
+	/*
+	 *  Special name handling
+	 */
+
+	h_varname = duk_get_hstring(ctx, -1);
+	DUK_ASSERT(h_varname != NULL);
+
+	if (h_varname == DUK_HTHREAD_STRING_LC_ARGUMENTS(thr)) {
+		DUK_DDD(DUK_DDDPRINT("flagging function as accessing 'arguments'"));
+		comp_ctx->curr_func.id_access_arguments = 1;
+	}
+
+	/*
+	 *  Inside one or more 'with' statements fall back to slow path always.
+	 *  (See e.g. test-stmt-with.js.)
+	 */
+
+	if (comp_ctx->curr_func.with_depth > 0) {
+		DUK_DDD(DUK_DDDPRINT("identifier lookup inside a 'with' -> fall back to slow path"));
+		goto slow_path;
+	}
+
+	/*
+	 *  Any catch bindings ("catch (e)") also affect identifier binding.
+	 *
+	 *  Currently, the varmap is modified for the duration of the catch
+	 *  clause to ensure any identifier accesses with the catch variable
+	 *  name will use slow path.
+	 */
+
+	duk_get_prop(ctx, comp_ctx->curr_func.varmap_idx);
+	if (duk_is_number(ctx, -1)) {
+		ret = duk_to_int(ctx, -1);
+		duk_pop(ctx);
+	} else {
+		duk_pop(ctx);
+		goto slow_path;
+	}
+
+	DUK_DDD(DUK_DDDPRINT("identifier lookup -> reg %ld", (long) ret));
+	return ret;
+
+ slow_path:
+	DUK_DDD(DUK_DDDPRINT("identifier lookup -> slow path"));
+
+	comp_ctx->curr_func.id_access_slow = 1;
+	return (duk_reg_t) -1;
+}
+
+/* Lookup an identifier name in the current varmap, indicating whether the
+ * identifier is register-bound and if not, allocating a constant for the
+ * identifier name.  Returns 1 if register-bound, 0 otherwise.  Caller can
+ * also check (out_reg_varbind >= 0) to check whether or not identifier is
+ * register bound.  The caller must NOT use out_rc_varname at all unless
+ * return code is 0 or out_reg_varbind is < 0; this is becuase out_rc_varname
+ * is unsigned and doesn't have a "unused" / none value.
+ */
+static duk_bool_t duk__lookup_lhs(duk_compiler_ctx *comp_ctx, duk_reg_t *out_reg_varbind, duk_regconst_t *out_rc_varname) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_reg_t reg_varbind;
+	duk_regconst_t rc_varname;
+
+	/* [ ... varname ] */
+
+	duk_dup_top(ctx);
+	reg_varbind = duk__lookup_active_register_binding(comp_ctx);
+
+	if (reg_varbind >= 0) {
+		*out_reg_varbind = reg_varbind;
+		*out_rc_varname = 0;  /* duk_regconst_t is unsigned, so use 0 as dummy value (ignored by caller) */
+		duk_pop(ctx);
+		return 1;
+	} else {
+		rc_varname = duk__getconst(comp_ctx);
+		*out_reg_varbind = -1;
+		*out_rc_varname = rc_varname;
+		return 0;
+	}
+}
+
+/*
+ *  Label handling
+ *
+ *  Labels are initially added with flags prohibiting both break and continue.
+ *  When the statement type is finally uncovered (after potentially multiple
+ *  labels), all the labels are updated to allow/prohibit break and continue.
+ */
+
+static void duk__add_label(duk_compiler_ctx *comp_ctx, duk_hstring *h_label, duk_int_t pc_label, duk_int_t label_id) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_size_t n;
+	duk_size_t new_size;
+	duk_uint8_t *p;
+	duk_labelinfo *li_start, *li;
+
+	/* Duplicate (shadowing) labels are not allowed, except for the empty
+	 * labels (which are used as default labels for switch and iteration
+	 * statements).
+	 *
+	 * We could also allow shadowing of non-empty pending labels without any
+	 * other issues than breaking the required label shadowing requirements
+	 * of the E5 specification, see Section 12.12.
+	 */
+
+	p = (duk_uint8_t *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(comp_ctx->curr_func.h_labelinfos);
+	li_start = (duk_labelinfo *) p;
+	li = (duk_labelinfo *) (p + DUK_HBUFFER_GET_SIZE(comp_ctx->curr_func.h_labelinfos));
+	n = (duk_size_t) (li - li_start);
+
+	while (li > li_start) {
+		li--;
+
+		if (li->h_label == h_label && h_label != DUK_HTHREAD_STRING_EMPTY_STRING(thr)) {
+			DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_DUPLICATE_LABEL);
+		}
+	}
+
+	duk_push_hstring(ctx, h_label);
+	DUK_ASSERT(n <= DUK_UARRIDX_MAX);  /* label limits */
+	(void) duk_put_prop_index(ctx, comp_ctx->curr_func.labelnames_idx, (duk_uarridx_t) n);
+
+	new_size = (n + 1) * sizeof(duk_labelinfo);
+	duk_hbuffer_resize(thr, comp_ctx->curr_func.h_labelinfos, new_size, new_size);
+	/* XXX: spare handling, slow now */
+
+	/* relookup after possible realloc */
+	p = (duk_uint8_t *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(comp_ctx->curr_func.h_labelinfos);
+	li_start = (duk_labelinfo *) p;
+	DUK_UNREF(li_start);  /* silence scan-build warning */
+	li = (duk_labelinfo *) (p + DUK_HBUFFER_GET_SIZE(comp_ctx->curr_func.h_labelinfos));
+	li--;
+
+	/* Labels need to be recorded as pending before we know whether they will be
+	 * actually be used as part of an iteration statement or a switch statement.
+	 * The flags to allow break/continue are updated when we figure out the
+	 * statement type.
+	 */
+
+	li->flags = 0;
+	li->label_id = label_id;
+	li->h_label = h_label;
+	li->catch_depth = comp_ctx->curr_func.catch_depth;   /* catch depth from current func */
+	li->pc_label = pc_label;
+
+	DUK_DDD(DUK_DDDPRINT("registered label: flags=0x%08lx, id=%ld, name=%!O, catch_depth=%ld, pc_label=%ld",
+	                     (unsigned long) li->flags, (long) li->label_id, (duk_heaphdr *) li->h_label,
+	                     (long) li->catch_depth, (long) li->pc_label));
+}
+
+/* Update all labels with matching label_id. */
+static void duk__update_label_flags(duk_compiler_ctx *comp_ctx, duk_int_t label_id, duk_small_uint_t flags) {
+	duk_uint8_t *p;
+	duk_labelinfo *li_start, *li;
+
+	p = (duk_uint8_t *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(comp_ctx->curr_func.h_labelinfos);
+	li_start = (duk_labelinfo *) p;
+	li = (duk_labelinfo *) (p + DUK_HBUFFER_GET_SIZE(comp_ctx->curr_func.h_labelinfos));
+
+	/* Match labels starting from latest; once label_id no longer matches, we can
+	 * safely exit without checking the rest of the labels (only the topmost labels
+	 * are ever updated).
+	 */
+	while (li > li_start) {
+		li--;
+
+		if (li->label_id != label_id) {
+			break;
+		}
+
+		DUK_DDD(DUK_DDDPRINT("updating (overwriting) label flags for li=%p, label_id=%ld, flags=%ld",
+		                     (void *) li, (long) label_id, (long) flags));
+
+		li->flags = flags;
+	}
+}
+
+/* Lookup active label information.  Break/continue distinction is necessary to handle switch
+ * statement related labels correctly: a switch will only catch a 'break', not a 'continue'.
+ *
+ * An explicit label cannot appear multiple times in the active set, but empty labels (unlabelled
+ * iteration and switch statements) can.  A break will match the closest unlabelled or labelled
+ * statement.  A continue will match the closest unlabelled or labelled iteration statement.  It is
+ * a syntax error if a continue matches a labelled switch statement; because an explicit label cannot
+ * be duplicated, the continue cannot match any valid label outside the switch.
+ *
+ * A side effect of these rules is that a LABEL statement related to a switch should never actually
+ * catch a continue abrupt completion at run-time.  Hence an INVALID opcode can be placed in the
+ * continue slot of the switch's LABEL statement.
+ */
+
+/* XXX: awkward, especially the bunch of separate output values -> output struct? */
+static void duk__lookup_active_label(duk_compiler_ctx *comp_ctx, duk_hstring *h_label, duk_bool_t is_break, duk_int_t *out_label_id, duk_int_t *out_label_catch_depth, duk_int_t *out_label_pc, duk_bool_t *out_is_closest) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_uint8_t *p;
+	duk_labelinfo *li_start, *li_end, *li;
+	duk_bool_t match = 0;
+
+	DUK_DDD(DUK_DDDPRINT("looking up active label: label='%!O', is_break=%ld",
+	                     (duk_heaphdr *) h_label, (long) is_break));
+
+	DUK_UNREF(ctx);
+
+	p = (duk_uint8_t *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(comp_ctx->curr_func.h_labelinfos);
+	li_start = (duk_labelinfo *) p;
+	li_end = (duk_labelinfo *) (p + DUK_HBUFFER_GET_SIZE(comp_ctx->curr_func.h_labelinfos));
+	li = li_end;
+
+	/* Match labels starting from latest label because there can be duplicate empty
+	 * labels in the label set.
+	 */
+	while (li > li_start) {
+		li--;
+
+		if (li->h_label != h_label) {
+			DUK_DDD(DUK_DDDPRINT("labelinfo[%ld] ->'%!O' != %!O",
+			                     (long) (li - li_start),
+			                     (duk_heaphdr *) li->h_label,
+			                     (duk_heaphdr *) h_label));
+			continue;
+		}
+
+		DUK_DDD(DUK_DDDPRINT("labelinfo[%ld] -> '%!O' label name matches (still need to check type)",
+		                     (long) (li - li_start), (duk_heaphdr *) h_label));
+
+		/* currently all labels accept a break, so no explicit check for it now */
+		DUK_ASSERT(li->flags & DUK_LABEL_FLAG_ALLOW_BREAK);
+
+		if (is_break) {
+			/* break matches always */
+			match = 1;
+			break;
+		} else if (li->flags & DUK_LABEL_FLAG_ALLOW_CONTINUE) {
+			/* iteration statements allow continue */
+			match = 1;
+			break;
+		} else {
+			/* continue matched this label -- we can only continue if this is the empty
+			 * label, for which duplication is allowed, and thus there is hope of
+			 * finding a match deeper in the label stack.
+			 */
+			if (h_label != DUK_HTHREAD_STRING_EMPTY_STRING(thr)) {
+				DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_INVALID_LABEL);
+			} else {
+				DUK_DDD(DUK_DDDPRINT("continue matched an empty label which does not "
+				                     "allow a continue -> continue lookup deeper in label stack"));
+			}
+		}
+	}
+	/* XXX: match flag is awkward, rework */
+	if (!match) {
+		DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_INVALID_LABEL);
+	}
+
+	DUK_DDD(DUK_DDDPRINT("label match: %!O -> label_id %ld, catch_depth=%ld, pc_label=%ld",
+	                     (duk_heaphdr *) h_label, (long) li->label_id,
+	                     (long) li->catch_depth, (long) li->pc_label));
+
+	*out_label_id = li->label_id;
+	*out_label_catch_depth = li->catch_depth;
+	*out_label_pc = li->pc_label;
+	*out_is_closest = (li == li_end - 1);
+}
+
+static void duk__reset_labels_to_length(duk_compiler_ctx *comp_ctx, duk_int_t len) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_size_t new_size;
+
+	/* XXX: duk_set_length */
+	new_size = sizeof(duk_labelinfo) * (duk_size_t) len;
+	duk_push_int(ctx, len);
+	duk_put_prop_stridx(ctx, comp_ctx->curr_func.labelnames_idx, DUK_STRIDX_LENGTH);
+	duk_hbuffer_resize(thr, comp_ctx->curr_func.h_labelinfos, new_size, new_size);  /* XXX: spare handling */
+}
+
+/*
+ *  Expression parsing: duk__expr_nud(), duk__expr_led(), duk__expr_lbp(), and helpers.
+ *
+ *  - duk__expr_nud(): ("null denotation"): process prev_token as a "start" of an expression (e.g. literal)
+ *  - duk__expr_led(): ("left denotation"): process prev_token in the "middle" of an expression (e.g. operator)
+ *  - duk__expr_lbp(): ("left-binding power"): return left-binding power of curr_token
+ */
+
+/* object literal key tracking flags */
+#define DUK__OBJ_LIT_KEY_PLAIN  (1 << 0)  /* key encountered as a plain property */
+#define DUK__OBJ_LIT_KEY_GET    (1 << 1)  /* key encountered as a getter */
+#define DUK__OBJ_LIT_KEY_SET    (1 << 2)  /* key encountered as a setter */
+
+static void duk__nud_array_literal(duk_compiler_ctx *comp_ctx, duk_ivalue *res) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_reg_t reg_obj;                 /* result reg */
+	duk_reg_t reg_temp;                /* temp reg */
+	duk_reg_t temp_start;              /* temp reg value for start of loop */
+	duk_small_uint_t max_init_values;  /* max # of values initialized in one MPUTARR set */
+	duk_small_uint_t num_values;       /* number of values in current MPUTARR set */
+	duk_uarridx_t curr_idx;            /* current (next) array index */
+	duk_uarridx_t start_idx;           /* start array index of current MPUTARR set */
+	duk_uarridx_t init_idx;            /* last array index explicitly initialized, +1 */
+	duk_bool_t require_comma;          /* next loop requires a comma */
+
+	/* DUK_TOK_LBRACKET already eaten, current token is right after that */
+	DUK_ASSERT(comp_ctx->prev_token.t == DUK_TOK_LBRACKET);
+
+	max_init_values = DUK__MAX_ARRAY_INIT_VALUES;  /* XXX: depend on available temps? */
+
+	reg_obj = DUK__ALLOCTEMP(comp_ctx);
+	duk__emit_extraop_b_c(comp_ctx,
+	                      DUK_EXTRAOP_NEWARR | DUK__EMIT_FLAG_B_IS_TARGET, 
+	                      reg_obj,
+	                      0);  /* XXX: patch initial size afterwards? */
+ 	temp_start = DUK__GETTEMP(comp_ctx);
+
+	/*
+	 *  Emit initializers in sets of maximum max_init_values.
+	 *  Corner cases such as single value initializers do not have
+	 *  special handling now.
+	 *
+	 *  Elided elements must not be emitted as 'undefined' values,
+	 *  because such values would be enumerable (which is incorrect).
+	 *  Also note that trailing elisions must be reflected in the
+	 *  length of the final array but cause no elements to be actually
+	 *  inserted.
+	 */
+
+	curr_idx = 0;
+	init_idx = 0;         /* tracks maximum initialized index + 1 */
+	start_idx = 0;
+	require_comma = 0;
+
+	for (;;) {
+		num_values = 0;
+		DUK__SETTEMP(comp_ctx, temp_start);
+
+		if (comp_ctx->curr_token.t == DUK_TOK_RBRACKET) {
+			break;
+		}
+
+		for (;;) {
+			if (comp_ctx->curr_token.t == DUK_TOK_RBRACKET) {
+				/* the outer loop will recheck and exit */
+				break;
+			}
+
+			/* comma check */
+			if (require_comma) {
+				if (comp_ctx->curr_token.t == DUK_TOK_COMMA) {
+					/* comma after a value, expected */
+					duk__advance(comp_ctx);
+					require_comma = 0;
+					continue;
+				} else {
+					goto syntax_error;
+				}
+			} else {
+				if (comp_ctx->curr_token.t == DUK_TOK_COMMA) {
+					/* elision - flush */
+					curr_idx++;
+					duk__advance(comp_ctx);
+					/* if num_values > 0, MPUTARR emitted by outer loop after break */
+					break;
+				}
+			}
+			/* else an array initializer element */
+
+			/* initial index */
+			if (num_values == 0) {
+				start_idx = curr_idx;
+				reg_temp = DUK__ALLOCTEMP(comp_ctx);
+				duk__emit_load_int32(comp_ctx, reg_temp, (duk_int32_t) start_idx);
+			}
+
+			reg_temp = DUK__ALLOCTEMP(comp_ctx);   /* alloc temp just in case, to update max temp */
+			DUK__SETTEMP(comp_ctx, reg_temp);
+			duk__expr_toforcedreg(comp_ctx, res, DUK__BP_COMMA /*rbp_flags*/, reg_temp /*forced_reg*/);
+			DUK__SETTEMP(comp_ctx, reg_temp + 1);
+
+			num_values++;
+			curr_idx++;
+			require_comma = 1;
+
+			if (num_values >= max_init_values) {
+				/* MPUTARR emitted by outer loop */
+				break;
+			}
+		}
+
+		if (num_values > 0) {
+			/* - A is a source register (it's not a write target, but used
+			 *   to identify the target object) but can be shuffled.
+			 * - B cannot be shuffled normally because it identifies a range
+			 *   of registers, the emitter has special handling for this
+			 *   (the "no shuffle" flag must not be set).
+			 * - C is a non-register number and cannot be shuffled, but
+			 *   never needs to be.
+			 */
+			duk__emit_a_b_c(comp_ctx,
+			                DUK_OP_MPUTARR |
+			                    DUK__EMIT_FLAG_NO_SHUFFLE_C |
+			                    DUK__EMIT_FLAG_A_IS_SOURCE,
+			                (duk_regconst_t) reg_obj,
+			                (duk_regconst_t) temp_start,
+			                (duk_regconst_t) num_values);
+			init_idx = start_idx + num_values;
+
+			/* num_values and temp_start reset at top of outer loop */
+		}	
+	}
+
+	DUK_ASSERT(comp_ctx->curr_token.t == DUK_TOK_RBRACKET);
+	duk__advance(comp_ctx);
+
+	DUK_DDD(DUK_DDDPRINT("array literal done, curridx=%ld, initidx=%ld",
+	                     (long) curr_idx, (long) init_idx));
+
+	/* trailing elisions? */
+	if (curr_idx > init_idx) {
+		/* yes, must set array length explicitly */
+		DUK_DDD(DUK_DDDPRINT("array literal has trailing elisions which affect its length"));
+		reg_temp = DUK__ALLOCTEMP(comp_ctx);
+		duk__emit_load_int32(comp_ctx, reg_temp, (duk_int_t) curr_idx);
+		duk__emit_extraop_b_c(comp_ctx,
+		                      DUK_EXTRAOP_SETALEN,
+		                      (duk_regconst_t) reg_obj,
+		                      (duk_regconst_t) reg_temp);
+	}
+
+	DUK__SETTEMP(comp_ctx, temp_start);
+
+	res->t = DUK_IVAL_PLAIN;
+	res->x1.t = DUK_ISPEC_REGCONST;
+	res->x1.regconst = (duk_regconst_t) reg_obj;
+	return;
+
+ syntax_error:
+	DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_INVALID_ARRAY_LITERAL);
+}
+
+/* duplicate/invalid key checks; returns 1 if syntax error */
+static duk_bool_t duk__nud_object_literal_key_check(duk_compiler_ctx *comp_ctx, duk_small_uint_t new_key_flags) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_small_uint_t key_flags;
+
+	/* [ ... key_obj key ] */
+
+	DUK_ASSERT(duk_is_string(ctx, -1));
+
+	/*
+	 *  'key_obj' tracks keys encountered so far by associating an
+	 *  integer with flags with already encountered keys.  The checks
+	 *  below implement E5 Section 11.1.5, step 4 for production:
+	 *
+	 *    PropertyNameAndValueList: PropertyNameAndValueList , PropertyAssignment
+	 */
+
+	duk_dup(ctx, -1);       /* [ ... key_obj key key ] */
+	duk_get_prop(ctx, -3);  /* [ ... key_obj key val ] */
+	key_flags = duk_to_int(ctx, -1);
+	duk_pop(ctx);           /* [ ... key_obj key ] */
+
+	if (new_key_flags & DUK__OBJ_LIT_KEY_PLAIN) {
+		if ((key_flags & DUK__OBJ_LIT_KEY_PLAIN) && comp_ctx->curr_func.is_strict) {
+			/* step 4.a */
+			DUK_DDD(DUK_DDDPRINT("duplicate key: plain key appears twice in strict mode"));
+			return 1;
+		}
+		if (key_flags & (DUK__OBJ_LIT_KEY_GET | DUK__OBJ_LIT_KEY_SET)) {
+			/* step 4.c */
+			DUK_DDD(DUK_DDDPRINT("duplicate key: plain key encountered after setter/getter"));
+			return 1;
+		}
+	} else {
+		if (key_flags & DUK__OBJ_LIT_KEY_PLAIN) {
+			/* step 4.b */
+			DUK_DDD(DUK_DDDPRINT("duplicate key: getter/setter encountered after plain key"));
+			return 1;
+		}
+		if (key_flags & new_key_flags) {
+			/* step 4.d */
+			DUK_DDD(DUK_DDDPRINT("duplicate key: getter/setter encountered twice"));
+			return 1;
+		}
+	}
+
+	new_key_flags |= key_flags;
+	DUK_DDD(DUK_DDDPRINT("setting/updating key %!T flags: 0x%08lx -> 0x%08lx",
+	                     (duk_tval *) duk_get_tval(ctx, -1),
+	                     (unsigned long) key_flags,
+	                     (unsigned long) new_key_flags));
+	duk_dup(ctx, -1);
+	duk_push_int(ctx, new_key_flags);   /* [ ... key_obj key key flags ] */
+	duk_put_prop(ctx, -4);              /* [ ... key_obj key ] */
+
+	return 0;
+}
+
+static void duk__nud_object_literal(duk_compiler_ctx *comp_ctx, duk_ivalue *res) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_reg_t reg_obj;                /* result reg */
+	duk_reg_t reg_key;                /* temp reg for key literal */
+	duk_reg_t reg_temp;               /* temp reg */
+	duk_reg_t temp_start;             /* temp reg value for start of loop */
+	duk_small_uint_t max_init_pairs;  /* max # of key-value pairs initialized in one MPUTOBJ set */
+	duk_small_uint_t num_pairs;       /* number of pairs in current MPUTOBJ set */
+	duk_bool_t first;                 /* first value: comma must not precede the value */
+	duk_bool_t is_set, is_get;        /* temps */
+
+	DUK_ASSERT(comp_ctx->prev_token.t == DUK_TOK_LCURLY);
+
+	max_init_pairs = DUK__MAX_OBJECT_INIT_PAIRS;  /* XXX: depend on available temps? */
+
+	reg_obj = DUK__ALLOCTEMP(comp_ctx);
+	duk__emit_extraop_b_c(comp_ctx,
+	                      DUK_EXTRAOP_NEWOBJ | DUK__EMIT_FLAG_B_IS_TARGET,
+	                      reg_obj,
+	                      0);  /* XXX: patch initial size afterwards? */
+	temp_start = DUK__GETTEMP(comp_ctx);
+
+	/* temp object for tracking / detecting duplicate keys */
+	duk_push_object(ctx);
+
+	/*
+	 *  Emit initializers in sets of maximum max_init_pairs keys.
+	 *  Setter/getter is handled separately and terminates the
+	 *  current set of initializer values.  Corner cases such as
+	 *  single value initializers do not have special handling now.
+	 */
+
+	first = 1;
+	for (;;) {
+		num_pairs = 0;
+		DUK__SETTEMP(comp_ctx, temp_start);
+
+		if (comp_ctx->curr_token.t == DUK_TOK_RCURLY) {
+			break;
+		}
+
+		for (;;) {
+			/*
+			 *  Three possible element formats:
+			 *    1) PropertyName : AssignmentExpression
+			 *    2) get PropertyName () { FunctionBody }
+			 *    3) set PropertyName ( PropertySetParameterList ) { FunctionBody }
+			 *
+			 *  PropertyName can be IdentifierName (includes reserved words), a string
+			 *  literal, or a number literal.  Note that IdentifierName allows 'get' and
+			 *  'set' too, so we need to look ahead to the next token to distinguish:
+			 *
+			 *     { get : 1 }
+			 *
+			 *  and
+			 *
+			 *     { get foo() { return 1 } }
+			 *     { get get() { return 1 } }    // 'get' as getter propertyname
+			 *
+			 *  Finally, a trailing comma is allowed.
+			 *
+			 *  Key name is coerced to string at compile time (and ends up as a
+			 *  a string constant) even for numeric keys (e.g. "{1:'foo'}").
+			 *  These could be emitted using e.g. LDINT, but that seems hardly
+			 *  worth the effort and would increase code size.
+			 */ 
+
+			DUK_DDD(DUK_DDDPRINT("object literal inner loop, curr_token->t = %ld",
+			                     (long) comp_ctx->curr_token.t));
+
+			if (comp_ctx->curr_token.t == DUK_TOK_RCURLY) {
+				/* the outer loop will recheck and exit */
+				break;
+			}
+			if (num_pairs >= max_init_pairs) {
+				/* MPUTOBJ emitted by outer loop */
+				break;
+			}
+
+			if (first) {
+				first = 0;
+			} else {
+				if (comp_ctx->curr_token.t != DUK_TOK_COMMA) {
+					goto syntax_error;
+				}
+				duk__advance(comp_ctx);
+				if (comp_ctx->curr_token.t == DUK_TOK_RCURLY) {
+					/* trailing comma followed by rcurly */
+					break;
+				}
+			}
+
+			/* advance to get one step of lookup */		
+			duk__advance(comp_ctx);
+
+			/* NOTE: "get" and "set" are not officially ReservedWords and the lexer
+			 * currently treats them always like ordinary identifiers (DUK_TOK_GET
+			 * and DUK_TOK_SET are unused).  They need to be detected based on the
+			 * identifier string content.
+			 */
+
+			is_get = (comp_ctx->prev_token.t == DUK_TOK_IDENTIFIER &&
+			          comp_ctx->prev_token.str1 == DUK_HTHREAD_STRING_GET(thr));
+			is_set = (comp_ctx->prev_token.t == DUK_TOK_IDENTIFIER &&
+			          comp_ctx->prev_token.str1 == DUK_HTHREAD_STRING_SET(thr));
+			if ((is_get || is_set) && comp_ctx->curr_token.t != DUK_TOK_COLON) {
+				/* getter/setter */
+				duk_int_t fnum;
+
+				if (comp_ctx->curr_token.t_nores == DUK_TOK_IDENTIFIER ||
+				    comp_ctx->curr_token.t_nores == DUK_TOK_STRING) {
+					/* same handling for identifiers and strings */
+					DUK_ASSERT(comp_ctx->curr_token.str1 != NULL);
+					duk_push_hstring(ctx, comp_ctx->curr_token.str1);
+				} else if (comp_ctx->curr_token.t == DUK_TOK_NUMBER) {
+					duk_push_number(ctx, comp_ctx->curr_token.num);
+					duk_to_string(ctx, -1);
+				} else {
+					goto syntax_error;
+				}
+
+				DUK_ASSERT(duk_is_string(ctx, -1));
+				if (duk__nud_object_literal_key_check(comp_ctx,
+				                                      (is_get ? DUK__OBJ_LIT_KEY_GET : DUK__OBJ_LIT_KEY_SET))) {
+					goto syntax_error;
+				}
+				reg_key = duk__getconst(comp_ctx);
+
+				if (num_pairs > 0) {
+					/* - A is a source register (it's not a write target, but used
+					 *   to identify the target object) but can be shuffled.
+					 * - B cannot be shuffled normally because it identifies a range
+					 *   of registers, the emitter has special handling for this
+					 *   (the "no shuffle" flag must not be set).
+					 * - C is a non-register number and cannot be shuffled, but
+					 *   never needs to be.
+					 */
+					duk__emit_a_b_c(comp_ctx,
+					                DUK_OP_MPUTOBJ |
+					                    DUK__EMIT_FLAG_NO_SHUFFLE_C |
+					                    DUK__EMIT_FLAG_A_IS_SOURCE,
+					                reg_obj,
+					                temp_start,
+					                num_pairs);
+					num_pairs = 0;
+					DUK__SETTEMP(comp_ctx, temp_start);
+				}
+
+				/* curr_token = get/set name */
+				fnum = duk__parse_func_like_fnum(comp_ctx, 0 /*is_decl*/, 1 /*is_setget*/);
+
+				DUK_ASSERT(DUK__GETTEMP(comp_ctx) == temp_start);
+				reg_temp = DUK__ALLOCTEMP(comp_ctx);
+				duk__emit_a_bc(comp_ctx,
+				               DUK_OP_LDCONST,
+				               (duk_regconst_t) reg_temp,
+				               (duk_regconst_t) reg_key);
+				reg_temp = DUK__ALLOCTEMP(comp_ctx);
+				duk__emit_a_bc(comp_ctx,
+				               DUK_OP_CLOSURE,
+				               (duk_regconst_t) reg_temp,
+				               (duk_regconst_t) fnum);
+
+				/* Slot C is used in a non-standard fashion (range of regs),
+				 * emitter code has special handling for it (must not set the
+				 * "no shuffle" flag).
+				 */
+				duk__emit_extraop_b_c(comp_ctx,
+				                      (is_get ? DUK_EXTRAOP_INITGET : DUK_EXTRAOP_INITSET),
+				                      reg_obj,
+				                      temp_start);   /* temp_start+0 = key, temp_start+1 = closure */
+
+				DUK__SETTEMP(comp_ctx, temp_start);
+			} else {
+				/* normal key/value */
+				if (comp_ctx->prev_token.t_nores == DUK_TOK_IDENTIFIER ||
+				    comp_ctx->prev_token.t_nores == DUK_TOK_STRING) {
+					/* same handling for identifiers and strings */
+					DUK_ASSERT(comp_ctx->prev_token.str1 != NULL);
+					duk_push_hstring(ctx, comp_ctx->prev_token.str1);
+				} else if (comp_ctx->prev_token.t == DUK_TOK_NUMBER) {
+					duk_push_number(ctx, comp_ctx->prev_token.num);
+					duk_to_string(ctx, -1);
+				} else {
+					goto syntax_error;
+				}
+
+				DUK_ASSERT(duk_is_string(ctx, -1));
+				if (duk__nud_object_literal_key_check(comp_ctx, DUK__OBJ_LIT_KEY_PLAIN)) {
+					goto syntax_error;
+				}
+				reg_key = duk__getconst(comp_ctx);
+
+				reg_temp = DUK__ALLOCTEMP(comp_ctx);
+				duk__emit_a_bc(comp_ctx,
+				               DUK_OP_LDCONST,
+				               (duk_regconst_t) reg_temp,
+				               (duk_regconst_t) reg_key);
+				duk__advance_expect(comp_ctx, DUK_TOK_COLON);
+
+				reg_temp = DUK__ALLOCTEMP(comp_ctx);  /* alloc temp just in case, to update max temp */
+				DUK__SETTEMP(comp_ctx, reg_temp);
+				duk__expr_toforcedreg(comp_ctx, res, DUK__BP_COMMA /*rbp_flags*/, reg_temp /*forced_reg*/);
+				DUK__SETTEMP(comp_ctx, reg_temp + 1);
+
+				num_pairs++;
+			}
+		}
+
+		if (num_pairs > 0) {
+			/* See MPUTOBJ comments above. */
+			duk__emit_a_b_c(comp_ctx,
+			                DUK_OP_MPUTOBJ |
+			                    DUK__EMIT_FLAG_NO_SHUFFLE_C |
+			                    DUK__EMIT_FLAG_A_IS_SOURCE,
+			                reg_obj,
+			                temp_start,
+			                num_pairs);
+
+			/* num_pairs and temp_start reset at top of outer loop */
+		}
+	}
+
+	DUK_ASSERT(comp_ctx->curr_token.t == DUK_TOK_RCURLY);
+	duk__advance(comp_ctx);
+
+	DUK__SETTEMP(comp_ctx, temp_start);
+
+	res->t = DUK_IVAL_PLAIN;
+	res->x1.t = DUK_ISPEC_REGCONST;
+	res->x1.regconst = (duk_regconst_t) reg_obj;
+
+	DUK_DDD(DUK_DDDPRINT("final tracking object: %!T",
+	                     (duk_tval *) duk_get_tval(ctx, -1)));
+	duk_pop(ctx);
+	return;
+
+ syntax_error:
+	DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_INVALID_OBJECT_LITERAL);
+}
+
+/* Parse argument list.  Arguments are written to temps starting from
+ * "next temp".  Returns number of arguments parsed.  Expects left paren
+ * to be already eaten, and eats the right paren before returning.
+ */
+static duk_int_t duk__parse_arguments(duk_compiler_ctx *comp_ctx, duk_ivalue *res) {
+	duk_int_t nargs = 0;
+	duk_reg_t reg_temp;
+
+	/* Note: expect that caller has already eaten the left paren */
+
+	DUK_DDD(DUK_DDDPRINT("start parsing arguments, prev_token.t=%ld, curr_token.t=%ld",
+	                     (long) comp_ctx->prev_token.t, (long) comp_ctx->curr_token.t));
+
+	for (;;) {
+		if (comp_ctx->curr_token.t == DUK_TOK_RPAREN) {
+			break;
+		}
+		if (nargs > 0) {
+			duk__advance_expect(comp_ctx, DUK_TOK_COMMA);
+		}
+
+		/* We want the argument expression value to go to "next temp"
+		 * without additional moves.  That should almost always be the
+		 * case, but we double check after expression parsing.
+		 *
+		 * This is not the cleanest possible approach.
+		 */
+
+		reg_temp = DUK__ALLOCTEMP(comp_ctx);  /* bump up "allocated" reg count, just in case */
+		DUK__SETTEMP(comp_ctx, reg_temp);
+
+		/* binding power must be high enough to NOT allow comma expressions directly */
+		duk__expr_toforcedreg(comp_ctx, res, DUK__BP_COMMA /*rbp_flags*/, reg_temp);  /* always allow 'in', coerce to 'tr' just in case */
+
+		DUK__SETTEMP(comp_ctx, reg_temp + 1);
+		nargs++;
+
+		DUK_DDD(DUK_DDDPRINT("argument #%ld written into reg %ld", (long) nargs, (long) reg_temp));
+	}
+
+	/* eat the right paren */
+	duk__advance_expect(comp_ctx, DUK_TOK_RPAREN);
+
+	DUK_DDD(DUK_DDDPRINT("end parsing arguments"));
+
+	return nargs;
+}
+
+static duk_bool_t duk__expr_is_empty(duk_compiler_ctx *comp_ctx) {
+	/* empty expressions can be detected conveniently with nud/led counts */
+	return (comp_ctx->curr_func.nud_count == 0) &&
+	       (comp_ctx->curr_func.led_count == 0);
+}
+
+static void duk__expr_nud(duk_compiler_ctx *comp_ctx, duk_ivalue *res) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_token *tk;
+	duk_reg_t temp_at_entry;
+	duk_small_int_t tok;
+	duk_uint32_t args;	/* temp variable to pass constants and flags to shared code */
+
+	/*
+	 *  ctx->prev_token	token to process with duk__expr_nud()
+	 *  ctx->curr_token	updated by caller
+	 *
+	 *  Note: the token in the switch below has already been eaten.
+	 */
+
+	temp_at_entry = DUK__GETTEMP(comp_ctx);
+
+	comp_ctx->curr_func.nud_count++;
+
+	tk = &comp_ctx->prev_token;
+	tok = tk->t;
+	res->t = DUK_IVAL_NONE;
+
+	DUK_DDD(DUK_DDDPRINT("duk__expr_nud(), prev_token.t=%ld, allow_in=%ld, paren_level=%ld",
+	                     (long) tk->t, (long) comp_ctx->curr_func.allow_in, (long) comp_ctx->curr_func.paren_level));
+
+	switch (tok) {
+
+	/* PRIMARY EXPRESSIONS */
+
+	case DUK_TOK_THIS: {
+		duk_reg_t reg_temp;
+		reg_temp = DUK__ALLOCTEMP(comp_ctx);
+		duk__emit_extraop_b(comp_ctx,
+		                    DUK_EXTRAOP_LDTHIS | DUK__EMIT_FLAG_B_IS_TARGET,
+		                    (duk_regconst_t) reg_temp);
+		res->t = DUK_IVAL_PLAIN;
+		res->x1.t = DUK_ISPEC_REGCONST;
+		res->x1.regconst = (duk_regconst_t) reg_temp;
+		return;
+	}
+	case DUK_TOK_IDENTIFIER: {
+		res->t = DUK_IVAL_VAR;
+		res->x1.t = DUK_ISPEC_VALUE;
+		duk_push_hstring(ctx, tk->str1);
+		duk_replace(ctx, res->x1.valstack_idx);
+		return;
+	}
+	case DUK_TOK_NULL: {
+		duk_push_null(ctx);
+		goto plain_value;
+	}
+	case DUK_TOK_TRUE: {
+		duk_push_true(ctx);
+		goto plain_value;
+	}
+	case DUK_TOK_FALSE: {
+		duk_push_false(ctx);
+		goto plain_value;
+	}
+	case DUK_TOK_NUMBER: {
+		duk_push_number(ctx, tk->num);
+		goto plain_value;
+	}
+	case DUK_TOK_STRING: {
+		DUK_ASSERT(tk->str1 != NULL);
+		duk_push_hstring(ctx, tk->str1);
+		goto plain_value;
+	}
+	case DUK_TOK_REGEXP: {
+#ifdef DUK_USE_REGEXP_SUPPORT
+		duk_reg_t reg_temp;
+		duk_regconst_t rc_re_bytecode;  /* const */
+		duk_regconst_t rc_re_source;    /* const */
+
+		DUK_ASSERT(tk->str1 != NULL);
+		DUK_ASSERT(tk->str2 != NULL);
+
+		DUK_DDD(DUK_DDDPRINT("emitting regexp op, str1=%!O, str2=%!O",
+		                     (duk_heaphdr *) tk->str1,
+		                     (duk_heaphdr *) tk->str2));
+
+		reg_temp = DUK__ALLOCTEMP(comp_ctx);
+		duk_push_hstring(ctx, tk->str1);
+		duk_push_hstring(ctx, tk->str2);
+
+		/* [ ... pattern flags ] */
+
+		duk_regexp_compile(thr);
+
+		/* [ ... escaped_source bytecode ] */
+
+		rc_re_bytecode = duk__getconst(comp_ctx);
+		rc_re_source = duk__getconst(comp_ctx);
+
+		duk__emit_a_b_c(comp_ctx,
+		                DUK_OP_REGEXP,
+		                (duk_regconst_t) reg_temp /*a*/,
+		                rc_re_bytecode /*b*/,
+		                rc_re_source /*c*/);
+
+		res->t = DUK_IVAL_PLAIN;
+		res->x1.t = DUK_ISPEC_REGCONST;
+		res->x1.regconst = (duk_regconst_t) reg_temp;
+		return;
+#else  /* DUK_USE_REGEXP_SUPPORT */
+		goto syntax_error;
+#endif  /* DUK_USE_REGEXP_SUPPORT */
+	}
+	case DUK_TOK_LBRACKET: {
+		DUK_DDD(DUK_DDDPRINT("parsing array literal"));
+		duk__nud_array_literal(comp_ctx, res);
+		return;
+	}
+	case DUK_TOK_LCURLY: {
+		DUK_DDD(DUK_DDDPRINT("parsing object literal"));
+		duk__nud_object_literal(comp_ctx, res);
+		return;
+	}
+	case DUK_TOK_LPAREN: {
+		duk_bool_t prev_allow_in;
+
+		comp_ctx->curr_func.paren_level++;
+		prev_allow_in = comp_ctx->curr_func.allow_in;
+		comp_ctx->curr_func.allow_in = 1; /* reset 'allow_in' for parenthesized expression */
+
+		duk__expr(comp_ctx, res, DUK__BP_FOR_EXPR /*rbp_flags*/);  /* Expression, terminates at a ')' */
+
+		duk__advance_expect(comp_ctx, DUK_TOK_RPAREN);
+		comp_ctx->curr_func.allow_in = prev_allow_in;
+		comp_ctx->curr_func.paren_level--;
+		return;
+	}
+
+	/* MEMBER/NEW/CALL EXPRESSIONS */
+
+	case DUK_TOK_NEW: {
+		/*
+		 *  Parsing an expression starting with 'new' is tricky because
+		 *  there are multiple possible productions deriving from
+		 *  LeftHandSideExpression which begin with 'new'.
+		 *
+		 *  We currently resort to one-token lookahead to distinguish the
+		 *  cases.  Hopefully this is correct.  The binding power must be
+		 *  such that parsing ends at an LPAREN (CallExpression) but not at
+		 *  a PERIOD or LBRACKET (MemberExpression).
+		 *
+		 *  See doc/compiler.txt for discussion on the parsing approach,
+		 *  and testcases/test-dev-new.js for a bunch of documented tests.
+		 */
+
+		duk_reg_t reg_target;
+		duk_int_t nargs;
+
+		DUK_DDD(DUK_DDDPRINT("begin parsing new expression"));
+
+		reg_target = DUK__ALLOCTEMP(comp_ctx);
+		duk__expr_toforcedreg(comp_ctx, res, DUK__BP_CALL /*rbp_flags*/, reg_target /*forced_reg*/);
+		DUK__SETTEMP(comp_ctx, reg_target + 1);
+
+		if (comp_ctx->curr_token.t == DUK_TOK_LPAREN) {
+			/* 'new' MemberExpression Arguments */
+			DUK_DDD(DUK_DDDPRINT("new expression has argument list"));
+			duk__advance(comp_ctx);
+			nargs = duk__parse_arguments(comp_ctx, res);  /* parse args starting from "next temp", reg_target + 1 */
+			/* right paren eaten */
+		} else {
+			/* 'new' MemberExpression */
+			DUK_DDD(DUK_DDDPRINT("new expression has no argument list"));
+			nargs = 0;
+		}
+
+		/* Opcode slot C is used in a non-standard way, so shuffling
+		 * is not allowed.
+		 */
+		duk__emit_a_b_c(comp_ctx,
+		              DUK_OP_NEW | DUK__EMIT_FLAG_NO_SHUFFLE_A | DUK__EMIT_FLAG_NO_SHUFFLE_C,
+		              0 /*unused*/,
+		              reg_target /*target*/,
+		              nargs /*num_args*/);
+
+		DUK_DDD(DUK_DDDPRINT("end parsing new expression"));
+
+		res->t = DUK_IVAL_PLAIN;
+		res->x1.t = DUK_ISPEC_REGCONST;
+		res->x1.regconst = (duk_regconst_t) reg_target;
+		return;
+	}
+
+	/* FUNCTION EXPRESSIONS */
+
+	case DUK_TOK_FUNCTION: {
+		/* Function expression.  Note that any statement beginning with 'function'
+		 * is handled by the statement parser as a function declaration, or a
+		 * non-standard function expression/statement (or a SyntaxError).  We only
+		 * handle actual function expressions (occurring inside an expression) here.
+		 *
+		 * O(depth^2) parse count for inner functions is handled by recording a
+		 * lexer offset on the first compilation pass, so that the function can
+		 * be efficiently skipped on the second pass.  This is encapsulated into
+		 * duk__parse_func_like_fnum().
+		 */
+
+		duk_reg_t reg_temp;
+		duk_int_t fnum;
+
+		reg_temp = DUK__ALLOCTEMP(comp_ctx);
+
+		/* curr_token follows 'function' */
+		fnum = duk__parse_func_like_fnum(comp_ctx, 0 /*is_decl*/, 0 /*is_setget*/);
+		DUK_DDD(DUK_DDDPRINT("parsed inner function -> fnum %ld", (long) fnum));
+
+		duk__emit_a_bc(comp_ctx,
+		               DUK_OP_CLOSURE,
+		               (duk_regconst_t) reg_temp /*a*/,
+		               (duk_regconst_t) fnum /*bc*/);
+
+		res->t = DUK_IVAL_PLAIN;
+		res->x1.t = DUK_ISPEC_REGCONST;
+		res->x1.regconst = (duk_regconst_t) reg_temp;
+		return;
+	}
+
+	/* UNARY EXPRESSIONS */
+
+	case DUK_TOK_DELETE: {
+		/* Delete semantics are a bit tricky.  The description in E5 specification
+		 * is kind of confusing, because it distinguishes between resolvability of
+		 * a reference (which is only known at runtime) seemingly at compile time
+		 * (= SyntaxError throwing).
+		 */
+		duk__expr(comp_ctx, res, DUK__BP_MULTIPLICATIVE /*rbp_flags*/);  /* UnaryExpression */
+		if (res->t == DUK_IVAL_VAR) {
+			/* not allowed in strict mode, regardless of whether resolves;
+			 * in non-strict mode DELVAR handles both non-resolving and
+			 * resolving cases (the specification description is a bit confusing).
+			 */
+
+			duk_reg_t reg_temp;
+			duk_reg_t reg_varbind;
+			duk_regconst_t rc_varname;
+
+			if (comp_ctx->curr_func.is_strict) {
+				DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_CANNOT_DELETE_IDENTIFIER);
+			}
+
+			DUK__SETTEMP(comp_ctx, temp_at_entry);
+			reg_temp = DUK__ALLOCTEMP(comp_ctx);
+
+			duk_dup(ctx, res->x1.valstack_idx);
+			if (duk__lookup_lhs(comp_ctx, &reg_varbind, &rc_varname)) {
+				/* register bound variables are non-configurable -> always false */
+				duk__emit_extraop_bc(comp_ctx,
+				                     DUK_EXTRAOP_LDFALSE,
+				                     (duk_regconst_t) reg_temp);
+			} else {
+				duk_dup(ctx, res->x1.valstack_idx);
+				rc_varname = duk__getconst(comp_ctx);
+				duk__emit_a_b(comp_ctx,
+				              DUK_OP_DELVAR,
+				              (duk_regconst_t) reg_temp,
+				              (duk_regconst_t) rc_varname);
+			}
+			res->t = DUK_IVAL_PLAIN;
+			res->x1.t = DUK_ISPEC_REGCONST;
+			res->x1.regconst = (duk_regconst_t) reg_temp;
+		} else if (res->t == DUK_IVAL_PROP) {
+			duk_reg_t reg_temp;
+			duk_reg_t reg_obj;
+			duk_regconst_t rc_key;
+
+			DUK__SETTEMP(comp_ctx, temp_at_entry);
+			reg_temp = DUK__ALLOCTEMP(comp_ctx);
+			reg_obj = duk__ispec_toregconst_raw(comp_ctx, &res->x1, -1 /*forced_reg*/, 0 /*flags*/);  /* don't allow const */
+			rc_key = duk__ispec_toregconst_raw(comp_ctx, &res->x2, -1 /*forced_reg*/, DUK__IVAL_FLAG_ALLOW_CONST /*flags*/);
+			duk__emit_a_b_c(comp_ctx,
+			                DUK_OP_DELPROP,
+			                (duk_regconst_t) reg_temp,
+			                (duk_regconst_t) reg_obj,
+			                rc_key);
+
+			res->t = DUK_IVAL_PLAIN;
+			res->x1.t = DUK_ISPEC_REGCONST;
+			res->x1.regconst = (duk_regconst_t) reg_temp;
+		} else {
+			/* non-Reference deletion is always 'true', even in strict mode */
+			duk_push_true(ctx);
+			goto plain_value;
+		}
+		return;
+	}
+	case DUK_TOK_VOID: {
+		duk__expr_toplain_ignore(comp_ctx, res, DUK__BP_MULTIPLICATIVE /*rbp_flags*/);  /* UnaryExpression */
+		duk_push_undefined(ctx);
+		goto plain_value;
+	}
+	case DUK_TOK_TYPEOF: {
+		/* 'typeof' must handle unresolvable references without throwing
+		 * a ReferenceError (E5 Section 11.4.3).  Register mapped values
+		 * will never be unresolvable so special handling is only required
+		 * when an identifier is a "slow path" one.
+		 */
+		duk__expr(comp_ctx, res, DUK__BP_MULTIPLICATIVE /*rbp_flags*/);  /* UnaryExpression */
+
+		if (res->t == DUK_IVAL_VAR) {
+			duk_reg_t reg_varbind;
+			duk_regconst_t rc_varname;
+			duk_reg_t reg_temp;
+
+			duk_dup(ctx, res->x1.valstack_idx);
+			if (!duk__lookup_lhs(comp_ctx, &reg_varbind, &rc_varname)) {
+				DUK_DDD(DUK_DDDPRINT("typeof for an identifier name which could not be resolved "
+				                     "at compile time, need to use special run-time handling"));
+				reg_temp = DUK__ALLOCTEMP(comp_ctx);
+				duk__emit_extraop_b_c(comp_ctx,
+				                      DUK_EXTRAOP_TYPEOFID | DUK__EMIT_FLAG_B_IS_TARGET,
+				                      reg_temp,
+				                      rc_varname);
+				res->t = DUK_IVAL_PLAIN;
+				res->x1.t = DUK_ISPEC_REGCONST;
+				res->x1.regconst = (duk_regconst_t) reg_temp;
+				return;
+			}
+		}
+
+		args = (DUK_EXTRAOP_TYPEOF << 8) + 0;
+		goto unary_extraop;
+	}
+	case DUK_TOK_INCREMENT: {
+		args = (DUK_EXTRAOP_INC << 8) + 0;
+		goto preincdec_extraop;
+	}
+	case DUK_TOK_DECREMENT: {
+		args = (DUK_EXTRAOP_DEC << 8) + 0;
+		goto preincdec_extraop;
+	}
+	case DUK_TOK_ADD: {
+		/* unary plus */
+		duk__expr(comp_ctx, res, DUK__BP_MULTIPLICATIVE /*rbp_flags*/);  /* UnaryExpression */
+		if (res->t == DUK_IVAL_PLAIN && res->x1.t == DUK_ISPEC_VALUE &&
+		    duk_is_number(ctx, res->x1.valstack_idx)) {
+			/* unary plus of a number is identity */
+			;
+			return;
+		}
+		args = (DUK_EXTRAOP_UNP << 8) + 0;
+		goto unary_extraop;
+	}
+	case DUK_TOK_SUB: {
+		/* unary minus */
+		duk__expr(comp_ctx, res, DUK__BP_MULTIPLICATIVE /*rbp_flags*/);  /* UnaryExpression */
+		if (res->t == DUK_IVAL_PLAIN && res->x1.t == DUK_ISPEC_VALUE &&
+		    duk_is_number(ctx, res->x1.valstack_idx)) {
+			/* this optimization is important to handle negative literals (which are not directly
+			 * provided by the lexical grammar
+			 */
+			duk_tval *tv_num = duk_get_tval(ctx, res->x1.valstack_idx);
+			duk_double_union du;
+
+			DUK_ASSERT(tv_num != NULL);
+			DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_num));
+			du.d = DUK_TVAL_GET_NUMBER(tv_num);
+			du.d = -du.d;
+			DUK_DBLUNION_NORMALIZE_NAN_CHECK(&du);
+			DUK_TVAL_SET_NUMBER(tv_num, du.d);
+			return;
+		}
+		args = (DUK_EXTRAOP_UNM << 8) + 0;
+		goto unary_extraop;
+	}
+	case DUK_TOK_BNOT: {
+		duk__expr(comp_ctx, res, DUK__BP_MULTIPLICATIVE /*rbp_flags*/);  /* UnaryExpression */
+		args = (DUK_OP_BNOT << 8) + 0;
+		goto unary;
+	}
+	case DUK_TOK_LNOT: {
+		duk__expr(comp_ctx, res, DUK__BP_MULTIPLICATIVE /*rbp_flags*/);  /* UnaryExpression */
+		if (res->t == DUK_IVAL_PLAIN && res->x1.t == DUK_ISPEC_VALUE) {
+			/* Very minimal inlining to handle common idioms '!0' and '!1',
+			 * and also boolean arguments like '!false' and '!true'.
+			 */
+			duk_tval *tv_val = duk_get_tval(ctx, res->x1.valstack_idx);
+
+			DUK_ASSERT(tv_val != NULL);
+			if (DUK_TVAL_IS_NUMBER(tv_val)) {
+				duk_double_t d;
+				d = DUK_TVAL_GET_NUMBER(tv_val);
+				if (d == 0.0) {
+					/* Matches both +0 and -0 on purpose. */
+					DUK_DDD(DUK_DDDPRINT("inlined lnot: !0 -> true"));
+					DUK_TVAL_SET_BOOLEAN_TRUE(tv_val);
+					return;
+				} else if (d == 1.0) {
+					DUK_DDD(DUK_DDDPRINT("inlined lnot: !1 -> false"));
+					DUK_TVAL_SET_BOOLEAN_FALSE(tv_val);
+					return;
+				}
+			} else if (DUK_TVAL_IS_BOOLEAN(tv_val)) {
+				duk_small_int_t v;
+				v = DUK_TVAL_GET_BOOLEAN(tv_val);
+				DUK_DDD(DUK_DDDPRINT("inlined lnot boolean: %ld", (long) v));
+				DUK_ASSERT(v == 0 || v == 1);
+				DUK_TVAL_SET_BOOLEAN(tv_val, v ^ 0x01);
+				return;
+			}
+		}
+		args = (DUK_OP_LNOT << 8) + 0;
+		goto unary;
+	}
+
+	}  /* end switch */
+
+	DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_PARSE_ERROR);
+	return;
+
+ unary:
+	{
+		/* Note: must coerce to a (writable) temp register, so that e.g. "!x" where x
+		 * is a reg-mapped variable works correctly (does not mutate the variable register).
+		 */
+
+		duk_regconst_t rc_temp;
+		rc_temp = duk__ivalue_toregconst_raw(comp_ctx, res, -1 /*forced_reg*/, DUK__IVAL_FLAG_REQUIRE_TEMP /*flags*/);
+		duk__emit_a_b(comp_ctx,
+		              args >> 8,
+		              rc_temp,
+		              rc_temp);
+		res->t = DUK_IVAL_PLAIN;
+		res->x1.t = DUK_ISPEC_REGCONST;
+		res->x1.regconst = rc_temp;
+		return;
+	}
+
+ unary_extraop:
+	{
+		/* XXX: refactor into unary2: above? */
+		duk_reg_t reg_temp;
+		reg_temp = duk__ivalue_toregconst_raw(comp_ctx, res, -1 /*forced_reg*/, DUK__IVAL_FLAG_REQUIRE_TEMP /*flags*/);
+		duk__emit_extraop_b_c(comp_ctx,
+		                      (args >> 8) | DUK__EMIT_FLAG_B_IS_TARGET,
+		                      (duk_regconst_t) reg_temp,
+		                      (duk_regconst_t) reg_temp);
+		res->t = DUK_IVAL_PLAIN;
+		res->x1.t = DUK_ISPEC_REGCONST;
+		res->x1.regconst = (duk_regconst_t) reg_temp;
+		return;
+	}
+
+ preincdec_extraop:
+	{
+		/* preincrement and predecrement */
+		duk_reg_t reg_res;
+		duk_small_uint_t args_op = args >> 8;
+
+		reg_res = DUK__ALLOCTEMP(comp_ctx);
+
+		duk__expr(comp_ctx, res, DUK__BP_MULTIPLICATIVE /*rbp_flags*/);  /* UnaryExpression */
+		if (res->t == DUK_IVAL_VAR) {
+			duk_hstring *h_varname;
+			duk_reg_t reg_varbind;
+			duk_regconst_t rc_varname;
+
+			h_varname = duk_get_hstring(ctx, res->x1.valstack_idx);
+			DUK_ASSERT(h_varname != NULL);
+
+			if (duk__hstring_is_eval_or_arguments_in_strict_mode(comp_ctx, h_varname)) {
+				goto syntax_error;
+			}
+
+			duk_dup(ctx, res->x1.valstack_idx);
+			if (duk__lookup_lhs(comp_ctx, &reg_varbind, &rc_varname)) {
+				duk__emit_extraop_b_c(comp_ctx,
+				                      args_op | DUK__EMIT_FLAG_B_IS_TARGET,
+				                      reg_varbind,
+				                      reg_varbind);
+				duk__emit_a_bc(comp_ctx,
+				               DUK_OP_LDREG,
+				               (duk_regconst_t) reg_res,
+				               (duk_regconst_t) reg_varbind);
+			} else {
+				duk__emit_a_bc(comp_ctx,
+				               DUK_OP_GETVAR,
+				               (duk_regconst_t) reg_res,
+				               rc_varname);
+				duk__emit_extraop_b_c(comp_ctx,
+				                      args_op | DUK__EMIT_FLAG_B_IS_TARGET,
+				                      (duk_regconst_t) reg_res,
+				                      (duk_regconst_t) reg_res);
+				duk__emit_a_bc(comp_ctx,
+				               DUK_OP_PUTVAR | DUK__EMIT_FLAG_A_IS_SOURCE,
+				               (duk_regconst_t) reg_res,
+				               rc_varname);
+			}
+
+			DUK_DDD(DUK_DDDPRINT("postincdec to '%!O' -> reg_varbind=%ld, rc_varname=%ld",
+			                     (duk_heaphdr *) h_varname, (long) reg_varbind, (long) rc_varname));
+		} else if (res->t == DUK_IVAL_PROP) {
+			duk_reg_t reg_obj;  /* allocate to reg only (not const) */
+			duk_regconst_t rc_key;
+			reg_obj = duk__ispec_toregconst_raw(comp_ctx, &res->x1, -1 /*forced_reg*/, 0 /*flags*/);  /* don't allow const */
+			rc_key = duk__ispec_toregconst_raw(comp_ctx, &res->x2, -1 /*forced_reg*/, DUK__IVAL_FLAG_ALLOW_CONST /*flags*/);
+			duk__emit_a_b_c(comp_ctx,
+			                DUK_OP_GETPROP,
+			                (duk_regconst_t) reg_res,
+			                (duk_regconst_t) reg_obj,
+			                rc_key);
+			duk__emit_extraop_b_c(comp_ctx,
+			                      args_op | DUK__EMIT_FLAG_B_IS_TARGET,
+			                      (duk_regconst_t) reg_res,
+			                      (duk_regconst_t) reg_res);
+			duk__emit_a_b_c(comp_ctx,
+			                DUK_OP_PUTPROP,
+			                (duk_regconst_t) reg_obj,
+			                rc_key,
+			                (duk_regconst_t) reg_res);
+		} else {
+			/* Technically return value is not needed because INVLHS will
+			 * unconditially throw a ReferenceError.  Coercion is necessary
+			 * for proper semantics (consider ToNumber() called for an object).
+			 */
+			duk__ivalue_toforcedreg(comp_ctx, res, reg_res);
+			duk__emit_extraop_b_c(comp_ctx,
+			                      DUK_EXTRAOP_TONUM | DUK__EMIT_FLAG_B_IS_TARGET,
+			                      (duk_regconst_t) reg_res,
+			                      (duk_regconst_t) reg_res);  /* for side effects */
+			duk__emit_extraop_only(comp_ctx,
+			                       DUK_EXTRAOP_INVLHS);
+		}
+		res->t = DUK_IVAL_PLAIN;
+		res->x1.t = DUK_ISPEC_REGCONST;
+		res->x1.regconst = (duk_regconst_t) reg_res;
+		DUK__SETTEMP(comp_ctx, reg_res + 1);
+		return;
+	}
+
+ plain_value:
+	{
+		/* Stack top contains plain value */
+		res->t = DUK_IVAL_PLAIN;
+		res->x1.t = DUK_ISPEC_VALUE;
+		duk_replace(ctx, res->x1.valstack_idx);
+		return;
+	}
+
+ syntax_error:
+	DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_INVALID_EXPRESSION);
+}
+
+/* XXX: add flag to indicate whether caller cares about return value; this
+ * affects e.g. handling of assignment expressions.  This change needs API
+ * changes elsewhere too.
+ */
+static void duk__expr_led(duk_compiler_ctx *comp_ctx, duk_ivalue *left, duk_ivalue *res) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_token *tk;
+	duk_small_int_t tok;
+	duk_uint32_t args;	/* temp variable to pass constants and flags to shared code */
+
+	/*
+	 *  ctx->prev_token	token to process with duk__expr_led()
+	 *  ctx->curr_token	updated by caller
+	 */
+
+	comp_ctx->curr_func.led_count++;
+
+	/* The token in the switch has already been eaten here */
+	tk = &comp_ctx->prev_token;
+	tok = tk->t;
+
+	DUK_DDD(DUK_DDDPRINT("duk__expr_led(), prev_token.t=%ld, allow_in=%ld, paren_level=%ld",
+	                     (long) tk->t, (long) comp_ctx->curr_func.allow_in, (long) comp_ctx->curr_func.paren_level));
+
+	/* XXX: default priority for infix operators is duk__expr_lbp(tok) -> get it here? */
+
+	switch (tok) {
+
+	/* PRIMARY EXPRESSIONS */
+
+	case DUK_TOK_PERIOD: {
+		/* XXX: this now coerces an identifier into a GETVAR to a temp, which
+		 * causes an extra LDREG in call setup.  It's sufficient to coerce to a
+		 * unary ivalue?
+		 */
+		duk__ivalue_toplain(comp_ctx, left);
+
+		/* NB: must accept reserved words as property name */
+		if (comp_ctx->curr_token.t_nores != DUK_TOK_IDENTIFIER) {
+			DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_EXPECTED_IDENTIFIER);
+		}
+
+		res->t = DUK_IVAL_PROP;
+		duk__copy_ispec(comp_ctx, &left->x1, &res->x1);  /* left.x1 -> res.x1 */
+		DUK_ASSERT(comp_ctx->curr_token.str1 != NULL);
+		duk_push_hstring(ctx, comp_ctx->curr_token.str1);
+		duk_replace(ctx, res->x2.valstack_idx);
+		res->x2.t = DUK_ISPEC_VALUE;
+
+		/* special RegExp literal handling after IdentifierName */
+		comp_ctx->curr_func.reject_regexp_in_adv = 1;
+
+		duk__advance(comp_ctx);
+		return;
+	}
+	case DUK_TOK_LBRACKET: {
+		/* XXX: optimize temp reg use */
+		/* XXX: similar coercion issue as in DUK_TOK_PERIOD */
+
+		duk__ivalue_toplain(comp_ctx, left);
+
+		duk__expr_toplain(comp_ctx, res, DUK__BP_FOR_EXPR /*rbp_flags*/);  /* Expression, ']' terminates */
+
+		duk__advance_expect(comp_ctx, DUK_TOK_RBRACKET);
+
+		/* XXX: coerce to regs? it might be better for enumeration use, where the
+		 * same PROP ivalue is used multiple times.  Or perhaps coerce PROP further
+		 * there?
+		 */
+
+		res->t = DUK_IVAL_PROP;
+		duk__copy_ispec(comp_ctx, &res->x1, &res->x2);   /* res.x1 -> res.x2 */
+		duk__copy_ispec(comp_ctx, &left->x1, &res->x1);  /* left.x1 -> res.x1 */
+		return;
+	}
+	case DUK_TOK_LPAREN: {
+		/* function call */
+		duk_reg_t reg_cs = DUK__ALLOCTEMPS(comp_ctx, 2);
+		duk_int_t nargs;
+		duk_small_uint_t call_flags = 0;
+
+		/*
+		 *  XXX: attempt to get the call result to "next temp" whenever
+		 *  possible to avoid unnecessary register shuffles.
+		 *
+		 *  XXX: CSPROP (and CSREG) can overwrite the call target register, and save one temp,
+		 *  if the call target is a temporary register and at the top of the temp reg "stack".
+		 */
+
+		/*
+		 *  Setup call: target and 'this' binding.  Three cases:
+		 *
+		 *    1. Identifier base (e.g. "foo()")
+		 *    2. Property base (e.g. "foo.bar()")
+		 *    3. Register base (e.g. "foo()()"; i.e. when a return value is a function)
+		 */
+
+		if (left->t == DUK_IVAL_VAR) {
+			duk_hstring *h_varname;
+			duk_reg_t reg_varbind;
+			duk_regconst_t rc_varname;
+
+			DUK_DDD(DUK_DDDPRINT("function call with identifier base"));
+
+			h_varname = duk_get_hstring(ctx, left->x1.valstack_idx);
+			DUK_ASSERT(h_varname != NULL);
+			if (h_varname == DUK_HTHREAD_STRING_EVAL(thr)) {
+				/* Potential direct eval call detected, flag the CALL
+				 * so that a run-time "direct eval" check is made and
+				 * special behavior may be triggered.  Note that this
+				 * does not prevent 'eval' from being register bound.
+				 */
+				DUK_DDD(DUK_DDDPRINT("function call with identifier 'eval' "
+				                     "-> enabling EVALCALL flag, marking function "
+				                     "as may_direct_eval"));
+				call_flags |= DUK_BC_CALL_FLAG_EVALCALL;
+
+				comp_ctx->curr_func.may_direct_eval = 1;
+			}
+
+			duk_dup(ctx, left->x1.valstack_idx);
+			if (duk__lookup_lhs(comp_ctx, &reg_varbind, &rc_varname)) {
+				duk__emit_a_b(comp_ctx,
+				              DUK_OP_CSREG,
+				              (duk_regconst_t) (reg_cs + 0),
+				              (duk_regconst_t) reg_varbind);
+			} else {
+				duk__emit_a_b(comp_ctx,
+				              DUK_OP_CSVAR,
+				              (duk_regconst_t) (reg_cs + 0),
+				              rc_varname);
+			}
+		} else if (left->t == DUK_IVAL_PROP) {
+			DUK_DDD(DUK_DDDPRINT("function call with property base"));
+			
+			duk__ispec_toforcedreg(comp_ctx, &left->x1, reg_cs + 0);  /* base */
+			duk__ispec_toforcedreg(comp_ctx, &left->x2, reg_cs + 1);  /* key */
+			duk__emit_a_b_c(comp_ctx,
+			                DUK_OP_CSPROP,
+			                (duk_regconst_t) (reg_cs + 0),
+			                (duk_regconst_t) (reg_cs + 0),
+			                (duk_regconst_t) (reg_cs + 1));  /* in-place setup */
+		} else {
+			DUK_DDD(DUK_DDDPRINT("function call with register base"));
+
+			duk__ivalue_toforcedreg(comp_ctx, left, reg_cs + 0);
+			duk__emit_a_b(comp_ctx,
+			              DUK_OP_CSREG,
+			              (duk_regconst_t) (reg_cs + 0),
+			              (duk_regconst_t) (reg_cs + 0));  /* in-place setup */
+		}
+
+		DUK__SETTEMP(comp_ctx, reg_cs + 2);
+		nargs = duk__parse_arguments(comp_ctx, res);  /* parse args starting from "next temp" */
+
+		/* Tailcalls are handled by back-patching the TAILCALL flag to the
+		 * already emitted instruction later (in return statement parser).
+		 * Since A and C have a special meaning here, they cannot be "shuffled".
+		 */
+
+		duk__emit_a_b_c(comp_ctx,
+		                DUK_OP_CALL | DUK__EMIT_FLAG_NO_SHUFFLE_A | DUK__EMIT_FLAG_NO_SHUFFLE_C,
+		                (duk_regconst_t) call_flags /*flags*/,
+		                (duk_regconst_t) reg_cs /*basereg*/,
+		                (duk_regconst_t) nargs /*numargs*/);
+		DUK__SETTEMP(comp_ctx, reg_cs + 1);    /* result in csreg */
+
+		res->t = DUK_IVAL_PLAIN;
+		res->x1.t = DUK_ISPEC_REGCONST;
+		res->x1.regconst = (duk_regconst_t) reg_cs;
+		return;
+	}
+
+	/* POSTFIX EXPRESSION */
+
+	case DUK_TOK_INCREMENT: {
+		args = (DUK_EXTRAOP_INC << 8) + 0;
+		goto postincdec_extraop;
+	}
+	case DUK_TOK_DECREMENT: {
+		args = (DUK_EXTRAOP_DEC << 8) + 0;
+		goto postincdec_extraop;
+	}
+
+	/* MULTIPLICATIVE EXPRESSION */
+
+	case DUK_TOK_MUL: {
+		args = (DUK_OP_MUL << 8) + DUK__BP_MULTIPLICATIVE;  /* UnaryExpression */
+		goto binary;
+	}
+	case DUK_TOK_DIV: {
+		args = (DUK_OP_DIV << 8) + DUK__BP_MULTIPLICATIVE;  /* UnaryExpression */
+		goto binary;
+	}
+	case DUK_TOK_MOD: {
+		args = (DUK_OP_MOD << 8) + DUK__BP_MULTIPLICATIVE;  /* UnaryExpression */
+		goto binary;
+	}
+
+	/* ADDITIVE EXPRESSION */
+
+	case DUK_TOK_ADD: {
+		args = (DUK_OP_ADD << 8) + DUK__BP_ADDITIVE;  /* MultiplicativeExpression */
+		goto binary;
+	}
+	case DUK_TOK_SUB: {
+		args = (DUK_OP_SUB << 8) + DUK__BP_ADDITIVE;  /* MultiplicativeExpression */
+		goto binary;
+	}
+
+	/* SHIFT EXPRESSION */
+
+	case DUK_TOK_ALSHIFT: {
+		/* << */
+		args = (DUK_OP_BASL << 8) + DUK__BP_SHIFT;
+		goto binary;
+	}
+	case DUK_TOK_ARSHIFT: {
+		/* >> */
+		args = (DUK_OP_BASR << 8) + DUK__BP_SHIFT;
+		goto binary;
+	}
+	case DUK_TOK_RSHIFT: {
+		/* >>> */
+		args = (DUK_OP_BLSR << 8) + DUK__BP_SHIFT;
+		goto binary;
+	}
+
+	/* RELATIONAL EXPRESSION */
+
+	case DUK_TOK_LT: {
+		/* < */
+		args = (DUK_OP_LT << 8) + DUK__BP_RELATIONAL;
+		goto binary;
+	}
+	case DUK_TOK_GT: {
+		args = (DUK_OP_GT << 8) + DUK__BP_RELATIONAL;
+		goto binary;
+	}
+	case DUK_TOK_LE: {
+		args = (DUK_OP_LE << 8) + DUK__BP_RELATIONAL;
+		goto binary;
+	}
+	case DUK_TOK_GE: {
+		args = (DUK_OP_GE << 8) + DUK__BP_RELATIONAL;
+		goto binary;
+	}
+	case DUK_TOK_INSTANCEOF: {
+		args = (DUK_OP_INSTOF << 8) + DUK__BP_RELATIONAL;
+		goto binary;
+	}
+	case DUK_TOK_IN: {
+		args = (DUK_OP_IN << 8) + DUK__BP_RELATIONAL;
+		goto binary;
+	}
+
+	/* EQUALITY EXPRESSION */
+
+	case DUK_TOK_EQ: {
+		args = (DUK_OP_EQ << 8) + DUK__BP_EQUALITY;
+		goto binary;
+	}
+	case DUK_TOK_NEQ: {
+		args = (DUK_OP_NEQ << 8) + DUK__BP_EQUALITY;
+		goto binary;
+	}
+	case DUK_TOK_SEQ: {
+		args = (DUK_OP_SEQ << 8) + DUK__BP_EQUALITY;
+		goto binary;
+	}
+	case DUK_TOK_SNEQ: {
+		args = (DUK_OP_SNEQ << 8) + DUK__BP_EQUALITY;
+		goto binary;
+	}
+
+	/* BITWISE EXPRESSIONS */
+
+	case DUK_TOK_BAND: {
+		args = (DUK_OP_BAND << 8) + DUK__BP_BAND;
+		goto binary;
+	}
+	case DUK_TOK_BXOR: {
+		args = (DUK_OP_BXOR << 8) + DUK__BP_BXOR;
+		goto binary;
+	}
+	case DUK_TOK_BOR: {
+		args = (DUK_OP_BOR << 8) + DUK__BP_BOR;
+		goto binary;
+	}
+
+	/* LOGICAL EXPRESSIONS */
+
+	case DUK_TOK_LAND: {
+		/* syntactically left-associative but parsed as right-associative */
+		args = (1 << 8) + DUK__BP_LAND - 1;
+		goto binary_logical;
+	}
+	case DUK_TOK_LOR: {
+		/* syntactically left-associative but parsed as right-associative */
+		args = (0 << 8) + DUK__BP_LOR - 1;
+		goto binary_logical;
+	}
+
+	/* CONDITIONAL EXPRESSION */
+
+	case DUK_TOK_QUESTION: {
+		/* XXX: common reg allocation need is to reuse a sub-expression's temp reg,
+		 * but only if it really is a temp.  Nothing fancy here now.
+		 */
+		duk_reg_t reg_temp;
+		duk_int_t pc_jump1;
+		duk_int_t pc_jump2;
+
+		reg_temp = DUK__ALLOCTEMP(comp_ctx);
+		duk__ivalue_toforcedreg(comp_ctx, left, reg_temp);
+		duk__emit_if_true_skip(comp_ctx, reg_temp);
+		pc_jump1 = duk__emit_jump_empty(comp_ctx);  /* jump to false */
+		duk__expr_toforcedreg(comp_ctx, res, DUK__BP_COMMA /*rbp_flags*/, reg_temp /*forced_reg*/);  /* AssignmentExpression */
+		duk__advance_expect(comp_ctx, DUK_TOK_COLON);
+		pc_jump2 = duk__emit_jump_empty(comp_ctx);  /* jump to end */
+		duk__patch_jump_here(comp_ctx, pc_jump1);
+		duk__expr_toforcedreg(comp_ctx, res, DUK__BP_COMMA /*rbp_flags*/, reg_temp /*forced_reg*/);  /* AssignmentExpression */
+		duk__patch_jump_here(comp_ctx, pc_jump2);
+
+		DUK__SETTEMP(comp_ctx, reg_temp + 1);
+		res->t = DUK_IVAL_PLAIN;
+		res->x1.t = DUK_ISPEC_REGCONST;
+		res->x1.regconst = (duk_regconst_t) reg_temp;
+		return;
+	}
+
+	/* ASSIGNMENT EXPRESSION */
+
+	case DUK_TOK_EQUALSIGN: {
+		/*
+		 *  Assignments are right associative, allows e.g.
+		 *    a = 5;
+		 *    a += b = 9;   // same as a += (b = 9)
+		 *  -> expression value 14, a = 14, b = 9
+		 *
+		 *  Right associativiness is reflected in the BP for recursion,
+		 *  "-1" ensures assignment operations are allowed.
+		 *
+		 *  XXX: just use DUK__BP_COMMA (i.e. no need for 2-step bp levels)?
+		 */
+		args = (DUK_OP_INVALID << 8) + DUK__BP_ASSIGNMENT - 1;   /* DUK_OP_INVALID marks a 'plain' assignment */
+		goto assign;
+	}
+	case DUK_TOK_ADD_EQ: {
+		/* right associative */
+		args = (DUK_OP_ADD << 8) + DUK__BP_ASSIGNMENT - 1;
+		goto assign;
+	}
+	case DUK_TOK_SUB_EQ: {
+		/* right associative */
+		args = (DUK_OP_SUB << 8) + DUK__BP_ASSIGNMENT - 1;
+		goto assign;
+	}
+	case DUK_TOK_MUL_EQ: {
+		/* right associative */
+		args = (DUK_OP_MUL << 8) + DUK__BP_ASSIGNMENT - 1;
+		goto assign;
+	}
+	case DUK_TOK_DIV_EQ: {
+		/* right associative */
+		args = (DUK_OP_DIV << 8) + DUK__BP_ASSIGNMENT - 1;
+		goto assign;
+	}
+	case DUK_TOK_MOD_EQ: {
+		/* right associative */
+		args = (DUK_OP_MOD << 8) + DUK__BP_ASSIGNMENT - 1;
+		goto assign;
+	}
+	case DUK_TOK_ALSHIFT_EQ: {
+		/* right associative */
+		args = (DUK_OP_BASL << 8) + DUK__BP_ASSIGNMENT - 1;
+		goto assign;
+	}
+	case DUK_TOK_ARSHIFT_EQ: {
+		/* right associative */
+		args = (DUK_OP_BASR << 8) + DUK__BP_ASSIGNMENT - 1;
+		goto assign;
+	}
+	case DUK_TOK_RSHIFT_EQ: {
+		/* right associative */
+		args = (DUK_OP_BLSR << 8) + DUK__BP_ASSIGNMENT - 1;
+		goto assign;
+	}
+	case DUK_TOK_BAND_EQ: {
+		/* right associative */
+		args = (DUK_OP_BAND << 8) + DUK__BP_ASSIGNMENT - 1;
+		goto assign;
+	}
+	case DUK_TOK_BOR_EQ: {
+		/* right associative */
+		args = (DUK_OP_BOR << 8) + DUK__BP_ASSIGNMENT - 1;
+		goto assign;
+	}
+	case DUK_TOK_BXOR_EQ: {
+		/* right associative */
+		args = (DUK_OP_BXOR << 8) + DUK__BP_ASSIGNMENT - 1;
+		goto assign;
+	}
+
+	/* COMMA */
+
+	case DUK_TOK_COMMA: {
+		/* right associative */
+
+		duk__ivalue_toplain_ignore(comp_ctx, left);  /* need side effects, not value */
+		duk__expr_toplain(comp_ctx, res, DUK__BP_COMMA - 1 /*rbp_flags*/);
+
+		/* return 'res' (of right part) as our result */
+		return;
+	}
+
+	default: {
+		break;
+	}
+	}
+
+	DUK_D(DUK_DPRINT("parse error: unexpected token: %ld", (long) tok));
+	DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_PARSE_ERROR);
+	return;
+
+#if 0
+	/* XXX: shared handling for 'duk__expr_lhs'? */
+	if (comp_ctx->curr_func.paren_level == 0 && XXX) {
+		comp_ctx->curr_func.duk__expr_lhs = 0;
+	}
+#endif
+
+ binary:
+	/*
+	 *  Shared handling of binary operations
+	 *
+	 *  args = (opcode << 8) + rbp
+	 */
+	{
+		duk__ivalue_toplain(comp_ctx, left);
+		duk__expr_toplain(comp_ctx, res, args & 0xff /*rbp_flags*/);
+
+		/* combine left->x1 and res->x1 (right->x1, really) -> (left->x1 OP res->x1) */
+		DUK_ASSERT(left->t == DUK_IVAL_PLAIN);
+		DUK_ASSERT(res->t == DUK_IVAL_PLAIN);
+
+		res->t = DUK_IVAL_ARITH;
+		res->op = args >> 8;
+
+		res->x2.t = res->x1.t;
+		res->x2.regconst = res->x1.regconst;
+		duk_copy(ctx, res->x1.valstack_idx, res->x2.valstack_idx);
+
+		res->x1.t = left->x1.t;
+		res->x1.regconst = left->x1.regconst;
+		duk_copy(ctx, left->x1.valstack_idx, res->x1.valstack_idx);
+
+		DUK_DDD(DUK_DDDPRINT("binary op, res: t=%ld, x1.t=%ld, x2.t=%ld",
+		                     (long) res->t, (long) res->x1.t, (long) res->x2.t));
+		return;
+	}
+
+ binary_logical:
+	/*
+	 *  Shared handling for logical AND and logical OR.
+	 *
+	 *  args = (truthval << 8) + rbp
+	 *
+	 *  Truthval determines when to skip right-hand-side.
+	 *  For logical AND truthval=1, for logical OR truthval=0.
+	 *
+	 *  See doc/compiler.txt for discussion on compiling logical
+	 *  AND and OR expressions.  The approach here is very simplistic,
+	 *  generating extra jumps and multiple evaluations of truth values,
+	 *  but generates code on-the-fly with only local back-patching.
+	 *
+	 *  Both logical AND and OR are syntactically left-associated.
+	 *  However, logical ANDs are compiled as right associative
+	 *  expressions, i.e. "A && B && C" as "A && (B && C)", to allow
+	 *  skip jumps to skip over the entire tail.  Similarly for logical OR.
+	 */
+
+	{
+		duk_reg_t reg_temp;
+		duk_int_t pc_jump;
+		duk_small_uint_t args_truthval = args >> 8;
+		duk_small_uint_t args_rbp = args & 0xff;
+
+		/* XXX: unoptimal use of temps, resetting */
+
+		reg_temp = DUK__ALLOCTEMP(comp_ctx);
+
+		duk__ivalue_toforcedreg(comp_ctx, left, reg_temp);
+		duk__emit_a_b(comp_ctx,
+		              DUK_OP_IF,
+		              (duk_regconst_t) args_truthval,
+		              (duk_regconst_t) reg_temp);  /* skip jump conditionally */
+		pc_jump = duk__emit_jump_empty(comp_ctx);
+		duk__expr_toforcedreg(comp_ctx, res, args_rbp /*rbp_flags*/, reg_temp /*forced_reg*/);
+		duk__patch_jump_here(comp_ctx, pc_jump);
+
+		res->t = DUK_IVAL_PLAIN;
+		res->x1.t = DUK_ISPEC_REGCONST;
+		res->x1.regconst = (duk_regconst_t) reg_temp;
+		return;
+	}
+
+ assign:
+	/*
+	 *  Shared assignment expression handling
+	 *
+	 *  args = (opcode << 8) + rbp
+	 *
+	 *  If 'opcode' is DUK_OP_INVALID, plain assignment without arithmetic.
+	 *  Syntactically valid left-hand-side forms which are not accepted as
+	 *  left-hand-side values (e.g. as in "f() = 1") must NOT cause a
+	 *  SyntaxError, but rather a run-time ReferenceError.
+	 */
+
+	{
+		duk_small_uint_t args_op = args >> 8;
+		duk_small_uint_t args_rbp = args & 0xff;
+
+		/* XXX: here we need to know if 'left' is left-hand-side compatible.
+		 * That information is no longer available from current expr parsing
+		 * state; it would need to be carried into the 'left' ivalue or by
+		 * some other means.
+		 */
+
+		if (left->t == DUK_IVAL_VAR) {
+			duk_hstring *h_varname;
+			duk_reg_t reg_varbind;
+			duk_regconst_t rc_varname;
+			duk_regconst_t rc_res;
+			duk_reg_t reg_temp;
+
+			/* already in fluly evaluated form */
+			DUK_ASSERT(left->x1.t == DUK_ISPEC_VALUE);
+
+			duk__expr_toreg(comp_ctx, res, args_rbp /*rbp_flags*/);
+			DUK_ASSERT(res->t == DUK_IVAL_PLAIN && res->x1.t == DUK_ISPEC_REGCONST);
+
+			h_varname = duk_get_hstring(ctx, left->x1.valstack_idx);
+			DUK_ASSERT(h_varname != NULL);
+
+			/* E5 Section 11.13.1 (and others for other assignments), step 4 */
+			if (duk__hstring_is_eval_or_arguments_in_strict_mode(comp_ctx, h_varname)) {
+				goto syntax_error_lvalue;
+			}
+
+			duk_dup(ctx, left->x1.valstack_idx);
+			(void) duk__lookup_lhs(comp_ctx, &reg_varbind, &rc_varname);
+
+			DUK_DDD(DUK_DDDPRINT("assign to '%!O' -> reg_varbind=%ld, rc_varname=%ld",
+			                     (duk_heaphdr *) h_varname, (long) reg_varbind, (long) rc_varname));
+
+			if (args_op == DUK_OP_INVALID) {
+				rc_res = res->x1.regconst;
+			} else {
+				reg_temp = DUK__ALLOCTEMP(comp_ctx);
+				if (reg_varbind >= 0) {
+					duk__emit_a_b_c(comp_ctx,
+					                args_op,
+					                (duk_regconst_t) reg_temp,
+					                (duk_regconst_t) reg_varbind,
+					                res->x1.regconst);
+				} else {
+					duk__emit_a_bc(comp_ctx,
+					               DUK_OP_GETVAR,
+					               (duk_regconst_t) reg_temp,
+					               rc_varname);
+					duk__emit_a_b_c(comp_ctx,
+					                args_op,
+					                (duk_regconst_t) reg_temp,
+					                (duk_regconst_t) reg_temp,
+					                res->x1.regconst);
+				}
+				rc_res = (duk_regconst_t) reg_temp;
+			}
+
+			if (reg_varbind >= 0) {
+				duk__emit_a_bc(comp_ctx,
+				               DUK_OP_LDREG,
+				               (duk_regconst_t) reg_varbind,
+				               rc_res);
+			} else {
+				/* Only a reg fits into 'A' and reg_res may be a const in
+				 * straight assignment.
+				 *
+				 * XXX: here the current A/B/C split is suboptimal: we could
+				 * just use 9 bits for reg_res (and support constants) and 17
+				 * instead of 18 bits for the varname const index.
+				 */
+				if (DUK__ISCONST(comp_ctx, rc_res)) {
+					reg_temp = DUK__ALLOCTEMP(comp_ctx);
+					duk__emit_a_bc(comp_ctx,
+					               DUK_OP_LDCONST,
+					               (duk_regconst_t) reg_temp,
+					               rc_res);
+					rc_res = (duk_regconst_t) reg_temp;
+				}
+				duk__emit_a_bc(comp_ctx,
+				               DUK_OP_PUTVAR | DUK__EMIT_FLAG_A_IS_SOURCE,
+				               rc_res,
+				               rc_varname);
+			}
+
+			res->t = DUK_IVAL_PLAIN;
+			res->x1.t = DUK_ISPEC_REGCONST;
+			res->x1.regconst = rc_res;
+		} else if (left->t == DUK_IVAL_PROP) {
+			/* E5 Section 11.13.1 (and others) step 4 never matches for prop writes -> no check */
+			duk_reg_t reg_obj;
+			duk_regconst_t rc_key;
+			duk_regconst_t rc_res;
+			duk_reg_t reg_temp;
+
+			duk__expr_toregconst(comp_ctx, res, args_rbp /*rbp_flags*/);
+			DUK_ASSERT(res->t == DUK_IVAL_PLAIN && res->x1.t == DUK_ISPEC_REGCONST);
+
+			/* Don't allow a constant for the object (even for a number etc), as
+			 * it goes into the 'A' field of the opcode.
+			 */
+
+			reg_obj = duk__ispec_toregconst_raw(comp_ctx, &left->x1, -1 /*forced_reg*/, 0 /*flags*/);  /* don't allow const */
+			rc_key = duk__ispec_toregconst_raw(comp_ctx, &left->x2, -1 /*forced_reg*/, DUK__IVAL_FLAG_ALLOW_CONST /*flags*/);
+	
+			if (args_op == DUK_OP_INVALID) {
+				rc_res = res->x1.regconst;
+			} else {
+				reg_temp = DUK__ALLOCTEMP(comp_ctx);
+				duk__emit_a_b_c(comp_ctx,
+				                DUK_OP_GETPROP,
+				                (duk_regconst_t) reg_temp,
+				                (duk_regconst_t) reg_obj,
+				                rc_key);
+				duk__emit_a_b_c(comp_ctx,
+				                args_op,
+				                (duk_regconst_t) reg_temp,
+				                (duk_regconst_t) reg_temp,
+				                res->x1.regconst);
+				rc_res = (duk_regconst_t) reg_temp;
+			}
+
+			duk__emit_a_b_c(comp_ctx,
+			                DUK_OP_PUTPROP,
+			                (duk_regconst_t) reg_obj,
+			                rc_key,
+			                rc_res);
+
+			res->t = DUK_IVAL_PLAIN;
+			res->x1.t = DUK_ISPEC_REGCONST;
+			res->x1.regconst = rc_res;
+		} else {
+			/* No support for lvalues returned from new or function call expressions.
+			 * However, these must NOT cause compile-time SyntaxErrors, but run-time
+			 * ReferenceErrors.  Both left and right sides of the assignment must be
+			 * evaluated before throwing a ReferenceError.  For instance:
+			 *
+			 *     f() = g();
+			 *
+			 * must result in f() being evaluated, then g() being evaluated, and
+			 * finally, a ReferenceError being thrown.  See E5 Section 11.13.1.
+			 */
+
+			duk_regconst_t rc_res;
+
+			/* first evaluate LHS fully to ensure all side effects are out */
+			duk__ivalue_toplain_ignore(comp_ctx, left);
+
+			/* then evaluate RHS fully (its value becomes the expression value too) */
+			rc_res = duk__expr_toregconst(comp_ctx, res, args_rbp /*rbp_flags*/);
+	
+			duk__emit_extraop_only(comp_ctx,
+			                       DUK_EXTRAOP_INVLHS);
+
+			/* XXX: this value is irrelevant because of INVLHS? */
+
+			res->t = DUK_IVAL_PLAIN;
+			res->x1.t = DUK_ISPEC_REGCONST;
+			res->x1.regconst = rc_res;
+		}
+
+		return;
+	}
+
+ postincdec_extraop:
+	{
+		/*
+		 *  Post-increment/decrement will return the original value as its
+		 *  result value.  However, even that value will be coerced using
+		 *  ToNumber().
+		 *
+		 *  XXX: the current solution for this is very ugly.
+		 *
+		 *  Note that post increment/decrement has a "no LineTerminator here"
+		 *  restriction.  This is handled by duk__expr_lbp(), which forcibly terminates
+		 *  the previous expression if a LineTerminator occurs before '++'/'--'.
+		 */
+
+		duk_reg_t reg_res;
+		duk_small_uint_t args_op = args >> 8;
+
+		reg_res = DUK__ALLOCTEMP(comp_ctx);
+
+		if (left->t == DUK_IVAL_VAR) {
+			duk_hstring *h_varname;
+			duk_reg_t reg_varbind;
+			duk_regconst_t rc_varname;
+
+			h_varname = duk_get_hstring(ctx, left->x1.valstack_idx);
+			DUK_ASSERT(h_varname != NULL);
+
+			if (duk__hstring_is_eval_or_arguments_in_strict_mode(comp_ctx, h_varname)) {
+				goto syntax_error;
+			}
+
+			duk_dup(ctx, left->x1.valstack_idx);
+			if (duk__lookup_lhs(comp_ctx, &reg_varbind, &rc_varname)) {
+				duk__emit_a_bc(comp_ctx,
+				               DUK_OP_LDREG,
+				               (duk_regconst_t) reg_res,
+				               (duk_regconst_t) reg_varbind);
+				duk__emit_extraop_b_c(comp_ctx,
+				                      DUK_EXTRAOP_TONUM | DUK__EMIT_FLAG_B_IS_TARGET,
+				                      (duk_regconst_t) reg_res,
+				                      (duk_regconst_t) reg_res);
+				duk__emit_extraop_b_c(comp_ctx,
+				                      args_op | DUK__EMIT_FLAG_B_IS_TARGET,
+				                      (duk_regconst_t) reg_varbind,
+				                      (duk_regconst_t) reg_res);
+			} else {
+				duk_reg_t reg_temp = DUK__ALLOCTEMP(comp_ctx);
+				duk__emit_a_bc(comp_ctx,
+				               DUK_OP_GETVAR,
+				               (duk_regconst_t) reg_res,
+				               rc_varname);
+				duk__emit_extraop_b_c(comp_ctx,
+				                      DUK_EXTRAOP_TONUM | DUK__EMIT_FLAG_B_IS_TARGET,
+				                      (duk_regconst_t) reg_res,
+				                      (duk_regconst_t) reg_res);
+				duk__emit_extraop_b_c(comp_ctx,
+				                      args_op | DUK__EMIT_FLAG_B_IS_TARGET,
+				                      (duk_regconst_t) reg_temp,
+				                      (duk_regconst_t) reg_res);
+				duk__emit_a_bc(comp_ctx,
+				               DUK_OP_PUTVAR | DUK__EMIT_FLAG_A_IS_SOURCE,
+				               (duk_regconst_t) reg_temp,
+				               rc_varname);
+			}
+
+			DUK_DDD(DUK_DDDPRINT("postincdec to '%!O' -> reg_varbind=%ld, rc_varname=%ld",
+			                     (duk_heaphdr *) h_varname, (long) reg_varbind, (long) rc_varname));
+		} else if (left->t == DUK_IVAL_PROP) {
+			duk_reg_t reg_obj;  /* allocate to reg only (not const) */
+			duk_regconst_t rc_key;
+			duk_reg_t reg_temp = DUK__ALLOCTEMP(comp_ctx);
+
+			reg_obj = duk__ispec_toregconst_raw(comp_ctx, &left->x1, -1 /*forced_reg*/, 0 /*flags*/);  /* don't allow const */
+			rc_key = duk__ispec_toregconst_raw(comp_ctx, &left->x2, -1 /*forced_reg*/, DUK__IVAL_FLAG_ALLOW_CONST /*flags*/);
+			duk__emit_a_b_c(comp_ctx,
+			                DUK_OP_GETPROP, 
+			                (duk_regconst_t) reg_res,
+			                (duk_regconst_t) reg_obj,
+			                rc_key);
+			duk__emit_extraop_b_c(comp_ctx,
+			                      DUK_EXTRAOP_TONUM | DUK__EMIT_FLAG_B_IS_TARGET,
+			                      (duk_regconst_t) reg_res,
+			                      (duk_regconst_t) reg_res);
+			duk__emit_extraop_b_c(comp_ctx,
+			                      args_op | DUK__EMIT_FLAG_B_IS_TARGET,
+			                      (duk_regconst_t) reg_temp,
+			                      (duk_regconst_t) reg_res);
+			duk__emit_a_b_c(comp_ctx,
+			                DUK_OP_PUTPROP,
+			                (duk_regconst_t) reg_obj,
+			                rc_key,
+			                (duk_regconst_t) reg_temp);
+		} else {
+			/* Technically return value is not needed because INVLHS will
+			 * unconditially throw a ReferenceError.  Coercion is necessary
+			 * for proper semantics (consider ToNumber() called for an object).
+			 */
+			duk__ivalue_toforcedreg(comp_ctx, left, reg_res);
+			duk__emit_extraop_b_c(comp_ctx,
+			                      DUK_EXTRAOP_TONUM | DUK__EMIT_FLAG_B_IS_TARGET,
+			                      (duk_regconst_t) reg_res,
+			                      (duk_regconst_t) reg_res);  /* for side effects */
+			duk__emit_extraop_only(comp_ctx,
+			                       DUK_EXTRAOP_INVLHS);
+		}
+
+		res->t = DUK_IVAL_PLAIN;
+		res->x1.t = DUK_ISPEC_REGCONST;
+		res->x1.regconst = (duk_regconst_t) reg_res;
+		DUK__SETTEMP(comp_ctx, reg_res + 1);
+		return;
+	}
+
+ syntax_error:
+	DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_INVALID_EXPRESSION);
+	return;
+
+ syntax_error_lvalue:
+	DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_INVALID_LVALUE);
+	return;
+}
+
+static duk_small_uint_t duk__expr_lbp(duk_compiler_ctx *comp_ctx) {
+	duk_small_int_t tok = comp_ctx->curr_token.t;
+
+	DUK_ASSERT(tok >= DUK_TOK_MINVAL && tok <= DUK_TOK_MAXVAL);
+	DUK_ASSERT(sizeof(duk__token_lbp) == DUK_TOK_MAXVAL + 1);
+
+	/* XXX: integrate support for this into led() instead?
+	 * Similar issue as post-increment/post-decrement.
+	 */
+
+	/* prevent duk__expr_led() by using a binding power less than anything valid */
+	if (tok == DUK_TOK_IN && !comp_ctx->curr_func.allow_in) {
+		return 0;
+	}
+
+	if ((tok == DUK_TOK_DECREMENT || tok == DUK_TOK_INCREMENT) &&
+	    (comp_ctx->curr_token.lineterm)) {
+		/* '++' or '--' in a post-increment/decrement position,
+		 * and a LineTerminator occurs between the operator and
+		 * the preceding expression.  Force the previous expr
+		 * to terminate, in effect treating e.g. "a,b\n++" as
+		 * "a,b;++" (= SyntaxError).
+		 */
+		return 0;
+	}
+
+	return DUK__TOKEN_LBP_GET_BP(duk__token_lbp[tok]);  /* format is bit packed */
+}
+
+/*
+ *  Expression parsing.
+ *
+ *  Upon entry to 'expr' and its variants, 'curr_tok' is assumed to be the
+ *  first token of the expression.  Upon exit, 'curr_tok' will be the first
+ *  token not part of the expression (e.g. semicolon terminating an expression
+ *  statement).
+ */
+
+#define DUK__EXPR_RBP_MASK           0xff
+#define DUK__EXPR_FLAG_REJECT_IN     (1 << 8)
+#define DUK__EXPR_FLAG_ALLOW_EMPTY   (1 << 9)
+
+/* main expression parser function */
+static void duk__expr(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_ivalue tmp_alloc;   /* 'res' is used for "left", and 'tmp' for "right" */
+	duk_ivalue *tmp = &tmp_alloc;
+	duk_small_uint_t rbp;
+
+	DUK__RECURSION_INCREASE(comp_ctx, thr);
+
+	duk_require_stack(ctx, DUK__PARSE_EXPR_SLOTS);
+
+	/* filter out flags from exprtop rbp_flags here to save space */
+	rbp = rbp_flags & DUK__EXPR_RBP_MASK;
+
+	DUK_DDD(DUK_DDDPRINT("duk__expr(), rbp_flags=%ld, rbp=%ld, allow_in=%ld, paren_level=%ld",
+	                     (long) rbp_flags, (long) rbp, (long) comp_ctx->curr_func.allow_in,
+	                     (long) comp_ctx->curr_func.paren_level));
+
+	DUK_MEMZERO(&tmp_alloc, sizeof(tmp_alloc));
+	tmp->x1.valstack_idx = duk_get_top(ctx);
+	tmp->x2.valstack_idx = tmp->x1.valstack_idx + 1;
+	duk_push_undefined(ctx);
+	duk_push_undefined(ctx);
+
+	/* XXX: where to release temp regs in intermediate expressions?
+	 * e.g. 1+2+3 -> don't inflate temp register count when parsing this.
+	 * that particular expression temp regs can be forced here.
+	 */
+
+	/* XXX: increase ctx->expr_tokens here for every consumed token
+	 * (this would be a nice statistic)?
+	 */
+
+	if (comp_ctx->curr_token.t == DUK_TOK_SEMICOLON || comp_ctx->curr_token.t == DUK_TOK_RPAREN) {
+		/* XXX: possibly incorrect handling of empty expression */
+		DUK_DDD(DUK_DDDPRINT("empty expression"));
+		if (!(rbp_flags & DUK__EXPR_FLAG_ALLOW_EMPTY)) {
+			DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_EMPTY_EXPR_NOT_ALLOWED);
+		}
+		res->t = DUK_IVAL_PLAIN;
+		res->x1.t = DUK_ISPEC_VALUE;
+		duk_push_undefined(ctx);
+		duk_replace(ctx, res->x1.valstack_idx);
+		goto cleanup;
+	}
+
+	duk__advance(comp_ctx);
+	duk__expr_nud(comp_ctx, res);  /* reuse 'res' as 'left' */
+	while (rbp < duk__expr_lbp(comp_ctx)) {
+		duk__advance(comp_ctx);
+		duk__expr_led(comp_ctx, res, tmp);
+		duk__copy_ivalue(comp_ctx, tmp, res);  /* tmp -> res */
+	}
+
+ cleanup:
+	/* final result is already in 'res' */
+
+	duk_pop_2(ctx);
+
+	DUK__RECURSION_DECREASE(comp_ctx, thr);
+}
+
+static void duk__exprtop(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags) {
+	duk_hthread *thr = comp_ctx->thr;
+
+	/* Note: these variables must reside in 'curr_func' instead of the global
+	 * context: when parsing function expressions, expression parsing is nested.
+	 */
+	comp_ctx->curr_func.nud_count = 0;
+	comp_ctx->curr_func.led_count = 0;
+	comp_ctx->curr_func.paren_level = 0;
+	comp_ctx->curr_func.expr_lhs = 1;
+	comp_ctx->curr_func.allow_in = (rbp_flags & DUK__EXPR_FLAG_REJECT_IN ? 0 : 1);
+
+	duk__expr(comp_ctx, res, rbp_flags);
+
+	if (!(rbp_flags & DUK__EXPR_FLAG_ALLOW_EMPTY) && duk__expr_is_empty(comp_ctx)) {
+		DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_EMPTY_EXPR_NOT_ALLOWED);
+	}
+}
+
+/* A bunch of helpers (for size optimization) that combine duk__expr()/duk__exprtop()
+ * and result conversions.
+ *
+ * Each helper needs at least 2-3 calls to make it worth while to wrap.
+ */
+
+static duk_reg_t duk__expr_toreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags) {
+	duk__expr(comp_ctx, res, rbp_flags);
+	return duk__ivalue_toreg(comp_ctx, res);
+}
+
+#if 0  /* unused */
+static duk_reg_t duk__expr_totempreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags) {
+	duk__expr(comp_ctx, res, rbp_flags);
+	return duk__ivalue_totempreg(comp_ctx, res);
+}
+#endif
+
+static void duk__expr_toforcedreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags, duk_reg_t forced_reg) {
+	DUK_ASSERT(forced_reg >= 0);
+	duk__expr(comp_ctx, res, rbp_flags);
+	duk__ivalue_toforcedreg(comp_ctx, res, forced_reg);
+}
+
+static duk_regconst_t duk__expr_toregconst(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags) {
+	duk__expr(comp_ctx, res, rbp_flags);
+	return duk__ivalue_toregconst(comp_ctx, res);
+}
+
+static void duk__expr_toplain(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags) {
+	duk__expr(comp_ctx, res, rbp_flags);
+	duk__ivalue_toplain(comp_ctx, res);
+}
+
+static void duk__expr_toplain_ignore(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags) {
+	duk__expr(comp_ctx, res, rbp_flags);
+	duk__ivalue_toplain_ignore(comp_ctx, res);
+}
+
+static duk_reg_t duk__exprtop_toreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags) {
+	duk__exprtop(comp_ctx, res, rbp_flags);
+	return duk__ivalue_toreg(comp_ctx, res);
+}
+
+#if 0  /* unused */
+static duk_reg_t duk__exprtop_totempreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags) {
+	duk__exprtop(comp_ctx, res, rbp_flags);
+	return duk__ivalue_totempreg(comp_ctx, res);
+}
+#endif
+
+#if 0  /* unused */
+static void duk__exprtop_toforcedreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags, duk_reg_t forced_reg) {
+	DUK_ASSERT(forced_reg >= 0);
+	duk__exprtop(comp_ctx, res, rbp_flags);
+	duk__ivalue_toforcedreg(comp_ctx, res, forced_reg);
+}
+#endif
+
+static duk_regconst_t duk__exprtop_toregconst(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags) {
+	duk__exprtop(comp_ctx, res, rbp_flags);
+	return duk__ivalue_toregconst(comp_ctx, res);
+}
+
+#if 0  /* unused */
+static void duk__exprtop_toplain_ignore(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int rbp_flags) {
+	duk__exprtop(comp_ctx, res, rbp_flags);
+	duk__ivalue_toplain_ignore(comp_ctx, res);
+}
+#endif
+
+/*
+ *  Parse an individual source element (top level statement) or a statement.
+ *
+ *  Handles labeled statements automatically (peeling away labels before
+ *  parsing an expression that follows the label(s)).
+ *
+ *  Upon entry, 'curr_tok' contains the first token of the statement (parsed
+ *  in "allow regexp literal" mode).  Upon exit, 'curr_tok' contains the first
+ *  token following the statement (if the statement has a terminator, this is
+ *  the token after the terminator).
+ */
+
+#ifdef DUK__HAS_VAL
+#undef DUK__HAS_VAL
+#endif
+#ifdef DUK__HAS_TERM
+#undef DUK__HAS_TERM
+#endif
+#ifdef DUK__ALLOW_AUTO_SEMI_ALWAYS
+#undef DUK__ALLOW_AUTO_SEMI_ALWAYS
+#endif
+#ifdef DUK__STILL_PROLOGUE
+#undef DUK__STILL_PROLOGUE
+#endif
+#ifdef DUK__IS_TERMINAL
+#undef DUK__IS_TERMINAL
+#endif
+
+#define DUK__HAS_VAL                  (1 << 0)  /* stmt has non-empty value */
+#define DUK__HAS_TERM                 (1 << 1)  /* stmt has explicit/implicit semicolon terminator */
+#define DUK__ALLOW_AUTO_SEMI_ALWAYS   (1 << 2)  /* allow automatic semicolon even without lineterm (compatibility) */
+#define DUK__STILL_PROLOGUE           (1 << 3)  /* statement does not terminate directive prologue */
+#define DUK__IS_TERMINAL              (1 << 4)  /* statement is guaranteed to be terminal (control doesn't flow to next statement) */
+
+/* Parse a single variable declaration (e.g. "i" or "i=10").  A leading 'var'
+ * has already been eaten.  These is no return value in 'res', it is used only
+ * as a temporary.
+ *
+ * When called from 'for-in' statement parser, the initializer expression must
+ * not allow the 'in' token.  The caller supply additional expression parsing
+ * flags (like DUK__EXPR_FLAG_REJECT_IN) in 'expr_flags'.
+ *
+ * Finally, out_rc_varname and out_reg_varbind are updated to reflect where
+ * the identifier is bound:
+ *
+ *    If register bound:      out_reg_varbind >= 0, out_rc_varname == 0 (ignore)
+ *    If not register bound:  out_reg_varbind < 0, out_rc_varname >= 0
+ *
+ * These allow the caller to use the variable for further assignment, e.g.
+ * as is done in 'for-in' parsing.
+ */
+
+static void duk__parse_var_decl(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t expr_flags, duk_reg_t *out_reg_varbind, duk_regconst_t *out_rc_varname) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_hstring *h_varname;
+	duk_reg_t reg_varbind;
+	duk_regconst_t rc_varname;
+
+	/* assume 'var' has been eaten */
+
+	/* Note: Identifier rejects reserved words */
+	if (comp_ctx->curr_token.t != DUK_TOK_IDENTIFIER) {
+		goto syntax_error;
+	}
+	h_varname = comp_ctx->curr_token.str1;
+
+	DUK_ASSERT(h_varname != NULL);
+
+	/* strict mode restrictions (E5 Section 12.2.1) */
+	if (duk__hstring_is_eval_or_arguments_in_strict_mode(comp_ctx, h_varname)) {
+		goto syntax_error;
+	}
+
+	/* register declarations in first pass */
+	if (comp_ctx->curr_func.in_scanning) {
+		duk_uarridx_t n;
+		DUK_DDD(DUK_DDDPRINT("register variable declaration %!O in pass 1",
+		                     (duk_heaphdr *) h_varname));
+		n = (duk_uarridx_t) duk_get_length(ctx, comp_ctx->curr_func.decls_idx);
+		duk_push_hstring(ctx, h_varname);
+		duk_put_prop_index(ctx, comp_ctx->curr_func.decls_idx, n);
+		duk_push_int(ctx, DUK_DECL_TYPE_VAR + (0 << 8));
+		duk_put_prop_index(ctx, comp_ctx->curr_func.decls_idx, n + 1);
+	}
+
+	duk_push_hstring(ctx, h_varname);  /* push before advancing to keep reachable */
+
+	/* register binding lookup is based on varmap (even in first pass) */
+	duk_dup_top(ctx);
+	(void) duk__lookup_lhs(comp_ctx, &reg_varbind, &rc_varname);
+
+	duk__advance(comp_ctx);  /* eat identifier */
+
+	if (comp_ctx->curr_token.t == DUK_TOK_EQUALSIGN) {
+		duk__advance(comp_ctx);
+
+		DUK_DDD(DUK_DDDPRINT("vardecl, assign to '%!O' -> reg_varbind=%ld, rc_varname=%ld",
+		                     (duk_heaphdr *) h_varname, (long) reg_varbind, (long) rc_varname));
+
+		duk__exprtop(comp_ctx, res, DUK__BP_COMMA | expr_flags /*rbp_flags*/);  /* AssignmentExpression */
+
+		if (reg_varbind >= 0) {
+			duk__ivalue_toforcedreg(comp_ctx, res, reg_varbind);
+		} else {
+			duk_reg_t reg_val;
+			reg_val = duk__ivalue_toreg(comp_ctx, res);
+			duk__emit_a_bc(comp_ctx,
+			               DUK_OP_PUTVAR | DUK__EMIT_FLAG_A_IS_SOURCE,
+			               (duk_regconst_t) reg_val,
+			               rc_varname);
+		}
+	}
+
+	duk_pop(ctx);  /* pop varname */
+
+	*out_rc_varname = rc_varname;
+	*out_reg_varbind = reg_varbind;
+
+	return;
+
+ syntax_error:
+	DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_INVALID_VAR_DECLARATION);
+}
+
+static void duk__parse_var_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res) {
+	duk_reg_t reg_varbind;
+	duk_regconst_t rc_varname;
+
+	duk__advance(comp_ctx);  /* eat 'var' */
+
+	for (;;) {
+		/* rc_varname and reg_varbind are ignored here */
+		duk__parse_var_decl(comp_ctx, res, 0, &reg_varbind, &rc_varname);
+
+		if (comp_ctx->curr_token.t != DUK_TOK_COMMA) {
+			break;
+		}
+		duk__advance(comp_ctx);
+	} 
+}
+
+static void duk__parse_for_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_int_t pc_label_site) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_int_t pc_v34_lhs;    /* start variant 3/4 left-hand-side code (L1 in doc/compiler.txt example) */
+	duk_reg_t temp_reset;    /* knock back "next temp" to this whenever possible */
+	duk_reg_t reg_temps;     /* preallocated temporaries (2) for variants 3 and 4 */
+
+	DUK_DDD(DUK_DDDPRINT("start parsing a for/for-in statement"));
+
+	/* Two temporaries are preallocated here for variants 3 and 4 which need
+	 * registers which are never clobbered by expressions in the loop
+	 * (concretely: for the enumerator object and the next enumerated value).
+	 * Variants 1 and 2 "release" these temps.
+	 */
+
+	reg_temps = DUK__ALLOCTEMPS(comp_ctx, 2);
+
+	temp_reset = DUK__GETTEMP(comp_ctx);
+
+	/*
+	 *  For/for-in main variants are:
+	 *
+	 *    1. for (ExpressionNoIn_opt; Expression_opt; Expression_opt) Statement
+	 *    2. for (var VariableDeclarationNoIn; Expression_opt; Expression_opt) Statement
+	 *    3. for (LeftHandSideExpression in Expression) Statement
+	 *    4. for (var VariableDeclarationNoIn in Expression) Statement
+	 *
+	 *  Parsing these without arbitrary lookahead or backtracking is relatively
+	 *  tricky but we manage to do so for now.
+	 *
+	 *  See doc/compiler.txt for a detailed discussion of control flow
+	 *  issues, evaluation order issues, etc.
+	 */
+	
+	duk__advance(comp_ctx);  /* eat 'for' */
+	duk__advance_expect(comp_ctx, DUK_TOK_LPAREN);
+
+	DUK_DDD(DUK_DDDPRINT("detecting for/for-in loop variant, pc=%ld", (long) duk__get_current_pc(comp_ctx)));
+
+	/* a label site has been emitted by duk__parse_stmt() automatically
+	 * (it will also emit the ENDLABEL).
+	 */
+
+	if (comp_ctx->curr_token.t == DUK_TOK_VAR) {
+		/*
+		 *  Variant 2 or 4
+		 */
+
+		duk_reg_t reg_varbind;       /* variable binding register if register-bound (otherwise < 0) */
+		duk_regconst_t rc_varname;   /* variable name reg/const, if variable not register-bound */
+
+		duk__advance(comp_ctx);  /* eat 'var' */
+		duk__parse_var_decl(comp_ctx, res, DUK__EXPR_FLAG_REJECT_IN, &reg_varbind, &rc_varname);
+		DUK__SETTEMP(comp_ctx, temp_reset);
+
+		if (comp_ctx->curr_token.t == DUK_TOK_IN) {
+			/*
+			 *  Variant 4
+			 */
+
+			DUK_DDD(DUK_DDDPRINT("detected for variant 4: for (var VariableDeclarationNoIn in Expression) Statement"));
+			pc_v34_lhs = duk__get_current_pc(comp_ctx);  /* jump is inserted here */
+			if (reg_varbind >= 0) {
+				duk__emit_a_bc(comp_ctx,
+				               DUK_OP_LDREG,
+				               (duk_regconst_t) reg_varbind,
+				               (duk_regconst_t) (reg_temps + 0));
+			} else {
+				duk__emit_a_bc(comp_ctx,
+				               DUK_OP_PUTVAR | DUK__EMIT_FLAG_A_IS_SOURCE,
+				               (duk_regconst_t) (reg_temps + 0),
+				               rc_varname);
+			}
+			goto parse_3_or_4;
+		} else {
+			/*
+			 *  Variant 2
+			 */
+
+			DUK_DDD(DUK_DDDPRINT("detected for variant 2: for (var VariableDeclarationNoIn; Expression_opt; Expression_opt) Statement"));
+			for (;;) {
+				/* more initializers */
+				if (comp_ctx->curr_token.t != DUK_TOK_COMMA) {
+					break;
+				}
+				DUK_DDD(DUK_DDDPRINT("variant 2 has another variable initializer"));
+
+				duk__advance(comp_ctx);  /* eat comma */
+				duk__parse_var_decl(comp_ctx, res, DUK__EXPR_FLAG_REJECT_IN, &reg_varbind, &rc_varname);
+			}
+			goto parse_1_or_2;
+		}
+	} else {
+		/*
+		 *  Variant 1 or 3
+		 */
+
+		pc_v34_lhs = duk__get_current_pc(comp_ctx);  /* jump is inserted here (variant 3) */
+
+		/* Note that duk__exprtop() here can clobber any reg above current temp_next,
+		 * so any loop variables (e.g. enumerator) must be "preallocated".
+		 */
+
+		/* don't coerce yet to a plain value (variant 3 needs special handling) */
+		duk__exprtop(comp_ctx, res, DUK__BP_FOR_EXPR | DUK__EXPR_FLAG_REJECT_IN | DUK__EXPR_FLAG_ALLOW_EMPTY /*rbp_flags*/);  /* Expression */
+		if (comp_ctx->curr_token.t == DUK_TOK_IN) {
+			/*
+			 *  Variant 3
+			 */
+
+			/* XXX: need to determine LHS type, and check that it is LHS compatible */
+			DUK_DDD(DUK_DDDPRINT("detected for variant 3: for (LeftHandSideExpression in Expression) Statement"));
+			if (duk__expr_is_empty(comp_ctx)) {
+				goto syntax_error;  /* LeftHandSideExpression does not allow empty expression */
+			}
+
+			if (res->t == DUK_IVAL_VAR) {
+				duk_reg_t reg_varbind;
+				duk_regconst_t rc_varname;
+
+				duk_dup(ctx, res->x1.valstack_idx);
+				if (duk__lookup_lhs(comp_ctx, &reg_varbind, &rc_varname)) {
+					duk__emit_a_bc(comp_ctx,
+					               DUK_OP_LDREG,
+					               (duk_regconst_t) reg_varbind,
+					               (duk_regconst_t) (reg_temps + 0));
+				} else {
+					duk__emit_a_bc(comp_ctx,
+					               DUK_OP_PUTVAR | DUK__EMIT_FLAG_A_IS_SOURCE,
+					               (duk_regconst_t) (reg_temps + 0),
+					               rc_varname);
+				}
+			} else if (res->t == DUK_IVAL_PROP) {
+				/* Don't allow a constant for the object (even for a number etc), as
+				 * it goes into the 'A' field of the opcode.
+				 */
+				duk_reg_t reg_obj;
+				duk_regconst_t rc_key;
+				reg_obj = duk__ispec_toregconst_raw(comp_ctx, &res->x1, -1 /*forced_reg*/, 0 /*flags*/);  /* don't allow const */
+				rc_key = duk__ispec_toregconst_raw(comp_ctx, &res->x2, -1 /*forced_reg*/, DUK__IVAL_FLAG_ALLOW_CONST /*flags*/);
+				duk__emit_a_b_c(comp_ctx,
+				                DUK_OP_PUTPROP,
+				                (duk_regconst_t) reg_obj,
+				                rc_key,
+				                (duk_regconst_t) (reg_temps + 0));
+			} else {
+				duk__ivalue_toplain_ignore(comp_ctx, res);  /* just in case */
+				duk__emit_extraop_only(comp_ctx,
+				                       DUK_EXTRAOP_INVLHS);
+			}
+			goto parse_3_or_4;
+		} else {
+			/*
+			 *  Variant 1
+			 */
+
+			DUK_DDD(DUK_DDDPRINT("detected for variant 1: for (ExpressionNoIn_opt; Expression_opt; Expression_opt) Statement"));
+			duk__ivalue_toplain_ignore(comp_ctx, res);
+			goto parse_1_or_2;
+		}
+	}
+
+ parse_1_or_2:
+	/*
+	 *  Parse variant 1 or 2.  The first part expression (which differs
+	 *  in the variants) has already been parsed and its code emitted.
+	 *
+	 *  reg_temps + 0: unused
+	 *  reg_temps + 1: unused
+	 */
+	{
+		duk_regconst_t rc_cond;
+		duk_int_t pc_l1, pc_l2, pc_l3, pc_l4;
+		duk_int_t pc_jumpto_l3, pc_jumpto_l4;
+		duk_bool_t expr_c_empty;
+
+		DUK_DDD(DUK_DDDPRINT("shared code for parsing variants 1 and 2"));
+
+		/* "release" preallocated temps since we won't need them */
+		temp_reset = reg_temps + 0;
+		DUK__SETTEMP(comp_ctx, temp_reset);
+
+		duk__advance_expect(comp_ctx, DUK_TOK_SEMICOLON);
+
+		pc_l1 = duk__get_current_pc(comp_ctx);
+		duk__exprtop(comp_ctx, res, DUK__BP_FOR_EXPR | DUK__EXPR_FLAG_ALLOW_EMPTY /*rbp_flags*/);  /* Expression_opt */
+		if (duk__expr_is_empty(comp_ctx)) {
+			/* no need to coerce */
+			pc_jumpto_l3 = duk__emit_jump_empty(comp_ctx);  /* to body */
+			pc_jumpto_l4 = -1;  /* omitted */
+		} else {
+			rc_cond = duk__ivalue_toregconst(comp_ctx, res);
+			duk__emit_if_false_skip(comp_ctx, rc_cond);
+			pc_jumpto_l3 = duk__emit_jump_empty(comp_ctx);  /* to body */
+			pc_jumpto_l4 = duk__emit_jump_empty(comp_ctx);  /* to exit */
+		}
+		DUK__SETTEMP(comp_ctx, temp_reset);
+
+		duk__advance_expect(comp_ctx, DUK_TOK_SEMICOLON);
+
+		pc_l2 = duk__get_current_pc(comp_ctx);
+		duk__exprtop(comp_ctx, res, DUK__BP_FOR_EXPR | DUK__EXPR_FLAG_ALLOW_EMPTY /*rbp_flags*/);  /* Expression_opt */
+		if (duk__expr_is_empty(comp_ctx)) {
+			/* no need to coerce */
+			expr_c_empty = 1;
+			/* JUMP L1 omitted */
+		} else {
+			duk__ivalue_toplain_ignore(comp_ctx, res);
+			expr_c_empty = 0;
+			duk__emit_jump(comp_ctx, pc_l1);
+		}
+		DUK__SETTEMP(comp_ctx, temp_reset);
+
+		duk__advance_expect(comp_ctx, DUK_TOK_RPAREN);
+
+		pc_l3 = duk__get_current_pc(comp_ctx);
+		duk__parse_stmt(comp_ctx, res, 0 /*allow_source_elem*/);
+		if (expr_c_empty) {
+			duk__emit_jump(comp_ctx, pc_l1);
+		} else {
+			duk__emit_jump(comp_ctx, pc_l2);
+		}
+		/* temp reset is not necessary after duk__parse_stmt(), which already does it */
+
+		pc_l4 = duk__get_current_pc(comp_ctx);
+
+		DUK_DDD(DUK_DDDPRINT("patching jumps: jumpto_l3: %ld->%ld, jumpto_l4: %ld->%ld, "
+		                     "break: %ld->%ld, continue: %ld->%ld",
+			             (long) pc_jumpto_l3, (long) pc_l3, (long) pc_jumpto_l4, (long) pc_l4,
+		                     (long) (pc_label_site + 1), (long) pc_l4, (long) (pc_label_site + 2), (long) pc_l2));
+
+		duk__patch_jump(comp_ctx, pc_jumpto_l3, pc_l3);
+		duk__patch_jump(comp_ctx, pc_jumpto_l4, pc_l4);
+		duk__patch_jump(comp_ctx,
+		                pc_label_site + 1,
+		                pc_l4);                         /* break jump */
+		duk__patch_jump(comp_ctx,
+		                pc_label_site + 2,
+		                expr_c_empty ? pc_l1 : pc_l2);  /* continue jump */
+	}
+	goto finished;
+
+ parse_3_or_4:
+	/*
+	 *  Parse variant 3 or 4.
+	 *
+	 *  For variant 3 (e.g. "for (A in C) D;") the code for A (except the
+	 *  final property/variable write) has already been emitted.  The first
+	 *  instruction of that code is at pc_v34_lhs; a JUMP needs to be inserted
+	 *  there to satisfy control flow needs.
+	 *
+	 *  For variant 4, if the variable declaration had an initializer
+	 *  (e.g. "for (var A = B in C) D;") the code for the assignment
+	 *  (B) has already been emitted.
+	 *
+	 *  Variables set before entering here:
+	 *
+	 *    pc_v34_lhs:    insert a "JUMP L2" here (see doc/compiler.txt example).
+	 *    reg_temps + 0: iteration target value (written to LHS)
+	 *    reg_temps + 1: enumerator object
+	 */
+	{
+		duk_int_t pc_l1, pc_l2, pc_l3, pc_l4, pc_l5;
+		duk_int_t pc_jumpto_l2, pc_jumpto_l3, pc_jumpto_l4, pc_jumpto_l5;
+		duk_reg_t reg_target;
+
+		DUK_DDD(DUK_DDDPRINT("shared code for parsing variants 3 and 4, pc_v34_lhs=%ld", (long) pc_v34_lhs));
+
+		DUK__SETTEMP(comp_ctx, temp_reset);
+
+		/* First we need to insert a jump in the middle of previously
+		 * emitted code to get the control flow right.  No jumps can
+		 * cross the position where the jump is inserted.  See doc/compiler.txt
+		 * for discussion on the intricacies of control flow and side effects
+		 * for variants 3 and 4.
+		 */
+
+		duk__insert_jump_entry(comp_ctx, pc_v34_lhs);
+		pc_jumpto_l2 = pc_v34_lhs;  /* inserted jump */
+		pc_l1 = pc_v34_lhs + 1;     /* +1, right after inserted jump */
+
+		/* The code for writing reg_temps + 0 to the left hand side has already
+		 * been emitted.
+		 */
+
+		pc_jumpto_l3 = duk__emit_jump_empty(comp_ctx);  /* -> loop body */
+
+		duk__advance(comp_ctx);  /* eat 'in' */
+
+		/* Parse enumeration target and initialize enumerator.  For 'null' and 'undefined',
+		 * INITENUM will creates a 'null' enumerator which works like an empty enumerator
+		 * (E5 Section 12.6.4, step 3).  Note that INITENUM requires the value to be in a
+		 * register (constant not allowed).
+	 	 */
+
+		pc_l2 = duk__get_current_pc(comp_ctx);
+		reg_target = duk__exprtop_toreg(comp_ctx, res, DUK__BP_FOR_EXPR /*rbp_flags*/);  /* Expression */
+		duk__emit_extraop_b_c(comp_ctx,
+		                      DUK_EXTRAOP_INITENUM,
+		                      (duk_regconst_t) (reg_temps + 1),
+		                      (duk_regconst_t) reg_target);
+		pc_jumpto_l4 = duk__emit_jump_empty(comp_ctx);
+		DUK__SETTEMP(comp_ctx, temp_reset);
+
+		duk__advance_expect(comp_ctx, DUK_TOK_RPAREN);
+
+		pc_l3 = duk__get_current_pc(comp_ctx);
+		duk__parse_stmt(comp_ctx, res, 0 /*allow_source_elem*/);
+		/* temp reset is not necessary after duk__parse_stmt(), which already does it */
+
+		pc_l4 = duk__get_current_pc(comp_ctx);
+		duk__emit_extraop_b_c(comp_ctx,
+		                      DUK_EXTRAOP_NEXTENUM,
+		                      (duk_regconst_t) (reg_temps + 0),
+		                      (duk_regconst_t) (reg_temps + 1));
+		pc_jumpto_l5 = duk__emit_jump_empty(comp_ctx);  /* NEXTENUM jump slot: executed when enum finished */
+		duk__emit_jump(comp_ctx, pc_l1);  /* jump to next loop, using reg_v34_iter as iterated value */
+
+		pc_l5 = duk__get_current_pc(comp_ctx);
+
+		/* XXX: since the enumerator may be a memory expensive object,
+		 * perhaps clear it explicitly here?  If so, break jump must
+		 * go through this clearing operation.
+		 */
+
+		DUK_DDD(DUK_DDDPRINT("patching jumps: jumpto_l2: %ld->%ld, jumpto_l3: %ld->%ld, "
+		                     "jumpto_l4: %ld->%ld, jumpto_l5: %ld->%ld, "
+		                     "break: %ld->%ld, continue: %ld->%ld",
+			             (long) pc_jumpto_l2, (long) pc_l2, (long) pc_jumpto_l3, (long) pc_l3,
+			             (long) pc_jumpto_l4, (long) pc_l4, (long) pc_jumpto_l5, (long) pc_l5,
+		                     (long) (pc_label_site + 1), (long) pc_l5, (long) (pc_label_site + 2), (long) pc_l4));
+
+		duk__patch_jump(comp_ctx, pc_jumpto_l2, pc_l2);
+		duk__patch_jump(comp_ctx, pc_jumpto_l3, pc_l3);
+		duk__patch_jump(comp_ctx, pc_jumpto_l4, pc_l4);
+		duk__patch_jump(comp_ctx, pc_jumpto_l5, pc_l5);
+		duk__patch_jump(comp_ctx, pc_label_site + 1, pc_l5);  /* break jump */
+		duk__patch_jump(comp_ctx, pc_label_site + 2, pc_l4);  /* continue jump */
+	}
+	goto finished;
+
+ finished:
+	DUK_DDD(DUK_DDDPRINT("end parsing a for/for-in statement"));
+	return;
+
+ syntax_error:		
+	DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_INVALID_FOR);
+}
+
+static void duk__parse_switch_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_int_t pc_label_site) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_reg_t temp_at_loop;
+	duk_regconst_t rc_switch;    /* reg/const for switch value */
+	duk_regconst_t rc_case;      /* reg/const for case value */
+	duk_reg_t reg_temp;          /* general temp register */
+	duk_int_t pc_prevcase = -1;
+	duk_int_t pc_prevstmt = -1;
+	duk_int_t pc_default = -1;   /* -1 == not set, -2 == pending (next statement list) */
+
+	/* Note: negative pc values are ignored when patching jumps, so no explicit checks needed */
+
+	/*
+	 *  Switch is pretty complicated because of several conflicting concerns:
+	 *
+	 *    - Want to generate code without an intermediate representation,
+	 *      i.e., in one go
+	 *
+	 *    - Case selectors are expressions, not values, and may thus e.g. throw
+	 *      exceptions (which causes evaluation order concerns)
+	 *
+	 *    - Evaluation semantics of case selectors and default clause need to be 
+	 *      carefully implemented to provide correct behavior even with case value
+	 *      side effects
+	 *
+	 *    - Fall through case and default clauses; avoiding dead JUMPs if case
+	 *      ends with an unconditional jump (a break or a continue)
+	 *
+	 *    - The same case value may occur multiple times, but evaluation rules
+	 *      only process the first match before switching to a "propagation" mode
+	 *      where case values are no longer evaluated
+	 *
+	 *  See E5 Section 12.11.  Also see doc/compiler.txt for compilation
+	 *  discussion.
+	 */
+
+	duk__advance(comp_ctx);
+	duk__advance_expect(comp_ctx, DUK_TOK_LPAREN);
+	rc_switch = duk__exprtop_toregconst(comp_ctx, res, DUK__BP_FOR_EXPR /*rbp_flags*/);
+	duk__advance_expect(comp_ctx, DUK_TOK_RPAREN);
+	duk__advance_expect(comp_ctx, DUK_TOK_LCURLY);
+
+	DUK_DDD(DUK_DDDPRINT("switch value in register %ld", (long) rc_switch));
+
+	temp_at_loop = DUK__GETTEMP(comp_ctx);
+
+	for (;;) {
+		duk_int_t num_stmts;
+		duk_small_int_t tok;
+
+		/* sufficient for keeping temp reg numbers in check */
+		DUK__SETTEMP(comp_ctx, temp_at_loop);
+
+		if (comp_ctx->curr_token.t == DUK_TOK_RCURLY) {
+			break;
+		}
+
+		/*
+		 *  Parse a case or default clause.
+		 */
+
+		if (comp_ctx->curr_token.t == DUK_TOK_CASE) {
+			/*
+			 *  Case clause.
+			 *
+			 *  Note: cannot use reg_case as a temp register (for SEQ target)
+			 *  because it may be a constant.
+			 */
+
+			duk__patch_jump_here(comp_ctx, pc_prevcase);  /* chain jumps for case
+			                                               * evaluation and checking
+			                                               */
+
+			duk__advance(comp_ctx);
+			rc_case = duk__exprtop_toregconst(comp_ctx, res, DUK__BP_FOR_EXPR /*rbp_flags*/);
+			duk__advance_expect(comp_ctx, DUK_TOK_COLON);
+
+			reg_temp = DUK__ALLOCTEMP(comp_ctx);
+			duk__emit_a_b_c(comp_ctx,
+			                DUK_OP_SEQ,
+			                (duk_regconst_t) reg_temp,
+			                rc_switch,
+			                rc_case);
+			duk__emit_if_true_skip(comp_ctx, (duk_regconst_t) reg_temp);
+
+			/* jump to next case clause */
+			pc_prevcase = duk__emit_jump_empty(comp_ctx);  /* no match, next case */
+
+			/* statements go here (if any) on next loop */
+		} else if (comp_ctx->curr_token.t == DUK_TOK_DEFAULT) {
+			/*
+			 *  Default clause.
+			 */
+
+			if (pc_default >= 0) {
+				goto syntax_error;
+			}
+			duk__advance(comp_ctx);
+			duk__advance_expect(comp_ctx, DUK_TOK_COLON);
+
+			/* default clause matches next statement list (if any) */
+			pc_default = -2;
+		} else {
+			/* Code is not accepted before the first case/default clause */
+			goto syntax_error;
+		}
+
+		/*
+		 *  Parse code after the clause.  Possible terminators are
+		 *  'case', 'default', and '}'.
+		 *
+		 *  Note that there may be no code at all, not even an empty statement,
+		 *  between case clauses.  This must be handled just like an empty statement
+		 *  (omitting seemingly pointless JUMPs), to avoid situations like
+		 *  test-bug-case-fallthrough.js.
+		 */
+
+		num_stmts = 0;
+		if (pc_default == -2) {
+			pc_default = duk__get_current_pc(comp_ctx);
+		}
+
+		/* Note: this is correct even for default clause statements:
+		 * they participate in 'fall-through' behavior even if the
+		 * default clause is in the middle.
+		 */
+		duk__patch_jump_here(comp_ctx, pc_prevstmt);  /* chain jumps for 'fall-through'
+		                                               * after a case matches.
+		                                               */
+
+		for (;;) {
+			tok = comp_ctx->curr_token.t;
+			if (tok == DUK_TOK_CASE || tok == DUK_TOK_DEFAULT ||
+			    tok == DUK_TOK_RCURLY) {
+				break;
+			}
+			num_stmts++;
+			duk__parse_stmt(comp_ctx, res, 0 /*allow_source_elem*/);
+		}
+
+		/* fall-through jump to next code of next case (backpatched) */
+		pc_prevstmt = duk__emit_jump_empty(comp_ctx);
+
+		/* XXX: would be nice to omit this jump when the jump is not
+		 * reachable, at least in the obvious cases (such as the case
+		 * ending with a 'break'.
+		 *
+		 * Perhaps duk__parse_stmt() could provide some info on whether
+		 * the statement is a "dead end"?
+		 *
+		 * If implemented, just set pc_prevstmt to -1 when not needed.
+		 */
+	}
+
+	DUK_ASSERT(comp_ctx->curr_token.t == DUK_TOK_RCURLY);
+	duk__advance(comp_ctx);
+
+	/* default case control flow patchup; note that if pc_prevcase < 0
+	 * (i.e. no case clauses), control enters default case automatically.
+	 */
+	if (pc_default >= 0) {
+		/* default case exists: go there if no case matches */
+		duk__patch_jump(comp_ctx, pc_prevcase, pc_default);
+	} else {
+		/* default case does not exist, or no statements present
+		 * after default case: finish case evaluation
+		 */
+		duk__patch_jump_here(comp_ctx, pc_prevcase);
+	}
+
+	/* fall-through control flow patchup; note that pc_prevstmt may be
+	 * < 0 (i.e. no case clauses), in which case this is a no-op.
+	 */
+	duk__patch_jump_here(comp_ctx, pc_prevstmt);
+
+	/* continue jump not patched, an INVALID opcode remains there */
+	duk__patch_jump_here(comp_ctx, pc_label_site + 1);  /* break jump */
+
+	/* Note: 'fast' breaks will jump to pc_label_site + 1, which will
+	 * then jump here.  The double jump will be eliminated by a
+	 * peephole pass, resulting in an optimal jump here.  The label
+	 * site jumps will remain in bytecode and will waste code size.
+	 */
+
+	return;
+
+ syntax_error:
+	DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_INVALID_SWITCH);
+}
+
+static void duk__parse_if_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res) {
+	duk_reg_t temp_reset;
+	duk_regconst_t rc_cond;
+	duk_int_t pc_jump_false;
+
+	DUK_DDD(DUK_DDDPRINT("begin parsing if statement"));
+
+	temp_reset = DUK__GETTEMP(comp_ctx);
+
+	duk__advance(comp_ctx);  /* eat 'if' */
+	duk__advance_expect(comp_ctx, DUK_TOK_LPAREN);
+
+	rc_cond = duk__exprtop_toregconst(comp_ctx, res, DUK__BP_FOR_EXPR /*rbp_flags*/);
+	duk__emit_if_true_skip(comp_ctx, rc_cond);
+	pc_jump_false = duk__emit_jump_empty(comp_ctx);  /* jump to end or else part */
+	DUK__SETTEMP(comp_ctx, temp_reset);
+
+	duk__advance_expect(comp_ctx, DUK_TOK_RPAREN);
+
+	duk__parse_stmt(comp_ctx, res, 0 /*allow_source_elem*/);
+
+	/* The 'else' ambiguity is resolved by 'else' binding to the innermost
+	 * construct, so greedy matching is correct here.
+	 */
+
+	if (comp_ctx->curr_token.t == DUK_TOK_ELSE) {
+		duk_int_t pc_jump_end;
+
+		DUK_DDD(DUK_DDDPRINT("if has else part"));
+
+		duk__advance(comp_ctx);
+
+		pc_jump_end = duk__emit_jump_empty(comp_ctx);  /* jump from true part to end */
+		duk__patch_jump_here(comp_ctx, pc_jump_false);
+
+		duk__parse_stmt(comp_ctx, res, 0 /*allow_source_elem*/);
+
+		duk__patch_jump_here(comp_ctx, pc_jump_end);
+	} else {
+		DUK_DDD(DUK_DDDPRINT("if does not have else part"));
+
+		duk__patch_jump_here(comp_ctx, pc_jump_false);
+	}
+
+	DUK_DDD(DUK_DDDPRINT("end parsing if statement"));
+}
+
+static void duk__parse_do_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_int_t pc_label_site) {
+	duk_regconst_t rc_cond;
+	duk_int_t pc_start;
+
+	DUK_DDD(DUK_DDDPRINT("begin parsing do statement"));
+
+	duk__advance(comp_ctx);  /* eat 'do' */
+
+	pc_start = duk__get_current_pc(comp_ctx);
+	duk__parse_stmt(comp_ctx, res, 0 /*allow_source_elem*/);
+	duk__patch_jump_here(comp_ctx, pc_label_site + 2);  /* continue jump */
+
+	duk__advance_expect(comp_ctx, DUK_TOK_WHILE);
+	duk__advance_expect(comp_ctx, DUK_TOK_LPAREN);
+
+	rc_cond = duk__exprtop_toregconst(comp_ctx, res, DUK__BP_FOR_EXPR /*rbp_flags*/);
+	duk__emit_if_false_skip(comp_ctx, rc_cond);
+	duk__emit_jump(comp_ctx, pc_start);
+	/* no need to reset temps, as we're finished emitting code */
+
+	duk__advance_expect(comp_ctx, DUK_TOK_RPAREN);
+
+	duk__patch_jump_here(comp_ctx, pc_label_site + 1);  /* break jump */
+
+	DUK_DDD(DUK_DDDPRINT("end parsing do statement"));
+}
+
+static void duk__parse_while_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_int_t pc_label_site) {
+	duk_reg_t temp_reset;
+	duk_regconst_t rc_cond;
+	duk_int_t pc_start;
+	duk_int_t pc_jump_false;
+
+	DUK_DDD(DUK_DDDPRINT("begin parsing while statement"));
+
+	temp_reset = DUK__GETTEMP(comp_ctx);
+
+	duk__advance(comp_ctx);  /* eat 'while' */
+
+	duk__advance_expect(comp_ctx, DUK_TOK_LPAREN);
+
+	pc_start = duk__get_current_pc(comp_ctx);
+	duk__patch_jump_here(comp_ctx, pc_label_site + 2);  /* continue jump */
+
+	rc_cond = duk__exprtop_toregconst(comp_ctx, res, DUK__BP_FOR_EXPR /*rbp_flags*/);
+	duk__emit_if_true_skip(comp_ctx, rc_cond);
+	pc_jump_false = duk__emit_jump_empty(comp_ctx);
+	DUK__SETTEMP(comp_ctx, temp_reset);
+
+	duk__advance_expect(comp_ctx, DUK_TOK_RPAREN);
+
+	duk__parse_stmt(comp_ctx, res, 0 /*allow_source_elem*/);
+	duk__emit_jump(comp_ctx, pc_start);
+
+	duk__patch_jump_here(comp_ctx, pc_jump_false);
+	duk__patch_jump_here(comp_ctx, pc_label_site + 1);  /* break jump */
+
+	DUK_DDD(DUK_DDDPRINT("end parsing while statement"));
+}
+
+static void duk__parse_break_or_continue_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_bool_t is_break = (comp_ctx->curr_token.t == DUK_TOK_BREAK);
+	duk_int_t label_id;
+	duk_int_t label_catch_depth;
+	duk_int_t label_pc;  /* points to LABEL; pc+1 = jump site for break; pc+2 = jump site for continue */
+	duk_bool_t label_is_closest;
+
+	DUK_UNREF(res);
+
+	duk__advance(comp_ctx);  /* eat 'break' or 'continue' */
+
+	if (comp_ctx->curr_token.t == DUK_TOK_SEMICOLON ||  /* explicit semi follows */
+	    comp_ctx->curr_token.lineterm ||                /* automatic semi will be inserted */
+	    comp_ctx->curr_token.allow_auto_semi) {         /* automatic semi will be inserted */
+		/* break/continue without label */
+
+		duk__lookup_active_label(comp_ctx, DUK_HTHREAD_STRING_EMPTY_STRING(thr), is_break, &label_id, &label_catch_depth, &label_pc, &label_is_closest);
+	} else if (comp_ctx->curr_token.t == DUK_TOK_IDENTIFIER) {
+		/* break/continue with label (label cannot be a reserved word, production is 'Identifier' */
+		DUK_ASSERT(comp_ctx->curr_token.str1 != NULL);
+		duk__lookup_active_label(comp_ctx, comp_ctx->curr_token.str1, is_break, &label_id, &label_catch_depth, &label_pc, &label_is_closest);
+		duk__advance(comp_ctx);
+	} else {
+		DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_INVALID_BREAK_CONT_LABEL);
+	}
+
+	/* Use a fast break/continue when possible.  A fast break/continue is
+	 * just a jump to the LABEL break/continue jump slot, which then jumps
+	 * to an appropriate place (for break, going through ENDLABEL correctly).
+	 * The peephole optimizer will optimize the jump to a direct one.
+	 */
+
+	if (label_catch_depth == comp_ctx->curr_func.catch_depth &&
+	    label_is_closest) {
+		DUK_DDD(DUK_DDDPRINT("break/continue: is_break=%ld, label_id=%ld, label_is_closest=%ld, "
+		                     "label_catch_depth=%ld, catch_depth=%ld "
+		                     "-> use fast variant (direct jump)",
+		                     (long) is_break, (long) label_id, (long) label_is_closest,
+		                     (long) label_catch_depth, (long) comp_ctx->curr_func.catch_depth));
+
+		duk__emit_jump(comp_ctx, label_pc + (is_break ? 1 : 2));
+	} else {
+		DUK_DDD(DUK_DDDPRINT("break/continue: is_break=%ld, label_id=%ld, label_is_closest=%ld, "
+		                     "label_catch_depth=%ld, catch_depth=%ld "
+		                     "-> use slow variant (longjmp)",
+		                     (long) is_break, (long) label_id, (long) label_is_closest,
+		                     (long) label_catch_depth, (long) comp_ctx->curr_func.catch_depth));
+
+		duk__emit_abc(comp_ctx,
+		              is_break ? DUK_OP_BREAK : DUK_OP_CONTINUE,
+		              (duk_regconst_t) label_id);
+	}
+}
+
+static void duk__parse_return_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_regconst_t rc_val;
+	duk_small_uint_t ret_flags;
+
+	duk__advance(comp_ctx);  /* eat 'return' */
+
+	/* A 'return' statement is only allowed inside an actual function body,
+	 * not as part of eval or global code.
+	 */
+	if (!comp_ctx->curr_func.is_function) {
+		DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_INVALID_RETURN);
+	}
+
+	/* Use a fast return when possible.  A fast return does not cause a longjmp()
+	 * unnecessarily.  A fast return can be done when no TCF catchers are active
+	 * (this includes 'try' and 'with' statements).  Active label catches do not
+	 * prevent a fast return; they're unwound on return automatically.
+	 */
+
+	ret_flags = 0;
+
+	if (comp_ctx->curr_token.t == DUK_TOK_SEMICOLON ||  /* explicit semi follows */
+	    comp_ctx->curr_token.lineterm ||                /* automatic semi will be inserted */
+	    comp_ctx->curr_token.allow_auto_semi) {         /* automatic semi will be inserted */
+		DUK_DDD(DUK_DDDPRINT("empty return value -> undefined"));
+		rc_val = 0;
+	} else {
+		duk_int_t pc_before_expr;
+		duk_int_t pc_after_expr;
+
+		DUK_DDD(DUK_DDDPRINT("return with a value"));
+
+		DUK_UNREF(pc_before_expr);
+		DUK_UNREF(pc_after_expr);
+
+		pc_before_expr = duk__get_current_pc(comp_ctx);
+		rc_val = duk__exprtop_toregconst(comp_ctx, res, DUK__BP_FOR_EXPR /*rbp_flags*/);
+		pc_after_expr = duk__get_current_pc(comp_ctx);
+
+		/* Tail call check: if last opcode emitted was CALL, and
+		 * the context allows it, change the CALL to a tailcall.
+		 * The non-standard 'caller' property disables tail calls
+		 * because they pose some special cases which haven't been
+		 * fixed yet.
+		 */
+
+#if defined(DUK_USE_TAILCALL)
+		if (comp_ctx->curr_func.catch_depth == 0 &&   /* no catchers */
+		    pc_after_expr > pc_before_expr) {         /* at least one opcode emitted */
+			duk_compiler_instr *instr;
+			duk_small_uint_t op;
+
+			instr = duk__get_instr_ptr(comp_ctx, pc_after_expr - 1);
+			DUK_ASSERT(instr != NULL);
+
+			op = (duk_small_uint_t) DUK_DEC_OP(instr->ins);
+			if (op == DUK_OP_CALL || op == DUK_OP_CALLI) {
+				DUK_DDD(DUK_DDDPRINT("return statement detected a tail call opportunity: "
+				                     "catch depth is 0, duk__exprtop() emitted >= 1 instructions, "
+				                     "and last instruction is a CALL "
+				                     "-> set TAILCALL flag"));
+				/* Just flip the single bit. */
+				instr->ins |= DUK_ENC_OP_A_B_C(0, DUK_BC_CALL_FLAG_TAILCALL, 0, 0);
+
+				/* In Duktape 0.10.0 no RETURN was emitted; the executor would
+				 * simulate a RETURN if a tailcall could not actually be performed
+				 * (e.g. if the target was a native function).  This would break
+				 * during execution if the target function turned out to be
+				 * thread yield/resume.  So now we just omit the RETURN which
+				 * also obviates the need for a simulated return in the executor
+				 * when a tailcall cannot be actually done as requested.
+				 *
+				 * See test-bug-tailcall-thread-yield-resume.js for discussion.
+				 */
+			}
+		}
+#endif  /* DUK_USE_TAILCALL */
+
+		ret_flags = DUK_BC_RETURN_FLAG_HAVE_RETVAL;
+	}
+
+	if (comp_ctx->curr_func.catch_depth == 0) {
+		DUK_DDD(DUK_DDDPRINT("fast return allowed -> use fast return"));
+		ret_flags |= DUK_BC_RETURN_FLAG_FAST;
+	} else {
+		DUK_DDD(DUK_DDDPRINT("fast return not allowed -> use slow return"));
+	}
+
+	duk__emit_a_b(comp_ctx,
+	              DUK_OP_RETURN,
+	              (duk_regconst_t) ret_flags /*flags*/,
+	              rc_val /*reg*/);
+}
+
+static void duk__parse_throw_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res) {
+	duk_reg_t reg_val;
+
+	duk__advance(comp_ctx);  /* eat 'throw' */
+
+	if (comp_ctx->curr_token.t == DUK_TOK_SEMICOLON ||  /* explicit semi follows */
+	    comp_ctx->curr_token.lineterm ||                /* automatic semi will be inserted */
+	    comp_ctx->curr_token.allow_auto_semi) {         /* automatic semi will be inserted */
+		DUK_DDD(DUK_DDDPRINT("empty throw value -> undefined"));
+		reg_val = DUK__ALLOCTEMP(comp_ctx);
+		duk__emit_extraop_bc(comp_ctx,
+		                     DUK_EXTRAOP_LDUNDEF,
+		                     (duk_regconst_t) reg_val);
+	} else {
+		DUK_DDD(DUK_DDDPRINT("throw with a value"));
+		reg_val = duk__exprtop_toreg(comp_ctx, res, DUK__BP_FOR_EXPR /*rbp_flags*/);
+	}
+
+	duk__emit_extraop_b_c(comp_ctx,
+	                      DUK_EXTRAOP_THROW,
+	                      (duk_regconst_t) reg_val,
+	                      (duk_regconst_t) 0);
+}
+
+static void duk__parse_try_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_reg_t reg_catch;      /* reg_catch+0 and reg_catch+1 are reserved for TRYCATCH */
+	duk_regconst_t rc_varname = 0;
+	duk_small_uint_t trycatch_flags = 0;
+	duk_int_t pc_trycatch = -1;
+	duk_int_t pc_catch = -1;
+	duk_int_t pc_finally = -1;
+
+	DUK_UNREF(res);
+
+	/*
+	 *  See the following documentation for discussion:
+	 *
+	 *    doc/execution.txt: control flow details
+	 *
+	 *  Try, catch, and finally "parts" are Blocks, not Statements, so
+	 *  they must always be delimited by curly braces.  This is unlike e.g.
+	 *  the if statement, which accepts any Statement.  This eliminates any
+	 *  questions of matching parts of nested try statements.  The Block
+	 *  parsing is implemented inline here (instead of calling out).
+	 *
+	 *  Finally part has a 'let scoped' variable, which requires a few kinks
+	 *  here.
+	 */
+
+	comp_ctx->curr_func.catch_depth++;
+
+	duk__advance(comp_ctx);  /* eat 'try' */
+
+	reg_catch = DUK__ALLOCTEMPS(comp_ctx, 2);
+
+	pc_trycatch = duk__get_current_pc(comp_ctx);
+	duk__emit_invalid(comp_ctx);  /* TRYCATCH, cannot emit now (not enough info) */
+	duk__emit_invalid(comp_ctx);  /* jump for 'catch' case */
+	duk__emit_invalid(comp_ctx);  /* jump for 'finally' case or end (if no finally) */
+
+	/* try part */
+	duk__advance_expect(comp_ctx, DUK_TOK_LCURLY);
+	duk__parse_stmts(comp_ctx, 0 /*allow_source_elem*/, 0 /*expect_eof*/);
+	/* the DUK_TOK_RCURLY is eaten by duk__parse_stmts() */
+	duk__emit_extraop_only(comp_ctx,
+	                       DUK_EXTRAOP_ENDTRY);
+
+	if (comp_ctx->curr_token.t == DUK_TOK_CATCH) {
+		/*
+		 *  The catch variable must be updated to reflect the new allocated
+		 *  register for the duration of the catch clause.  We need to store
+		 *  and restore the original value for the varmap entry (if any).
+		 */
+
+		/*
+		 *  Note: currently register bindings must be fixed for the entire
+		 *  function.  So, even though the catch variable is in a register
+		 *  we know, we must use an explicit environment record and slow path
+		 *  accesses to read/write the catch binding to make closures created
+		 *  within the catch clause work correctly.  This restriction should
+		 *  be fixable (at least in common cases) later.
+		 *
+		 *  See: test-bug-catch-binding-2.js.
+		 *
+		 *  XXX: improve to get fast path access to most catch clauses.
+		 */
+
+		duk_hstring *h_var;
+		duk_int_t varmap_value;  /* for storing/restoring the varmap binding for catch variable */
+
+		DUK_DDD(DUK_DDDPRINT("stack top at start of catch clause: %ld", (long) duk_get_top(ctx)));
+
+		trycatch_flags |= DUK_BC_TRYCATCH_FLAG_HAVE_CATCH;
+
+		pc_catch = duk__get_current_pc(comp_ctx);
+
+		duk__advance(comp_ctx);
+		duk__advance_expect(comp_ctx, DUK_TOK_LPAREN);
+
+		if (comp_ctx->curr_token.t != DUK_TOK_IDENTIFIER) {
+			/* Identifier, i.e. don't allow reserved words */
+			goto syntax_error;
+		}
+		h_var = comp_ctx->curr_token.str1;
+		DUK_ASSERT(h_var != NULL);
+
+		duk_push_hstring(ctx, h_var);  /* keep in on valstack, use borrowed ref below */
+
+		if (comp_ctx->curr_func.is_strict &&
+		    ((h_var == DUK_HTHREAD_STRING_EVAL(thr)) ||
+		     (h_var == DUK_HTHREAD_STRING_LC_ARGUMENTS(thr)))) {
+			DUK_DDD(DUK_DDDPRINT("catch identifier 'eval' or 'arguments' in strict mode -> SyntaxError"));
+			goto syntax_error;
+		}
+
+		duk_dup_top(ctx);
+		rc_varname = duk__getconst(comp_ctx);
+		DUK_DDD(DUK_DDDPRINT("catch clause, rc_varname=0x%08lx (%ld)",
+		                     (unsigned long) rc_varname, (long) rc_varname));
+
+		duk__advance(comp_ctx);
+		duk__advance_expect(comp_ctx, DUK_TOK_RPAREN);
+
+		duk__advance_expect(comp_ctx, DUK_TOK_LCURLY);
+
+		DUK_DDD(DUK_DDDPRINT("varmap before modifying for catch clause: %!iT",
+		                     (duk_tval *) duk_get_tval(ctx, comp_ctx->curr_func.varmap_idx)));
+
+		duk_dup_top(ctx);
+		duk_get_prop(ctx, comp_ctx->curr_func.varmap_idx);
+		if (duk_is_undefined(ctx, -1)) {
+			varmap_value = -2;
+		} else if (duk_is_null(ctx, -1)) {
+			varmap_value = -1;
+		} else {
+			DUK_ASSERT(duk_is_number(ctx, -1));
+			varmap_value = duk_get_int(ctx, -1);
+			DUK_ASSERT(varmap_value >= 0);
+		}
+		duk_pop(ctx);
+
+#if 0
+		/* It'd be nice to do something like this - but it doesn't
+		 * work for closures created inside the catch clause.
+		 */
+		duk_dup_top(ctx);
+		duk_push_int(ctx, (duk_int_t) (reg_catch + 0));
+		duk_put_prop(ctx, comp_ctx->curr_func.varmap_idx);
+#endif
+		duk_dup_top(ctx);
+		duk_push_null(ctx);
+		duk_put_prop(ctx, comp_ctx->curr_func.varmap_idx);
+
+		duk__emit_a_bc(comp_ctx,
+		               DUK_OP_PUTVAR | DUK__EMIT_FLAG_A_IS_SOURCE,
+		               (duk_regconst_t) (reg_catch + 0) /*value*/,
+		               rc_varname /*varname*/);
+
+		DUK_DDD(DUK_DDDPRINT("varmap before parsing catch clause: %!iT",
+		                     (duk_tval *) duk_get_tval(ctx, comp_ctx->curr_func.varmap_idx)));
+
+		duk__parse_stmts(comp_ctx, 0 /*allow_source_elem*/, 0 /*expect_eof*/);
+		/* the DUK_TOK_RCURLY is eaten by duk__parse_stmts() */
+
+		if (varmap_value == -2) {
+			/* not present */
+			duk_del_prop(ctx, comp_ctx->curr_func.varmap_idx);
+		} else {
+			if (varmap_value == -1) {
+				duk_push_null(ctx);
+			} else {
+				DUK_ASSERT(varmap_value >= 0);
+				duk_push_int(ctx, varmap_value);
+			}
+			duk_put_prop(ctx, comp_ctx->curr_func.varmap_idx);
+		}
+		/* varname is popped by above code */
+
+		DUK_DDD(DUK_DDDPRINT("varmap after restore catch clause: %!iT",
+		                     (duk_tval *) duk_get_tval(ctx, comp_ctx->curr_func.varmap_idx)));
+
+		duk__emit_extraop_only(comp_ctx,
+		                       DUK_EXTRAOP_ENDCATCH);
+
+		/*
+		 *  XXX: for now, indicate that an expensive catch binding
+		 *  declarative environment is always needed.  If we don't
+		 *  need it, we don't need the const_varname either.
+		 */
+
+		trycatch_flags |= DUK_BC_TRYCATCH_FLAG_CATCH_BINDING;
+
+		DUK_DDD(DUK_DDDPRINT("stack top at end of catch clause: %ld", (long) duk_get_top(ctx)));
+	}
+
+	if (comp_ctx->curr_token.t == DUK_TOK_FINALLY) {
+		trycatch_flags |= DUK_BC_TRYCATCH_FLAG_HAVE_FINALLY;
+
+		pc_finally = duk__get_current_pc(comp_ctx);
+
+		duk__advance(comp_ctx);
+
+		duk__advance_expect(comp_ctx, DUK_TOK_LCURLY);
+		duk__parse_stmts(comp_ctx, 0 /*allow_source_elem*/, 0 /*expect_eof*/);
+		/* the DUK_TOK_RCURLY is eaten by duk__parse_stmts() */
+		duk__emit_extraop_b(comp_ctx,
+		                    DUK_EXTRAOP_ENDFIN,
+		                    reg_catch);  /* rethrow */
+	}
+
+	if (!(trycatch_flags & DUK_BC_TRYCATCH_FLAG_HAVE_CATCH) &&
+	    !(trycatch_flags & DUK_BC_TRYCATCH_FLAG_HAVE_FINALLY)) {
+		/* must have catch and/or finally */
+		goto syntax_error;
+	}
+
+	duk__patch_trycatch(comp_ctx,
+	                    pc_trycatch,
+	                    reg_catch,
+	                    rc_varname,
+	                    trycatch_flags);
+
+	if (trycatch_flags & DUK_BC_TRYCATCH_FLAG_HAVE_CATCH) {
+		DUK_ASSERT(pc_catch >= 0);
+		duk__patch_jump(comp_ctx, pc_trycatch + 1, pc_catch);
+	}
+
+	if (trycatch_flags & DUK_BC_TRYCATCH_FLAG_HAVE_FINALLY) {
+		DUK_ASSERT(pc_finally >= 0);
+		duk__patch_jump(comp_ctx, pc_trycatch + 2, pc_finally);
+	} else {
+		/* without finally, the second jump slot is used to jump to end of stmt */
+		duk__patch_jump_here(comp_ctx, pc_trycatch + 2);
+	}
+
+	comp_ctx->curr_func.catch_depth--;
+	return;
+
+ syntax_error:
+	DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_INVALID_TRY);
+}
+
+static void duk__parse_with_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res) {
+	duk_int_t pc_trycatch;
+	duk_int_t pc_finished;
+	duk_reg_t reg_catch;
+	duk_regconst_t rc_target;
+	duk_small_uint_t trycatch_flags;
+
+	if (comp_ctx->curr_func.is_strict) {
+		DUK_ERROR(comp_ctx->thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_WITH_IN_STRICT_MODE);
+	}
+
+	comp_ctx->curr_func.catch_depth++;
+
+	duk__advance(comp_ctx);  /* eat 'with' */
+
+	reg_catch = DUK__ALLOCTEMPS(comp_ctx, 2);
+
+	duk__advance_expect(comp_ctx, DUK_TOK_LPAREN);
+	rc_target = duk__exprtop_toregconst(comp_ctx, res, DUK__BP_FOR_EXPR /*rbp_flags*/);
+	duk__advance_expect(comp_ctx, DUK_TOK_RPAREN);
+
+	pc_trycatch = duk__get_current_pc(comp_ctx);
+	trycatch_flags = DUK_BC_TRYCATCH_FLAG_WITH_BINDING;
+	duk__emit_a_b_c(comp_ctx,
+	                DUK_OP_TRYCATCH,
+	                (duk_regconst_t) trycatch_flags /*a*/,
+	                (duk_regconst_t) reg_catch /*b*/,
+	                rc_target /*c*/);
+	duk__emit_invalid(comp_ctx);  /* catch jump */
+	duk__emit_invalid(comp_ctx);  /* finished jump */
+
+	duk__parse_stmt(comp_ctx, res, 0 /*allow_source_elem*/);
+	duk__emit_extraop_only(comp_ctx,
+	                       DUK_EXTRAOP_ENDTRY);
+
+	pc_finished = duk__get_current_pc(comp_ctx);
+
+	duk__patch_jump(comp_ctx, pc_trycatch + 2, pc_finished);
+
+	comp_ctx->curr_func.catch_depth--;
+}
+
+static duk_int_t duk__stmt_label_site(duk_compiler_ctx *comp_ctx, duk_int_t label_id) {
+	/* if a site already exists, nop: max one label site per statement */
+	if (label_id >= 0) {
+		return label_id;
+	}
+
+	label_id = comp_ctx->curr_func.label_next++;
+	DUK_DDD(DUK_DDDPRINT("allocated new label id for label site: %ld", (long) label_id));
+
+	duk__emit_abc(comp_ctx,
+	              DUK_OP_LABEL,
+	              (duk_regconst_t) label_id);
+	duk__emit_invalid(comp_ctx);
+	duk__emit_invalid(comp_ctx);
+
+	return label_id;
+}
+
+/* Parse a single statement.
+ *
+ * Creates a label site (with an empty label) automatically for iteration
+ * statements.  Also "peels off" any label statements for explicit labels.
+ */
+static void duk__parse_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_bool_t allow_source_elem) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_bool_t dir_prol_at_entry;    /* directive prologue status at entry */
+	duk_reg_t temp_at_entry;
+	duk_uarridx_t labels_len_at_entry;
+	duk_int_t pc_at_entry;           /* assumed to also be PC of "LABEL" */
+	duk_int_t stmt_id;
+	duk_small_uint_t stmt_flags = 0;
+	duk_int_t label_id = -1;
+	duk_small_uint_t tok;
+
+	DUK__RECURSION_INCREASE(comp_ctx, thr);
+
+	temp_at_entry = DUK__GETTEMP(comp_ctx);
+	pc_at_entry = duk__get_current_pc(comp_ctx);
+	labels_len_at_entry = (duk_uarridx_t) duk_get_length(ctx, comp_ctx->curr_func.labelnames_idx);
+	stmt_id = comp_ctx->curr_func.stmt_next++;
+	dir_prol_at_entry = comp_ctx->curr_func.in_directive_prologue;
+
+	DUK_UNREF(stmt_id);
+
+	DUK_DDD(DUK_DDDPRINT("parsing a statement, stmt_id=%ld, temp_at_entry=%ld, labels_len_at_entry=%ld, "
+	                     "is_strict=%ld, in_directive_prologue=%ld, catch_depth=%ld",
+	                     (long) stmt_id, (long) temp_at_entry, (long) labels_len_at_entry,
+	                     (long) comp_ctx->curr_func.is_strict, (long) comp_ctx->curr_func.in_directive_prologue,
+	                     (long) comp_ctx->curr_func.catch_depth));
+
+	/* The directive prologue flag is cleared by default so that it is
+	 * unset for any recursive statement parsing.  It is only "revived"
+	 * if a directive is detected.  (We could also make directives only
+	 * allowed if 'allow_source_elem' was true.)
+	 */
+	comp_ctx->curr_func.in_directive_prologue = 0;
+
+ retry_parse:
+
+	DUK_DDD(DUK_DDDPRINT("try stmt parse, stmt_id=%ld, label_id=%ld, allow_source_elem=%ld, catch_depth=%ld",
+	                     (long) stmt_id, (long) label_id, (long) allow_source_elem,
+	                     (long) comp_ctx->curr_func.catch_depth));
+
+	/*
+	 *  Detect iteration statements; if encountered, establish an
+	 *  empty label.
+	 */
+
+	tok = comp_ctx->curr_token.t;
+	if (tok == DUK_TOK_FOR || tok == DUK_TOK_DO || tok == DUK_TOK_WHILE ||
+	    tok == DUK_TOK_SWITCH) {
+		DUK_DDD(DUK_DDDPRINT("iteration/switch statement -> add empty label"));
+
+		label_id = duk__stmt_label_site(comp_ctx, label_id);
+		duk__add_label(comp_ctx,
+		               DUK_HTHREAD_STRING_EMPTY_STRING(thr),
+		               pc_at_entry /*pc_label*/,
+		               label_id);
+	}
+
+	/*
+	 *  Main switch for statement / source element type.
+	 */
+
+	switch (comp_ctx->curr_token.t) {
+	case DUK_TOK_FUNCTION: {
+		/*
+		 *  Function declaration, function expression, or (non-standard)
+		 *  function statement.
+		 *
+		 *  The E5 specification only allows function declarations at
+		 *  the top level (in "source elements").  An ExpressionStatement
+		 *  is explicitly not allowed to begin with a "function" keyword
+		 *  (E5 Section 12.4).  Hence any non-error semantics for such
+		 *  non-top-level statements are non-standard.  Duktape semantics
+		 *  for function statements are modelled after V8, see
+		 *  test-dev-func-decl-outside-top.js.
+		 */
+
+#if defined(DUK_USE_NONSTD_FUNC_STMT)
+		/* Lenient: allow function declarations outside top level in
+		 * non-strict mode but reject them in strict mode.
+		 */
+		if (allow_source_elem || !comp_ctx->curr_func.is_strict)
+#else  /* DUK_USE_NONSTD_FUNC_STMT */
+		/* Strict: never allow function declarations outside top level. */
+		if (allow_source_elem)
+#endif  /* DUK_USE_NONSTD_FUNC_STMT */
+		{
+			/* FunctionDeclaration: not strictly a statement but handled as such.
+			 *
+		 	 * O(depth^2) parse count for inner functions is handled by recording a
+			 * lexer offset on the first compilation pass, so that the function can
+			 * be efficiently skipped on the second pass.  This is encapsulated into
+			 * duk__parse_func_like_fnum().
+			 */
+
+			duk_int_t fnum;
+
+			DUK_DDD(DUK_DDDPRINT("function declaration statement"));
+
+			duk__advance(comp_ctx);  /* eat 'function' */
+			fnum = duk__parse_func_like_fnum(comp_ctx, 1 /*is_decl*/, 0 /*is_setget*/);
+
+			if (comp_ctx->curr_func.in_scanning) {
+				duk_uarridx_t n;
+				duk_hstring *h_funcname;
+
+				duk_get_prop_index(ctx, comp_ctx->curr_func.funcs_idx, fnum * 3);
+				duk_get_prop_stridx(ctx, -1, DUK_STRIDX_NAME);  /* -> [ ... func name ] */
+				h_funcname = duk_get_hstring(ctx, -1);
+				DUK_ASSERT(h_funcname != NULL);
+
+				DUK_DDD(DUK_DDDPRINT("register function declaration %!O in pass 1, fnum %ld",
+				                     (duk_heaphdr *) h_funcname, (long) fnum));
+				n = (duk_uarridx_t) duk_get_length(ctx, comp_ctx->curr_func.decls_idx);
+				duk_push_hstring(ctx, h_funcname);
+				duk_put_prop_index(ctx, comp_ctx->curr_func.decls_idx, n);
+				duk_push_int(ctx, (duk_int_t) (DUK_DECL_TYPE_FUNC + (fnum << 8)));
+				duk_put_prop_index(ctx, comp_ctx->curr_func.decls_idx, n + 1);
+
+				duk_pop_n(ctx, 2);
+			}
+
+			/* no statement value (unlike function expression) */
+			stmt_flags = 0;
+			break;
+		} else {
+			DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_FUNC_STMT_NOT_ALLOWED);
+		}
+		break;
+	}
+	case DUK_TOK_LCURLY: {
+		DUK_DDD(DUK_DDDPRINT("block statement"));
+		duk__advance(comp_ctx);
+		duk__parse_stmts(comp_ctx, 0 /*allow_source_elem*/, 0 /*expect_eof*/);
+		/* the DUK_TOK_RCURLY is eaten by duk__parse_stmts() */
+		stmt_flags = 0;
+		break;
+	}
+	case DUK_TOK_VAR: {
+		DUK_DDD(DUK_DDDPRINT("variable declaration statement"));
+		duk__parse_var_stmt(comp_ctx, res);
+		stmt_flags = DUK__HAS_TERM;
+		break;
+	}
+	case DUK_TOK_SEMICOLON: {
+		/* empty statement with an explicit semicolon */
+		DUK_DDD(DUK_DDDPRINT("empty statement"));
+		stmt_flags = DUK__HAS_TERM;
+		break;
+	}
+	case DUK_TOK_IF: {
+		DUK_DDD(DUK_DDDPRINT("if statement"));
+		duk__parse_if_stmt(comp_ctx, res);
+		stmt_flags = 0;
+		break;
+	}
+	case DUK_TOK_DO: {
+		/*
+		 *  Do-while statement is mostly trivial, but there is special
+		 *  handling for automatic semicolon handling (triggered by the
+		 *  DUK__ALLOW_AUTO_SEMI_ALWAYS) flag related to a bug filed at:
+		 *
+		 *    https://bugs.ecmascript.org/show_bug.cgi?id=8
+		 *
+		 *  See doc/compiler.txt for details.
+		 */
+		DUK_DDD(DUK_DDDPRINT("do statement"));
+		DUK_ASSERT(label_id >= 0);
+		duk__update_label_flags(comp_ctx,
+		                        label_id,
+		                        DUK_LABEL_FLAG_ALLOW_BREAK | DUK_LABEL_FLAG_ALLOW_CONTINUE);
+		duk__parse_do_stmt(comp_ctx, res, pc_at_entry);
+		stmt_flags = DUK__HAS_TERM | DUK__ALLOW_AUTO_SEMI_ALWAYS;  /* DUK__ALLOW_AUTO_SEMI_ALWAYS workaround */
+		break;
+	}
+	case DUK_TOK_WHILE: {
+		DUK_DDD(DUK_DDDPRINT("while statement"));
+		DUK_ASSERT(label_id >= 0);
+		duk__update_label_flags(comp_ctx,
+		                        label_id,
+		                        DUK_LABEL_FLAG_ALLOW_BREAK | DUK_LABEL_FLAG_ALLOW_CONTINUE);
+		duk__parse_while_stmt(comp_ctx, res, pc_at_entry);
+		stmt_flags = 0;
+		break;
+	}
+	case DUK_TOK_FOR: {
+		/*
+		 *  For/for-in statement is complicated to parse because
+		 *  determining the statement type (three-part for vs. a
+		 *  for-in) requires potential backtracking.
+		 *
+		 *  See the helper for the messy stuff.
+		 */
+		DUK_DDD(DUK_DDDPRINT("for/for-in statement"));
+		DUK_ASSERT(label_id >= 0);
+		duk__update_label_flags(comp_ctx,
+		                        label_id,
+		                        DUK_LABEL_FLAG_ALLOW_BREAK | DUK_LABEL_FLAG_ALLOW_CONTINUE);
+		duk__parse_for_stmt(comp_ctx, res, pc_at_entry);
+		stmt_flags = 0;
+		break;
+	}
+	case DUK_TOK_CONTINUE:
+	case DUK_TOK_BREAK: {
+		DUK_DDD(DUK_DDDPRINT("break/continue statement"));
+		duk__parse_break_or_continue_stmt(comp_ctx, res);
+		stmt_flags = DUK__HAS_TERM | DUK__IS_TERMINAL;
+		break;
+	}
+	case DUK_TOK_RETURN: {
+		DUK_DDD(DUK_DDDPRINT("return statement"));
+		duk__parse_return_stmt(comp_ctx, res);
+		stmt_flags = DUK__HAS_TERM | DUK__IS_TERMINAL;
+		break;
+	}
+	case DUK_TOK_WITH: {
+		DUK_DDD(DUK_DDDPRINT("with statement"));
+		comp_ctx->curr_func.with_depth++;
+		duk__parse_with_stmt(comp_ctx, res);
+		comp_ctx->curr_func.with_depth--;
+		stmt_flags = 0;
+		break;
+	}
+	case DUK_TOK_SWITCH: {
+		/*
+		 *  The switch statement is pretty messy to compile.
+		 *  See the helper for details.
+		 */
+		DUK_DDD(DUK_DDDPRINT("switch statement"));
+		DUK_ASSERT(label_id >= 0);
+		duk__update_label_flags(comp_ctx,
+		                        label_id,
+		                        DUK_LABEL_FLAG_ALLOW_BREAK);  /* don't allow continue */
+		duk__parse_switch_stmt(comp_ctx, res, pc_at_entry);
+		stmt_flags = 0;
+		break;
+	}
+	case DUK_TOK_THROW: {
+		DUK_DDD(DUK_DDDPRINT("throw statement"));
+		duk__parse_throw_stmt(comp_ctx, res);
+		stmt_flags = DUK__HAS_TERM | DUK__IS_TERMINAL;
+		break;
+	}
+	case DUK_TOK_TRY: {
+		DUK_DDD(DUK_DDDPRINT("try statement"));
+		duk__parse_try_stmt(comp_ctx, res);
+		stmt_flags = 0;
+		break;
+	}
+	case DUK_TOK_DEBUGGER: {
+		DUK_DDD(DUK_DDDPRINT("debugger statement: ignored"));
+		duk__advance(comp_ctx);
+		stmt_flags = DUK__HAS_TERM;
+		break;
+	}
+	default: {
+		/*
+		 *  Else, must be one of:
+		 *    - ExpressionStatement, possibly a directive (String)
+		 *    - LabelledStatement (Identifier followed by ':')
+		 *
+		 *  Expressions beginning with 'function' keyword are covered by a case
+		 *  above (such expressions are not allowed in standard E5 anyway).
+		 *  Also expressions starting with '{' are interpreted as block
+		 *  statements.  See E5 Section 12.4.
+		 *
+		 *  Directive detection is tricky; see E5 Section 14.1 on directive
+		 *  prologue.  A directive is an expression statement with a single
+		 *  string literal and an explicit or automatic semicolon.  Escape
+		 *  characters are significant and no parens etc are allowed:
+		 *
+		 *    'use strict';          // valid 'use strict' directive
+		 *    'use\u0020strict';     // valid directive, not a 'use strict' directive
+		 *    ('use strict');        // not a valid directive
+		 *
+		 *  The expression is determined to consist of a single string literal
+		 *  based on duk__expr_nud() and duk__expr_led() call counts.  The string literal
+		 *  of a 'use strict' directive is determined to lack any escapes based
+		 *  num_escapes count from the lexer.  Note that other directives may be
+		 *  allowed to contain escapes, so a directive with escapes does not
+		 *  terminate a directive prologue.
+		 *
+		 *  We rely on the fact that the expression parser will not emit any
+		 *  code for a single token expression.  However, it will generate an
+		 *  intermediate value which we will then successfully ignore.
+		 *
+		 *  A similar approach is used for labels.
+		 */
+
+		duk_bool_t single_token;
+
+		DUK_DDD(DUK_DDDPRINT("expression statement"));
+		duk__exprtop(comp_ctx, res, DUK__BP_FOR_EXPR /*rbp_flags*/);
+
+		single_token = (comp_ctx->curr_func.nud_count == 1 &&  /* one token */
+		                comp_ctx->curr_func.led_count == 0);   /* no operators */
+
+		if (single_token &&
+		    comp_ctx->prev_token.t == DUK_TOK_IDENTIFIER &&
+		    comp_ctx->curr_token.t == DUK_TOK_COLON) {
+			/*
+			 *  Detected label
+			 */
+
+			duk_hstring *h_lab;
+
+			/* expected ival */
+			DUK_ASSERT(res->t == DUK_IVAL_VAR);
+			DUK_ASSERT(res->x1.t == DUK_ISPEC_VALUE);
+			DUK_ASSERT(DUK_TVAL_IS_STRING(duk_get_tval(ctx, res->x1.valstack_idx)));
+			h_lab = comp_ctx->prev_token.str1;
+			DUK_ASSERT(h_lab != NULL);
+
+			DUK_DDD(DUK_DDDPRINT("explicit label site for label '%!O'",
+			                     (duk_heaphdr *) h_lab));
+
+			duk__advance(comp_ctx);  /* eat colon */
+
+			label_id = duk__stmt_label_site(comp_ctx, label_id);
+
+			duk__add_label(comp_ctx,
+			               h_lab,
+			               pc_at_entry /*pc_label*/,
+			               label_id);
+	
+			/* a statement following a label cannot be a source element
+			 * (a function declaration).
+			 */
+			allow_source_elem = 0;
+
+			DUK_DDD(DUK_DDDPRINT("label handled, retry statement parsing"));
+			goto retry_parse;
+		}
+
+		stmt_flags = 0;
+
+		if (dir_prol_at_entry &&                           /* still in prologue */
+		    single_token &&                                /* single string token */
+		    comp_ctx->prev_token.t == DUK_TOK_STRING) {
+			/*
+			 *  Detected a directive
+
+			 */
+			duk_hstring *h_dir;
+
+			/* expected ival */
+			DUK_ASSERT(res->t == DUK_IVAL_PLAIN);
+			DUK_ASSERT(res->x1.t == DUK_ISPEC_VALUE);
+			DUK_ASSERT(DUK_TVAL_IS_STRING(duk_get_tval(ctx, res->x1.valstack_idx)));
+			h_dir = comp_ctx->prev_token.str1;
+			DUK_ASSERT(h_dir != NULL);
+
+			stmt_flags |= DUK__STILL_PROLOGUE;
+
+			/* Note: escaped characters differentiate directives */
+
+			if (comp_ctx->prev_token.num_escapes > 0) {
+				DUK_DDD(DUK_DDDPRINT("directive contains escapes: valid directive "
+				                     "but we ignore such directives"));
+			} else {
+				/* XXX: how to compare 'use strict' most compactly?
+				 * We don't necessarily want to add it to the built-ins
+				 * because it's not needed at run time.
+				 * The length comparisons are present to handle
+				 * strings like "use strict\u0000foo" as required.
+				 */
+
+				if (DUK_HSTRING_GET_BYTELEN(h_dir) == 10 &&
+				    DUK_STRNCMP((const char *) DUK_HSTRING_GET_DATA(h_dir), "use strict", 10) == 0) {
+					DUK_DDD(DUK_DDDPRINT("use strict directive detected: strict flag %ld -> %ld",
+					                     (long) comp_ctx->curr_func.is_strict, (long) 1));
+					comp_ctx->curr_func.is_strict = 1;
+				} else if (DUK_HSTRING_GET_BYTELEN(h_dir) == 14 &&
+				           DUK_STRNCMP((const char *) DUK_HSTRING_GET_DATA(h_dir), "use duk notail", 14) == 0) {
+					DUK_DDD(DUK_DDDPRINT("use duk notail directive detected: notail flag %ld -> %ld",
+					                     (long) comp_ctx->curr_func.is_notail, (long) 1));
+					comp_ctx->curr_func.is_notail = 1;
+				} else {
+					DUK_DD(DUK_DDPRINT("unknown directive: '%!O', ignoring but not terminating "
+					                   "directive prologue", (duk_hobject *) h_dir));
+				}
+			}
+		} else {
+			DUK_DDD(DUK_DDDPRINT("non-directive expression statement or no longer in prologue; "
+			                     "prologue terminated if still active"));
+                }
+
+		stmt_flags |= DUK__HAS_VAL | DUK__HAS_TERM;
+	}
+	}  /* end switch (tok) */
+
+	/*
+	 *  Statement value handling.
+	 *
+	 *  Global code and eval code has an implicit return value
+	 *  which comes from the last statement with a value
+	 *  (technically a non-"empty" continuation, which is
+	 *  different from an empty statement).
+	 *
+	 *  Since we don't know whether a later statement will
+	 *  override the value of the current statement, we need
+	 *  to coerce the statement value to a register allocated
+	 *  for implicit return values.  In other cases we need
+	 *  to coerce the statement value to a plain value to get
+	 *  any side effects out (consider e.g. "foo.bar;").
+	 */
+
+	/* XXX: what about statements which leave a half-cooked value in 'res'
+	 * but have no stmt value?  Any such statements?
+	 */
+
+	if (stmt_flags & DUK__HAS_VAL) {
+		duk_reg_t reg_stmt_value = comp_ctx->curr_func.reg_stmt_value;
+		if (reg_stmt_value >= 0) {
+			duk__ivalue_toforcedreg(comp_ctx, res, reg_stmt_value);
+		} else {
+			duk__ivalue_toplain_ignore(comp_ctx, res);
+		}
+	} else {
+		;
+	}
+
+	/*
+	 *  Statement terminator check, including automatic semicolon
+	 *  handling.  After this step, 'curr_tok' should be the first
+	 *  token after a possible statement terminator.
+	 */
+
+	if (stmt_flags & DUK__HAS_TERM) {
+		if (comp_ctx->curr_token.t == DUK_TOK_SEMICOLON) {
+			DUK_DDD(DUK_DDDPRINT("explicit semicolon terminates statement"));
+			duk__advance(comp_ctx);
+		} else {
+			if (comp_ctx->curr_token.allow_auto_semi) {
+				DUK_DDD(DUK_DDDPRINT("automatic semicolon terminates statement"));
+			} else if (stmt_flags & DUK__ALLOW_AUTO_SEMI_ALWAYS) {
+				/* XXX: make this lenience dependent on flags or strictness? */
+				DUK_DDD(DUK_DDDPRINT("automatic semicolon terminates statement (allowed for compatibility "
+				                     "even though no lineterm present before next token)"));
+			} else {
+				DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_UNTERMINATED_STMT);
+			}
+		}
+	} else {
+		DUK_DDD(DUK_DDDPRINT("statement has no terminator"));
+	}
+
+	/*
+	 *  Directive prologue tracking.
+	 */
+
+	if (stmt_flags & DUK__STILL_PROLOGUE) {
+		DUK_DDD(DUK_DDDPRINT("setting in_directive_prologue"));
+		comp_ctx->curr_func.in_directive_prologue = 1;
+	}
+
+	/*
+	 *  Cleanups (all statement parsing flows through here).
+	 *
+	 *  Pop label site and reset labels.  Reset 'next temp' to value at
+	 *  entry to reuse temps.
+	 */
+
+	if (label_id >= 0) {
+		duk__emit_abc(comp_ctx, DUK_OP_ENDLABEL, label_id);
+	}
+
+	DUK__SETTEMP(comp_ctx, temp_at_entry);
+
+	duk__reset_labels_to_length(comp_ctx, labels_len_at_entry);
+
+	/* XXX: return indication of "terminalness" (e.g. a 'throw' is terminal) */
+
+	DUK__RECURSION_DECREASE(comp_ctx, thr);
+}
+
+#undef DUK__HAS_VAL
+#undef DUK__HAS_TERM
+#undef DUK__ALLOW_AUTO_SEMI_ALWAYS
+
+/*
+ *  Parse a statement list.
+ *
+ *  Handles automatic semicolon insertion and implicit return value.
+ *
+ *  Upon entry, 'curr_tok' should contain the first token of the first
+ *  statement (parsed in the "allow regexp literal" mode).  Upon exit,
+ *  'curr_tok' contains the token following the statement list terminator
+ *  (EOF or closing brace).
+ */
+
+static void duk__parse_stmts(duk_compiler_ctx *comp_ctx, duk_bool_t allow_source_elem, duk_bool_t expect_eof) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_ivalue res_alloc;
+	duk_ivalue *res = &res_alloc;
+
+	/* Setup state.  Initial ivalue is 'undefined'. */
+
+	duk_require_stack(ctx, DUK__PARSE_STATEMENTS_SLOTS);
+
+	/* XXX: 'res' setup can be moved to function body level; in fact, two 'res'
+	 * intermediate values suffice for parsing of each function.  Nesting is needed
+	 * for nested functions (which may occur inside expressions).
+	 */
+
+	DUK_MEMZERO(&res_alloc, sizeof(res_alloc));
+	res->t = DUK_IVAL_PLAIN;
+	res->x1.t = DUK_ISPEC_VALUE;
+	res->x1.valstack_idx = duk_get_top(ctx);
+	res->x2.valstack_idx = res->x1.valstack_idx + 1;
+	duk_push_undefined(ctx);
+	duk_push_undefined(ctx);
+
+	/* Parse statements until a closing token (EOF or '}') is found. */
+
+	for (;;) {
+		/* Check whether statement list ends. */
+
+		if (expect_eof) {
+			if (comp_ctx->curr_token.t == DUK_TOK_EOF) {
+				break;
+			}
+		} else {
+			if (comp_ctx->curr_token.t == DUK_TOK_RCURLY) {
+				break;
+			}
+		}
+
+		/* Check statement type based on the first token type.
+		 *
+		 * Note: expression parsing helpers expect 'curr_tok' to
+		 * contain the first token of the expression upon entry.
+		 */
+
+		DUK_DDD(DUK_DDDPRINT("TOKEN %ld (non-whitespace, non-comment)", (long) comp_ctx->curr_token.t));
+
+		duk__parse_stmt(comp_ctx, res, allow_source_elem);
+	}
+
+	duk__advance(comp_ctx);
+
+	/* Tear down state. */
+
+	duk_pop_2(ctx);
+}
+
+/*
+ *  Declaration binding instantiation conceptually happens when calling a
+ *  function; for us it essentially means that function prologue.  The
+ *  conceptual process is described in E5 Section 10.5.
+ *
+ *  We need to keep track of all encountered identifiers to (1) create an
+ *  identifier-to-register map ("varmap"); and (2) detect duplicate
+ *  declarations.  Identifiers which are not bound to registers still need
+ *  to be tracked for detecting duplicates.  Currently such identifiers
+ *  are put into the varmap with a 'null' value, which is later cleaned up.
+ *
+ *  To support functions with a large number of variable and function
+ *  declarations, registers are not allocated beyond a certain limit;
+ *  after that limit, variables and functions need slow path access.
+ *  Arguments are currently always register bound, which imposes a hard
+ *  (and relatively small) argument count limit.
+ *
+ *  Some bindings in E5 are not configurable (= deletable) and almost all
+ *  are mutable (writable).  Exceptions are:
+ * 
+ *    - The 'arguments' binding, established only if no shadowing argument
+ *      or function declaration exists.  We handle 'arguments' creation
+ *      and binding through an explicit slow path environment record.
+ *
+ *    - The "name" binding for a named function expression.  This is also
+ *      handled through an explicit slow path environment record.
+ */
+
+/* XXX: add support for variables to not be register bound always, to 
+ * handle cases with a very large number of variables?
+ */
+
+static void duk__init_varmap_and_prologue_for_pass2(duk_compiler_ctx *comp_ctx, duk_reg_t *out_stmt_value_reg) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_hstring *h_name;
+	duk_bool_t configurable_bindings;
+	duk_uarridx_t num_args;
+	duk_uarridx_t num_decls;
+	duk_regconst_t rc_name;
+	duk_small_uint_t declvar_flags;
+	duk_uarridx_t i;
+#ifdef DUK_USE_ASSERTIONS
+	duk_idx_t entry_top;
+#endif
+
+#ifdef DUK_USE_ASSERTIONS
+	entry_top = duk_get_top(ctx);
+#endif
+
+	/*
+	 *  Preliminaries
+	 */
+
+	configurable_bindings = comp_ctx->curr_func.is_eval;
+	DUK_DDD(DUK_DDDPRINT("configurable_bindings=%ld", (long) configurable_bindings));
+
+	/* varmap is already in comp_ctx->curr_func.varmap_idx */
+
+	/*
+	 *  Function formal arguments, always bound to registers
+	 *  (there's no support for shuffling them now).
+	 */
+
+	num_args = (duk_uarridx_t) duk_get_length(ctx, comp_ctx->curr_func.argnames_idx);
+	DUK_DDD(DUK_DDDPRINT("num_args=%ld", (long) num_args));
+	/* XXX: check num_args */
+
+	for (i = 0; i < num_args; i++) {
+		duk_get_prop_index(ctx, comp_ctx->curr_func.argnames_idx, i);
+		h_name = duk_get_hstring(ctx, -1);
+		DUK_ASSERT(h_name != NULL);
+
+		if (comp_ctx->curr_func.is_strict) {
+			if (duk__hstring_is_eval_or_arguments(comp_ctx, h_name)) {
+				DUK_DDD(DUK_DDDPRINT("arg named 'eval' or 'arguments' in strict mode -> SyntaxError"));
+				goto error_argname;
+			}
+			duk_dup_top(ctx);
+			if (duk_has_prop(ctx, comp_ctx->curr_func.varmap_idx)) {
+				DUK_DDD(DUK_DDDPRINT("duplicate arg name in strict mode -> SyntaxError"));
+				goto error_argname;
+			}
+
+			/* Ensure argument name is not a reserved word in current
+			 * (final) strictness.  Formal argument parsing may not
+			 * catch reserved names if strictness changes during
+			 * parsing.
+			 *
+			 * We only need to do this in strict mode because non-strict
+			 * keyword are always detected in formal argument parsing.
+			 */
+
+			if (DUK_HSTRING_HAS_STRICT_RESERVED_WORD(h_name)) {
+				goto error_argname;
+			}
+		}
+
+		/* overwrite any previous binding of the same name; the effect is
+		 * that last argument of a certain name wins.
+		 */
+
+		/* only functions can have arguments */
+		DUK_ASSERT(comp_ctx->curr_func.is_function);
+		duk_push_uarridx(ctx, i);  /* -> [ ... name index ] */
+		duk_put_prop(ctx, comp_ctx->curr_func.varmap_idx); /* -> [ ... ] */
+
+		/* no code needs to be emitted, the regs already have values */
+	}
+
+	/* use temp_next for tracking register allocations */
+	DUK__SETTEMP_CHECKMAX(comp_ctx, (duk_reg_t) num_args);
+
+	/*
+	 *  After arguments, allocate special registers (like shuffling temps)
+	 */
+
+	if (out_stmt_value_reg) {
+		*out_stmt_value_reg = DUK__ALLOCTEMP(comp_ctx);
+	}
+	if (comp_ctx->curr_func.needs_shuffle) {
+		duk_reg_t shuffle_base = DUK__ALLOCTEMPS(comp_ctx, 3);
+		comp_ctx->curr_func.shuffle1 = shuffle_base;
+		comp_ctx->curr_func.shuffle2 = shuffle_base + 1;
+		comp_ctx->curr_func.shuffle3 = shuffle_base + 2;
+		DUK_D(DUK_DPRINT("shuffle registers needed by function, allocated: %ld %ld %ld",
+		                 (long) comp_ctx->curr_func.shuffle1,
+		                 (long) comp_ctx->curr_func.shuffle2,
+		                 (long) comp_ctx->curr_func.shuffle3));
+	}
+	if (comp_ctx->curr_func.temp_next > 0x100) {
+		DUK_D(DUK_DPRINT("not enough 8-bit regs: temp_next=%ld", (long) comp_ctx->curr_func.temp_next));
+		goto error_outofregs;
+	}
+
+	/*
+	 *  Function declarations
+	 */
+
+	num_decls = (duk_uarridx_t) duk_get_length(ctx, comp_ctx->curr_func.decls_idx);
+	DUK_DDD(DUK_DDDPRINT("num_decls=%ld -> %!T",
+	                     (long) num_decls,
+	                     (duk_tval *) duk_get_tval(ctx, comp_ctx->curr_func.decls_idx)));
+	for (i = 0; i < num_decls; i += 2) {
+		duk_int_t decl_type;
+		duk_int_t fnum;
+
+		duk_get_prop_index(ctx, comp_ctx->curr_func.decls_idx, i + 1);  /* decl type */
+		decl_type = duk_to_int(ctx, -1);
+		fnum = decl_type >> 8;  /* XXX: macros */
+		decl_type = decl_type & 0xff;
+		duk_pop(ctx);
+
+		if (decl_type != DUK_DECL_TYPE_FUNC) {
+			continue;
+		}
+
+		duk_get_prop_index(ctx, comp_ctx->curr_func.decls_idx, i);  /* decl name */
+
+		/* XXX: spilling */
+		if (comp_ctx->curr_func.is_function) {
+			duk_reg_t reg_bind;
+			duk_dup_top(ctx);
+			if (duk_has_prop(ctx, comp_ctx->curr_func.varmap_idx)) {
+				/* shadowed; update value */
+				duk_dup_top(ctx);
+				duk_get_prop(ctx, comp_ctx->curr_func.varmap_idx);
+				reg_bind = duk_to_int(ctx, -1);  /* [ ... name reg_bind ] */
+				duk__emit_a_bc(comp_ctx,
+				               DUK_OP_CLOSURE,
+				               (duk_regconst_t) reg_bind,
+				               (duk_regconst_t) fnum);
+			} else {
+				/* function: always register bound */
+				reg_bind = DUK__ALLOCTEMP(comp_ctx);
+				duk__emit_a_bc(comp_ctx,
+				               DUK_OP_CLOSURE,
+				               (duk_regconst_t) reg_bind,
+				               (duk_regconst_t) fnum);
+				duk_push_int(ctx, (duk_int_t) reg_bind);
+			}
+		} else {
+			/* Function declaration for global/eval code is emitted even
+			 * for duplicates, because of E5 Section 10.5, step 5.e of
+			 * E5.1 (special behavior for variable bound to global object).
+			 *
+			 * DECLVAR will not re-declare a variable as such, but will
+			 * update the binding value.
+			 */
+
+			duk_reg_t reg_temp = DUK__ALLOCTEMP(comp_ctx);
+			duk_dup_top(ctx);
+			rc_name = duk__getconst(comp_ctx);
+			duk_push_null(ctx);
+
+			duk__emit_a_bc(comp_ctx,
+			               DUK_OP_CLOSURE,
+			               (duk_regconst_t) reg_temp,
+			               (duk_regconst_t) fnum);
+
+			declvar_flags = DUK_PROPDESC_FLAG_WRITABLE |
+			                DUK_PROPDESC_FLAG_ENUMERABLE |
+			                DUK_BC_DECLVAR_FLAG_FUNC_DECL;
+
+			if (configurable_bindings) {
+				declvar_flags |= DUK_PROPDESC_FLAG_CONFIGURABLE;
+			}
+
+			duk__emit_a_b_c(comp_ctx,
+			                DUK_OP_DECLVAR,
+			                (duk_regconst_t) declvar_flags /*flags*/,
+			                rc_name /*name*/,
+			                (duk_regconst_t) reg_temp /*value*/);
+
+			DUK__SETTEMP(comp_ctx, reg_temp);  /* forget temp */
+		}
+
+		DUK_DDD(DUK_DDDPRINT("function declaration to varmap: %!T -> %!T",
+		                     (duk_tval *) duk_get_tval(ctx, -2),
+		                     (duk_tval *) duk_get_tval(ctx, -1)));
+
+		duk_put_prop(ctx, comp_ctx->curr_func.varmap_idx);  /* [ ... name reg/null ] -> [ ... ] */
+	}
+
+	/*
+	 *  'arguments' binding is special; if a shadowing argument or
+	 *  function declaration exists, an arguments object will
+	 *  definitely not be needed, regardless of whether the identifier
+	 *  'arguments' is referenced inside the function body.
+	 */
+
+	if (duk_has_prop_stridx(ctx, comp_ctx->curr_func.varmap_idx, DUK_STRIDX_LC_ARGUMENTS)) {
+		DUK_DDD(DUK_DDDPRINT("'arguments' is shadowed by argument or function declaration "
+		                     "-> arguments object creation can be skipped"));
+		comp_ctx->curr_func.is_arguments_shadowed = 1;
+	}
+
+	/*
+	 *  Variable declarations.
+	 *
+	 *  Unlike function declarations, variable declaration values don't get
+	 *  assigned on entry.  If a binding of the same name already exists, just
+	 *  ignore it silently.
+	 */
+
+	for (i = 0; i < num_decls; i += 2) {
+		duk_int_t decl_type;
+
+		duk_get_prop_index(ctx, comp_ctx->curr_func.decls_idx, i + 1);  /* decl type */
+		decl_type = duk_to_int(ctx, -1);
+		decl_type = decl_type & 0xff;
+		duk_pop(ctx);
+
+		if (decl_type != DUK_DECL_TYPE_VAR) {
+			continue;
+		}
+
+		duk_get_prop_index(ctx, comp_ctx->curr_func.decls_idx, i);  /* decl name */
+
+		if (duk_has_prop(ctx, comp_ctx->curr_func.varmap_idx)) {
+			/* shadowed, ignore */
+		} else {
+			duk_get_prop_index(ctx, comp_ctx->curr_func.decls_idx, i);  /* decl name */
+			h_name = duk_get_hstring(ctx, -1);
+			DUK_ASSERT(h_name != NULL);
+
+			if (h_name == DUK_HTHREAD_STRING_LC_ARGUMENTS(thr) &&
+			    !comp_ctx->curr_func.is_arguments_shadowed) {
+				/* E5 Section steps 7-8 */
+				DUK_DDD(DUK_DDDPRINT("'arguments' not shadowed by a function declaration, "
+				                     "but appears as a variable declaration -> treat as "
+				                     "a no-op for variable declaration purposes"));
+				duk_pop(ctx);
+				continue;
+			}
+
+			/* XXX: spilling */
+			if (comp_ctx->curr_func.is_function) {
+				duk_reg_t reg_bind = DUK__ALLOCTEMP(comp_ctx);
+				/* no need to init reg, it will be undefined on entry */
+				duk_push_int(ctx, (duk_int_t) reg_bind);
+			} else {
+				duk_dup_top(ctx);
+				rc_name = duk__getconst(comp_ctx);
+				duk_push_null(ctx);
+
+				declvar_flags = DUK_PROPDESC_FLAG_WRITABLE |
+			                        DUK_PROPDESC_FLAG_ENUMERABLE |
+				                DUK_BC_DECLVAR_FLAG_UNDEF_VALUE;
+				if (configurable_bindings) {
+					declvar_flags |= DUK_PROPDESC_FLAG_CONFIGURABLE;
+				}
+
+				duk__emit_a_b_c(comp_ctx,
+				                DUK_OP_DECLVAR,
+				                (duk_regconst_t) declvar_flags /*flags*/,
+				                rc_name /*name*/,
+				                (duk_regconst_t) 0 /*value*/);
+			}
+
+			duk_put_prop(ctx, comp_ctx->curr_func.varmap_idx);  /* [ ... name reg/null ] -> [ ... ] */
+		}
+	}
+
+	/*
+	 *  Wrap up
+	 */
+
+	DUK_DDD(DUK_DDDPRINT("varmap: %!T, is_arguments_shadowed=%ld",
+	                     (duk_tval *) duk_get_tval(ctx, comp_ctx->curr_func.varmap_idx),
+	                     (long) comp_ctx->curr_func.is_arguments_shadowed));
+
+	DUK_ASSERT_TOP(ctx, entry_top);
+	return;
+
+ error_outofregs:
+	DUK_ERROR(thr, DUK_ERR_RANGE_ERROR, DUK_STR_REG_LIMIT);
+	DUK_UNREACHABLE();
+	return;
+
+ error_argname:
+	DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_INVALID_ARG_NAME);
+	DUK_UNREACHABLE();
+	return;
+}
+
+/*
+ *  Parse a function-body-like expression (FunctionBody or Program
+ *  in E5 grammar) using a two-pass parse.  The productions appear
+ *  in the following contexts:
+ *
+ *    - function expression
+ *    - function statement
+ *    - function declaration
+ *    - getter in object literal
+ *    - setter in object literal
+ *    - global code
+ *    - eval code
+ *    - Function constructor body
+ *
+ *  This function only parses the statement list of the body; the argument
+ *  list and possible function name must be initialized by the caller.
+ *  For instance, for Function constructor, the argument names are originally
+ *  on the value stack.  The parsing of statements ends either at an EOF or
+ *  a closing brace; this is controlled by an input flag.
+ *
+ *  Note that there are many differences affecting parsing and even code
+ *  generation:
+ *
+ *    - Global and eval code have an implicit return value generated
+ *      by the last statement; function code does not
+ *
+ *    - Global code, eval code, and Function constructor body end in
+ *      an EOF, other bodies in a closing brace ('}')
+ *
+ *  Upon entry, 'curr_tok' is ignored and the function will pull in the
+ *  first token on its own.  Upon exit, 'curr_tok' is the terminating
+ *  token (EOF or closing brace).
+ */
+
+static void duk__parse_func_body(duk_compiler_ctx *comp_ctx, duk_bool_t expect_eof, duk_bool_t implicit_return_value) {
+	duk_compiler_func *func = &comp_ctx->curr_func;
+	duk_hthread *thr = comp_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_reg_t reg_stmt_value = -1;
+	duk_lexer_point lex_pt;
+	duk_reg_t temp_first;
+
+	DUK_ASSERT(comp_ctx != NULL);
+	DUK_ASSERT(func != NULL);
+
+	DUK__RECURSION_INCREASE(comp_ctx, thr);
+
+	duk_require_stack(ctx, DUK__FUNCTION_BODY_REQUIRE_SLOTS);
+
+	/*
+	 *  Store lexer position for a later rewind
+	 */
+
+	DUK_LEXER_GETPOINT(&comp_ctx->lex, &lex_pt);
+
+	/*
+	 *  Program code (global and eval code) has an implicit return value
+	 *  from the last statement value (e.g. eval("1; 2+3;") returns 3).
+	 *  This is not the case with functions.  If implicit statement return
+	 *  value is requested, all statements are coerced to a register
+	 *  allocated here, and used in the implicit return statement below.
+	 */
+
+	/* XXX: this is pointless here because pass 1 is throw-away */
+	if (implicit_return_value) {
+		reg_stmt_value = DUK__ALLOCTEMP(comp_ctx);
+
+		/* If an implicit return value is needed by caller, it must be
+		 * initialized to 'undefined' because we don't know whether any
+		 * non-empty (where "empty" is a continuation type, and different
+		 * from an empty statement) statements will be executed.
+		 *
+		 * However, since 1st pass is a throwaway one, no need to emit
+		 * it here.
+		 */
+#if 0
+		duk__emit_extraop_bc(comp_ctx,
+		                     DUK_EXTRAOP_LDUNDEF,
+		                     0);
+#endif
+	}
+
+	/*
+	 *  First pass parsing.
+	 */
+
+	func->in_directive_prologue = 1;
+	func->in_scanning = 1;
+	func->may_direct_eval = 0;
+	func->id_access_arguments = 0;
+	func->id_access_slow = 0;
+	func->reg_stmt_value = reg_stmt_value;
+
+	/* Need to set curr_token.t because lexing regexp mode depends on current
+	 * token type.  Zero value causes "allow regexp" mode.
+	 */
+	comp_ctx->curr_token.t = 0;
+	duk__advance(comp_ctx);  /* duk__parse_stmts() expects curr_tok to be set; parse in "allow regexp literal" mode with current strictness */
+
+	DUK_DDD(DUK_DDDPRINT("begin 1st pass"));
+	duk__parse_stmts(comp_ctx,
+	                 1,             /* allow source elements */
+	                 expect_eof);   /* expect EOF instead of } */
+	DUK_DDD(DUK_DDDPRINT("end 1st pass"));
+
+	/*
+	 *  Rewind lexer.
+	 *
+	 *  duk__parse_stmts() expects curr_tok to be set; parse in "allow regexp
+	 *  literal" mode with current strictness.
+	 *
+	 *  curr_token line number info should be initialized for pass 2 before
+	 *  generating prologue, to ensure prologue bytecode gets nice line numbers.
+	 */
+
+	DUK_DDD(DUK_DDDPRINT("rewind lexer"));
+	DUK_LEXER_SETPOINT(&comp_ctx->lex, &lex_pt);
+	comp_ctx->curr_token.t = 0;  /* this is needed for regexp mode */
+	duk__advance(comp_ctx);
+
+	/*
+	 *  Reset function state and perform register allocation, which creates
+	 *  'varmap' for second pass.  Function prologue for variable declarations,
+	 *  binding value initializations etc is emitted as a by-product.
+	 *
+	 *  Strict mode restrictions for duplicate and invalid argument
+	 *  names are checked here now that we know whether the function
+	 *  is actually strict.  See: test-dev-strict-mode-boundary.js.
+	 */
+
+	duk__reset_func_for_pass2(comp_ctx);
+	func->in_directive_prologue = 1;
+	func->in_scanning = 0;
+
+	/* must be able to emit code, alloc consts, etc. */
+
+	duk__init_varmap_and_prologue_for_pass2(comp_ctx,
+	                                        (implicit_return_value ? &reg_stmt_value : NULL));
+	func->reg_stmt_value = reg_stmt_value;
+
+	temp_first = DUK__GETTEMP(comp_ctx);
+
+	func->temp_first = temp_first;
+	func->temp_next = temp_first;
+	func->stmt_next = 0;
+	func->label_next = 0;
+
+	/* XXX: init or assert catch depth etc -- all values */
+	func->id_access_arguments = 0;
+	func->id_access_slow = 0;
+
+	/*
+	 *  Check function name validity now that we know strictness.
+	 *  This only applies to function declarations and expressions,
+	 *  not setter/getter name.
+	 *
+	 *  See: test-dev-strict-mode-boundary.js
+	 */
+
+	if (func->is_function && !func->is_setget && func->h_name != NULL) {
+		if (func->is_strict) {
+			if (duk__hstring_is_eval_or_arguments(comp_ctx, func->h_name)) {
+				DUK_DDD(DUK_DDDPRINT("func name is 'eval' or 'arguments' in strict mode"));
+				goto error_funcname;
+			}
+			if (DUK_HSTRING_HAS_STRICT_RESERVED_WORD(func->h_name)) {
+				DUK_DDD(DUK_DDDPRINT("func name is a reserved word in strict mode"));
+				goto error_funcname;
+			}
+		} else {
+			if (DUK_HSTRING_HAS_RESERVED_WORD(func->h_name) &&
+			    !DUK_HSTRING_HAS_STRICT_RESERVED_WORD(func->h_name)) {
+				DUK_DDD(DUK_DDDPRINT("func name is a reserved word in non-strict mode"));
+				goto error_funcname;
+			}
+		}
+	}
+
+	/*
+	 *  Second pass parsing.
+	 */
+
+	if (implicit_return_value) {
+		/* Default implicit return value. */
+		duk__emit_extraop_bc(comp_ctx,
+		                     DUK_EXTRAOP_LDUNDEF,
+		                     0);
+	}
+
+	DUK_DDD(DUK_DDDPRINT("begin 2nd pass"));
+	duk__parse_stmts(comp_ctx,
+	                 1,             /* allow source elements */
+	                 expect_eof);   /* expect EOF instead of } */
+	DUK_DDD(DUK_DDDPRINT("end 2nd pass"));
+
+	/*
+	 *  Emit a final RETURN.
+	 *
+	 *  It would be nice to avoid emitting an unnecessary "return" opcode
+	 *  if the current PC is not reachable.  However, this cannot be reliably
+	 *  detected; even if the previous instruction is an unconditional jump,
+	 *  there may be a previous jump which jumps to current PC (which is the
+	 *  case for iteration and conditional statements, for instance).
+	 */
+
+	/* XXX: request a "last statement is terminal" from duk__parse_stmt() and duk__parse_stmts();
+	 * we could avoid the last RETURN if we could ensure there is no way to get here
+	 * (directly or via a jump)
+	 */
+
+	DUK_ASSERT(comp_ctx->curr_func.catch_depth == 0);  /* fast returns are always OK here */
+	if (reg_stmt_value >= 0) {
+		duk__emit_a_b(comp_ctx,
+		              DUK_OP_RETURN,
+		              (duk_regconst_t) (DUK_BC_RETURN_FLAG_HAVE_RETVAL | DUK_BC_RETURN_FLAG_FAST) /*flags*/,
+		              (duk_regconst_t) reg_stmt_value /*reg*/);
+	} else {
+		duk__emit_a_b(comp_ctx,
+		              DUK_OP_RETURN,
+		              (duk_regconst_t) DUK_BC_RETURN_FLAG_FAST /*flags*/,
+		              (duk_regconst_t) 0 /*reg(ignored)*/);
+	}
+
+	/*
+	 *  Peephole optimize JUMP chains.
+	 */
+
+	duk__peephole_optimize_bytecode(comp_ctx);
+
+	/*
+	 *  comp_ctx->curr_func is now ready to be converted into an actual
+	 *  function template.
+	 */
+
+	DUK__RECURSION_DECREASE(comp_ctx, thr);
+	return;
+
+ error_funcname:
+	DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_INVALID_FUNC_NAME);
+}
+
+/*
+ *  Parse a function-like expression:
+ *
+ *    - function expression
+ *    - function declaration
+ *    - function statement (non-standard)
+ *    - setter/getter
+ *
+ *  Adds the function to comp_ctx->curr_func function table and returns the
+ *  function number.
+ *
+ *  On entry, curr_token points to:
+ *
+ *    - the token after 'function' for function expression/declaration/statement
+ *    - the token after 'set' or 'get' for setter/getter
+ */
+
+/* Parse formals. */
+static void duk__parse_func_formals(duk_compiler_ctx *comp_ctx) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_bool_t first = 1;
+	duk_uarridx_t n;
+
+	for (;;) {
+		if (comp_ctx->curr_token.t == DUK_TOK_RPAREN) {
+			break;
+		}
+
+		if (first) {
+			/* no comma */
+			first = 0;
+		} else {
+			duk__advance_expect(comp_ctx, DUK_TOK_COMMA);
+		}
+
+		/* Note: when parsing a formal list in non-strict context, e.g.
+		 * "implements" is parsed as an identifier.  When the function is
+		 * later detected to be strict, the argument list must be rechecked
+		 * against a larger set of reserved words (that of strict mode).
+		 * This is handled by duk__parse_func_body().  Here we recognize
+		 * whatever tokens are considered reserved in current strictness
+		 * (which is not always enough).
+		 */
+
+		if (comp_ctx->curr_token.t != DUK_TOK_IDENTIFIER) {
+			DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "expected identifier");
+		}
+		DUK_ASSERT(comp_ctx->curr_token.t == DUK_TOK_IDENTIFIER);
+		DUK_ASSERT(comp_ctx->curr_token.str1 != NULL);
+		DUK_DDD(DUK_DDDPRINT("formal argument: %!O",
+		                     (duk_heaphdr *) comp_ctx->curr_token.str1));
+
+		/* XXX: append primitive */
+		duk_push_hstring(ctx, comp_ctx->curr_token.str1);
+		n = (duk_uarridx_t) duk_get_length(ctx, comp_ctx->curr_func.argnames_idx);
+		duk_put_prop_index(ctx, comp_ctx->curr_func.argnames_idx, n);
+
+		duk__advance(comp_ctx);  /* eat identifier */
+	}
+}
+
+/* Parse a function-like expression, assuming that 'comp_ctx->curr_func' is
+ * correctly set up.  Assumes that curr_token is just after 'function' (or
+ * 'set'/'get' etc).
+ */
+static void duk__parse_func_like_raw(duk_compiler_ctx *comp_ctx, duk_bool_t is_decl, duk_bool_t is_setget) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+
+	DUK_ASSERT(comp_ctx->curr_func.num_formals == 0);
+	DUK_ASSERT(comp_ctx->curr_func.is_function == 1);
+	DUK_ASSERT(comp_ctx->curr_func.is_eval == 0);
+	DUK_ASSERT(comp_ctx->curr_func.is_global == 0);
+	DUK_ASSERT(comp_ctx->curr_func.is_setget == is_setget);
+	DUK_ASSERT(comp_ctx->curr_func.is_decl == is_decl);
+
+	/*
+	 *  Function name (if any)
+	 *
+	 *  We don't check for prohibited names here, because we don't
+	 *  yet know whether the function will be strict.  Function body
+	 *  parsing handles this retroactively.
+	 *
+	 *  For function expressions and declarations function name must
+	 *  be an Identifer (excludes reserved words).  For setter/getter
+	 *  it is a PropertyName which allows reserved words and also
+	 *  strings and numbers (e.g. "{ get 1() { ... } }").
+	 */
+
+	if (is_setget) {
+		/* PropertyName -> IdentifierName | StringLiteral | NumericLiteral */
+		if (comp_ctx->curr_token.t_nores == DUK_TOK_IDENTIFIER ||
+		    comp_ctx->curr_token.t == DUK_TOK_STRING) {
+			duk_push_hstring(ctx, comp_ctx->curr_token.str1);       /* keep in valstack */
+		} else if (comp_ctx->curr_token.t == DUK_TOK_NUMBER) {
+			duk_push_number(ctx, comp_ctx->curr_token.num);
+			duk_to_string(ctx, -1);
+		} else {
+			DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_INVALID_GETSET_NAME);
+		}
+		comp_ctx->curr_func.h_name = duk_get_hstring(ctx, -1);  /* borrowed reference */
+		DUK_ASSERT(comp_ctx->curr_func.h_name != NULL);
+		duk__advance(comp_ctx);
+	} else {
+		/* Function name is an Identifier (not IdentifierName), but we get
+		 * the raw name (not recognizing keywords) here and perform the name
+		 * checks only after pass 1.
+		 */
+		if (comp_ctx->curr_token.t_nores == DUK_TOK_IDENTIFIER) {
+			duk_push_hstring(ctx, comp_ctx->curr_token.str1);       /* keep in valstack */
+			comp_ctx->curr_func.h_name = duk_get_hstring(ctx, -1);  /* borrowed reference */
+			DUK_ASSERT(comp_ctx->curr_func.h_name != NULL);
+			duk__advance(comp_ctx);
+		} else {
+			/* valstack will be unbalanced, which is OK */
+			DUK_ASSERT(!is_setget);
+			if (is_decl) {
+				DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_FUNC_NAME_REQUIRED);
+			}
+		}
+	}
+
+	DUK_DDD(DUK_DDDPRINT("function name: %!O",
+	                     (duk_heaphdr *) comp_ctx->curr_func.h_name));
+
+	/*
+	 *  Formal argument list
+	 *
+	 *  We don't check for prohibited names or for duplicate argument
+	 *  names here, becase we don't yet know whether the function will
+	 *  be strict.  Function body parsing handles this retroactively.
+	 */
+
+	duk__advance_expect(comp_ctx, DUK_TOK_LPAREN);
+
+	duk__parse_func_formals(comp_ctx);
+
+	DUK_ASSERT(comp_ctx->curr_token.t == DUK_TOK_RPAREN);
+	duk__advance(comp_ctx);
+
+	/*
+	 *  Parse function body
+	 */
+
+	duk__parse_func_body(comp_ctx,
+	                     0,   /* expect_eof */
+	                     0);  /* implicit_return_value */
+
+	/*
+	 *  Convert duk_compiler_func to a function template and add it
+	 *  to the parent function table.
+	 */
+
+	duk__convert_to_func_template(comp_ctx);  /* -> [ ... func ] */
+}
+
+/* Parse an inner function, adding the function template to the current function's
+ * function table.  Return a function number to be used by the outer function.
+ *
+ * Avoiding O(depth^2) inner function parsing is handled here.  On the first pass,
+ * compile and register the function normally into the 'funcs' array, also recording
+ * a lexer point (offset/line) to the closing brace of the function.  On the second
+ * pass, skip the function and return the same 'fnum' as on the first pass by using
+ * a running counter.
+ *
+ * An unfortunate side effect of this is that when parsing the inner function, almost
+ * nothing is known of the outer function, i.e. the inner function's scope.  We don't
+ * need that information at the moment, but it would allow some optimizations if it
+ * were used.
+ */
+static duk_int_t duk__parse_func_like_fnum(duk_compiler_ctx *comp_ctx, duk_bool_t is_decl, duk_bool_t is_setget) {
+	duk_hthread *thr = comp_ctx->thr;
+	duk_context *ctx = (duk_context *) thr;
+	duk_compiler_func old_func;
+	duk_idx_t entry_top;
+	duk_int_t fnum;
+
+	/*
+	 *  On second pass, skip the function.
+	 */
+
+	if (!comp_ctx->curr_func.in_scanning) {
+		duk_lexer_point lex_pt;
+
+		fnum = comp_ctx->curr_func.fnum_next++;
+		duk_get_prop_index(ctx, comp_ctx->curr_func.funcs_idx, (duk_uarridx_t) (fnum * 3 + 1));
+		lex_pt.offset = duk_to_int(ctx, -1);
+		duk_pop(ctx);
+		duk_get_prop_index(ctx, comp_ctx->curr_func.funcs_idx, (duk_uarridx_t) (fnum * 3 + 2));
+		lex_pt.line = duk_to_int(ctx, -1);
+		duk_pop(ctx);
+
+		DUK_DDD(DUK_DDDPRINT("second pass of an inner func, skip the function, reparse closing brace; lex offset=%ld, line=%ld",
+		                     (long) lex_pt.offset, (long) lex_pt.line));
+
+		DUK_LEXER_SETPOINT(&comp_ctx->lex, &lex_pt);
+		comp_ctx->curr_token.t = 0;  /* this is needed for regexp mode */
+		duk__advance(comp_ctx);
+		duk__advance_expect(comp_ctx, DUK_TOK_RCURLY);
+
+		return fnum;
+	}
+
+	/*
+	 *  On first pass, perform actual parsing.  Remember valstack top on entry
+	 *  to restore it later, and switch to using a new function in comp_ctx.
+	 */
+
+	entry_top = duk_get_top(ctx);
+	DUK_DDD(DUK_DDDPRINT("before func: entry_top=%ld, curr_tok.start_offset=%ld",
+	                     (long) entry_top, (long) comp_ctx->curr_token.start_offset));
+
+	DUK_MEMCPY(&old_func, &comp_ctx->curr_func, sizeof(duk_compiler_func));
+
+	DUK_MEMZERO(&comp_ctx->curr_func, sizeof(duk_compiler_func));
+	duk__init_func_valstack_slots(comp_ctx);
+	DUK_ASSERT(comp_ctx->curr_func.num_formals == 0);
+
+	/* inherit initial strictness from parent */
+	comp_ctx->curr_func.is_strict = old_func.is_strict;
+
+	DUK_ASSERT(comp_ctx->curr_func.is_notail == 0);
+	comp_ctx->curr_func.is_function = 1;
+	DUK_ASSERT(comp_ctx->curr_func.is_eval == 0);
+	DUK_ASSERT(comp_ctx->curr_func.is_global == 0);
+	comp_ctx->curr_func.is_setget = is_setget;
+	comp_ctx->curr_func.is_decl = is_decl;
+
+	/*
+	 *  Parse inner function
+	 */
+
+	duk__parse_func_like_raw(comp_ctx, is_decl, is_setget);  /* pushes function template */
+
+	/* prev_token.start_offset points to the closing brace here; when skipping
+	 * we're going to reparse the closing brace to ensure semicolon insertion
+	 * etc work as expected.
+	 */
+	DUK_DDD(DUK_DDDPRINT("after func: prev_tok.start_offset=%ld, curr_tok.start_offset=%ld",
+	                     (long) comp_ctx->prev_token.start_offset, (long) comp_ctx->curr_token.start_offset));
+	DUK_ASSERT(comp_ctx->lex.input[comp_ctx->prev_token.start_offset] == (duk_uint8_t) DUK_ASC_RCURLY);
+
+	/* XXX: append primitive */
+	DUK_ASSERT(duk_get_length(ctx, old_func.funcs_idx) == (duk_size_t) (old_func.fnum_next * 3));
+	fnum = old_func.fnum_next++;
+
+	if (fnum >= DUK__MAX_FUNCS) {
+		DUK_ERROR(comp_ctx->thr, DUK_ERR_INTERNAL_ERROR, DUK_STR_FUNC_LIMIT);
+	}
+
+	/* array writes autoincrement length */
+	(void) duk_put_prop_index(ctx, old_func.funcs_idx, (duk_uarridx_t) (fnum * 3));
+	duk_push_size_t(ctx, comp_ctx->prev_token.start_offset);
+	(void) duk_put_prop_index(ctx, old_func.funcs_idx, (duk_uarridx_t) (fnum * 3 + 1));
+	duk_push_int(ctx, comp_ctx->prev_token.start_line);
+	(void) duk_put_prop_index(ctx, old_func.funcs_idx, (duk_uarridx_t) (fnum * 3 + 2));
+
+	/*
+	 *  Cleanup: restore original function, restore valstack state.
+	 */
+	
+	DUK_MEMCPY((void *) &comp_ctx->curr_func, (void *) &old_func, sizeof(duk_compiler_func));
+	duk_set_top(ctx, entry_top);
+
+	DUK_ASSERT_TOP(ctx, entry_top);
+
+	return fnum;
+}
+
+/*
+ *  Compile input string into an executable function template without
+ *  arguments.
+ *
+ *  The string is parsed as the "Program" production of Ecmascript E5.
+ *  Compilation context can be either global code or eval code (see E5
+ *  Sections 14 and 15.1.2.1).
+ *
+ *  Input stack:  [ ... filename ]
+ *  Output stack: [ ... func_template ]
+ */
+
+/* XXX: source code property */
+
+static duk_ret_t duk__js_compile_raw(duk_context *ctx) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_hstring *h_filename;
+	duk__compiler_stkstate *comp_stk;
+	duk_compiler_ctx *comp_ctx;
+	duk_lexer_point *lex_pt;
+	duk_compiler_func *func;
+	duk_idx_t entry_top;
+	duk_bool_t is_strict;
+	duk_bool_t is_eval;
+	duk_bool_t is_funcexpr;
+	duk_small_uint_t flags;
+
+	DUK_ASSERT(thr != NULL);
+
+	/*
+	 *  Arguments check
+	 */
+
+	entry_top = duk_get_top(ctx);
+	DUK_ASSERT(entry_top >= 2);
+
+	comp_stk = (duk__compiler_stkstate *) duk_require_pointer(ctx, -1);
+	comp_ctx = &comp_stk->comp_ctx_alloc;
+	lex_pt = &comp_stk->lex_pt_alloc;
+	DUK_ASSERT(comp_ctx != NULL);
+	DUK_ASSERT(lex_pt != NULL);
+
+	flags = comp_stk->flags;
+	is_eval = (flags & DUK_JS_COMPILE_FLAG_EVAL ? 1 : 0);
+	is_strict = (flags & DUK_JS_COMPILE_FLAG_STRICT ? 1 : 0);
+	is_funcexpr = (flags & DUK_JS_COMPILE_FLAG_FUNCEXPR ? 1 : 0);
+
+	h_filename = duk_get_hstring(ctx, -2);  /* may be undefined */
+
+	/*
+	 *  Init compiler and lexer contexts
+	 */
+
+	func = &comp_ctx->curr_func;
+#ifdef DUK_USE_EXPLICIT_NULL_INIT
+	comp_ctx->thr = NULL;
+	comp_ctx->h_filename = NULL;
+	comp_ctx->prev_token.str1 = NULL;
+	comp_ctx->prev_token.str2 = NULL;
+	comp_ctx->curr_token.str1 = NULL;
+	comp_ctx->curr_token.str2 = NULL;
+#endif
+
+	duk_require_stack(ctx, DUK__COMPILE_ENTRY_SLOTS);
+
+	duk_push_dynamic_buffer(ctx, 0);       /* entry_top + 0 */
+	duk_push_undefined(ctx);               /* entry_top + 1 */
+	duk_push_undefined(ctx);               /* entry_top + 2 */
+	duk_push_undefined(ctx);               /* entry_top + 3 */
+	duk_push_undefined(ctx);               /* entry_top + 4 */
+
+	comp_ctx->thr = thr;
+	comp_ctx->h_filename = h_filename;
+	comp_ctx->tok11_idx = entry_top + 1;
+	comp_ctx->tok12_idx = entry_top + 2;
+	comp_ctx->tok21_idx = entry_top + 3;
+	comp_ctx->tok22_idx = entry_top + 4;
+	comp_ctx->recursion_limit = DUK_COMPILER_RECURSION_LIMIT;
+
+	/* comp_ctx->lex has been pre-initialized by caller: it has been
+	 * zeroed and input/input_length has been set.
+	 */
+	comp_ctx->lex.thr = thr;
+	/* comp_ctx->lex.input and comp_ctx->lex.input_length filled by caller */
+	comp_ctx->lex.slot1_idx = comp_ctx->tok11_idx;
+	comp_ctx->lex.slot2_idx = comp_ctx->tok12_idx;
+	comp_ctx->lex.buf_idx = entry_top + 0;
+	comp_ctx->lex.buf = (duk_hbuffer_dynamic *) duk_get_hbuffer(ctx, entry_top + 0);
+	DUK_ASSERT(comp_ctx->lex.buf != NULL);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(comp_ctx->lex.buf));
+	comp_ctx->lex.token_limit = DUK_COMPILER_TOKEN_LIMIT;
+
+	lex_pt->offset = 0;
+	lex_pt->line = 1;
+	DUK_LEXER_SETPOINT(&comp_ctx->lex, lex_pt);    /* fills window */
+
+	/*
+	 *  Initialize function state for a zero-argument function
+	 */
+
+	duk__init_func_valstack_slots(comp_ctx);
+	DUK_ASSERT(func->num_formals == 0);
+
+	if (is_funcexpr) {
+		/* Name will be filled from function expression, not by caller.
+		 * This case is used by Function constructor and duk_compile()
+		 * API with the DUK_COMPILE_FUNCTION option.
+		 */
+		DUK_ASSERT(func->h_name == NULL);
+	} else {
+		duk_push_hstring_stridx(ctx, (is_eval ? DUK_STRIDX_EVAL :
+		                                        DUK_STRIDX_GLOBAL));
+		func->h_name = duk_get_hstring(ctx, -1);
+	}
+
+	/*
+	 *  Parse a function body or a function-like expression, depending
+	 *  on flags.
+	 */
+
+	func->is_strict = is_strict;
+	func->is_setget = 0;
+	func->is_decl = 0;
+
+	if (is_funcexpr) {
+		func->is_function = 1;
+		func->is_eval = 0;
+		func->is_global = 0;
+
+		duk__advance(comp_ctx);  /* init 'curr_token' */
+		duk__advance_expect(comp_ctx, DUK_TOK_FUNCTION);
+		(void) duk__parse_func_like_raw(comp_ctx,
+		                                0,      /* is_decl */
+		                                0);     /* is_setget */
+	} else {
+		func->is_function = 0;
+		func->is_eval = is_eval;
+		func->is_global = !is_eval;
+
+		duk__parse_func_body(comp_ctx,
+		                     1,             /* expect_eof */
+		                     1);            /* implicit_return_value */
+	}
+
+	/*
+	 *  Convert duk_compiler_func to a function template
+	 */
+
+	duk__convert_to_func_template(comp_ctx);
+
+	/*
+	 *  Wrapping duk_safe_call() will mangle the stack, just return stack top
+	 */
+
+	/* [ ... filename (temps) func ] */
+
+	return 1;
+}
+
+void duk_js_compile(duk_hthread *thr, const duk_uint8_t *src_buffer, duk_size_t src_length, duk_small_uint_t flags) {
+	duk_context *ctx = (duk_context *) thr;
+	duk__compiler_stkstate comp_stk;
+
+	/* XXX: this illustrates that a C catchpoint implemented using duk_safe_call()
+	 * is a bit heavy at the moment.  The wrapper compiles to ~180 bytes on x64.
+	 * Alternatives would be nice.
+	 */
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(src_buffer != NULL);
+
+	/* preinitialize lexer state partially */
+	DUK_MEMZERO(&comp_stk, sizeof(comp_stk));
+	comp_stk.flags = flags;
+	DUK_LEXER_INITCTX(&comp_stk.comp_ctx_alloc.lex);
+	comp_stk.comp_ctx_alloc.lex.input = src_buffer;
+	comp_stk.comp_ctx_alloc.lex.input_length = src_length;
+
+	duk_push_pointer(ctx, (void *) &comp_stk);
+
+	/* [ ... filename &comp_stk ] */
+
+	if (duk_safe_call(ctx, duk__js_compile_raw, 2 /*nargs*/, 1 /*nret*/) != DUK_EXEC_SUCCESS) {
+		/* This now adds a line number to -any- error thrown during compilation.
+		 * Usually compilation errors are SyntaxErrors but they could also be
+		 * out-of-memory errors and the like.
+		 */
+
+		DUK_DDD(DUK_DDDPRINT("compile error, before adding line info: %!T",
+		                     (duk_tval *) duk_get_tval(ctx, -1)));
+		if (duk_is_object(ctx, -1)) {
+			if (duk_get_prop_stridx(ctx, -1, DUK_STRIDX_MESSAGE)) {
+				duk_push_sprintf(ctx, " (line %ld)", (long) comp_stk.comp_ctx_alloc.curr_token.start_line);
+				duk_concat(ctx, 2);
+				duk_put_prop_stridx(ctx, -2, DUK_STRIDX_MESSAGE);
+			} else {
+				duk_pop(ctx);
+			}
+		}
+		DUK_DDD(DUK_DDDPRINT("compile error, after adding line info: %!T",
+		                     (duk_tval *) duk_get_tval(ctx, -1)));
+		duk_throw(ctx);
+	}
+
+	/* [ ... template ] */
+}
+#line 1 "duk_js_executor.c"
+/*
+ *  Ecmascript bytecode executor.
+ */
+
+/* include removed: duk_internal.h */
+
+/*
+ *  Local forward declarations
+ */
+
+static void duk__reconfig_valstack(duk_hthread *thr, duk_size_t act_idx, duk_small_uint_t retval_count);
+
+/*
+ *  Helper for finding the final non-bound function in a "bound function" chain.
+ */
+
+/* XXX: overlap with other helpers, rework */
+static duk_hobject *duk__find_nonbound_function(duk_hthread *thr, duk_hobject *func) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_uint_t sanity;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(func != NULL);
+	DUK_ASSERT(DUK_HOBJECT_HAS_BOUND(func));
+
+	sanity = DUK_HOBJECT_BOUND_CHAIN_SANITY;
+	do {	
+		if (!DUK_HOBJECT_HAS_BOUND(func)) {
+			break;
+		}
+
+		duk_push_hobject(ctx, func);
+		duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_TARGET);
+		func = duk_require_hobject(ctx, -1);
+		duk_pop_2(ctx);
+	} while (--sanity > 0);
+
+	if (sanity == 0) {
+		DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, DUK_STR_BOUND_CHAIN_LIMIT);
+	}
+
+	DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));
+	DUK_ASSERT(DUK_HOBJECT_HAS_COMPILEDFUNCTION(func) || DUK_HOBJECT_HAS_NATIVEFUNCTION(func));
+
+	return func;
+}
+
+/*
+ *  Arithmetic, binary, and logical helpers.
+ *
+ *  Note: there is no opcode for logical AND or logical OR; this is on
+ *  purpose, because the evalution order semantics for them make such
+ *  opcodes pretty pointless (short circuiting means they are most
+ *  comfortably implemented as jumps).  However, a logical NOT opcode
+ *  is useful.
+ *
+ *  Note: careful with duk_tval pointers here: they are potentially
+ *  invalidated by any DECREF and almost any API call.
+ */
+
+static duk_double_t duk__compute_mod(duk_double_t d1, duk_double_t d2) {
+	/*
+	 *  Ecmascript modulus ('%') does not match IEEE 754 "remainder"
+	 *  operation (implemented by remainder() in C99) but does seem
+	 *  to match ANSI C fmod().
+	 *
+	 *  Compare E5 Section 11.5.3 and "man fmod".
+	 */
+
+	return (duk_double_t) DUK_FMOD((double) d1, (double) d2);
+}
+
+static void duk__vm_arith_add(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y, duk_small_uint_fast_t idx_z) {
+	/*
+	 *  Addition operator is different from other arithmetic
+	 *  operations in that it also provides string concatenation.
+	 *  Hence it is implemented separately.
+	 *
+	 *  There is a fast path for number addition.  Other cases go
+	 *  through potentially multiple coercions as described in the
+	 *  E5 specification.  It may be possible to reduce the number
+	 *  of coercions, but this must be done carefully to preserve
+	 *  the exact semantics.
+	 *
+	 *  E5 Section 11.6.1.
+	 *
+	 *  Custom types also have special behavior implemented here.
+	 */
+
+	duk_context *ctx = (duk_context *) thr;
+	duk_double_union du;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(tv_x != NULL);  /* may be reg or const */
+	DUK_ASSERT(tv_y != NULL);  /* may be reg or const */
+	DUK_ASSERT_DISABLE(idx_z >= 0);  /* unsigned */
+	DUK_ASSERT((duk_uint_t) idx_z < (duk_uint_t) duk_get_top(ctx));
+
+	/*
+	 *  Fast paths
+	 */
+
+	if (DUK_TVAL_IS_NUMBER(tv_x) && DUK_TVAL_IS_NUMBER(tv_y)) {
+		duk_tval tv_tmp;
+		duk_tval *tv_z;
+
+		du.d = DUK_TVAL_GET_NUMBER(tv_x) + DUK_TVAL_GET_NUMBER(tv_y);
+		DUK_DBLUNION_NORMALIZE_NAN_CHECK(&du);
+		DUK_ASSERT(DUK_DBLUNION_IS_NORMALIZED(&du));
+
+		tv_z = thr->valstack_bottom + idx_z;
+		DUK_TVAL_SET_TVAL(&tv_tmp, tv_z);
+		DUK_TVAL_SET_NUMBER(tv_z, du.d);
+		DUK_ASSERT(!DUK_TVAL_IS_HEAP_ALLOCATED(tv_z));  /* no need to incref */
+		DUK_TVAL_DECREF(thr, &tv_tmp);   /* side effects */
+		return;
+	}
+
+	/*
+	 *  Slow path: potentially requires function calls for coercion
+	 */
+
+	duk_push_tval(ctx, tv_x);
+	duk_push_tval(ctx, tv_y);
+	duk_to_primitive(ctx, -2, DUK_HINT_NONE);  /* side effects -> don't use tv_x, tv_y after */
+	duk_to_primitive(ctx, -1, DUK_HINT_NONE);
+
+	/* As a first approximation, buffer values are coerced to strings
+	 * for addition.  This means that adding two buffers currently
+	 * results in a string.
+	 */
+	if (duk_check_type_mask(ctx, -2, DUK_TYPE_MASK_STRING | DUK_TYPE_MASK_BUFFER) ||
+	    duk_check_type_mask(ctx, -1, DUK_TYPE_MASK_STRING | DUK_TYPE_MASK_BUFFER)) {
+		duk_to_string(ctx, -2);
+		duk_to_string(ctx, -1);
+		duk_concat(ctx, 2);  /* [... s1 s2] -> [... s1+s2] */
+		duk_replace(ctx, (duk_idx_t) idx_z);  /* side effects */
+	} else {
+		duk_double_t d1, d2;
+
+		d1 = duk_to_number(ctx, -2);
+		d2 = duk_to_number(ctx, -1);
+		DUK_ASSERT(duk_is_number(ctx, -2));
+		DUK_ASSERT(duk_is_number(ctx, -1));
+		DUK_ASSERT_DOUBLE_IS_NORMALIZED(d1);
+		DUK_ASSERT_DOUBLE_IS_NORMALIZED(d2);
+
+		du.d = d1 + d2;
+		DUK_DBLUNION_NORMALIZE_NAN_CHECK(&du);
+		DUK_ASSERT(DUK_DBLUNION_IS_NORMALIZED(&du));
+
+		duk_pop_2(ctx);
+		duk_push_number(ctx, du.d);
+		duk_replace(ctx, (duk_idx_t) idx_z);  /* side effects */
+	}
+}
+
+static void duk__vm_arith_binary_op(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y, duk_idx_t idx_z, duk_small_uint_fast_t opcode) {
+	/*
+	 *  Arithmetic operations other than '+' have number-only semantics
+	 *  and are implemented here.  The separate switch-case here means a
+	 *  "double dispatch" of the arithmetic opcode, but saves code space.
+	 *
+	 *  E5 Sections 11.5, 11.5.1, 11.5.2, 11.5.3, 11.6, 11.6.1, 11.6.2, 11.6.3.
+	 */
+
+	duk_context *ctx = (duk_context *) thr;
+	duk_tval tv_tmp;
+	duk_tval *tv_z;
+	duk_double_t d1, d2;
+	duk_double_union du;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(tv_x != NULL);  /* may be reg or const */
+	DUK_ASSERT(tv_y != NULL);  /* may be reg or const */
+	DUK_ASSERT_DISABLE(idx_z >= 0);  /* unsigned */
+	DUK_ASSERT((duk_uint_t) idx_z < (duk_uint_t) duk_get_top(ctx));
+
+	if (DUK_TVAL_IS_NUMBER(tv_x) && DUK_TVAL_IS_NUMBER(tv_y)) {
+		/* fast path */
+		d1 = DUK_TVAL_GET_NUMBER(tv_x);
+		d2 = DUK_TVAL_GET_NUMBER(tv_y);
+	} else {
+		duk_push_tval(ctx, tv_x);
+		duk_push_tval(ctx, tv_y);
+		d1 = duk_to_number(ctx, -2);  /* side effects */
+		d2 = duk_to_number(ctx, -1);
+		DUK_ASSERT(duk_is_number(ctx, -2));
+		DUK_ASSERT(duk_is_number(ctx, -1));
+		DUK_ASSERT_DOUBLE_IS_NORMALIZED(d1);
+		DUK_ASSERT_DOUBLE_IS_NORMALIZED(d2);
+		duk_pop_2(ctx);
+	}
+
+	switch (opcode) {
+	case DUK_OP_SUB: {
+		du.d = d1 - d2;
+		break;
+	}
+	case DUK_OP_MUL: {
+		du.d = d1 * d2;
+		break;
+	}
+	case DUK_OP_DIV: {
+		du.d = d1 / d2;
+		break;
+	}
+	case DUK_OP_MOD: {
+		du.d = duk__compute_mod(d1, d2);
+		break;
+	}
+	default: {
+		du.d = DUK_DOUBLE_NAN;  /* should not happen */
+		break;
+	}
+	}
+
+	/* important to use normalized NaN with 8-byte tagged types */
+	DUK_DBLUNION_NORMALIZE_NAN_CHECK(&du);
+	DUK_ASSERT(DUK_DBLUNION_IS_NORMALIZED(&du));
+	
+	tv_z = thr->valstack_bottom + idx_z;
+	DUK_TVAL_SET_TVAL(&tv_tmp, tv_z);
+	DUK_TVAL_SET_NUMBER(tv_z, du.d);
+	DUK_ASSERT(!DUK_TVAL_IS_HEAP_ALLOCATED(tv_z));  /* no need to incref */
+	DUK_TVAL_DECREF(thr, &tv_tmp);   /* side effects */
+}
+
+static void duk__vm_bitwise_binary_op(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y, duk_small_uint_fast_t idx_z, duk_small_uint_fast_t opcode) {
+	/*
+	 *  Binary bitwise operations use different coercions (ToInt32, ToUint32)
+	 *  depending on the operation.  We coerce the arguments first using
+	 *  ToInt32(), and then cast to an 32-bit value if necessary.  Note that
+	 *  such casts must be correct even if there is no native 32-bit type
+	 *  (e.g., duk_int32_t and duk_uint32_t are 64-bit).
+	 *
+	 *  E5 Sections 11.10, 11.7.1, 11.7.2, 11.7.3
+	 */
+
+	duk_context *ctx = (duk_context *) thr;
+	duk_tval tv_tmp;
+	duk_tval *tv_z;
+	duk_int32_t i1, i2;
+	duk_double_t val;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(tv_x != NULL);  /* may be reg or const */
+	DUK_ASSERT(tv_y != NULL);  /* may be reg or const */
+	DUK_ASSERT_DISABLE(idx_z >= 0);  /* unsigned */
+	DUK_ASSERT((duk_uint_t) idx_z < (duk_uint_t) duk_get_top(ctx));
+
+	duk_push_tval(ctx, tv_x);
+	duk_push_tval(ctx, tv_y);
+	i1 = duk_to_int32(ctx, -2);
+	i2 = duk_to_int32(ctx, -1);
+	duk_pop_2(ctx);
+
+	switch (opcode) {
+	case DUK_OP_BAND: {
+		val = (duk_double_t) (i1 & i2);
+		break;
+	}
+	case DUK_OP_BOR: {
+		val = (duk_double_t) (i1 | i2);
+		break;
+	}
+	case DUK_OP_BXOR: {
+		val = (duk_double_t) (i1 ^ i2);
+		break;
+	}
+	case DUK_OP_BASL: {
+		/* Signed shift, named "arithmetic" (asl) because the result
+		 * is signed, e.g. 4294967295 << 1 -> -2.  Note that result
+		 * must be masked.
+		 */
+
+		duk_uint32_t u2;
+		duk_int32_t i3;
+
+		u2 = ((duk_uint32_t) i2) & 0xffffffffUL;
+		i3 = i1 << (u2 & 0x1f);                     /* E5 Section 11.7.1, steps 7 and 8 */
+		i3 = i3 & ((duk_int32_t) 0xffffffffUL);      /* Note: left shift, should mask */
+		val = (duk_double_t) i3;
+		break;
+	}
+	case DUK_OP_BASR: {
+		/* signed shift */
+
+		duk_uint32_t u2;
+
+		u2 = ((duk_uint32_t) i2) & 0xffffffffUL;
+		val = (duk_double_t) (i1 >> (u2 & 0x1f));     /* E5 Section 11.7.2, steps 7 and 8 */
+		break;
+	}
+	case DUK_OP_BLSR: {
+		/* unsigned shift */
+
+		duk_uint32_t u1;
+		duk_uint32_t u2;
+
+		u1 = ((duk_uint32_t) i1) & 0xffffffffUL;
+		u2 = ((duk_uint32_t) i2) & 0xffffffffUL;
+
+		val = (duk_double_t) (u1 >> (u2 & 0x1f));     /* E5 Section 11.7.2, steps 7 and 8 */
+		break;
+	}
+	default: {
+		val = (duk_double_t) 0;  /* should not happen */
+		break;
+	}
+	}
+
+	DUK_ASSERT(!DUK_ISNAN(val));            /* 'val' is never NaN, so no need to normalize */
+	DUK_ASSERT_DOUBLE_IS_NORMALIZED(val);   /* always normalized */
+
+	tv_z = thr->valstack_bottom + idx_z;
+	DUK_TVAL_SET_TVAL(&tv_tmp, tv_z);
+	DUK_TVAL_SET_NUMBER(tv_z, val);
+	DUK_ASSERT(!DUK_TVAL_IS_HEAP_ALLOCATED(tv_z));  /* no need to incref */
+	DUK_TVAL_DECREF(thr, &tv_tmp);   /* side effects */
+}
+
+static void duk__vm_arith_unary_op(duk_hthread *thr, duk_tval *tv_x, duk_small_uint_fast_t idx_z, duk_small_uint_fast_t opcode) {
+	/*
+	 *  Arithmetic operations other than '+' have number-only semantics
+	 *  and are implemented here.  The separate switch-case here means a
+	 *  "double dispatch" of the arithmetic opcode, but saves code space.
+	 *
+	 *  E5 Sections 11.5, 11.5.1, 11.5.2, 11.5.3, 11.6, 11.6.1, 11.6.2, 11.6.3.
+	 */
+
+	duk_context *ctx = (duk_context *) thr;
+	duk_tval tv_tmp;
+	duk_tval *tv_z;
+	duk_double_t d1;
+	duk_double_union du;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(tv_x != NULL);  /* may be reg or const */
+	DUK_ASSERT_DISABLE(idx_z >= 0);  /* unsigned */
+	DUK_ASSERT((duk_uint_t) idx_z < (duk_uint_t) duk_get_top(ctx));
+
+	if (DUK_TVAL_IS_NUMBER(tv_x)) {
+		/* fast path */
+		d1 = DUK_TVAL_GET_NUMBER(tv_x);
+	} else {
+		duk_push_tval(ctx, tv_x);
+		d1 = duk_to_number(ctx, -1);  /* side effects */
+		DUK_ASSERT(duk_is_number(ctx, -1));
+		DUK_ASSERT_DOUBLE_IS_NORMALIZED(d1);
+		duk_pop(ctx);
+	}
+
+	switch (opcode) {
+	case DUK_EXTRAOP_UNM: {
+		du.d = -d1;
+		break;
+	}
+	case DUK_EXTRAOP_UNP: {
+		du.d = d1;
+		break;
+	}
+	case DUK_EXTRAOP_INC: {
+		du.d = d1 + 1.0;
+		break;
+	}
+	case DUK_EXTRAOP_DEC: {
+		du.d = d1 - 1.0;
+		break;
+	}
+	default: {
+		du.d = DUK_DOUBLE_NAN;  /* should not happen */
+		break;
+	}
+	}
+
+	DUK_DBLUNION_NORMALIZE_NAN_CHECK(&du);
+	DUK_ASSERT(DUK_DBLUNION_IS_NORMALIZED(&du));
+
+	tv_z = thr->valstack_bottom + idx_z;
+	DUK_TVAL_SET_TVAL(&tv_tmp, tv_z);
+	DUK_TVAL_SET_NUMBER(tv_z, du.d);
+	DUK_ASSERT(!DUK_TVAL_IS_HEAP_ALLOCATED(tv_z));  /* no need to incref */
+	DUK_TVAL_DECREF(thr, &tv_tmp);   /* side effects */
+}
+
+static void duk__vm_bitwise_not(duk_hthread *thr, duk_tval *tv_x, duk_small_uint_fast_t idx_z) {
+	/*
+	 *  E5 Section 11.4.8
+	 */
+
+	duk_context *ctx = (duk_context *) thr;
+	duk_tval tv_tmp;
+	duk_tval *tv_z;
+	duk_int32_t i1, i2;
+	duk_double_t val;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(tv_x != NULL);  /* may be reg or const */
+	DUK_ASSERT_DISABLE(idx_z >= 0);
+	DUK_ASSERT((duk_uint_t) idx_z < (duk_uint_t) duk_get_top(ctx));
+
+	duk_push_tval(ctx, tv_x);
+	i1 = duk_to_int32(ctx, -1);
+	duk_pop(ctx);
+
+	i2 = ~i1;
+	val = (duk_double_t) i2;
+
+	DUK_ASSERT(!DUK_ISNAN(val));            /* 'val' is never NaN, so no need to normalize */
+	DUK_ASSERT_DOUBLE_IS_NORMALIZED(val);   /* always normalized */
+
+	tv_z = thr->valstack_bottom + idx_z;
+	DUK_TVAL_SET_TVAL(&tv_tmp, tv_z);
+	DUK_TVAL_SET_NUMBER(tv_z, val);
+	DUK_ASSERT(!DUK_TVAL_IS_HEAP_ALLOCATED(tv_z));  /* no need to incref */
+	DUK_TVAL_DECREF(thr, &tv_tmp);   /* side effects */
+}
+
+static void duk__vm_logical_not(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_z) {
+	/*
+	 *  E5 Section 11.4.9
+	 */
+
+	duk_tval tv_tmp;
+	duk_bool_t res;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(tv_x != NULL);  /* may be reg or const */
+	DUK_ASSERT(tv_z != NULL);  /* reg */
+
+	/* ToBoolean() does not require any operations with side effects so
+	 * we can do it efficiently.  For footprint it would be better to use
+	 * duk_js_toboolean() and then push+replace to the result slot.
+	 */
+	res = duk_js_toboolean(tv_x);  /* does not modify tv_x */
+	DUK_ASSERT(res == 0 || res == 1);
+	res ^= 1;
+	DUK_TVAL_SET_TVAL(&tv_tmp, tv_z);
+	DUK_TVAL_SET_BOOLEAN(tv_z, res);  /* no need to incref */
+	DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+}
+
+/*
+ *  Longjmp handler for the bytecode executor (and a bunch of static
+ *  helpers for it).
+ *
+ *  Any type of longjmp() can be caught here, including intra-function
+ *  longjmp()s like 'break', 'continue', (slow) 'return', 'yield', etc.
+ *
+ *  Error policy: should not ordinarily throw errors.  Errors thrown
+ *  will bubble outwards.
+ *
+ *  Returns:
+ *    0   restart execution
+ *    1   bytecode executor finished
+ *    2   rethrow longjmp
+ */
+
+/* XXX: duk_api operations for cross-thread reg manipulation? */
+/* XXX: post-condition: value stack must be correct; for ecmascript functions, clamped to 'nregs' */
+
+#define DUK__LONGJMP_RESTART   0  /* state updated, restart bytecode execution */
+#define DUK__LONGJMP_FINISHED  1  /* exit bytecode executor with return value */
+#define DUK__LONGJMP_RETHROW   2  /* exit bytecode executor by rethrowing an error to caller */
+
+/* only called when act_idx points to an Ecmascript function */
+static void duk__reconfig_valstack(duk_hthread *thr, duk_size_t act_idx, duk_small_uint_t retval_count) {
+	duk_hcompiledfunction *h_func;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT_DISABLE(act_idx >= 0);  /* unsigned */
+	DUK_ASSERT(thr->callstack[act_idx].func != NULL);
+	DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(thr->callstack[act_idx].func));
+	DUK_ASSERT_DISABLE(thr->callstack[act_idx].idx_retval >= 0);  /* unsigned */
+
+	thr->valstack_bottom = thr->valstack + thr->callstack[act_idx].idx_bottom;
+
+	/* clamp so that retval is at the top (retval_count == 1) or register just before
+	 * intended retval is at the top (retval_count == 0, happens e.g. with 'finally').
+	 */
+	duk_set_top((duk_context *) thr, 
+	            (duk_idx_t) (thr->callstack[act_idx].idx_retval -
+	                         thr->callstack[act_idx].idx_bottom +
+	                         retval_count));
+
+	/*
+	 *  When returning to an Ecmascript function, extend the valstack
+	 *  top to 'nregs' always.
+	 */
+
+	h_func = (duk_hcompiledfunction *) thr->callstack[act_idx].func;
+
+	duk_require_valstack_resize((duk_context *) thr,
+	                            (thr->valstack_bottom - thr->valstack) +          /* bottom of current func */
+	                                h_func->nregs +                               /* reg count */
+	                                DUK_VALSTACK_INTERNAL_EXTRA,                  /* + spare */
+	                            1);                                               /* allow_shrink */
+
+	duk_set_top((duk_context *) thr, h_func->nregs);
+}
+
+static void duk__handle_catch_or_finally(duk_hthread *thr, duk_size_t cat_idx, duk_bool_t is_finally) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_tval tv_tmp;
+	duk_tval *tv1;
+
+	DUK_DDD(DUK_DDDPRINT("handling catch/finally, cat_idx=%ld, is_finally=%ld",
+	                     (long) cat_idx, (long) is_finally));
+
+	/*
+	 *  Set caught value and longjmp type to catcher regs.
+	 */
+
+	DUK_DDD(DUK_DDDPRINT("writing catch registers: idx_base=%ld -> %!T, idx_base+1=%ld -> %!T",
+	                     (long) thr->catchstack[cat_idx].idx_base,
+	                     (duk_tval *) &thr->heap->lj.value1,
+	                     (long) (thr->catchstack[cat_idx].idx_base + 1),
+	                     (duk_tval *) &thr->heap->lj.value2));
+
+	tv1 = thr->valstack + thr->catchstack[cat_idx].idx_base;
+	DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
+	DUK_TVAL_SET_TVAL(tv1, &thr->heap->lj.value1);
+	DUK_TVAL_INCREF(thr, tv1);
+	DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+
+	tv1 = thr->valstack + thr->catchstack[cat_idx].idx_base + 1;
+	DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
+	DUK_TVAL_SET_NUMBER(tv1, (duk_double_t) thr->heap->lj.type);  /* XXX: set int */
+	DUK_ASSERT(!DUK_TVAL_IS_HEAP_ALLOCATED(tv1));   /* no need to incref */
+	DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+
+	/*
+	 *  Unwind catchstack and callstack.
+	 *
+	 *  The 'cat_idx' catcher is always kept, even when executing finally.
+	 */
+
+	duk_hthread_catchstack_unwind(thr, cat_idx + 1);
+	duk_hthread_callstack_unwind(thr, thr->catchstack[cat_idx].callstack_index + 1);
+
+	/*
+	 *  Reconfigure valstack to 'nregs' (this is always the case for
+	 *  Ecmascript functions).
+	 */
+
+	DUK_ASSERT(thr->callstack_top >= 1);
+	DUK_ASSERT(thr->callstack[thr->callstack_top - 1].func != NULL);
+	DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(thr->callstack[thr->callstack_top - 1].func));
+
+	thr->valstack_bottom = thr->valstack + (thr->callstack + thr->callstack_top - 1)->idx_bottom;
+	duk_set_top((duk_context *) thr, ((duk_hcompiledfunction *) (thr->callstack + thr->callstack_top - 1)->func)->nregs);
+
+	/*
+	 *  Reset PC: resume execution from catch or finally jump slot.
+	 */
+
+	(thr->callstack + thr->callstack_top - 1)->pc =
+		thr->catchstack[cat_idx].pc_base + (is_finally ? 1 : 0);
+
+	/*
+	 *  If entering a 'catch' block which requires an automatic
+	 *  catch variable binding, create the lexical environment.
+	 *
+	 *  The binding is mutable (= writable) but not deletable.
+	 *  Step 4 for the catch production in E5 Section 12.14;
+	 *  no value is given for CreateMutableBinding 'D' argument,
+	 *  which implies the binding is not deletable.
+	 */
+
+	if (!is_finally && DUK_CAT_HAS_CATCH_BINDING_ENABLED(&thr->catchstack[cat_idx])) {
+		duk_activation *act;
+		duk_hobject *new_env;
+		duk_hobject *act_lex_env;
+
+		DUK_DDD(DUK_DDDPRINT("catcher has an automatic catch binding"));
+
+		/* Note: 'act' is dangerous here because it may get invalidate at many
+		 * points, so we re-lookup it multiple times.
+		 */
+		DUK_ASSERT(thr->callstack_top >= 1);
+		act = thr->callstack + thr->callstack_top - 1;
+
+		if (act->lex_env == NULL) {
+			DUK_ASSERT(act->var_env == NULL);
+			DUK_DDD(DUK_DDDPRINT("delayed environment initialization"));
+
+			/* this may have side effects, so re-lookup act */
+			duk_js_init_activation_environment_records_delayed(thr, act);
+			act = thr->callstack + thr->callstack_top - 1;
+		}
+		DUK_ASSERT(act->lex_env != NULL);
+		DUK_ASSERT(act->var_env != NULL);
+		DUK_ASSERT(act->func != NULL);
+		DUK_UNREF(act);  /* unreferenced without assertions */
+
+		act = thr->callstack + thr->callstack_top - 1;
+		act_lex_env = act->lex_env;
+		act = NULL;  /* invalidated */
+
+		(void) duk_push_object_helper_proto(ctx,
+		                                    DUK_HOBJECT_FLAG_EXTENSIBLE |
+		                                    DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DECENV),
+		                                    act_lex_env);
+		new_env = duk_require_hobject(ctx, -1);
+		DUK_ASSERT(new_env != NULL);
+		DUK_DDD(DUK_DDDPRINT("new_env allocated: %!iO", (duk_heaphdr *) new_env));
+
+		/* Note: currently the catch binding is handled without a register
+		 * binding because we don't support dynamic register bindings (they
+		 * must be fixed for an entire function).  So, there is no need to
+		 * record regbases etc.
+		 */
+
+		DUK_ASSERT(thr->catchstack[cat_idx].h_varname != NULL);
+		duk_push_hstring(ctx, thr->catchstack[cat_idx].h_varname);
+		duk_push_tval(ctx, &thr->heap->lj.value1);
+		duk_def_prop(ctx, -3, DUK_PROPDESC_FLAGS_W);  /* writable, not configurable */
+
+		act = thr->callstack + thr->callstack_top - 1;
+		act->lex_env = new_env;
+		DUK_HOBJECT_INCREF(thr, new_env);  /* reachable through activation */
+
+		DUK_CAT_SET_LEXENV_ACTIVE(&thr->catchstack[cat_idx]);
+
+		duk_pop(ctx);
+
+		DUK_DDD(DUK_DDDPRINT("new_env finished: %!iO", (duk_heaphdr *) new_env));
+	}
+
+	if (is_finally) {
+		DUK_CAT_CLEAR_FINALLY_ENABLED(&thr->catchstack[cat_idx]);
+	} else {
+		DUK_CAT_CLEAR_CATCH_ENABLED(&thr->catchstack[cat_idx]);
+	}
+}
+
+static void duk__handle_label(duk_hthread *thr, duk_size_t cat_idx) {
+	/* no callstack changes, no value stack changes */
+
+	/* +0 = break, +1 = continue */
+	(thr->callstack + thr->callstack_top - 1)->pc =
+		thr->catchstack[cat_idx].pc_base + (thr->heap->lj.type == DUK_LJ_TYPE_CONTINUE ? 1 : 0);
+
+	duk_hthread_catchstack_unwind(thr, cat_idx + 1);  /* keep label catcher */
+	/* no need to unwind callstack */
+}
+
+/* Note: called for DUK_LJ_TYPE_YIELD and for DUK_LJ_TYPE_RETURN, when a
+ * return terminates a thread and yields to the resumer.
+ */
+static void duk__handle_yield(duk_hthread *thr, duk_hthread *resumer, duk_size_t act_idx) {
+	duk_tval tv_tmp;
+	duk_tval *tv1;
+
+	/* this may also be called for DUK_LJ_TYPE_RETURN; this is OK as long as
+	 * lj.value1 is correct.
+	 */
+
+	DUK_ASSERT(resumer->callstack[act_idx].func != NULL);
+	DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(resumer->callstack[act_idx].func));  /* resume caller must be an ecmascript func */
+
+	DUK_DDD(DUK_DDDPRINT("resume idx_retval is %ld", (long) resumer->callstack[act_idx].idx_retval));
+
+	tv1 = resumer->valstack + resumer->callstack[act_idx].idx_retval;  /* return value from Duktape.Thread.resume() */
+	DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
+	DUK_TVAL_SET_TVAL(tv1, &thr->heap->lj.value1);
+	DUK_TVAL_INCREF(thr, tv1);
+	DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+
+	duk_hthread_callstack_unwind(resumer, act_idx + 1);  /* unwind to 'resume' caller */
+
+	/* no need to unwind catchstack */
+	duk__reconfig_valstack(resumer, act_idx, 1);  /* 1 = have retval */
+
+	/* caller must change active thread, and set thr->resumer to NULL */
+}
+
+static duk_small_uint_t duk__handle_longjmp(duk_hthread *thr,
+                                            duk_hthread *entry_thread,
+                                            duk_size_t entry_callstack_top) {
+	duk_tval tv_tmp;
+	duk_size_t entry_callstack_index;
+	duk_small_uint_t retval = DUK__LONGJMP_RESTART;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(entry_thread != NULL);
+	DUK_ASSERT(entry_callstack_top > 0);  /* guarantees entry_callstack_top - 1 >= 0 */
+
+	entry_callstack_index = entry_callstack_top - 1;
+
+	/* 'thr' is the current thread, as no-one resumes except us and we
+	 * switch 'thr' in that case.
+	 */
+
+	/*
+	 *  (Re)try handling the longjmp.
+	 *
+	 *  A longjmp handler may convert the longjmp to a different type and
+	 *  "virtually" rethrow by goto'ing to 'check_longjmp'.  Before the goto,
+	 *  the following must be updated:
+	 *    - the heap 'lj' state
+	 *    - 'thr' must reflect the "throwing" thread
+	 */
+
+ check_longjmp:
+
+	DUK_DD(DUK_DDPRINT("handling longjmp: type=%ld, value1=%!T, value2=%!T, iserror=%ld",
+	                   (long) thr->heap->lj.type,
+	                   (duk_tval *) &thr->heap->lj.value1,
+	                   (duk_tval *) &thr->heap->lj.value2,
+	                   (long) thr->heap->lj.iserror));
+
+	switch (thr->heap->lj.type) {
+
+	case DUK_LJ_TYPE_RESUME: {
+		/*
+		 *  Note: lj.value1 is 'value', lj.value2 is 'resumee'.
+		 *  This differs from YIELD.
+		 */
+
+		duk_tval *tv;
+		duk_tval *tv2;
+		duk_size_t act_idx;
+		duk_hthread *resumee;
+
+		/* duk_bi_duk_object_yield() and duk_bi_duk_object_resume() ensure all of these are met */
+
+		DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING);                                                         /* unchanged by Duktape.Thread.resume() */
+		DUK_ASSERT(thr->callstack_top >= 2);                                                                         /* Ecmascript activation + Duktape.Thread.resume() activation */
+		DUK_ASSERT((thr->callstack + thr->callstack_top - 1)->func != NULL &&
+		           DUK_HOBJECT_IS_NATIVEFUNCTION((thr->callstack + thr->callstack_top - 1)->func) &&
+		           ((duk_hnativefunction *) (thr->callstack + thr->callstack_top - 1)->func)->func == duk_bi_thread_resume);
+		DUK_ASSERT((thr->callstack + thr->callstack_top - 2)->func != NULL &&
+		           DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->callstack + thr->callstack_top - 2)->func));                /* an Ecmascript function */
+		DUK_ASSERT_DISABLE((thr->callstack + thr->callstack_top - 2)->idx_retval >= 0);                              /* unsigned */
+
+		tv = &thr->heap->lj.value2;  /* resumee */
+		DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
+		DUK_ASSERT(DUK_TVAL_GET_OBJECT(tv) != NULL);
+		DUK_ASSERT(DUK_HOBJECT_IS_THREAD(DUK_TVAL_GET_OBJECT(tv)));
+		resumee = (duk_hthread *) DUK_TVAL_GET_OBJECT(tv);
+
+		DUK_ASSERT(resumee != NULL);
+		DUK_ASSERT(resumee->resumer == NULL);
+		DUK_ASSERT(resumee->state == DUK_HTHREAD_STATE_INACTIVE ||
+		           resumee->state == DUK_HTHREAD_STATE_YIELDED);                                                     /* checked by Duktape.Thread.resume() */
+		DUK_ASSERT(resumee->state != DUK_HTHREAD_STATE_YIELDED ||
+		           resumee->callstack_top >= 2);                                                                     /* YIELDED: Ecmascript activation + Duktape.Thread.yield() activation */
+		DUK_ASSERT(resumee->state != DUK_HTHREAD_STATE_YIELDED ||
+		           ((resumee->callstack + resumee->callstack_top - 1)->func != NULL &&
+		            DUK_HOBJECT_IS_NATIVEFUNCTION((resumee->callstack + resumee->callstack_top - 1)->func) &&
+		            ((duk_hnativefunction *) (resumee->callstack + resumee->callstack_top - 1)->func)->func == duk_bi_thread_yield));
+		DUK_ASSERT(resumee->state != DUK_HTHREAD_STATE_YIELDED ||
+		           ((resumee->callstack + resumee->callstack_top - 2)->func != NULL &&
+		            DUK_HOBJECT_IS_COMPILEDFUNCTION((resumee->callstack + resumee->callstack_top - 2)->func)));      /* an Ecmascript function */
+		DUK_ASSERT_DISABLE(resumee->state != DUK_HTHREAD_STATE_YIELDED ||
+		           (resumee->callstack + resumee->callstack_top - 2)->idx_retval >= 0);                              /* idx_retval unsigned */
+		DUK_ASSERT(resumee->state != DUK_HTHREAD_STATE_INACTIVE ||
+		           resumee->callstack_top == 0);                                                                     /* INACTIVE: no activation, single function value on valstack */
+		DUK_ASSERT(resumee->state != DUK_HTHREAD_STATE_INACTIVE ||
+		           (resumee->valstack_top == resumee->valstack + 1 &&
+		            DUK_TVAL_IS_OBJECT(resumee->valstack_top - 1) &&
+		            DUK_HOBJECT_IS_COMPILEDFUNCTION(DUK_TVAL_GET_OBJECT(resumee->valstack_top - 1))));
+
+		if (thr->heap->lj.iserror) {
+			/*
+			 *  Throw the error in the resumed thread's context; the
+			 *  error value is pushed onto the resumee valstack.
+			 *
+			 *  Note: the callstack of the target may empty in this case
+			 *  too (i.e. the target thread has never been resumed).  The
+			 *  value stack will contain the initial function in that case,
+			 *  which we simply ignore.
+			 */
+
+			resumee->resumer = thr;
+			resumee->state = DUK_HTHREAD_STATE_RUNNING;
+			thr->state = DUK_HTHREAD_STATE_RESUMED;	
+			DUK_HEAP_SWITCH_THREAD(thr->heap, resumee);
+			thr = resumee;
+
+			thr->heap->lj.type = DUK_LJ_TYPE_THROW;
+
+			/* thr->heap->lj.value1 is already the value to throw */
+			/* thr->heap->lj.value2 is 'thread', will be wiped out at the end */
+
+			DUK_ASSERT(thr->heap->lj.iserror);  /* already set */
+
+			DUK_DD(DUK_DDPRINT("-> resume with an error, converted to a throw in the resumee, propagate"));
+			goto check_longjmp;
+		} else if (resumee->state == DUK_HTHREAD_STATE_YIELDED) {
+			act_idx = resumee->callstack_top - 2;  /* Ecmascript function */
+			DUK_ASSERT_DISABLE(resumee->callstack[act_idx].idx_retval >= 0);  /* unsigned */
+
+			tv = resumee->valstack + resumee->callstack[act_idx].idx_retval;  /* return value from Duktape.Thread.yield() */
+			DUK_ASSERT(tv >= resumee->valstack && tv < resumee->valstack_top);
+			tv2 = &thr->heap->lj.value1;
+			DUK_TVAL_SET_TVAL(&tv_tmp, tv);
+			DUK_TVAL_SET_TVAL(tv, tv2);
+			DUK_TVAL_INCREF(thr, tv);
+			DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+
+			duk_hthread_callstack_unwind(resumee, act_idx + 1);  /* unwind to 'yield' caller */
+
+			/* no need to unwind catchstack */
+
+			duk__reconfig_valstack(resumee, act_idx, 1);  /* 1 = have retval */
+
+			resumee->resumer = thr;
+			resumee->state = DUK_HTHREAD_STATE_RUNNING;
+			thr->state = DUK_HTHREAD_STATE_RESUMED;
+			DUK_HEAP_SWITCH_THREAD(thr->heap, resumee);
+#if 0
+			thr = resumee;  /* not needed, as we exit right away */
+#endif
+			DUK_DD(DUK_DDPRINT("-> resume with a value, restart execution in resumee"));
+			retval = DUK__LONGJMP_RESTART;
+			goto wipe_and_return;
+		} else {
+			int call_flags;
+
+			/* resumee: [... initial_func]  (currently actually: [initial_func]) */
+
+			duk_push_undefined((duk_context *) resumee);
+			tv = &thr->heap->lj.value1;
+			duk_push_tval((duk_context *) resumee, tv);
+
+			/* resumee: [... initial_func undefined(= this) resume_value ] */
+
+			call_flags = DUK_CALL_FLAG_IS_RESUME;  /* is resume, not a tailcall */
+
+			duk_handle_ecma_call_setup(resumee,
+			                           1,              /* num_stack_args */
+			                           call_flags);    /* call_flags */
+
+			resumee->resumer = thr;
+			resumee->state = DUK_HTHREAD_STATE_RUNNING;
+			thr->state = DUK_HTHREAD_STATE_RESUMED;
+			DUK_HEAP_SWITCH_THREAD(thr->heap, resumee);
+#if 0
+			thr = resumee;  /* not needed, as we exit right away */
+#endif
+			DUK_DD(DUK_DDPRINT("-> resume with a value, restart execution in resumee"));
+			retval = DUK__LONGJMP_RESTART;
+			goto wipe_and_return;
+		}
+		DUK_UNREACHABLE();
+		break;  /* never here */
+	}
+
+	case DUK_LJ_TYPE_YIELD: {
+		/*
+		 *  Currently only allowed only if yielding thread has only
+		 *  Ecmascript activations (except for the Duktape.Thread.yield()
+		 *  call at the callstack top) and none of them constructor
+		 *  calls.
+		 *
+		 *  This excludes the 'entry' thread which will always have
+		 *  a preventcount > 0.
+		 */
+
+		duk_hthread *resumer;
+
+		/* duk_bi_duk_object_yield() and duk_bi_duk_object_resume() ensure all of these are met */
+
+		DUK_ASSERT(thr != entry_thread);                                                                             /* Duktape.Thread.yield() should prevent */
+		DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING);                                                         /* unchanged from Duktape.Thread.yield() */
+		DUK_ASSERT(thr->callstack_top >= 2);                                                                         /* Ecmascript activation + Duktape.Thread.yield() activation */
+		DUK_ASSERT((thr->callstack + thr->callstack_top - 1)->func != NULL &&
+		           DUK_HOBJECT_IS_NATIVEFUNCTION((thr->callstack + thr->callstack_top - 1)->func) &&
+		           ((duk_hnativefunction *) (thr->callstack + thr->callstack_top - 1)->func)->func == duk_bi_thread_yield);
+		DUK_ASSERT((thr->callstack + thr->callstack_top - 2)->func != NULL &&
+		           DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->callstack + thr->callstack_top - 2)->func));                /* an Ecmascript function */
+		DUK_ASSERT_DISABLE((thr->callstack + thr->callstack_top - 2)->idx_retval >= 0);                              /* unsigned */
+
+		resumer = thr->resumer;
+
+		DUK_ASSERT(resumer != NULL);
+		DUK_ASSERT(resumer->state == DUK_HTHREAD_STATE_RESUMED);                                                     /* written by a previous RESUME handling */
+		DUK_ASSERT(resumer->callstack_top >= 2);                                                                     /* Ecmascript activation + Duktape.Thread.resume() activation */
+		DUK_ASSERT((resumer->callstack + resumer->callstack_top - 1)->func != NULL &&
+		           DUK_HOBJECT_IS_NATIVEFUNCTION((resumer->callstack + resumer->callstack_top - 1)->func) &&
+		           ((duk_hnativefunction *) (resumer->callstack + resumer->callstack_top - 1)->func)->func == duk_bi_thread_resume);
+		DUK_ASSERT((resumer->callstack + resumer->callstack_top - 2)->func != NULL &&
+		           DUK_HOBJECT_IS_COMPILEDFUNCTION((resumer->callstack + resumer->callstack_top - 2)->func));        /* an Ecmascript function */
+		DUK_ASSERT_DISABLE((resumer->callstack + resumer->callstack_top - 2)->idx_retval >= 0);                      /* unsigned */
+
+		if (thr->heap->lj.iserror) {
+			thr->state = DUK_HTHREAD_STATE_YIELDED;
+			thr->resumer = NULL;
+			resumer->state = DUK_HTHREAD_STATE_RUNNING;
+			DUK_HEAP_SWITCH_THREAD(thr->heap, resumer);
+			thr = resumer;
+
+			thr->heap->lj.type = DUK_LJ_TYPE_THROW;
+			/* lj.value1 is already set */
+			DUK_ASSERT(thr->heap->lj.iserror);  /* already set */
+
+			DUK_DD(DUK_DDPRINT("-> yield an error, converted to a throw in the resumer, propagate"));
+			goto check_longjmp;
+		} else {
+			duk__handle_yield(thr, resumer, resumer->callstack_top - 2);
+
+			thr->state = DUK_HTHREAD_STATE_YIELDED;
+			thr->resumer = NULL;
+			resumer->state = DUK_HTHREAD_STATE_RUNNING;
+			DUK_HEAP_SWITCH_THREAD(thr->heap, resumer);
+#if 0
+			thr = resumer;  /* not needed, as we exit right away */
+#endif
+
+			DUK_DD(DUK_DDPRINT("-> yield a value, restart execution in resumer"));
+			retval = DUK__LONGJMP_RESTART;
+			goto wipe_and_return;
+		}
+		DUK_UNREACHABLE();
+		break;  /* never here */
+	}
+
+	case DUK_LJ_TYPE_RETURN: {
+		/*
+		 *  Four possible outcomes:
+		 *    * A 'finally' in the same function catches the 'return'.
+		 *      (or)
+		 *    * The return happens at the entry level of the bytecode
+		 *      executor, so return from the executor (in C stack).
+		 *      (or)
+		 *    * There is a calling (Ecmascript) activation in the call
+		 *      stack => return to it.
+		 *      (or)
+		 *    * There is no calling activation, and the thread is
+		 *      terminated.  There is always a resumer in this case,
+		 *      which gets the return value similarly to a 'yield'
+		 *      (except that the current thread can no longer be
+		 *      resumed).
+		 */
+
+		duk_tval *tv1;
+		duk_hthread *resumer;
+		duk_catcher *cat;
+		duk_size_t orig_callstack_index;
+
+		DUK_ASSERT(thr != NULL);
+		DUK_ASSERT(thr->callstack_top >= 1);
+		DUK_ASSERT(thr->catchstack != NULL);
+
+		/* XXX: does not work if thr->catchstack is NULL */
+		/* XXX: does not work if thr->catchstack is allocated but lowest pointer */
+
+		cat = thr->catchstack + thr->catchstack_top - 1;  /* may be < thr->catchstack initially */
+		DUK_ASSERT(thr->callstack_top > 0);  /* ensures callstack_top - 1 >= 0 */
+		orig_callstack_index = thr->callstack_top - 1;
+
+		while (cat >= thr->catchstack) {
+			if (cat->callstack_index != orig_callstack_index) {
+				break;
+			}
+			if (DUK_CAT_GET_TYPE(cat) == DUK_CAT_TYPE_TCF &&
+			    DUK_CAT_HAS_FINALLY_ENABLED(cat)) {
+				/* 'finally' catches */
+				duk__handle_catch_or_finally(thr,
+				                             cat - thr->catchstack,
+				                             1); /* is_finally */
+
+				DUK_DD(DUK_DDPRINT("-> return caught by a finally (in the same function), restart execution"));
+				retval = DUK__LONGJMP_RESTART;
+				goto wipe_and_return;
+			}
+			cat--;
+		}
+		/* if out of catchstack, cat = thr->catchstack - 1 */
+
+		DUK_DD(DUK_DDPRINT("no catcher in catch stack, return to calling activation / yield"));
+
+		/* return to calling activation (if any) */
+
+		if (thr == entry_thread &&
+		    thr->callstack_top == entry_callstack_top) {
+			/* return to the bytecode executor caller */
+
+			duk_push_tval((duk_context *) thr, &thr->heap->lj.value1);
+
+			/* [ ... retval ] */
+
+			DUK_DD(DUK_DDPRINT("-> return propagated up to entry level, exit bytecode executor"));
+			retval = DUK__LONGJMP_FINISHED;
+			goto wipe_and_return;
+		}
+
+		if (thr->callstack_top >= 2) {
+			/* there is a caller; it MUST be an Ecmascript caller (otherwise it would
+			 * match entry level check)
+			 */
+
+			DUK_DDD(DUK_DDDPRINT("slow return to Ecmascript caller, idx_retval=%ld, lj_value1=%!T",
+			                     (long) (thr->callstack + thr->callstack_top - 2)->idx_retval,
+			                     (duk_tval *) &thr->heap->lj.value1));
+
+			DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->callstack + thr->callstack_top - 2)->func));   /* must be ecmascript */
+
+			tv1 = thr->valstack + (thr->callstack + thr->callstack_top - 2)->idx_retval;
+			DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
+			DUK_TVAL_SET_TVAL(tv1, &thr->heap->lj.value1);
+			DUK_TVAL_INCREF(thr, tv1);
+			DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+
+			DUK_DDD(DUK_DDDPRINT("return value at idx_retval=%ld is %!T",
+			                     (long) (thr->callstack + thr->callstack_top - 2)->idx_retval,
+			                     (duk_tval *) (thr->valstack + (thr->callstack + thr->callstack_top - 2)->idx_retval)));
+
+			duk_hthread_catchstack_unwind(thr, (cat - thr->catchstack) + 1);  /* leave 'cat' as top catcher (also works if catchstack exhausted) */
+			duk_hthread_callstack_unwind(thr, thr->callstack_top - 1);
+			duk__reconfig_valstack(thr, thr->callstack_top - 1, 1);    /* new top, i.e. callee */
+
+			DUK_DD(DUK_DDPRINT("-> return not caught, restart execution in caller"));
+			retval = DUK__LONGJMP_RESTART;
+			goto wipe_and_return;
+		}
+	
+		DUK_DD(DUK_DDPRINT("no calling activation, thread finishes (similar to yield)"));
+
+		DUK_ASSERT(thr->resumer != NULL);
+		DUK_ASSERT(thr->resumer->callstack_top >= 2);  /* Ecmascript activation + Duktape.Thread.resume() activation */
+		DUK_ASSERT((thr->resumer->callstack + thr->resumer->callstack_top - 1)->func != NULL &&
+		           DUK_HOBJECT_IS_NATIVEFUNCTION((thr->resumer->callstack + thr->resumer->callstack_top - 1)->func) &&
+		           ((duk_hnativefunction *) (thr->resumer->callstack + thr->resumer->callstack_top - 1)->func)->func == duk_bi_thread_resume);  /* Duktape.Thread.resume() */
+		DUK_ASSERT((thr->resumer->callstack + thr->resumer->callstack_top - 2)->func != NULL &&
+		           DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->resumer->callstack + thr->resumer->callstack_top - 2)->func));  /* an Ecmascript function */
+		DUK_ASSERT_DISABLE((thr->resumer->callstack + thr->resumer->callstack_top - 2)->idx_retval >= 0);                /* unsigned */
+		DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING);
+		DUK_ASSERT(thr->resumer->state == DUK_HTHREAD_STATE_RESUMED);
+
+		resumer = thr->resumer;
+
+		duk__handle_yield(thr, resumer, resumer->callstack_top - 2);
+
+		duk_hthread_terminate(thr);  /* updates thread state, minimizes its allocations */
+		DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_TERMINATED);
+
+		thr->resumer = NULL;
+		resumer->state = DUK_HTHREAD_STATE_RUNNING;
+		DUK_HEAP_SWITCH_THREAD(thr->heap, resumer);
+#if 0
+		thr = resumer;  /* not needed */
+#endif
+
+		DUK_DD(DUK_DDPRINT("-> return not caught, thread terminated; handle like yield, restart execution in resumer"));
+		retval = DUK__LONGJMP_RESTART;
+		goto wipe_and_return;
+	}
+
+	case DUK_LJ_TYPE_BREAK:
+	case DUK_LJ_TYPE_CONTINUE: {
+		/*
+		 *  Find a matching label catcher or 'finally' catcher in
+		 *  the same function.
+		 *  
+		 *  A label catcher must always exist and will match unless
+		 *  a 'finally' captures the break/continue first.  It is the
+		 *  compiler's responsibility to ensure that labels are used
+		 *  correctly.
+		 */
+
+		duk_catcher *cat;
+		duk_size_t orig_callstack_index;
+		duk_uint_t lj_label;
+
+		cat = thr->catchstack + thr->catchstack_top - 1;
+		orig_callstack_index = cat->callstack_index;
+
+		DUK_ASSERT(DUK_TVAL_IS_NUMBER(&thr->heap->lj.value1));
+		lj_label = (duk_uint_t) DUK_TVAL_GET_NUMBER(&thr->heap->lj.value1);
+
+		DUK_DDD(DUK_DDDPRINT("handling break/continue with label=%ld, callstack index=%ld",
+		                     (long) lj_label, (long) cat->callstack_index));
+
+		while (cat >= thr->catchstack) {
+			if (cat->callstack_index != orig_callstack_index) {
+				break;
+			}
+			DUK_DDD(DUK_DDDPRINT("considering catcher %ld: type=%ld label=%ld",
+			                     (long) (cat - thr->catchstack),
+			                     (long) DUK_CAT_GET_TYPE(cat),
+			                     (long) DUK_CAT_GET_LABEL(cat)));
+
+			if (DUK_CAT_GET_TYPE(cat) == DUK_CAT_TYPE_TCF &&
+			    DUK_CAT_HAS_FINALLY_ENABLED(cat)) {
+				/* finally catches */
+				duk__handle_catch_or_finally(thr,
+				                             cat - thr->catchstack,
+				                             1); /* is_finally */
+
+				DUK_DD(DUK_DDPRINT("-> break/continue caught by a finally (in the same function), restart execution"));
+				retval = DUK__LONGJMP_RESTART;
+				goto wipe_and_return;
+			}
+			if (DUK_CAT_GET_TYPE(cat) == DUK_CAT_TYPE_LABEL &&
+			    (duk_uint_t) DUK_CAT_GET_LABEL(cat) == lj_label) {
+				/* found label */
+				duk__handle_label(thr,
+				                  cat - thr->catchstack);
+
+				/* FIXME: reset valstack to 'nregs' (or assert it) */
+
+				DUK_DD(DUK_DDPRINT("-> break/continue caught by a label catcher (in the same function), restart execution"));
+				retval = DUK__LONGJMP_RESTART;
+				goto wipe_and_return;
+			}
+			cat--;
+		}
+
+		/* should never happen, but be robust */
+		DUK_D(DUK_DPRINT("break/continue not caught by anything in the current function (should never happen)"));
+		goto convert_to_internal_error;
+	}
+
+	case DUK_LJ_TYPE_THROW: {
+		/*
+		 *  Three possible outcomes:
+		 *    * A try or finally catcher is found => resume there.
+		 *      (or)
+		 *    * The error propagates to the bytecode executor entry
+		 *      level (and we're in the entry thread) => rethrow
+		 *      with a new longjmp(), after restoring the previous
+		 *      catchpoint.
+		 *    * The error is not caught in the current thread, so
+		 *      the thread finishes with an error.  This works like
+		 *      a yielded error, except that the thread is finished
+		 *      and can no longer be resumed.  (There is always a
+		 *      resumer in this case.)
+		 *
+		 *  Note: until we hit the entry level, there can only be
+		 *  Ecmascript activations.
+		 */
+
+		duk_catcher *cat;
+		duk_hthread *resumer;
+
+		cat = thr->catchstack + thr->catchstack_top - 1;
+		while (cat >= thr->catchstack) {
+			if (thr == entry_thread &&
+			    cat->callstack_index < entry_callstack_index) {
+				/* entry level reached */
+				break;
+			}
+
+			if (DUK_CAT_HAS_CATCH_ENABLED(cat)) {
+				/* try catches */
+				DUK_ASSERT(DUK_CAT_GET_TYPE(cat) == DUK_CAT_TYPE_TCF);
+
+				duk__handle_catch_or_finally(thr,
+				                             cat - thr->catchstack,
+				                             0); /* is_finally */
+
+				DUK_DD(DUK_DDPRINT("-> throw caught by a 'catch' clause, restart execution"));
+				retval = DUK__LONGJMP_RESTART;
+				goto wipe_and_return;
+			}
+
+			if (DUK_CAT_HAS_FINALLY_ENABLED(cat)) {
+				DUK_ASSERT(DUK_CAT_GET_TYPE(cat) == DUK_CAT_TYPE_TCF);
+				DUK_ASSERT(!DUK_CAT_HAS_CATCH_ENABLED(cat));
+
+				duk__handle_catch_or_finally(thr,
+				                             cat - thr->catchstack,
+				                             1); /* is_finally */
+
+				/* FIXME: reset valstack to 'nregs' (or assert it) */
+
+				DUK_DD(DUK_DDPRINT("-> throw caught by a 'finally' clause, restart execution"));
+				retval = DUK__LONGJMP_RESTART;
+				goto wipe_and_return;
+			}
+
+			cat--;
+		}
+
+		if (thr == entry_thread) {
+			/* not caught by anything before entry level; rethrow and let the
+			 * final catcher unwind everything
+			 */
+#if 0
+			duk_hthread_catchstack_unwind(thr, (cat - thr->catchstack) + 1);  /* leave 'cat' as top catcher (also works if catchstack exhausted) */
+			duk_hthread_callstack_unwind(thr, entry_callstack_index + 1);
+
+#endif
+			DUK_D(DUK_DPRINT("-> throw propagated up to entry level, rethrow and exit bytecode executor"));
+			retval = DUK__LONGJMP_RETHROW;
+			goto just_return;
+			/* Note: MUST NOT wipe_and_return here, as heap->lj must remain intact */
+		}
+
+		DUK_DD(DUK_DDPRINT("not caught by current thread, yield error to resumer"));
+
+		/* not caught by current thread, thread terminates (yield error to resumer);
+		 * note that this may cause a cascade if the resumer terminates with an uncaught
+		 * exception etc (this is OK, but needs careful testing)
+		 */
+
+		DUK_ASSERT(thr->resumer != NULL);
+		DUK_ASSERT(thr->resumer->callstack_top >= 2);  /* Ecmascript activation + Duktape.Thread.resume() activation */
+		DUK_ASSERT((thr->resumer->callstack + thr->resumer->callstack_top - 1)->func != NULL &&
+		           DUK_HOBJECT_IS_NATIVEFUNCTION((thr->resumer->callstack + thr->resumer->callstack_top - 1)->func) &&
+		           ((duk_hnativefunction *) (thr->resumer->callstack + thr->resumer->callstack_top - 1)->func)->func == duk_bi_thread_resume);  /* Duktape.Thread.resume() */
+		DUK_ASSERT((thr->resumer->callstack + thr->resumer->callstack_top - 2)->func != NULL &&
+		           DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->resumer->callstack + thr->resumer->callstack_top - 2)->func));  /* an Ecmascript function */
+
+		resumer = thr->resumer;
+
+		/* reset longjmp */
+
+		DUK_ASSERT(thr->heap->lj.type == DUK_LJ_TYPE_THROW);  /* already set */
+		/* lj.value1 already set */
+
+		duk_hthread_terminate(thr);  /* updates thread state, minimizes its allocations */
+		DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_TERMINATED);
+
+		thr->resumer = NULL;
+		resumer->state = DUK_HTHREAD_STATE_RUNNING;
+		DUK_HEAP_SWITCH_THREAD(thr->heap, resumer);
+		thr = resumer;
+		goto check_longjmp;
+	}
+
+	case DUK_LJ_TYPE_NORMAL: {
+		DUK_D(DUK_DPRINT("caught DUK_LJ_TYPE_NORMAL, should never happen, treat as internal error"));
+		goto convert_to_internal_error;
+	}
+
+	default: {
+		/* should never happen, but be robust */
+		DUK_D(DUK_DPRINT("caught unknown longjmp type %ld, treat as internal error", (long) thr->heap->lj.type));
+		goto convert_to_internal_error;
+	}
+
+	}  /* end switch */
+
+	DUK_UNREACHABLE();
+
+ wipe_and_return:
+	/* this is not strictly necessary, but helps debugging */
+	thr->heap->lj.type = DUK_LJ_TYPE_UNKNOWN;
+	thr->heap->lj.iserror = 0;
+
+	DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value1);
+	DUK_TVAL_SET_UNDEFINED_UNUSED(&thr->heap->lj.value1);
+	DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+
+	DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value2);
+	DUK_TVAL_SET_UNDEFINED_UNUSED(&thr->heap->lj.value2);
+	DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+
+ just_return:
+	return retval;
+
+ convert_to_internal_error:
+	/* This could also be thrown internally (set the error, goto check_longjmp),
+	 * but it's better for internal errors to bubble outwards.
+	 */
+	DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, DUK_STR_INTERNAL_ERROR_EXEC_LONGJMP);
+	DUK_UNREACHABLE();
+	return retval;
+}
+
+/*
+ *  Executor interrupt handling
+ *
+ *  The handler is called whenever the interrupt countdown reaches zero
+ *  (or below).  The handler must perform whatever checks are activated,
+ *  e.g. check for cumulative step count to impose an execution step
+ *  limit or check for breakpoints or other debugger interaction.
+ *
+ *  When the actions are done, the handler must reinit the interrupt
+ *  init and counter values.  The 'init' value must indicate how many
+ *  bytecode instructions are executed before the next interrupt.  The
+ *  counter must interface with the bytecode executor loop.  Concretely,
+ *  the new init value is normally one higher than the new counter value.
+ *  For instance, to execute exactly one bytecode instruction the init
+ *  value is set to 1 and the counter to 0.  If an error is thrown by the
+ *  interrupt handler, the counters are set to the same value (e.g. both
+ *  to 0 to cause an interrupt when the next bytecode instruction is about
+ *  to be executed after error handling).
+ *
+ *  Maintaining the init/counter value properly is important for accurate
+ *  behavior.  For instance, executor step limit needs a cumulative step
+ *  count which is simply computed as a sum of 'init' values.  This must
+ *  work accurately even when single stepping.
+ */
+
+#ifdef DUK_USE_INTERRUPT_COUNTER
+static void duk__executor_interrupt(duk_hthread *thr) {
+	duk_int_t ctr;
+	duk_activation *act;
+	duk_hcompiledfunction *fun;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->callstack != NULL);
+	DUK_ASSERT(thr->callstack_top > 0);
+
+	act = thr->callstack + thr->callstack_top - 1;
+	fun = (duk_hcompiledfunction *) act->func;
+	DUK_ASSERT(DUK_HOBJECT_HAS_COMPILEDFUNCTION((duk_hobject *) fun));
+	DUK_UNREF(fun);
+
+	ctr = DUK_HEAP_INTCTR_DEFAULT;
+
+#if 0
+	/* XXX: cumulative instruction count example */
+	static int step_count = 0;
+	step_count += thr->heap->interrupt_init;
+	if (step_count >= 1000000L) {
+		/* Keep throwing an error whenever we get here.  The unusual values
+		 * are set this way because no instruction is ever executed, we just
+		 * throw an error until all try/catch/finally and other catchpoints
+		 * have been exhausted.
+		 */
+		DUK_D(DUK_DPRINT("execution step limit reached, throwing a RangeError"));
+		thr->heap->interrupt_init = 0;
+		thr->heap->interrupt_counter = 0;
+		thr->interrupt_counter = 0;
+		DUK_ERROR(thr, DUK_ERR_RANGE_ERROR, "execution step limit");
+	}
+#endif
+
+#if 0
+	/* XXX: debugger integration: single step, breakpoint checks, etc */
+	if (0) {
+		/* Cause an interrupt after executing one instruction. */
+		ctr = 1;
+	}
+#endif
+
+	DUK_DDD(DUK_DDDPRINT("executor interrupt finished, cstop=%ld, pc=%ld, nextctr=%ld",
+	                     (long) thr->callstack_top, (long) act->pc, (long) ctr));
+
+	/* The counter value is one less than the init value: init value should
+	 * indicate how many instructions are executed before interrupt.  To
+	 * execute 1 instruction, counter must be 0.
+	 */
+	thr->heap->interrupt_init = ctr;
+	thr->heap->interrupt_counter = ctr - 1;
+	thr->interrupt_counter = ctr - 1;
+}
+#endif  /* DUK_USE_INTERRUPT_COUNTER */
+
+/*
+ *  Ecmascript bytecode executor.
+ *
+ *  Resume execution for the current thread from its current activation.
+ *  Returns when execution would return from the entry level activation,
+ *  leaving a single return value on top of the stack.  Function calls
+ *  and thread resumptions are handled internally.  If an error occurs,
+ *  a longjmp() with type DUK_LJ_TYPE_THROW is called on the entry level
+ *  setjmp() jmpbuf.
+ *
+ *  Ecmascript function calls and coroutine resumptions are handled
+ *  internally without recursive C calls.  Other function calls are
+ *  handled using duk_handle_call(), increasing C recursion depth.
+ *
+ *  There are many other tricky control flow situations, such as:
+ *
+ *    - Break and continue (fast and slow)
+ *    - Return (fast and slow)
+ *    - Error throwing
+ *    - Thread resume and yield
+ *
+ *  For more detailed notes, see doc/execution.txt.
+ *
+ *  Note: setjmp() and local variables have a nasty interaction,
+ *  see execution.txt; non-volatile locals modified after setjmp()
+ *  call are not guaranteed to keep their value.
+ */
+
+#define DUK__STRICT()       (DUK_HOBJECT_HAS_STRICT(&(fun)->obj))
+#define DUK__REG(x)         (thr->valstack_bottom[(x)])
+#define DUK__REGP(x)        (&thr->valstack_bottom[(x)])
+#define DUK__CONST(x)       (DUK_HCOMPILEDFUNCTION_GET_CONSTS_BASE(fun)[(x)])
+#define DUK__CONSTP(x)      (&DUK_HCOMPILEDFUNCTION_GET_CONSTS_BASE(fun)[(x)])
+#define DUK__REGCONST(x)    ((x) < DUK_BC_REGLIMIT ? DUK__REG((x)) : DUK__CONST((x) - DUK_BC_REGLIMIT))
+#define DUK__REGCONSTP(x)   ((x) < DUK_BC_REGLIMIT ? DUK__REGP((x)) : DUK__CONSTP((x) - DUK_BC_REGLIMIT))
+
+#ifdef DUK_USE_VERBOSE_EXECUTOR_ERRORS
+#define DUK__INTERNAL_ERROR(msg)  do { \
+		DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, (msg)); \
+	} while (0)
+#else
+#define DUK__INTERNAL_ERROR(msg)  do { \
+		goto internal_error; \
+	} while (0)
+#endif
+
+void duk_js_execute_bytecode(duk_hthread *entry_thread) {
+	/* entry level info */
+	duk_size_t entry_callstack_top;
+	duk_int_t entry_call_recursion_depth;
+	duk_jmpbuf *entry_jmpbuf_ptr;
+
+	/* "hot" variables for interpretation -- not volatile, value not guaranteed in setjmp error handling */
+	duk_hthread *thr;             /* stable */
+	duk_activation *act;          /* semi-stable (ok as long as callstack not resized) */
+	duk_hcompiledfunction *fun;   /* stable */
+	duk_instr_t *bcode;           /* stable */
+	/* 'consts' is computed on-the-fly */
+	/* 'funcs' is quite rarely used, so no local for it */
+
+	/* "hot" temps for interpretation -- not volatile, value not guaranteed in setjmp error handling */
+	duk_uint_fast32_t ins;  /* XXX: check performance impact on x64 between fast/non-fast variant */
+
+	/* jmpbuf */
+	duk_jmpbuf jmpbuf;
+
+#ifdef DUK_USE_INTERRUPT_COUNTER
+	duk_int_t int_ctr;
+#endif
+
+#ifdef DUK_USE_ASSERTIONS
+	duk_size_t valstack_top_base;    /* valstack top, should match before interpreting each op (no leftovers) */
+#endif
+
+	/* XXX: document assumptions on setjmp and volatile variables
+	 * (see duk_handle_call()).
+	 */
+
+	/*
+	 *  Preliminaries
+	 */
+
+	DUK_ASSERT(entry_thread != NULL);
+	DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR((duk_heaphdr *) entry_thread);
+	DUK_ASSERT(entry_thread->callstack_top >= 1);  /* at least one activation, ours */
+	DUK_ASSERT((entry_thread->callstack + entry_thread->callstack_top - 1)->func != NULL);
+	DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION((entry_thread->callstack + entry_thread->callstack_top - 1)->func));
+
+	thr = entry_thread;
+
+	entry_callstack_top = thr->callstack_top;
+	entry_call_recursion_depth = thr->heap->call_recursion_depth;
+	entry_jmpbuf_ptr = thr->heap->lj.jmpbuf_ptr;
+
+	/*
+	 *  Setjmp catchpoint setup.
+	 *
+	 *  Note: we currently assume that the setjmp() catchpoint is
+	 *  not re-entrant (longjmp() cannot be called more than once
+	 *  for a single setjmp()).
+	 */
+
+ reset_setjmp_catchpoint:
+
+	DUK_ASSERT(thr != NULL);
+	thr->heap->lj.jmpbuf_ptr = &jmpbuf;
+	DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL);
+
+	if (DUK_SETJMP(thr->heap->lj.jmpbuf_ptr->jb)) {
+		/*
+		 *  Note: any local variables accessed here must have their value
+		 *  assigned *before* the setjmp() call, OR they must be declared
+		 *  volatile.  Otherwise their value is not guaranteed to be correct.
+		 *
+		 *  'thr' might seem to be a risky variable because it is changed
+		 *  for yield and resume.  However, yield and resume are handled
+		 *  using longjmp()s.
+		 */
+
+		duk_small_uint_t lj_ret;
+
+		/* XXX: signalling the need to shrink check (only if unwound) */
+
+		DUK_DDD(DUK_DDDPRINT("longjmp caught by bytecode executor, thr=%p, curr_thread=%p",
+		                     (void *) thr, (void *) ((thr && thr->heap) ? thr->heap->curr_thread : NULL)));
+
+		/* must be restored here to handle e.g. yields properly */
+		thr->heap->call_recursion_depth = entry_call_recursion_depth;
+
+		/* Longjmp callers should not switch threads, the longjmp handler
+		 * does that (even for RESUME and YIELD).
+		 */
+
+		DUK_ASSERT(thr != NULL);
+		DUK_ASSERT(thr == thr->heap->curr_thread);
+
+		/* Switch to caller's setjmp() catcher so that if an error occurs
+		 * during error handling, it is always propagated outwards instead
+		 * of causing an infinite loop in our own handler.
+		 */
+
+		DUK_DDD(DUK_DDDPRINT("restore jmpbuf_ptr: %p -> %p",
+		                     (void *) ((thr && thr->heap) ? thr->heap->lj.jmpbuf_ptr : NULL),
+		                     (void *) entry_jmpbuf_ptr));
+		thr->heap->lj.jmpbuf_ptr = entry_jmpbuf_ptr;
+
+		lj_ret = duk__handle_longjmp(thr, entry_thread, entry_callstack_top);
+
+		if (lj_ret == DUK__LONGJMP_RESTART) {
+			/*
+			 *  Restart bytecode execution, possibly with a changed thread.
+			 */
+			thr = thr->heap->curr_thread;
+			goto reset_setjmp_catchpoint;
+		} else if (lj_ret == DUK__LONGJMP_RETHROW) {
+			/*
+			 *  Rethrow error to calling state.
+			 */
+
+			/* thread may have changed (e.g. YIELD converted to THROW) */
+			thr = thr->heap->curr_thread;
+
+			DUK_ASSERT(thr->heap->lj.jmpbuf_ptr == entry_jmpbuf_ptr);
+
+			duk_err_longjmp(thr);
+			DUK_UNREACHABLE();
+		} else {
+			/*
+			 *  Return from bytecode executor with a return value.
+			 */
+			DUK_ASSERT(lj_ret == DUK__LONGJMP_FINISHED);
+
+			/* XXX: return assertions for valstack, callstack, catchstack */
+
+			DUK_ASSERT(thr->heap->lj.jmpbuf_ptr == entry_jmpbuf_ptr);
+			return;
+		}
+		DUK_UNREACHABLE();
+	}
+
+	/*
+	 *  Restart execution by reloading thread state.
+	 *
+	 *  Note that 'thr' and any thread configuration may have changed,
+	 *  so all local variables are suspect.
+	 *
+	 *  The number of local variables should be kept to a minimum: if
+	 *  the variables are spilled, they will need to be loaded from
+	 *  memory anyway.
+	 */
+
+ restart_execution:
+
+	/* Lookup current thread; note that we can use 'thr' for this even
+	 * though it is not the current thread (any thread will do).
+	 */
+	thr = thr->heap->curr_thread;
+#ifdef DUK_USE_INTERRUPT_COUNTER
+	thr->interrupt_counter = thr->heap->interrupt_counter;
+#endif
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->callstack_top >= 1);
+	DUK_ASSERT((thr->callstack + thr->callstack_top - 1)->func != NULL);
+	DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->callstack + thr->callstack_top - 1)->func));
+
+	/* XXX: shrink check flag? */
+
+	/* assume that thr->valstack_bottom has been set-up before getting here */
+	act = thr->callstack + thr->callstack_top - 1;
+	fun = (duk_hcompiledfunction *) act->func;
+	bcode = DUK_HCOMPILEDFUNCTION_GET_CODE_BASE(fun);
+
+	DUK_ASSERT(thr->valstack_top - thr->valstack_bottom >= fun->nregs);
+	DUK_ASSERT(thr->valstack_top - thr->valstack_bottom == fun->nregs);  /* XXX: correct? */
+
+	/*
+	 *  Bytecode interpreter.
+	 *
+	 *  The interpreter must be very careful with memory pointers, as
+	 *  many pointers are not guaranteed to be 'stable' and may be
+	 *  reallocated and relocated on-the-fly quite easily (e.g. by a
+	 *  memory allocation or a property access).
+	 *
+	 *  The following are assumed to have stable pointers:
+	 *    - the current thread
+	 *    - the current function
+	 *    - the bytecode, constant table, inner function table of the
+	 *      current function (as they are a part of the function allocation)
+	 *
+	 *  The following are assumed to have semi-stable pointers:
+	 *    - the current activation entry: stable as long as callstack
+	 *      is not changed (reallocated by growing or shrinking), or
+	 *      by any garbage collection invocation (through finalizers)
+	 *    - Note in particular that ANY DECREF can invalidate the
+	 *      activation pointer
+	 *
+	 *  The following are not assumed to have stable pointers at all:
+	 *    - the value stack (registers) of the current thread
+	 *    - the catch stack of the current thread
+	 *
+	 *  See execution.txt for discussion.
+	 */
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(act != NULL);
+	DUK_ASSERT(fun != NULL);
+	DUK_ASSERT(bcode != NULL);
+
+	DUK_DD(DUK_DDPRINT("restarting execution, thr %p, act %p (idx %ld), fun %p, bcode %p, "
+	                   "consts %p, funcs %p, lev %ld, regbot %ld, regtop %ld, catchstack_top=%ld, "
+	                   "preventcount=%ld",
+	                   (void *) thr,
+	                   (void *) act,
+	                   (long) (thr->callstack_top - 1),
+	                   (void *) fun,
+	                   (void *) bcode,
+	                   (void *) DUK_HCOMPILEDFUNCTION_GET_CONSTS_BASE(fun),
+	                   (void *) DUK_HCOMPILEDFUNCTION_GET_FUNCS_BASE(fun),
+	                   (long) (thr->callstack_top - 1),
+	                   (long) (thr->valstack_bottom - thr->valstack),
+	                   (long) (thr->valstack_top - thr->valstack),
+	                   (long) thr->catchstack_top,
+	                   (long) thr->callstack_preventcount));
+
+#ifdef DUK_USE_ASSERTIONS
+	valstack_top_base = (duk_size_t) (thr->valstack_top - thr->valstack);
+#endif
+
+	for (;;) {
+		DUK_ASSERT(thr->callstack_top >= 1);
+		DUK_ASSERT(thr->valstack_top - thr->valstack_bottom == fun->nregs);
+		DUK_ASSERT((duk_size_t) (thr->valstack_top - thr->valstack) == valstack_top_base);
+
+		/* Executor interrupt counter check, used to implement breakpoints,
+		 * debugging interface, execution timeouts, etc.  The counter is heap
+		 * specific but is maintained in the current thread to make the check
+		 * as fast as possible.  The counter is copied back to the heap struct
+		 * whenever a thread switch occurs by the DUK_HEAP_SWITCH_THREAD() macro.
+		 */
+#ifdef DUK_USE_INTERRUPT_COUNTER
+		int_ctr = thr->interrupt_counter;
+		if (DUK_LIKELY(int_ctr > 0)) {
+			thr->interrupt_counter = int_ctr - 1;
+		} else {
+			/* Trigger at zero or below */
+			duk__executor_interrupt(thr);
+		}
+#endif
+
+		/* Because ANY DECREF potentially invalidates 'act' now (through
+		 * finalization), we need to re-lookup 'act' in almost every case.
+		 *
+		 * XXX: future work for performance optimization:
+		 * This is not nice; it would be nice if the program counter was a
+		 * behind a stable pointer.  For instance, put a raw bytecode pointer
+		 * into duk_hthread struct (not into the callstack); since bytecode
+		 * has a stable pointer this would work nicely.  Whenever a call is
+		 * made, the bytecode pointer could be backed up as an integer index
+		 * to the calling activation.  Perhaps add a macro for setting up a
+		 * new activation (same as for setting up / switching threads)?
+		 */
+
+		act = thr->callstack + thr->callstack_top - 1;
+		DUK_ASSERT(bcode + act->pc >= DUK_HCOMPILEDFUNCTION_GET_CODE_BASE(fun));
+		DUK_ASSERT(bcode + act->pc < DUK_HCOMPILEDFUNCTION_GET_CODE_END(fun));
+
+		DUK_DDD(DUK_DDDPRINT("executing bytecode: pc=%ld ins=0x%08lx, op=%ld, valstack_top=%ld/%ld  -->  %!I",
+		                     (long) act->pc,
+		                     (unsigned long) bcode[act->pc],
+		                     (long) DUK_DEC_OP(bcode[act->pc]),
+		                     (long) (thr->valstack_top - thr->valstack),
+		                     (long) (thr->valstack_end - thr->valstack),
+		                     (duk_instr_t) bcode[act->pc]));
+
+		ins = bcode[act->pc++];
+
+		/* Typing: use duk_small_(u)int_fast_t when decoding small
+		 * opcode fields (op, A, B, C) and duk_(u)int_fast_t when
+		 * decoding larger fields (e.g. BC which is 18 bits).  Use
+		 * unsigned variant by default, signed when the value is used
+		 * in signed arithmetic.  Using variable names such as 'a', 'b',
+		 * 'c', 'bc', etc makes it easier to spot typing mismatches.
+		 */
+
+		/* XXX: the best typing needs to be validated by perf measurement:
+		 * e.g. using a small type which is the cast to a larger duk_idx_t
+		 * may be slower than declaring the variable as a duk_idx_t in the
+		 * first place.
+		 */
+
+		/* XXX: use macros for the repetitive tval/refcount handling. */
+
+		switch ((int) DUK_DEC_OP(ins)) {
+		/* XXX: switch cast? */
+
+		case DUK_OP_LDREG: {
+			duk_small_uint_fast_t a;
+			duk_uint_fast_t bc;
+			duk_tval tv_tmp;
+			duk_tval *tv1, *tv2;
+
+			a = DUK_DEC_A(ins); tv1 = DUK__REGP(a);
+			bc = DUK_DEC_BC(ins); tv2 = DUK__REGP(bc);
+			DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
+			DUK_TVAL_SET_TVAL(tv1, tv2);
+			DUK_TVAL_INCREF(thr, tv1);
+			DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+			break;
+		}
+
+		case DUK_OP_STREG: {
+			duk_small_uint_fast_t a;
+			duk_uint_fast_t bc;
+			duk_tval tv_tmp;
+			duk_tval *tv1, *tv2;
+
+			a = DUK_DEC_A(ins); tv1 = DUK__REGP(a);
+			bc = DUK_DEC_BC(ins); tv2 = DUK__REGP(bc);
+			DUK_TVAL_SET_TVAL(&tv_tmp, tv2);
+			DUK_TVAL_SET_TVAL(tv2, tv1);
+			DUK_TVAL_INCREF(thr, tv2);
+			DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+			break;
+		}
+
+		case DUK_OP_LDCONST: {
+			duk_small_uint_fast_t a;
+			duk_uint_fast_t bc;
+			duk_tval tv_tmp;
+			duk_tval *tv1, *tv2;
+
+			a = DUK_DEC_A(ins); tv1 = DUK__REGP(a);
+			bc = DUK_DEC_BC(ins); tv2 = DUK__CONSTP(bc);
+			DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
+			DUK_TVAL_SET_TVAL(tv1, tv2);
+			DUK_TVAL_INCREF(thr, tv2);  /* may be e.g. string */
+			DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+			break;
+		}
+
+		case DUK_OP_LDINT: {
+			duk_small_uint_fast_t a;
+			duk_int_fast_t bc;
+			duk_tval tv_tmp;
+			duk_tval *tv1;
+			duk_double_t val;
+
+			a = DUK_DEC_A(ins); tv1 = DUK__REGP(a);
+			bc = DUK_DEC_BC(ins); val = (duk_double_t) (bc - DUK_BC_LDINT_BIAS);
+			DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
+			DUK_TVAL_SET_NUMBER(tv1, val);
+			DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+			break;
+		}
+
+		case DUK_OP_LDINTX: {
+			duk_small_uint_fast_t a;
+			duk_tval *tv1;
+			duk_double_t val;
+
+			a = DUK_DEC_A(ins); tv1 = DUK__REGP(a);
+			if (!DUK_TVAL_IS_NUMBER(tv1)) {
+				DUK__INTERNAL_ERROR("LDINTX target not a number");
+			}
+			val = DUK_TVAL_GET_NUMBER(tv1) * ((duk_double_t) (1L << DUK_BC_LDINTX_SHIFT)) +
+			      (duk_double_t) DUK_DEC_BC(ins);
+			DUK_TVAL_SET_NUMBER(tv1, val);
+			break;
+		}
+
+		case DUK_OP_MPUTOBJ:
+		case DUK_OP_MPUTOBJI: {
+			duk_context *ctx = (duk_context *) thr;
+			duk_small_uint_fast_t a;
+			duk_tval *tv1;
+			duk_hobject *obj;
+			duk_uint_fast_t idx;
+			duk_small_uint_fast_t count;
+
+			/* A -> register of target object
+			 * B -> first register of key/value pair list
+			 * C -> number of key/value pairs
+			 */
+
+			a = DUK_DEC_A(ins); tv1 = DUK__REGP(a);
+			if (!DUK_TVAL_IS_OBJECT(tv1)) {
+				DUK__INTERNAL_ERROR("MPUTOBJ target not an object");
+			}
+			obj = DUK_TVAL_GET_OBJECT(tv1);
+
+			idx = (duk_uint_fast_t) DUK_DEC_B(ins);
+			if (DUK_DEC_OP(ins) == DUK_OP_MPUTOBJI) {
+				duk_tval *tv_ind = DUK__REGP(idx);
+				if (!DUK_TVAL_IS_NUMBER(tv_ind)) {
+					DUK__INTERNAL_ERROR("MPUTOBJI target is not a number");
+				}
+				idx = (duk_uint_fast_t) DUK_TVAL_GET_NUMBER(tv_ind);
+			}
+
+			count = (duk_small_uint_fast_t) DUK_DEC_C(ins);
+
+#if defined(DUK_USE_EXEC_INDIRECT_BOUND_CHECK)
+			if (DUK_UNLIKELY(idx + count * 2 > (duk_uint_fast_t) duk_get_top(ctx))) {
+				/* XXX: use duk_is_valid_index() instead? */
+				/* XXX: improve check; check against nregs, not against top */
+				DUK__INTERNAL_ERROR("MPUTOBJ out of bounds");
+			}
+#endif
+
+			duk_push_hobject(ctx, obj);
+
+			while (count > 0) {
+				/* XXX: faster initialization (direct access or better primitives) */
+
+				duk_push_tval(ctx, DUK__REGP(idx));
+				if (!duk_is_string(ctx, -1)) {
+					DUK__INTERNAL_ERROR("MPUTOBJ key not a string");
+				}
+				duk_push_tval(ctx, DUK__REGP(idx + 1));  /* -> [... obj key value] */
+				duk_def_prop_wec(ctx, -3);               /* -> [... obj] */
+
+				count--;
+				idx += 2;
+			}
+
+			duk_pop(ctx);  /* [... obj] -> [...] */
+			break;
+		}
+
+		case DUK_OP_MPUTARR:
+		case DUK_OP_MPUTARRI: {
+			duk_context *ctx = (duk_context *) thr;
+			duk_small_uint_fast_t a;
+			duk_tval *tv1;
+			duk_hobject *obj;
+			duk_uint_fast_t idx;
+			duk_small_uint_fast_t count;
+			duk_uint32_t arr_idx;
+
+			/* A -> register of target object
+			 * B -> first register of value data (start_index, value1, value2, ..., valueN)
+			 * C -> number of key/value pairs (N)
+			 */
+
+			a = DUK_DEC_A(ins); tv1 = DUK__REGP(a);
+			if (!DUK_TVAL_IS_OBJECT(tv1)) {
+				DUK__INTERNAL_ERROR("MPUTARR target not an object");
+			}
+			obj = DUK_TVAL_GET_OBJECT(tv1);
+
+			idx = (duk_uint_fast_t) DUK_DEC_B(ins);
+			if (DUK_DEC_OP(ins) == DUK_OP_MPUTARRI) {
+				duk_tval *tv_ind = DUK__REGP(idx);
+				if (!DUK_TVAL_IS_NUMBER(tv_ind)) {
+					DUK__INTERNAL_ERROR("MPUTARRI target is not a number");
+				}
+				idx = (duk_uint_fast_t) DUK_TVAL_GET_NUMBER(tv_ind);
+			}
+
+			count = (duk_small_uint_fast_t) DUK_DEC_C(ins);
+
+#if defined(DUK_USE_EXEC_INDIRECT_BOUND_CHECK)
+			if (idx + count + 1 > (duk_uint_fast_t) duk_get_top(ctx)) {
+				/* XXX: use duk_is_valid_index() instead? */
+				/* XXX: improve check; check against nregs, not against top */
+				DUK__INTERNAL_ERROR("MPUTARR out of bounds");
+			}
+#endif
+
+			tv1 = DUK__REGP(idx);
+			if (!DUK_TVAL_IS_NUMBER(tv1)) {
+				DUK__INTERNAL_ERROR("MPUTARR start index not a number");
+			}
+			arr_idx = (duk_uint32_t) DUK_TVAL_GET_NUMBER(tv1);
+			idx++;
+
+			duk_push_hobject(ctx, obj);
+
+			while (count > 0) {
+				/* duk_def_prop() will define an own property without any array
+				 * special behaviors.  We'll need to set the array length explicitly
+				 * in the end.  For arrays with elisions, the compiler will emit an
+				 * explicit SETALEN which will update the length.
+				 */
+
+				/*
+				 * XXX: because we're dealing with 'own' properties of a fresh array,
+				 * the array initializer should just ensure that the array has a large
+				 * enough array part and write the values directly into array part,
+				 * and finally set 'length' manually in the end (as already happens now).
+				 */
+
+				duk_push_tval(ctx, DUK__REGP(idx));          /* -> [... obj value] */
+				duk_def_prop_index_wec(ctx, -2, arr_idx);    /* -> [... obj] */
+
+				/* XXX: could use at least one fewer loop counters */
+				count--;
+				idx++;
+				arr_idx++;
+			}
+
+			/* XXX: E5.1 Section 11.1.4 coerces the final length through
+			 * ToUint32() which is odd but happens now as a side effect of
+			 * 'arr_idx' type.
+			 */
+			duk_hobject_set_length(thr, obj, (duk_uint32_t) arr_idx);
+
+			duk_pop(ctx);  /* [... obj] -> [...] */
+			break;
+		}
+
+		case DUK_OP_NEW:
+		case DUK_OP_NEWI: {
+			duk_context *ctx = (duk_context *) thr;
+			duk_small_uint_fast_t c = DUK_DEC_C(ins);
+			duk_uint_fast_t idx;
+			duk_small_uint_fast_t i;
+
+			/* A -> unused (reserved for flags, for consistency with DUK_OP_CALL)
+			 * B -> target register and start reg: constructor, arg1, ..., argN
+			 *      (for DUK_OP_NEWI, 'b' is indirect)
+			 * C -> num args (N)
+			 */
+
+			/* Note: duk_new() will call the constuctor using duk_handle_call().
+			 * A constructor call prevents a yield from inside the constructor,
+			 * even if the constructor is an Ecmascript function.
+			 */
+
+			/* XXX: unnecessary copying of values?  Just set 'top' to
+			 * b + c, and let the return handling fix up the stack frame?
+			 */
+
+			idx = (duk_uint_fast_t) DUK_DEC_B(ins);
+			if (DUK_DEC_OP(ins) == DUK_OP_NEWI) {
+				duk_tval *tv_ind = DUK__REGP(idx);
+				if (!DUK_TVAL_IS_NUMBER(tv_ind)) {
+					DUK__INTERNAL_ERROR("NEWI target is not a number");
+				}
+				idx = (duk_uint_fast_t) DUK_TVAL_GET_NUMBER(tv_ind);
+			}
+
+#if defined(DUK_USE_EXEC_INDIRECT_BOUND_CHECK)
+			if (idx + c + 1 > (duk_uint_fast_t) duk_get_top(ctx)) {
+				/* XXX: use duk_is_valid_index() instead? */
+				/* XXX: improve check; check against nregs, not against top */
+				DUK__INTERNAL_ERROR("NEW out of bounds");
+			}
+#endif
+
+			duk_require_stack(ctx, (duk_idx_t) c);
+			duk_push_tval(ctx, DUK__REGP(idx));
+			for (i = 0; i < c; i++) {
+				duk_push_tval(ctx, DUK__REGP(idx + i + 1));
+			}
+			duk_new(ctx, (duk_idx_t) c);  /* [... constructor arg1 ... argN] -> [retval] */
+			DUK_DDD(DUK_DDDPRINT("NEW -> %!iT", (duk_tval *) duk_get_tval(ctx, -1)));
+			duk_replace(ctx, (duk_idx_t) idx);
+			break;
+		}
+
+		case DUK_OP_REGEXP: {
+#ifdef DUK_USE_REGEXP_SUPPORT
+			duk_context *ctx = (duk_context *) thr;
+			duk_small_uint_fast_t a = DUK_DEC_A(ins);
+			duk_small_uint_fast_t b = DUK_DEC_B(ins);
+			duk_small_uint_fast_t c = DUK_DEC_C(ins);
+
+			/* A -> target register
+			 * B -> bytecode (also contains flags)
+			 * C -> escaped source
+			 */
+
+			duk_push_tval(ctx, DUK__REGCONSTP(c));
+			duk_push_tval(ctx, DUK__REGCONSTP(b));  /* -> [ ... escaped_source bytecode ] */
+			duk_regexp_create_instance(thr);   /* -> [ ... regexp_instance ] */
+			DUK_DDD(DUK_DDDPRINT("regexp instance: %!iT", (duk_tval *) duk_get_tval(ctx, -1)));
+			duk_replace(ctx, (duk_idx_t) a);
+#else
+			/* The compiler should never emit DUK_OP_REGEXP if there is no
+			 * regexp support.
+			 */
+			DUK__INTERNAL_ERROR("no regexp support");
+#endif
+
+			break;
+		}
+
+		case DUK_OP_CSREG:
+		case DUK_OP_CSREGI: {
+			/*
+			 *  Assuming a register binds to a variable declared within this
+			 *  function (a declarative binding), the 'this' for the call
+			 *  setup is always 'undefined'.  E5 Section 10.2.1.1.6.
+			 */
+
+			duk_context *ctx = (duk_context *) thr;
+			duk_small_uint_fast_t b = DUK_DEC_B(ins);  /* restricted to regs */
+			duk_uint_fast_t idx;
+
+			/* A -> target register (A, A+1) for call setup
+			 *      (for DUK_OP_CSREGI, 'a' is indirect)
+			 * B -> register containing target function (not type checked here)
+			 */
+
+			/* XXX: direct manipulation, or duk_replace_tval() */
+
+			/* Note: target registers a and a+1 may overlap with DUK__REGP(b).
+			 * Careful here.
+			 */
+
+			idx = (duk_uint_fast_t) DUK_DEC_A(ins);
+			if (DUK_DEC_OP(ins) == DUK_OP_CSREGI) {
+				duk_tval *tv_ind = DUK__REGP(idx);
+				if (!DUK_TVAL_IS_NUMBER(tv_ind)) {
+					DUK__INTERNAL_ERROR("CSREGI target is not a number");
+				}
+				idx = (duk_uint_fast_t) DUK_TVAL_GET_NUMBER(tv_ind);
+			}
+
+#if defined(DUK_USE_EXEC_INDIRECT_BOUND_CHECK)
+			if (idx + 2 > (duk_uint_fast_t) duk_get_top(ctx)) {
+				/* XXX: use duk_is_valid_index() instead? */
+				/* XXX: improve check; check against nregs, not against top */
+				DUK__INTERNAL_ERROR("CSREG out of bounds");
+			}
+#endif
+
+			duk_push_tval(ctx, DUK__REGP(b));
+			duk_replace(ctx, (duk_idx_t) idx);
+			duk_push_undefined(ctx);
+			duk_replace(ctx, (duk_idx_t) (idx + 1));
+			break;
+		}
+
+		case DUK_OP_GETVAR: {
+			duk_context *ctx = (duk_context *) thr;
+			duk_small_uint_fast_t a = DUK_DEC_A(ins);
+			duk_uint_fast_t bc = DUK_DEC_BC(ins);
+			duk_tval *tv1;
+			duk_hstring *name;
+
+			tv1 = DUK__CONSTP(bc);
+			if (!DUK_TVAL_IS_STRING(tv1)) {
+				DUK_DDD(DUK_DDDPRINT("GETVAR not a string: %!T", (duk_tval *) tv1));
+				DUK__INTERNAL_ERROR("GETVAR name not a string");
+			}
+			name = DUK_TVAL_GET_STRING(tv1);
+			DUK_DDD(DUK_DDDPRINT("GETVAR: '%!O'", (duk_heaphdr *) name));
+			(void) duk_js_getvar_activation(thr, act, name, 1 /*throw*/);  /* -> [... val this] */
+
+			duk_pop(ctx);  /* 'this' binding is not needed here */
+			duk_replace(ctx, (duk_idx_t) a);
+			break;
+		}
+
+		case DUK_OP_PUTVAR: {
+			duk_small_uint_fast_t a = DUK_DEC_A(ins);
+			duk_uint_fast_t bc = DUK_DEC_BC(ins);
+			duk_tval *tv1;
+			duk_hstring *name;
+
+			tv1 = DUK__CONSTP(bc);
+			if (!DUK_TVAL_IS_STRING(tv1)) {
+				DUK__INTERNAL_ERROR("PUTVAR name not a string");
+			}
+			name = DUK_TVAL_GET_STRING(tv1);
+
+			/* XXX: putvar takes a duk_tval pointer, which is awkward and
+			 * should be reworked.
+			 */
+
+			tv1 = DUK__REGP(a);  /* val */
+			duk_js_putvar_activation(thr, act, name, tv1, DUK__STRICT());
+			break;
+		}
+
+		case DUK_OP_DECLVAR: {
+			duk_context *ctx = (duk_context *) thr;
+			duk_small_uint_fast_t a = DUK_DEC_A(ins);
+			duk_small_uint_fast_t b = DUK_DEC_B(ins);
+			duk_small_uint_fast_t c = DUK_DEC_C(ins);
+			duk_tval *tv1;
+			duk_hstring *name;
+			duk_small_uint_t prop_flags;
+			duk_bool_t is_func_decl;
+			duk_bool_t is_undef_value;
+
+			tv1 = DUK__REGCONSTP(b);
+			if (!DUK_TVAL_IS_STRING(tv1)) {
+				DUK__INTERNAL_ERROR("DECLVAR name not a string");
+			}
+			name = DUK_TVAL_GET_STRING(tv1);
+
+			is_undef_value = ((a & DUK_BC_DECLVAR_FLAG_UNDEF_VALUE) != 0);
+			is_func_decl = ((a & DUK_BC_DECLVAR_FLAG_FUNC_DECL) != 0);
+
+			/* XXX: declvar takes an duk_tval pointer, which is awkward and
+			 * should be reworked.
+			 */
+
+			/* Compiler is responsible for selecting property flags (configurability,
+			 * writability, etc).
+			 */
+			prop_flags = a & DUK_PROPDESC_FLAGS_MASK;
+
+			if (is_undef_value) {
+				duk_push_undefined(ctx);
+			} else {
+				duk_push_tval(ctx, DUK__REGCONSTP(c));
+			}
+			tv1 = duk_get_tval(ctx, -1);
+
+			if (duk_js_declvar_activation(thr, act, name, tv1, prop_flags, is_func_decl)) {
+				/* already declared, must update binding value */
+				tv1 = duk_get_tval(ctx, -1);
+				duk_js_putvar_activation(thr, act, name, tv1, DUK__STRICT());
+			}
+
+			duk_pop(ctx);
+			break;
+		}
+
+		case DUK_OP_DELVAR: {
+			duk_context *ctx = (duk_context *) thr;
+			duk_small_uint_fast_t a = DUK_DEC_A(ins);
+			duk_small_uint_fast_t b = DUK_DEC_B(ins);
+			duk_tval *tv1;
+			duk_hstring *name;
+			duk_bool_t rc;
+
+			tv1 = DUK__REGCONSTP(b);
+			if (!DUK_TVAL_IS_STRING(tv1)) {
+				DUK__INTERNAL_ERROR("DELVAR name not a string");
+			}
+			name = DUK_TVAL_GET_STRING(tv1);
+			DUK_DDD(DUK_DDDPRINT("DELVAR '%!O'", (duk_heaphdr *) name));
+			rc = duk_js_delvar_activation(thr, act, name);
+
+			duk_push_boolean(ctx, rc);
+			duk_replace(ctx, (duk_idx_t) a);
+			break;
+		}
+
+		case DUK_OP_CSVAR:
+		case DUK_OP_CSVARI: {
+			/* 'this' value:
+			 * E5 Section 6.b.i
+			 *
+			 * The only (standard) case where the 'this' binding is non-null is when
+			 *   (1) the variable is found in an object environment record, and
+			 *   (2) that object environment record is a 'with' block.
+			 *
+			 */
+
+			duk_context *ctx = (duk_context *) thr;
+			duk_small_uint_fast_t b = DUK_DEC_B(ins);
+			duk_uint_fast_t idx;
+			duk_tval *tv1;
+			duk_hstring *name;
+
+			tv1 = DUK__REGCONSTP(b);
+			if (!DUK_TVAL_IS_STRING(tv1)) {
+				DUK__INTERNAL_ERROR("CSVAR name not a string");
+			}
+			name = DUK_TVAL_GET_STRING(tv1);
+			(void) duk_js_getvar_activation(thr, act, name, 1 /*throw*/);  /* -> [... val this] */
+
+			/* Note: target registers a and a+1 may overlap with DUK__REGCONSTP(b)
+			 * and DUK__REGCONSTP(c).  Careful here.
+			 */
+
+			idx = (duk_uint_fast_t) DUK_DEC_A(ins);
+			if (DUK_DEC_OP(ins) == DUK_OP_CSVARI) {
+				duk_tval *tv_ind = DUK__REGP(idx);
+				if (!DUK_TVAL_IS_NUMBER(tv_ind)) {
+					DUK__INTERNAL_ERROR("CSVARI target is not a number");
+				}
+				idx = (duk_uint_fast_t) DUK_TVAL_GET_NUMBER(tv_ind);
+			}
+
+#if defined(DUK_USE_EXEC_INDIRECT_BOUND_CHECK)
+			if (idx + 2 > (duk_uint_fast_t) duk_get_top(ctx)) {
+				/* XXX: use duk_is_valid_index() instead? */
+				/* XXX: improve check; check against nregs, not against top */
+				DUK__INTERNAL_ERROR("CSVAR out of bounds");
+			}
+#endif
+
+			duk_replace(ctx, (duk_idx_t) (idx + 1));  /* 'this' binding */
+			duk_replace(ctx, (duk_idx_t) idx);        /* variable value (function, we hope, not checked here) */
+			break;
+		}
+
+		case DUK_OP_CLOSURE: {
+			duk_context *ctx = (duk_context *) thr;
+			duk_small_uint_fast_t a = DUK_DEC_A(ins);
+			duk_uint_fast_t bc = DUK_DEC_BC(ins);
+			duk_hobject *fun_temp;
+
+			/* A -> target reg
+			 * BC -> inner function index
+			 */
+
+			DUK_DDD(DUK_DDDPRINT("CLOSURE to target register %ld, fnum %ld (count %ld)",
+			                     (long) a, (long) bc, (long) DUK_HCOMPILEDFUNCTION_GET_FUNCS_COUNT(fun)));
+
+			DUK_ASSERT_DISABLE(bc >= 0); /* unsigned */
+			DUK_ASSERT((duk_uint_t) bc < (duk_uint_t) DUK_HCOMPILEDFUNCTION_GET_FUNCS_COUNT(fun));
+			fun_temp = DUK_HCOMPILEDFUNCTION_GET_FUNCS_BASE(fun)[bc];
+			DUK_ASSERT(fun_temp != NULL);
+			DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(fun_temp));
+
+			DUK_DDD(DUK_DDDPRINT("CLOSURE: function template is: %p -> %!O",
+			                     (void *) fun_temp, (duk_heaphdr *) fun_temp));
+
+			if (act->lex_env == NULL) {
+				DUK_ASSERT(act->var_env == NULL);
+				duk_js_init_activation_environment_records_delayed(thr, act);
+			}
+			DUK_ASSERT(act->lex_env != NULL);
+			DUK_ASSERT(act->var_env != NULL);
+
+			/* functions always have a NEWENV flag, i.e. they get a
+			 * new variable declaration environment, so only lex_env
+			 * matters here.
+			 */
+			duk_js_push_closure(thr,
+			                    (duk_hcompiledfunction *) fun_temp,
+			                    act->var_env,
+			                    act->lex_env);
+			duk_replace(ctx, (duk_idx_t) a);
+
+			break;
+		}
+
+		case DUK_OP_GETPROP: {
+			duk_context *ctx = (duk_context *) thr;
+			duk_small_uint_fast_t a = DUK_DEC_A(ins);
+			duk_small_uint_fast_t b = DUK_DEC_B(ins);
+			duk_small_uint_fast_t c = DUK_DEC_C(ins);
+			duk_tval *tv_obj;
+			duk_tval *tv_key;
+			duk_bool_t rc;
+
+			/* A -> target reg
+			 * B -> object reg/const (may be const e.g. in "'foo'[1]")
+			 * C -> key reg/const
+			 */
+
+			tv_obj = DUK__REGCONSTP(b);
+			tv_key = DUK__REGCONSTP(c);
+			DUK_DDD(DUK_DDDPRINT("GETPROP: a=%ld obj=%!T, key=%!T",
+			                     (long) a,
+			                     (duk_tval *) DUK__REGCONSTP(b),
+			                     (duk_tval *) DUK__REGCONSTP(c)));
+			rc = duk_hobject_getprop(thr, tv_obj, tv_key);  /* -> [val] */
+			DUK_UNREF(rc);  /* ignore */
+			DUK_DDD(DUK_DDDPRINT("GETPROP --> %!T",
+			                     (duk_tval *) duk_get_tval(ctx, -1)));
+			tv_obj = NULL;  /* invalidated */
+			tv_key = NULL;  /* invalidated */
+
+			duk_replace(ctx, (duk_idx_t) a);    /* val */
+			break;
+		}
+
+		case DUK_OP_PUTPROP: {
+			duk_small_uint_fast_t a = DUK_DEC_A(ins);
+			duk_small_uint_fast_t b = DUK_DEC_B(ins);
+			duk_small_uint_fast_t c = DUK_DEC_C(ins);
+			duk_tval *tv_obj;
+			duk_tval *tv_key;
+			duk_tval *tv_val;
+			duk_bool_t rc;
+
+			/* A -> object reg
+			 * B -> key reg/const
+			 * C -> value reg/const
+			 *
+			 * Note: intentional difference to register arrangement
+			 * of e.g. GETPROP; 'A' must contain a register-only value.
+			 */
+
+			tv_obj = DUK__REGP(a);
+			tv_key = DUK__REGCONSTP(b);
+			tv_val = DUK__REGCONSTP(c);
+			DUK_DDD(DUK_DDDPRINT("PUTPROP: obj=%!T, key=%!T, val=%!T",
+			                     (duk_tval *) DUK__REGP(a),
+			                     (duk_tval *) DUK__REGCONSTP(b),
+			                     (duk_tval *) DUK__REGCONSTP(c)));
+			rc = duk_hobject_putprop(thr, tv_obj, tv_key, tv_val, DUK__STRICT());
+			DUK_UNREF(rc);  /* ignore */
+			DUK_DDD(DUK_DDDPRINT("PUTPROP --> obj=%!T, key=%!T, val=%!T",
+			                     (duk_tval *) DUK__REGP(a),
+			                     (duk_tval *) DUK__REGCONSTP(b),
+			                     (duk_tval *) DUK__REGCONSTP(c)));
+			tv_obj = NULL;  /* invalidated */
+			tv_key = NULL;  /* invalidated */
+			tv_val = NULL;  /* invalidated */
+
+			break;
+		}
+
+		case DUK_OP_DELPROP: {
+			duk_context *ctx = (duk_context *) thr;
+			duk_small_uint_fast_t a = DUK_DEC_A(ins);
+			duk_small_uint_fast_t b = DUK_DEC_B(ins);
+			duk_small_uint_fast_t c = DUK_DEC_C(ins);
+			duk_tval *tv_obj;
+			duk_tval *tv_key;
+			duk_bool_t rc;
+
+			/* A -> result reg
+			 * B -> object reg
+			 * C -> key reg/const
+			 */
+
+			tv_obj = DUK__REGP(b);
+			tv_key = DUK__REGCONSTP(c);
+			rc = duk_hobject_delprop(thr, tv_obj, tv_key, DUK__STRICT());
+			tv_obj = NULL;  /* invalidated */
+			tv_key = NULL;  /* invalidated */
+
+			duk_push_boolean(ctx, rc);
+			duk_replace(ctx, (duk_idx_t) a);    /* result */
+			break;
+		}
+
+		case DUK_OP_CSPROP:
+		case DUK_OP_CSPROPI: {
+			duk_context *ctx = (duk_context *) thr;
+			duk_small_uint_fast_t b = DUK_DEC_B(ins);
+			duk_small_uint_fast_t c = DUK_DEC_C(ins);
+			duk_uint_fast_t idx;
+			duk_tval *tv_obj;
+			duk_tval *tv_key;
+			duk_bool_t rc;
+
+			/* E5 Section 11.2.3, step 6.a.i */
+			/* E5 Section 10.4.3 */
+
+			/* FIXME: allow object to be a const, e.g. in 'foo'.toString() */
+
+			tv_obj = DUK__REGP(b);
+			tv_key = DUK__REGCONSTP(c);
+			rc = duk_hobject_getprop(thr, tv_obj, tv_key);  /* -> [val] */
+			DUK_UNREF(rc);  /* unused */
+			tv_obj = NULL;  /* invalidated */
+			tv_key = NULL;  /* invalidated */
+
+			/* Note: target registers a and a+1 may overlap with DUK__REGP(b)
+			 * and DUK__REGCONSTP(c).  Careful here.
+			 */
+
+			idx = (duk_uint_fast_t) DUK_DEC_A(ins);
+			if (DUK_DEC_OP(ins) == DUK_OP_CSPROPI) {
+				duk_tval *tv_ind = DUK__REGP(idx);
+				if (!DUK_TVAL_IS_NUMBER(tv_ind)) {
+					DUK__INTERNAL_ERROR("CSPROPI target is not a number");
+				}
+				idx = (duk_uint_fast_t) DUK_TVAL_GET_NUMBER(tv_ind);
+			}
+
+#if defined(DUK_USE_EXEC_INDIRECT_BOUND_CHECK)
+			if (idx + 2 > (duk_uint_fast_t) duk_get_top(ctx)) {
+				/* XXX: use duk_is_valid_index() instead? */
+				/* XXX: improve check; check against nregs, not against top */
+				DUK__INTERNAL_ERROR("CSPROP out of bounds");
+			}
+#endif
+
+			duk_push_tval(ctx, DUK__REGP(b));         /* [ ... val obj ] */
+			duk_replace(ctx, (duk_idx_t) (idx + 1));  /* 'this' binding */
+			duk_replace(ctx, (duk_idx_t) idx);        /* val */
+			break;
+		}
+
+		case DUK_OP_ADD:
+		case DUK_OP_SUB:
+		case DUK_OP_MUL:
+		case DUK_OP_DIV:
+		case DUK_OP_MOD: {
+			duk_small_uint_fast_t a = DUK_DEC_A(ins);
+			duk_small_uint_fast_t b = DUK_DEC_B(ins);
+			duk_small_uint_fast_t c = DUK_DEC_C(ins);
+			duk_small_uint_fast_t op = DUK_DEC_OP(ins);
+
+			if (op == DUK_OP_ADD) {
+				/*
+				 *  Handling DUK_OP_ADD this way is more compact (experimentally)
+				 *  than a separate case with separate argument decoding.
+				 */
+				duk__vm_arith_add(thr, DUK__REGCONSTP(b), DUK__REGCONSTP(c), a);
+			} else {
+				duk__vm_arith_binary_op(thr, DUK__REGCONSTP(b), DUK__REGCONSTP(c), a, op);
+			}
+			break;
+		}
+
+		case DUK_OP_BAND:
+		case DUK_OP_BOR:
+		case DUK_OP_BXOR:
+		case DUK_OP_BASL:
+		case DUK_OP_BLSR:
+		case DUK_OP_BASR: {
+			duk_small_uint_fast_t a = DUK_DEC_A(ins);
+			duk_small_uint_fast_t b = DUK_DEC_B(ins);
+			duk_small_uint_fast_t c = DUK_DEC_C(ins);
+			duk_small_uint_fast_t op = DUK_DEC_OP(ins);
+
+			duk__vm_bitwise_binary_op(thr, DUK__REGCONSTP(b), DUK__REGCONSTP(c), a, op);
+			break;
+		}
+
+		case DUK_OP_BNOT: {
+			duk_small_uint_fast_t a = DUK_DEC_A(ins);
+			duk_small_uint_fast_t b = DUK_DEC_B(ins);
+
+			duk__vm_bitwise_not(thr, DUK__REGCONSTP(b), a);
+			break;
+		}
+
+		case DUK_OP_LNOT: {
+			duk_small_uint_fast_t a = DUK_DEC_A(ins);
+			duk_small_uint_fast_t b = DUK_DEC_B(ins);
+
+			duk__vm_logical_not(thr, DUK__REGCONSTP(b), DUK__REGP(a));
+			break;
+		}
+
+		case DUK_OP_EQ:
+		case DUK_OP_NEQ: {
+			duk_context *ctx = (duk_context *) thr;
+			duk_small_uint_fast_t a = DUK_DEC_A(ins);
+			duk_small_uint_fast_t b = DUK_DEC_B(ins);
+			duk_small_uint_fast_t c = DUK_DEC_C(ins);
+			duk_bool_t tmp;
+
+			/* E5 Sections 11.9.1, 11.9.3 */
+			tmp = duk_js_equals(thr, DUK__REGCONSTP(b), DUK__REGCONSTP(c));
+			if (DUK_DEC_OP(ins) == DUK_OP_NEQ) {
+				tmp = !tmp;
+			}
+			duk_push_boolean(ctx, tmp);
+			duk_replace(ctx, (duk_idx_t) a);
+			break;
+		}
+
+		case DUK_OP_SEQ:
+		case DUK_OP_SNEQ: {
+			duk_context *ctx = (duk_context *) thr;
+			duk_small_uint_fast_t a = DUK_DEC_A(ins);
+			duk_small_uint_fast_t b = DUK_DEC_B(ins);
+			duk_small_uint_fast_t c = DUK_DEC_C(ins);
+			duk_bool_t tmp;
+
+			/* E5 Sections 11.9.1, 11.9.3 */
+			tmp = duk_js_strict_equals(DUK__REGCONSTP(b), DUK__REGCONSTP(c));
+			if (DUK_DEC_OP(ins) == DUK_OP_SNEQ) {
+				tmp = !tmp;
+			}
+			duk_push_boolean(ctx, tmp);
+			duk_replace(ctx, (duk_idx_t) a);
+			break;
+		}
+
+		/* Note: combining comparison ops must be done carefully because
+		 * of uncomparable values (NaN): it's not necessarily true that
+		 * (x >= y) === !(x < y).  Also, evaluation order matters, and
+		 * although it would only seem to affect the compiler this is
+		 * actually not the case, because there are also run-time coercions
+		 * of the arguments (with potential side effects).
+		 *
+		 * XXX: can be combined; check code size.
+		 */
+
+		case DUK_OP_GT: {
+			duk_context *ctx = (duk_context *) thr;
+			duk_small_uint_fast_t a = DUK_DEC_A(ins);
+			duk_small_uint_fast_t b = DUK_DEC_B(ins);
+			duk_small_uint_fast_t c = DUK_DEC_C(ins);
+			duk_bool_t tmp;
+
+			/* x > y  -->  y < x */
+			tmp = duk_js_compare_helper(thr,
+			                            DUK__REGCONSTP(c),  /* y */
+			                            DUK__REGCONSTP(b),  /* x */
+			                            0);                 /* flags */
+
+			duk_push_boolean(ctx, tmp);
+			duk_replace(ctx, (duk_idx_t) a);
+			break;
+		}
+
+		case DUK_OP_GE: {
+			duk_context *ctx = (duk_context *) thr;
+			duk_small_uint_fast_t a = DUK_DEC_A(ins);
+			duk_small_uint_fast_t b = DUK_DEC_B(ins);
+			duk_small_uint_fast_t c = DUK_DEC_C(ins);
+			duk_bool_t tmp;
+
+			/* x >= y  -->  not (x < y) */
+			tmp = duk_js_compare_helper(thr,
+			                            DUK__REGCONSTP(b),  /* x */
+			                            DUK__REGCONSTP(c),  /* y */
+			                            DUK_COMPARE_FLAG_EVAL_LEFT_FIRST |
+			                            DUK_COMPARE_FLAG_NEGATE);  /* flags */
+
+			duk_push_boolean(ctx, tmp);
+			duk_replace(ctx, (duk_idx_t) a);
+			break;
+		}
+
+		case DUK_OP_LT: {
+			duk_context *ctx = (duk_context *) thr;
+			duk_small_uint_fast_t a = DUK_DEC_A(ins);
+			duk_small_uint_fast_t b = DUK_DEC_B(ins);
+			duk_small_uint_fast_t c = DUK_DEC_C(ins);
+			duk_bool_t tmp;
+
+			/* x < y */
+			tmp = duk_js_compare_helper(thr,
+			                            DUK__REGCONSTP(b),  /* x */
+			                            DUK__REGCONSTP(c),  /* y */
+			                            DUK_COMPARE_FLAG_EVAL_LEFT_FIRST);  /* flags */
+
+			duk_push_boolean(ctx, tmp);
+			duk_replace(ctx, (duk_idx_t) a);
+			break;
+		}
+
+		case DUK_OP_LE: {
+			duk_context *ctx = (duk_context *) thr;
+			duk_small_uint_fast_t a = DUK_DEC_A(ins);
+			duk_small_uint_fast_t b = DUK_DEC_B(ins);
+			duk_small_uint_fast_t c = DUK_DEC_C(ins);
+			duk_bool_t tmp;
+
+			/* x <= y  -->  not (x > y)  -->  not (y < x) */
+			tmp = duk_js_compare_helper(thr,
+			                            DUK__REGCONSTP(c),  /* y */
+			                            DUK__REGCONSTP(b),  /* x */
+			                            DUK_COMPARE_FLAG_NEGATE);  /* flags */
+
+			duk_push_boolean(ctx, tmp);
+			duk_replace(ctx, (duk_idx_t) a);
+			break;
+		}
+
+		case DUK_OP_IF: {
+			duk_small_uint_fast_t a = DUK_DEC_A(ins);
+			duk_small_uint_fast_t b = DUK_DEC_B(ins);
+			duk_bool_t tmp;
+
+			tmp = duk_js_toboolean(DUK__REGCONSTP(b));
+			if (tmp == (duk_bool_t) a) {
+				/* if boolean matches A, skip next inst */
+				act->pc++;
+			} else {
+				;
+			}
+			break;
+		}
+
+		case DUK_OP_INSTOF: {
+			duk_context *ctx = (duk_context *) thr;
+			duk_small_uint_fast_t a = DUK_DEC_A(ins);
+			duk_small_uint_fast_t b = DUK_DEC_B(ins);
+			duk_small_uint_fast_t c = DUK_DEC_C(ins);
+			duk_bool_t tmp;
+
+			tmp = duk_js_instanceof(thr, DUK__REGCONSTP(b), DUK__REGCONSTP(c));
+			duk_push_boolean(ctx, tmp);
+			duk_replace(ctx, (duk_idx_t) a);
+			break;
+		}
+
+		case DUK_OP_IN: {
+			duk_context *ctx = (duk_context *) thr;
+			duk_small_uint_fast_t a = DUK_DEC_A(ins);
+			duk_small_uint_fast_t b = DUK_DEC_B(ins);
+			duk_small_uint_fast_t c = DUK_DEC_C(ins);
+			duk_bool_t tmp;
+
+			tmp = duk_js_in(thr, DUK__REGCONSTP(b), DUK__REGCONSTP(c));
+			duk_push_boolean(ctx, tmp);
+			duk_replace(ctx, (duk_idx_t) a);
+			break;
+		}
+
+		case DUK_OP_JUMP: {
+			duk_int_fast_t abc = DUK_DEC_ABC(ins);
+
+			act->pc += abc - DUK_BC_JUMP_BIAS;
+			break;
+		}
+
+		case DUK_OP_RETURN: {
+			duk_context *ctx = (duk_context *) thr;
+			duk_small_uint_fast_t a = DUK_DEC_A(ins);
+			duk_small_uint_fast_t b = DUK_DEC_B(ins);
+			/* duk_small_uint_fast_t c = DUK_DEC_C(ins); */
+
+			/* A -> flags
+			 * B -> return value reg/const
+			 * C -> currently unused
+			 */
+
+			/* FIXME: fast return not implemented, always do a slow return now */
+			/* FIXME: limit fast return to the case with no catchstack at all (not even labels)?) */
+			if (a & DUK_BC_RETURN_FLAG_FAST && 0 /*FIXME*/) {
+				/* fast return: no TCF catchers (but may have e.g. labels) */
+				DUK__INTERNAL_ERROR("FIXME: fast return unimplemented");
+			} else {
+				/* slow return */
+
+				DUK_DDD(DUK_DDDPRINT("SLOWRETURN a=%ld b=%ld", (long) a, (long) b));
+
+				if (a & DUK_BC_RETURN_FLAG_HAVE_RETVAL) {
+					duk_push_tval(ctx, DUK__REGCONSTP(b));
+				} else {
+					duk_push_undefined(ctx);
+				}
+
+				duk_err_setup_heap_ljstate(thr, DUK_LJ_TYPE_RETURN);
+
+				DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL);  /* in bytecode executor, should always be set */
+				duk_err_longjmp(thr);
+				DUK_UNREACHABLE();
+			}
+			break;
+		}
+
+		case DUK_OP_CALL:
+		case DUK_OP_CALLI: {
+			duk_context *ctx = (duk_context *) thr;
+			duk_small_uint_fast_t a = DUK_DEC_A(ins);
+			duk_small_uint_fast_t c = DUK_DEC_C(ins);
+			duk_uint_fast_t idx;
+			duk_small_uint_t call_flags;
+			duk_small_uint_t flag_tailcall;
+			duk_small_uint_t flag_evalcall;
+			duk_tval *tv_func;
+			duk_hobject *obj_func;        /* target function, possibly a bound function */
+			duk_hobject *obj_final_func;  /* final target function, non-bound function */
+
+			/* A -> flags
+			 * B -> base register for call (base -> func, base+1 -> this, base+2 -> arg1 ... base+2+N-1 -> argN)
+			 *      (for DUK_OP_CALLI, 'b' is indirect)
+			 * C -> nargs
+			 */
+
+			/* these are not necessarily 0 or 1 (may be other non-zero), that's ok */
+			flag_tailcall = (a & DUK_BC_CALL_FLAG_TAILCALL);
+			flag_evalcall = (a & DUK_BC_CALL_FLAG_EVALCALL);
+
+			idx = (duk_uint_fast_t) DUK_DEC_B(ins);
+			if (DUK_DEC_OP(ins) == DUK_OP_CALLI) {
+				duk_tval *tv_ind = DUK__REGP(idx);
+				if (!DUK_TVAL_IS_NUMBER(tv_ind)) {
+					DUK__INTERNAL_ERROR("CALLI target is not a number");
+				}
+				idx = (duk_uint_fast_t) DUK_TVAL_GET_NUMBER(tv_ind);
+			}
+
+#if defined(DUK_USE_EXEC_INDIRECT_BOUND_CHECK)
+			if (!duk_is_valid_index(ctx, (duk_idx_t) idx)) {
+				/* XXX: improve check; check against nregs, not against top */
+				DUK__INTERNAL_ERROR("CALL out of bounds");
+			}
+#endif
+
+			tv_func = DUK__REGP(idx);
+			if (!DUK_TVAL_IS_OBJECT(tv_func)) {
+				DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "call target not an object");
+			}
+			obj_func = DUK_TVAL_GET_OBJECT(tv_func);
+
+			/*
+			 *  To determine whether to use an optimized Ecmascript-to-Ecmascript
+			 *  call, we need to know whether the final, non-bound function is an
+			 *  Ecmascript function.  We need to follow the "bound" chain to do that;
+			 *  the "bound" chain will be followed for the second time when calling.
+			 *  This overhead only affects bound functions (in particular, helper
+			 *  functions should not be called if the immediate target function is
+			 *  not bound).
+			 * 
+			 *  Even so, this awkward solution could be avoided by e.g. replicating
+			 *  final, non-bound target function flags to the bound function objects
+			 *  (so that a bound function would e.g. have both a "BOUND" flag and
+			 *  a "COMPILEDFUNCTION" flag).  Also, bound functions could also keep
+			 *  a direct reference to the final non-bound function ("shortcut").
+			 */
+
+			if (DUK_HOBJECT_HAS_BOUND(obj_func)) {
+				obj_final_func = duk__find_nonbound_function(thr, obj_func);
+			} else {
+				obj_final_func = obj_func;
+			}
+
+			duk_set_top(ctx, (duk_idx_t) (idx + c + 2));   /* [ ... func this arg1 ... argN ] */
+
+			if (DUK_HOBJECT_IS_COMPILEDFUNCTION(obj_final_func)) {
+				/*
+				 *  Ecmascript-to-Ecmascript call: avoid C recursion
+				 *  by being clever.
+				 */
+
+				/* Compared to duk_handle_call():
+				 *   - protected call: never
+				 *   - ignore recursion limit: never
+				 */
+
+				/* XXX: optimize flag handling, by coordinating with bytecode */
+
+				call_flags = 0;
+				if (flag_tailcall) {
+					/* We request a tailcall, but in some corner cases
+					 * call handling can decide that a tailcall is
+					 * actually not possible.
+					 * See: test-bug-tailcall-preventyield-assert.c.
+					 */
+					call_flags |= DUK_CALL_FLAG_IS_TAILCALL;
+				}
+
+				duk_handle_ecma_call_setup(thr,
+				                           c,              /* num_stack_args */
+				                           call_flags);    /* call_flags */
+
+				/* restart execution -> starts executing new function */
+				goto restart_execution;
+			} else {
+				/*
+				 *  Other cases, use C recursion.
+				 *
+				 *  If a tailcall was requested we ignore it and execute a normal call.
+				 *  Since Duktape 0.11.0 the compiler emits a RETURN opcode even after
+				 *  a tailcall to avoid test-bug-tailcall-thread-yield-resume.js.
+				 *
+				 *  Direct eval call: (1) call target (before following bound function
+				 *  chain) is the built-in eval() function, and (2) call was made with
+				 *  the identifier 'eval'.
+				 */
+
+				call_flags = 0;  /* not protected, respect reclimit, not constructor */
+
+				if (DUK_HOBJECT_IS_NATIVEFUNCTION(obj_func) &&
+				    ((duk_hnativefunction *) obj_func)->func == duk_bi_global_object_eval) {
+					if (flag_evalcall) {
+						DUK_DDD(DUK_DDDPRINT("call target is eval, call identifier was 'eval' -> direct eval"));
+						call_flags |= DUK_CALL_FLAG_DIRECT_EVAL;
+					} else {
+						DUK_DDD(DUK_DDDPRINT("call target is eval, call identifier was not 'eval' -> indirect eval"));
+					}
+				}
+
+				duk_handle_call(thr,
+				                c,            /* num_stack_args */
+				                call_flags);  /* call_flags */
+
+				/* FIXME: who should restore? */
+				duk_require_stack_top(ctx, (duk_idx_t) fun->nregs);  /* may have shrunk by inner calls, must recheck */
+				duk_set_top(ctx, (duk_idx_t) fun->nregs);
+
+				/* No need to reinit setjmp() catchpoint, as call handling
+				 * will store and restore our state.
+				 */
+			}
+			break;
+		}
+
+		case DUK_OP_LABEL: {
+			duk_catcher *cat;
+			duk_uint_fast_t abc = DUK_DEC_ABC(ins);
+
+			/* allocate catcher and populate it (should be atomic) */
+
+			duk_hthread_catchstack_grow(thr);
+			cat = thr->catchstack + thr->catchstack_top;
+			thr->catchstack_top++;
+
+			cat->flags = DUK_CAT_TYPE_LABEL | (abc << DUK_CAT_LABEL_SHIFT);
+			cat->callstack_index = thr->callstack_top - 1;
+			cat->pc_base = act->pc;  /* pre-incremented, points to first jump slot */
+			cat->idx_base = 0;  /* unused for label */
+			cat->h_varname = NULL;
+
+			DUK_DDD(DUK_DDDPRINT("LABEL catcher: flags=0x%08lx, callstack_index=%ld, pc_base=%ld, "
+			                     "idx_base=%ld, h_varname=%!O, label_id=%ld",
+			                     (long) cat->flags, (long) cat->callstack_index, (long) cat->pc_base,
+			                     (long) cat->idx_base, (duk_heaphdr *) cat->h_varname, (long) DUK_CAT_GET_LABEL(cat)));
+
+			act->pc += 2;  /* skip jump slots */
+			break;
+		}
+
+		case DUK_OP_ENDLABEL: {
+			duk_catcher *cat;
+#if defined(DUK_USE_DDDPRINT) || defined(DUK_USE_ASSERTIONS)
+			duk_uint_fast_t abc = DUK_DEC_ABC(ins);
+#endif
+#if defined(DUK_USE_DDDPRINT)
+			DUK_DDD(DUK_DDDPRINT("ENDLABEL %ld", (long) abc));
+#endif
+
+			DUK_ASSERT(thr->catchstack_top >= 1);
+
+			cat = thr->catchstack + thr->catchstack_top - 1;
+			DUK_UNREF(cat);
+			DUK_ASSERT(DUK_CAT_GET_TYPE(cat) == DUK_CAT_TYPE_LABEL);
+			DUK_ASSERT((duk_uint_fast_t) DUK_CAT_GET_LABEL(cat) == abc);
+
+			duk_hthread_catchstack_unwind(thr, thr->catchstack_top - 1);
+			/* no need to unwind callstack */
+			break;
+		}
+
+		case DUK_OP_BREAK: {
+			duk_context *ctx = (duk_context *) thr;
+			duk_uint_fast_t abc = DUK_DEC_ABC(ins);
+
+			/* always the "slow break" variant (longjmp'ing); a "fast break" is
+			 * simply an DUK_OP_JUMP.
+			 */
+
+			DUK_DDD(DUK_DDDPRINT("BREAK: %ld", (long) abc));
+
+			duk_push_uint(ctx, (duk_uint_t) abc);
+			duk_err_setup_heap_ljstate(thr, DUK_LJ_TYPE_BREAK);
+
+			DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL);  /* always in executor */
+			duk_err_longjmp(thr);
+
+			DUK_UNREACHABLE();
+			break;
+		}
+
+		case DUK_OP_CONTINUE: {
+			duk_context *ctx = (duk_context *) thr;
+			duk_uint_fast_t abc = DUK_DEC_ABC(ins);
+
+			/* always the "slow continue" variant (longjmp'ing); a "fast continue" is
+			 * simply an DUK_OP_JUMP.
+			 */
+
+			DUK_DDD(DUK_DDDPRINT("CONTINUE: %ld", (long) abc));
+
+			duk_push_uint(ctx, (duk_uint_t) abc);
+			duk_err_setup_heap_ljstate(thr, DUK_LJ_TYPE_CONTINUE);
+
+			DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL);  /* always in executor */
+			duk_err_longjmp(thr);
+
+			DUK_UNREACHABLE();
+			break;
+		}
+
+		case DUK_OP_TRYCATCH: {
+			duk_context *ctx = (duk_context *) thr;
+			duk_catcher *cat;
+			duk_tval *tv1;
+			duk_small_uint_fast_t a;
+			duk_small_uint_fast_t b;
+			duk_small_uint_fast_t c;
+
+			/* A -> flags
+			 * B -> reg_catch; base register for 2 regs
+			 * C -> semantics depend on flags: var_name or with_target
+			 *
+			 *      If DUK_BC_TRYCATCH_FLAG_CATCH_BINDING set:
+			 *          C is constant index for catch binding variable name.
+			 *          Automatic declarative environment is established for
+			 *          the duration of the 'catch' clause.
+			 *
+			 *      If DUK_BC_TRYCATCH_FLAG_WITH_BINDING set:
+			 *          C is reg/const index for with 'target value', which
+			 *          is coerced to an object and then used as a binding
+			 *          object for an environment record.  The binding is
+			 *          initialized here, for the 'try' clause.
+			 *
+			 * Note that a TRYCATCH generated for a 'with' statement has no
+			 * catch or finally parts.
+			 */
+
+			/* XXX: side effect handling is quite awkward here */
+
+			DUK_DDD(DUK_DDDPRINT("TRYCATCH: reg_catch=%ld, var_name/with_target=%ld, have_catch=%ld, "
+			                     "have_finally=%ld, catch_binding=%ld, with_binding=%ld (flags=0x%02lx)",
+			                     (long) DUK_DEC_B(ins),
+			                     (long) DUK_DEC_C(ins),
+			                     (long) (DUK_DEC_A(ins) & DUK_BC_TRYCATCH_FLAG_HAVE_CATCH ? 1 : 0),
+			                     (long) (DUK_DEC_A(ins) & DUK_BC_TRYCATCH_FLAG_HAVE_FINALLY ? 1 : 0),
+			                     (long) (DUK_DEC_A(ins) & DUK_BC_TRYCATCH_FLAG_CATCH_BINDING ? 1 : 0),
+			                     (long) (DUK_DEC_A(ins) & DUK_BC_TRYCATCH_FLAG_WITH_BINDING ? 1 : 0),
+			                     (unsigned long) DUK_DEC_A(ins)));
+
+			a = DUK_DEC_A(ins);
+			b = DUK_DEC_B(ins);
+			c = DUK_DEC_C(ins);
+
+			DUK_ASSERT(thr->callstack_top >= 1);
+
+			/* 'with' target must be created first, in case we run out of memory */
+			/* XXX: refactor out? */
+
+			if (a & DUK_BC_TRYCATCH_FLAG_WITH_BINDING) {
+				DUK_DDD(DUK_DDDPRINT("need to initialize a with binding object"));
+
+				if (act->lex_env == NULL) {
+					DUK_ASSERT(act->var_env == NULL);
+					DUK_DDD(DUK_DDDPRINT("delayed environment initialization"));
+
+					/* must relookup act in case of side effects */
+					duk_js_init_activation_environment_records_delayed(thr, act);
+					act = thr->callstack + thr->callstack_top - 1;
+				}
+				DUK_ASSERT(act->lex_env != NULL);
+				DUK_ASSERT(act->var_env != NULL);
+
+				(void) duk_push_object_helper(ctx,
+				                              DUK_HOBJECT_FLAG_EXTENSIBLE |
+				                              DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJENV),
+				                              -1);  /* no prototype, updated below */
+
+				duk_push_tval(ctx, DUK__REGCONSTP(c));
+				duk_to_object(ctx, -1);
+				duk_dup(ctx, -1);
+
+				/* [ ... env target ] */
+				/* [ ... env target target ] */
+
+				duk_def_prop_stridx(thr, -3, DUK_STRIDX_INT_TARGET, DUK_PROPDESC_FLAGS_NONE);
+				duk_def_prop_stridx(thr, -2, DUK_STRIDX_INT_THIS, DUK_PROPDESC_FLAGS_NONE);  /* always provideThis=true */
+
+				/* [ ... env ] */
+
+				DUK_DDD(DUK_DDDPRINT("environment for with binding: %!iT",
+				                     (duk_tval *) duk_get_tval(ctx, -1)));
+			}
+
+			/* allocate catcher and populate it (should be atomic) */
+
+			duk_hthread_catchstack_grow(thr);
+			cat = thr->catchstack + thr->catchstack_top;
+			DUK_ASSERT(thr->catchstack_top + 1 <= thr->catchstack_size);
+			thr->catchstack_top++;
+
+			cat->flags = DUK_CAT_TYPE_TCF;
+			cat->h_varname = NULL;
+
+			if (a & DUK_BC_TRYCATCH_FLAG_HAVE_CATCH) {
+				cat->flags |= DUK_CAT_FLAG_CATCH_ENABLED;
+			}
+			if (a & DUK_BC_TRYCATCH_FLAG_HAVE_FINALLY) {
+				cat->flags |= DUK_CAT_FLAG_FINALLY_ENABLED;
+			}
+			if (a & DUK_BC_TRYCATCH_FLAG_CATCH_BINDING) {
+				DUK_DDD(DUK_DDDPRINT("catch binding flag set to catcher"));
+				cat->flags |= DUK_CAT_FLAG_CATCH_BINDING_ENABLED;
+				tv1 = DUK__CONSTP(c);
+				DUK_ASSERT(DUK_TVAL_IS_STRING(tv1));
+				cat->h_varname = DUK_TVAL_GET_STRING(tv1);
+			} else if (a & DUK_BC_TRYCATCH_FLAG_WITH_BINDING) {
+				/* env created above to stack top */
+				duk_hobject *new_env;
+
+				DUK_DDD(DUK_DDDPRINT("lexenv active flag set to catcher"));
+				cat->flags |= DUK_CAT_FLAG_LEXENV_ACTIVE;
+
+				DUK_DDD(DUK_DDDPRINT("activating object env: %!iT",
+				                     (duk_tval *) duk_get_tval(ctx, -1)));
+				DUK_ASSERT(act->lex_env != NULL);
+				new_env = duk_get_hobject(ctx, -1);
+				DUK_ASSERT(new_env != NULL);
+
+				act = thr->callstack + thr->callstack_top - 1;  /* relookup (side effects) */
+				DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, new_env, act->lex_env);
+
+				act = thr->callstack + thr->callstack_top - 1;  /* relookup (side effects) */
+				act->lex_env = new_env;
+				DUK_HOBJECT_INCREF(thr, new_env);
+				duk_pop(ctx);
+			} else {
+				;
+			}
+
+			cat = thr->catchstack + thr->catchstack_top - 1;  /* relookup (side effects) */
+			cat->callstack_index = thr->callstack_top - 1;
+			cat->pc_base = act->pc;  /* pre-incremented, points to first jump slot */
+			cat->idx_base = (duk_size_t) (thr->valstack_bottom - thr->valstack) + b;
+
+			DUK_DDD(DUK_DDDPRINT("TRYCATCH catcher: flags=0x%08lx, callstack_index=%ld, pc_base=%ld, "
+			                     "idx_base=%ld, h_varname=%!O",
+			                     (unsigned long) cat->flags, (long) cat->callstack_index,
+			                     (long) cat->pc_base, (long) cat->idx_base, (duk_heaphdr *) cat->h_varname));
+
+			act->pc += 2;  /* skip jump slots */
+			break;
+		}
+
+		case DUK_OP_EXTRA: {
+			/* XXX: shared decoding of 'b' and 'c'? */
+
+			duk_small_uint_fast_t extraop = DUK_DEC_A(ins);
+			switch ((int) extraop) {
+			/* XXX: switch cast? */
+
+			case DUK_EXTRAOP_NOP: {
+				/* nop */
+				break;
+			}
+
+			case DUK_EXTRAOP_LDTHIS: {
+				/* Note: 'this' may be bound to any value, not just an object */
+				duk_small_uint_fast_t b = DUK_DEC_B(ins);
+				duk_tval tv_tmp;
+				duk_tval *tv1, *tv2;
+
+				tv1 = DUK__REGP(b);
+				tv2 = thr->valstack_bottom - 1;  /* 'this binding' is just under bottom */
+				DUK_ASSERT(tv2 >= thr->valstack);
+
+				DUK_DDD(DUK_DDDPRINT("LDTHIS: %!T to r%ld", (duk_tval *) tv2, (long) b));
+
+				DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
+				DUK_TVAL_SET_TVAL(tv1, tv2);
+				DUK_TVAL_INCREF(thr, tv1);
+				DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+				break;
+			}
+
+			case DUK_EXTRAOP_LDUNDEF: {
+				duk_uint_fast_t bc = DUK_DEC_BC(ins);
+				duk_tval tv_tmp;
+				duk_tval *tv1;
+
+				tv1 = DUK__REGP(bc);
+				DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
+				DUK_TVAL_SET_UNDEFINED_ACTUAL(tv1);
+				DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+				break;
+			}
+
+			case DUK_EXTRAOP_LDNULL: {
+				duk_uint_fast_t bc = DUK_DEC_BC(ins);
+				duk_tval tv_tmp;
+				duk_tval *tv1;
+
+				tv1 = DUK__REGP(bc);
+				DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
+				DUK_TVAL_SET_NULL(tv1);
+				DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+				break;
+			}
+
+			case DUK_EXTRAOP_LDTRUE:
+			case DUK_EXTRAOP_LDFALSE: {
+				duk_uint_fast_t bc = DUK_DEC_BC(ins);
+				duk_tval tv_tmp;
+				duk_tval *tv1;
+				duk_small_uint_fast_t bval = (extraop == DUK_EXTRAOP_LDTRUE ? 1 : 0);
+
+				tv1 = DUK__REGP(bc);
+				DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
+				DUK_TVAL_SET_BOOLEAN(tv1, bval);
+				DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+				break;
+			}
+
+			case DUK_EXTRAOP_NEWOBJ: {
+				duk_context *ctx = (duk_context *) thr;
+				duk_small_uint_fast_t b = DUK_DEC_B(ins);
+
+				duk_push_object(ctx);
+				duk_replace(ctx, (duk_idx_t) b);
+				break;
+			}
+
+			case DUK_EXTRAOP_NEWARR: {
+				duk_context *ctx = (duk_context *) thr;
+				duk_small_uint_fast_t b = DUK_DEC_B(ins);
+
+				duk_push_array(ctx);
+				duk_replace(ctx, (duk_idx_t) b);
+				break;
+			}
+
+			case DUK_EXTRAOP_SETALEN: {
+				duk_small_uint_fast_t b;
+				duk_small_uint_fast_t c;
+				duk_tval *tv1;
+				duk_hobject *h;
+				duk_uint32_t len;
+
+				b = DUK_DEC_B(ins); tv1 = DUK__REGP(b);
+				DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv1));
+				h = DUK_TVAL_GET_OBJECT(tv1);
+
+				c= DUK_DEC_C(ins); tv1 = DUK__REGP(c);
+				DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv1));
+				len = (duk_uint32_t) DUK_TVAL_GET_NUMBER(tv1);
+
+				duk_hobject_set_length(thr, h, len);
+
+				break;
+			}
+
+			case DUK_EXTRAOP_TYPEOF: {
+				duk_context *ctx = (duk_context *) thr;
+				duk_small_uint_fast_t b = DUK_DEC_B(ins);
+				duk_small_uint_fast_t c = DUK_DEC_C(ins);
+				duk_push_hstring(ctx, duk_js_typeof(thr, DUK__REGCONSTP(c)));
+				duk_replace(ctx, (duk_idx_t) b);
+				break;
+			}
+
+			case DUK_EXTRAOP_TYPEOFID: {
+				duk_context *ctx = (duk_context *) thr;
+				duk_small_uint_fast_t b = DUK_DEC_B(ins);
+				duk_small_uint_fast_t c = DUK_DEC_C(ins);
+				duk_hstring *name;
+				duk_tval *tv;
+
+				/* B -> target register
+				 * C -> constant index of identifier name
+				 */
+
+				tv = DUK__REGCONSTP(c);  /* FIXME: this could be a DUK__CONSTP instead */
+				DUK_ASSERT(DUK_TVAL_IS_STRING(tv));
+				name = DUK_TVAL_GET_STRING(tv);
+				if (duk_js_getvar_activation(thr, act, name, 0 /*throw*/)) {
+					/* -> [... val this] */
+					tv = duk_get_tval(ctx, -2);
+					duk_push_hstring(ctx, duk_js_typeof(thr, tv));
+					duk_replace(ctx, (duk_idx_t) b);
+					duk_pop_2(ctx);
+				} else {
+					/* unresolvable, no stack changes */
+					duk_push_hstring_stridx(ctx, DUK_STRIDX_LC_UNDEFINED);
+					duk_replace(ctx, (duk_idx_t) b);
+				}
+
+				break;
+			}
+
+			case DUK_EXTRAOP_TONUM: {
+				duk_context *ctx = (duk_context *) thr;
+				duk_small_uint_fast_t b = DUK_DEC_B(ins);
+				duk_small_uint_fast_t c = DUK_DEC_C(ins);
+				duk_dup(ctx, (duk_idx_t) c);
+				duk_to_number(ctx, -1);
+				duk_replace(ctx, (duk_idx_t) b);
+				break;
+			}
+
+			case DUK_EXTRAOP_INITENUM: {
+				duk_context *ctx = (duk_context *) thr;
+				duk_small_uint_fast_t b = DUK_DEC_B(ins);
+				duk_small_uint_fast_t c = DUK_DEC_C(ins);
+
+				/*
+				 *  Enumeration semantics come from for-in statement, E5 Section 12.6.4.
+				 *  If called with 'null' or 'undefined', this opcode returns 'null' as
+				 *  the enumerator, which is special cased in NEXTENUM.  This simplifies
+				 *  the compiler part
+				 */
+
+				/* B -> register for writing enumerator object
+				 * C -> value to be enumerated (expect a register)
+				 */
+
+				if (duk_is_null_or_undefined(ctx, (duk_idx_t) c)) {
+					duk_push_null(ctx);
+					duk_replace(ctx, (duk_idx_t) b);
+				} else {
+					duk_dup(ctx, (duk_idx_t) c);
+					duk_to_object(ctx, -1);
+					duk_hobject_enumerator_create(ctx, 0 /*enum_flags*/);  /* [ ... val ] --> [ ... enum ] */
+					duk_replace(ctx, (duk_idx_t) b);
+				}
+				break;
+			}
+
+			case DUK_EXTRAOP_NEXTENUM: {
+				duk_context *ctx = (duk_context *) thr;
+				duk_small_uint_fast_t b = DUK_DEC_B(ins);
+				duk_small_uint_fast_t c = DUK_DEC_C(ins);
+
+				/*
+				 *  NEXTENUM checks whether the enumerator still has unenumerated
+				 *  keys.  If so, the next key is loaded to the target register
+				 *  and the next instruction is skipped.  Otherwise the next instruction
+				 *  will be executed, jumping out of the enumeration loop.
+				 */
+
+				/* B -> target register for next key
+				 * C -> enum register
+				 */
+
+				DUK_DDD(DUK_DDDPRINT("NEXTENUM: b->%!T, c->%!T",
+				                     (duk_tval *) duk_get_tval(ctx, (duk_idx_t) b),
+				                     (duk_tval *) duk_get_tval(ctx, (duk_idx_t) c)));
+
+				if (duk_is_object(ctx, (duk_idx_t) c)) {
+					/* XXX: assert 'c' is an enumerator */
+					duk_dup(ctx, (duk_idx_t) c);
+					if (duk_hobject_enumerator_next(ctx, 0 /*get_value*/)) {
+						/* [ ... enum ] -> [ ... next_key ] */
+						DUK_DDD(DUK_DDDPRINT("enum active, next key is %!T, skip jump slot ",
+						                     (duk_tval *) duk_get_tval(ctx, -1)));
+						act->pc++;;
+					} else {
+						/* [ ... enum ] -> [ ... ] */
+						DUK_DDD(DUK_DDDPRINT("enum finished, execute jump slot"));
+						duk_push_undefined(ctx);
+					}
+					duk_replace(ctx, (duk_idx_t) b);
+				} else {
+					/* 'null' enumerator case -> behave as with an empty enumerator */
+					DUK_ASSERT(duk_is_null(ctx, (duk_idx_t) c));
+					DUK_DDD(DUK_DDDPRINT("enum is null, execute jump slot"));
+				}
+				break;				
+			}
+
+			case DUK_EXTRAOP_INITSET:
+			case DUK_EXTRAOP_INITSETI:
+			case DUK_EXTRAOP_INITGET:
+			case DUK_EXTRAOP_INITGETI: {
+				duk_context *ctx = (duk_context *) thr;
+				duk_bool_t is_set = (extraop == DUK_EXTRAOP_INITSET || extraop == DUK_EXTRAOP_INITSETI);
+				duk_small_uint_fast_t b = DUK_DEC_B(ins);
+				duk_uint_fast_t idx;
+
+				/* B -> object register
+				 * C -> C+0 contains key, C+1 closure (value)
+				 */
+
+				/*
+				 *  INITSET/INITGET are only used to initialize object literal keys.
+				 *  The compiler ensures that there cannot be a previous data property
+				 *  of the same name.  It also ensures that setter and getter can only
+				 *  be initialized once (or not at all).
+				 */
+
+				idx = (duk_uint_fast_t) DUK_DEC_C(ins);
+				if (extraop == DUK_EXTRAOP_INITSETI || extraop == DUK_EXTRAOP_INITGETI) {
+					duk_tval *tv_ind = DUK__REGP(idx);
+					if (!DUK_TVAL_IS_NUMBER(tv_ind)) {
+						DUK__INTERNAL_ERROR("DUK_EXTRAOP_INITSETI/DUK_EXTRAOP_INITGETI target is not a number");
+					}
+					idx = (duk_uint_fast_t) DUK_TVAL_GET_NUMBER(tv_ind);
+				}
+
+#if defined(DUK_USE_EXEC_INDIRECT_BOUND_CHECK)
+				if (idx + 2 > (duk_uint_fast_t) duk_get_top(ctx)) {
+					/* XXX: use duk_is_valid_index() instead? */
+					/* XXX: improve check; check against nregs, not against top */
+					DUK__INTERNAL_ERROR("INITSET/INITGET out of bounds");
+				}
+#endif
+
+				/* XXX: this is now a very unoptimal implementation -- this can be
+				 * made very simple by direct manipulation of the object internals,
+				 * given the guarantees above.
+				 */
+
+				duk_push_hobject_bidx(ctx, DUK_BIDX_OBJECT_CONSTRUCTOR);
+				duk_get_prop_stridx(ctx, -1, DUK_STRIDX_DEFINE_PROPERTY);
+				duk_push_undefined(ctx);
+				duk_dup(ctx, (duk_idx_t) b);
+				duk_dup(ctx, (duk_idx_t) (idx + 0));
+				duk_push_object(ctx);  /* -> [ Object defineProperty undefined obj key desc ] */
+
+				duk_push_true(ctx);
+				duk_put_prop_stridx(ctx, -2, DUK_STRIDX_ENUMERABLE);
+				duk_push_true(ctx);
+				duk_put_prop_stridx(ctx, -2, DUK_STRIDX_CONFIGURABLE);
+				duk_dup(ctx, (duk_idx_t) (idx + 1));
+				duk_put_prop_stridx(ctx, -2, (is_set ? DUK_STRIDX_SET : DUK_STRIDX_GET));
+
+				DUK_DDD(DUK_DDDPRINT("INITGET/INITSET: obj=%!T, key=%!T, desc=%!T",
+				                     (duk_tval *) duk_get_tval(ctx, -3),
+				                     (duk_tval *) duk_get_tval(ctx, -2),
+				                     (duk_tval *) duk_get_tval(ctx, -1)));
+
+				duk_call_method(ctx, 3);  /* -> [ Object res ] */
+				duk_pop_2(ctx);
+
+				DUK_DDD(DUK_DDDPRINT("INITGET/INITSET AFTER: obj=%!T",
+				                     (duk_tval *) duk_get_tval(ctx, (duk_idx_t) b)));
+				break;
+			}
+
+			case DUK_EXTRAOP_ENDTRY: {
+				duk_catcher *cat;
+				duk_tval tv_tmp;
+				duk_tval *tv1;
+
+				DUK_ASSERT(thr->catchstack_top >= 1);
+				DUK_ASSERT(thr->callstack_top >= 1);
+				DUK_ASSERT(thr->catchstack[thr->catchstack_top - 1].callstack_index == thr->callstack_top - 1);
+
+				cat = thr->catchstack + thr->catchstack_top - 1;
+
+				DUK_DDD(DUK_DDDPRINT("ENDTRY: clearing catch active flag (regardless of whether it was set or not)"));
+				DUK_CAT_CLEAR_CATCH_ENABLED(cat);
+
+				if (DUK_CAT_HAS_FINALLY_ENABLED(cat)) {
+					DUK_DDD(DUK_DDDPRINT("ENDTRY: finally part is active, jump through 2nd jump slot with 'normal continuation'"));
+			
+					tv1 = thr->valstack + cat->idx_base;
+					DUK_ASSERT(tv1 >= thr->valstack && tv1 < thr->valstack_top);
+					DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
+					DUK_TVAL_SET_UNDEFINED_ACTUAL(tv1);
+					DUK_TVAL_DECREF(thr, &tv_tmp);     /* side effects */
+					tv1 = NULL;
+
+					tv1 = thr->valstack + cat->idx_base + 1;
+					DUK_ASSERT(tv1 >= thr->valstack && tv1 < thr->valstack_top);
+					DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
+					DUK_TVAL_SET_NUMBER(tv1, (duk_double_t) DUK_LJ_TYPE_NORMAL);  /* XXX: set int */
+					DUK_TVAL_DECREF(thr, &tv_tmp);     /* side effects */
+					tv1 = NULL;
+
+					DUK_CAT_CLEAR_FINALLY_ENABLED(cat);
+				} else {
+					DUK_DDD(DUK_DDDPRINT("ENDTRY: no finally part, dismantle catcher, jump through 2nd jump slot (to end of statement)"));
+					duk_hthread_catchstack_unwind(thr, thr->catchstack_top - 1);
+					/* no need to unwind callstack */
+				}
+
+				act->pc = cat->pc_base + 1;
+				break;
+			}
+
+			case DUK_EXTRAOP_ENDCATCH: {
+				duk_catcher *cat;
+				duk_tval tv_tmp;
+				duk_tval *tv1;
+
+				DUK_ASSERT(thr->catchstack_top >= 1);
+				DUK_ASSERT(thr->callstack_top >= 1);
+				DUK_ASSERT(thr->catchstack[thr->catchstack_top - 1].callstack_index == thr->callstack_top - 1);
+
+				cat = thr->catchstack + thr->catchstack_top - 1;
+				DUK_ASSERT(!DUK_CAT_HAS_CATCH_ENABLED(cat));  /* cleared before entering catch part */
+
+				if (DUK_CAT_HAS_LEXENV_ACTIVE(cat)) {
+					duk_hobject *prev_env;
+
+					/* 'with' binding has no catch clause, so can't be here unless a normal try-catch */
+					DUK_ASSERT(DUK_CAT_HAS_CATCH_BINDING_ENABLED(cat));
+					DUK_ASSERT(act->lex_env != NULL);
+
+					DUK_DDD(DUK_DDDPRINT("ENDCATCH: popping catcher part lexical environment"));
+
+					prev_env = act->lex_env;
+					DUK_ASSERT(prev_env != NULL);
+					act->lex_env = prev_env->prototype;
+					DUK_CAT_CLEAR_LEXENV_ACTIVE(cat);
+					DUK_HOBJECT_DECREF(thr, prev_env);  /* side effects */
+				}
+
+				if (DUK_CAT_HAS_FINALLY_ENABLED(cat)) {
+					DUK_DDD(DUK_DDDPRINT("ENDCATCH: finally part is active, jump through 2nd jump slot with 'normal continuation'"));
+			
+					tv1 = thr->valstack + cat->idx_base;
+					DUK_ASSERT(tv1 >= thr->valstack && tv1 < thr->valstack_top);
+					DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
+					DUK_TVAL_SET_UNDEFINED_ACTUAL(tv1);
+					DUK_TVAL_DECREF(thr, &tv_tmp);     /* side effects */
+					tv1 = NULL;
+
+					tv1 = thr->valstack + cat->idx_base + 1;
+					DUK_ASSERT(tv1 >= thr->valstack && tv1 < thr->valstack_top);
+					DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
+					DUK_TVAL_SET_NUMBER(tv1, (duk_double_t) DUK_LJ_TYPE_NORMAL);  /* XXX: set int */
+					DUK_TVAL_DECREF(thr, &tv_tmp);     /* side effects */
+					tv1 = NULL;
+
+					DUK_CAT_CLEAR_FINALLY_ENABLED(cat);
+				} else {
+					DUK_DDD(DUK_DDDPRINT("ENDCATCH: no finally part, dismantle catcher, jump through 2nd jump slot (to end of statement)"));
+					duk_hthread_catchstack_unwind(thr, thr->catchstack_top - 1);
+					/* no need to unwind callstack */
+				}
+
+				act->pc = cat->pc_base + 1;
+				break;
+			}
+
+			case DUK_EXTRAOP_ENDFIN: {
+				duk_context *ctx = (duk_context *) thr;
+				duk_catcher *cat;
+				duk_tval *tv1;
+				duk_small_uint_fast_t cont_type;
+
+				DUK_ASSERT(thr->catchstack_top >= 1);
+				DUK_ASSERT(thr->callstack_top >= 1);
+				DUK_ASSERT(thr->catchstack[thr->catchstack_top - 1].callstack_index == thr->callstack_top - 1);
+
+				cat = thr->catchstack + thr->catchstack_top - 1;
+
+				/* CATCH flag may be enabled or disabled here; it may be enabled if
+				 * the statement has a catch block but the try block does not throw
+				 * an error.
+				 */
+				DUK_ASSERT(!DUK_CAT_HAS_FINALLY_ENABLED(cat));  /* cleared before entering finally */
+				/* XXX: assert idx_base */
+
+				DUK_DDD(DUK_DDDPRINT("ENDFIN: completion value=%!T, type=%!T",
+				                     (duk_tval *) (thr->valstack + cat->idx_base + 0),
+				                     (duk_tval *) (thr->valstack + cat->idx_base + 1)));
+
+				tv1 = thr->valstack + cat->idx_base + 1;  /* type */
+				DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv1));
+				cont_type = (duk_small_uint_fast_t) DUK_TVAL_GET_NUMBER(tv1);
+
+				if (cont_type == DUK_LJ_TYPE_NORMAL) {
+					DUK_DDD(DUK_DDDPRINT("ENDFIN: finally part finishing with 'normal' (non-abrupt) completion -> "
+					                     "dismantle catcher, resume execution after ENDFIN"));
+					duk_hthread_catchstack_unwind(thr, thr->catchstack_top - 1);
+					/* no need to unwind callstack */
+				} else {
+					DUK_DDD(DUK_DDDPRINT("ENDFIN: finally part finishing with abrupt completion, lj_type=%ld -> "
+					                     "dismantle catcher, re-throw error",
+					                     (long) cont_type));
+
+					duk_push_tval(ctx, thr->valstack + cat->idx_base);
+
+					/* XXX: assert lj type valid */
+					duk_err_setup_heap_ljstate(thr, (duk_small_int_t) cont_type);
+
+					DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL);  /* always in executor */
+					duk_err_longjmp(thr);
+					DUK_UNREACHABLE();
+				}
+
+				/* continue execution after ENDFIN */
+				break;
+			}
+
+			case DUK_EXTRAOP_THROW: {
+				duk_context *ctx = (duk_context *) thr;
+				duk_small_uint_fast_t b = DUK_DEC_B(ins);
+
+				/* Note: errors are augmented when they are created, not
+				 * when they are thrown.  So, don't augment here, it would
+				 * break re-throwing for instance.
+				 */
+
+				duk_dup(ctx, (duk_idx_t) b);
+				DUK_DDD(DUK_DDDPRINT("THROW ERROR (BYTECODE): %!dT (before throw augment)",
+				                     (duk_tval *) duk_get_tval(ctx, -1)));
+#if defined(DUK_USE_AUGMENT_ERROR_THROW)
+				duk_err_augment_error_throw(thr);
+				DUK_DDD(DUK_DDDPRINT("THROW ERROR (BYTECODE): %!dT (after throw augment)",
+				                     (duk_tval *) duk_get_tval(ctx, -1)));
+#endif
+
+				duk_err_setup_heap_ljstate(thr, DUK_LJ_TYPE_THROW);
+
+				DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL);  /* always in executor */
+				duk_err_longjmp(thr);
+
+				DUK_UNREACHABLE();
+				break;
+			}
+
+			case DUK_EXTRAOP_INVLHS: {
+				DUK_ERROR(thr, DUK_ERR_REFERENCE_ERROR, "invalid lvalue");
+
+				DUK_UNREACHABLE();
+				break;
+			}
+
+			case DUK_EXTRAOP_UNM:
+			case DUK_EXTRAOP_UNP:
+			case DUK_EXTRAOP_INC:
+			case DUK_EXTRAOP_DEC: {
+				duk_small_uint_fast_t b = DUK_DEC_B(ins);
+				duk_small_uint_fast_t c = DUK_DEC_C(ins);
+
+				duk__vm_arith_unary_op(thr, DUK__REGCONSTP(c), b, extraop);
+				break;
+			}
+
+#ifdef DUK_USE_DEBUG
+			case DUK_EXTRAOP_DUMPREG: {
+				DUK_D(DUK_DPRINT("DUMPREG: %ld -> %!T",
+				                 (long) DUK_DEC_BC(ins),
+				                 (duk_tval *) duk_get_tval((duk_context *) thr, (duk_idx_t) DUK_DEC_BC(ins))));
+				break;
+			}
+
+			case DUK_EXTRAOP_DUMPREGS: {
+				duk_idx_t i, i_top;
+				i_top = duk_get_top((duk_context *) thr);
+				DUK_D(DUK_DPRINT("DUMPREGS: %ld regs", (long) i_top));
+				for (i = 0; i < i_top; i++) {
+					DUK_D(DUK_DPRINT("  r%ld -> %!dT",
+					                 (long) i,
+					                 (duk_tval *) duk_get_tval((duk_context *) thr, i)));
+				}
+				break;
+			}
+
+			case DUK_EXTRAOP_DUMPTHREAD: {
+				DUK_DEBUG_DUMP_HTHREAD(thr);
+				break;
+			}
+
+			case DUK_EXTRAOP_LOGMARK: {
+				DUK_D(DUK_DPRINT("LOGMARK: mark %ld at pc %ld", (long) DUK_DEC_BC(ins), (long) (act->pc - 1)));  /* -1, autoinc */
+				break;
+			}
+#endif  /* DUK_USE_DEBUG */
+
+			default: {
+				DUK__INTERNAL_ERROR("invalid extra opcode");
+			}
+
+			}  /* end switch */
+
+			break;
+		}
+
+		case DUK_OP_INVALID: {
+			DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "INVALID opcode (%ld)", (long) DUK_DEC_ABC(ins));
+			break;
+		}
+
+		default: {
+			/* this should never be possible, because the switch-case is
+			 * comprehensive
+			 */
+			DUK__INTERNAL_ERROR("invalid opcode");
+			break;
+		}
+
+		}  /* end switch */
+	}
+	DUK_UNREACHABLE();
+
+#ifndef DUK_USE_VERBOSE_EXECUTOR_ERRORS
+ internal_error:
+	DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "internal error in bytecode executor");
+#endif
+}
+
+#undef DUK__INTERNAL_ERROR
+#line 1 "duk_js_ops.c"
+/*
+ *  Ecmascript specification algorithm and conversion helpers.
+ *
+ *  These helpers encapsulate the primitive Ecmascript operation
+ *  semantics, and are used by the bytecode executor and the API
+ *  (among other places).  Note that some primitives are only
+ *  implemented as part of the API and have no "internal" helper.
+ *  (This is the case when an internal helper would not really be
+ *  useful; e.g. the operation is rare, uses value stack heavily,
+ *  etc.)
+ *
+ *  The operation arguments depend on what is required to implement
+ *  the operation:
+ *
+ *    - If an operation is simple and stateless, and has no side
+ *      effects, it won't take an duk_hthread argument and its
+ *      arguments may be duk_tval pointers (which are safe as long
+ *      as no side effects take place).
+ *
+ *    - If complex coercions are required (e.g. a "ToNumber" coercion)
+ *      or errors may be thrown, the operation takes an duk_hthread
+ *      argument.  This also implies that the operation may have
+ *      arbitrary side effects, invalidating any duk_tval pointers.
+ *
+ *    - For operations with potential side effects, arguments can be
+ *      taken in several ways:
+ *
+ *      a) as duk_tval pointers, which makes sense if the "common case"
+ *         can be resolved without side effects (e.g. coercion); the
+ *         arguments are pushed to the valstack for coercion if
+ *         necessary
+ *
+ *      b) as duk_tval values
+ *
+ *      c) implicitly on value stack top
+ *
+ *      d) as indices to the value stack
+ *
+ *  Future work:
+ *
+ *     - Argument styles may not be the most sensible in every case now.
+ *
+ *     - In-place coercions might be useful for several operations, if
+ *       in-place coercion is OK for the bytecode executor and the API.
+ */
+
+/* include removed: duk_internal.h */
+
+/*
+ *  [[DefaultValue]]  (E5 Section 8.12.8)
+ *
+ *  ==> implemented in the API.
+ */
+
+/*
+ *  ToPrimitive()  (E5 Section 9.1)
+ *
+ *  ==> implemented in the API.
+ */
+
+/*
+ *  ToBoolean()  (E5 Section 9.2)
+ */
+
+duk_bool_t duk_js_toboolean(duk_tval *tv) {
+	switch (DUK_TVAL_GET_TAG(tv)) {
+	case DUK_TAG_UNDEFINED:
+	case DUK_TAG_NULL:
+		return 0;
+	case DUK_TAG_BOOLEAN:
+		return DUK_TVAL_GET_BOOLEAN(tv);
+	case DUK_TAG_STRING: {
+		duk_hstring *h = DUK_TVAL_GET_STRING(tv);
+		DUK_ASSERT(h != NULL);
+		return (h->blen > 0 ? 1 : 0);
+	}
+	case DUK_TAG_OBJECT: {
+		return 1;
+	}
+	case DUK_TAG_BUFFER: {
+		/* mimic semantics for strings */
+		duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv);
+		DUK_ASSERT(h != NULL);
+		return (DUK_HBUFFER_GET_SIZE(h) > 0 ? 1 : 0);
+	}
+	case DUK_TAG_POINTER: {
+		void *p = DUK_TVAL_GET_POINTER(tv);
+		return (p != NULL ? 1 : 0);
+	}
+	default: {
+		/* number */
+		int c;
+		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
+		c = DUK_FPCLASSIFY(DUK_TVAL_GET_NUMBER(tv));
+		if (c == DUK_FP_ZERO || c == DUK_FP_NAN) {
+			return 0;
+		} else {
+			return 1;
+		}
+	}
+	}
+	DUK_UNREACHABLE();
+}
+
+/*
+ *  ToNumber()  (E5 Section 9.3)
+ *
+ *  Value to convert must be on stack top, and is popped before exit.
+ *
+ *  See: http://www.cs.indiana.edu/~burger/FP-Printing-PLDI96.pdf
+ *       http://www.cs.indiana.edu/~burger/fp/index.html
+ *
+ *  Notes on the conversion:
+ *
+ *    - There are specific requirements on the accuracy of the conversion
+ *      through a "Mathematical Value" (MV), so this conversion is not
+ *      trivial.
+ *
+ *    - Quick rejects (e.g. based on first char) are difficult because
+ *      the grammar allows leading and trailing white space.
+ *
+ *    - Quick reject based on string length is difficult even after
+ *      accounting for white space; there may be arbitrarily many
+ *      decimal digits.
+ *
+ *    - Standard grammar allows decimal values ("123"), hex values
+ *      ("0x123") and infinities
+ *
+ *    - Unlike source code literals, ToNumber() coerces empty strings
+ *      and strings with only whitespace to zero (not NaN).
+ */	
+
+/* E5 Section 9.3.1 */
+static duk_double_t duk__tonumber_string_raw(duk_hthread *thr) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_small_uint_t s2n_flags;
+	duk_double_t d;
+
+	/* Quite lenient, e.g. allow empty as zero, but don't allow trailing
+	 * garbage.
+	 */
+	s2n_flags = DUK_S2N_FLAG_TRIM_WHITE |
+	            DUK_S2N_FLAG_ALLOW_EXP |
+	            DUK_S2N_FLAG_ALLOW_PLUS |
+	            DUK_S2N_FLAG_ALLOW_MINUS |
+	            DUK_S2N_FLAG_ALLOW_INF |
+	            DUK_S2N_FLAG_ALLOW_FRAC |
+	            DUK_S2N_FLAG_ALLOW_NAKED_FRAC |
+	            DUK_S2N_FLAG_ALLOW_EMPTY_FRAC |
+	            DUK_S2N_FLAG_ALLOW_EMPTY_AS_ZERO |
+	            DUK_S2N_FLAG_ALLOW_LEADING_ZERO |
+	            DUK_S2N_FLAG_ALLOW_AUTO_HEX_INT;
+
+	duk_numconv_parse(ctx, 10 /*radix*/, s2n_flags);
+	d = duk_get_number(ctx, -1);
+	duk_pop(ctx);
+
+	return d;
+}
+
+duk_double_t duk_js_tonumber(duk_hthread *thr, duk_tval *tv) {
+	duk_context *ctx = (duk_hthread *) thr;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(tv != NULL);
+
+	switch (DUK_TVAL_GET_TAG(tv)) {
+	case DUK_TAG_UNDEFINED: {
+		/* return a specific NaN (although not strictly necessary) */
+		duk_double_union du;
+		DUK_DBLUNION_SET_NAN(&du);
+		DUK_ASSERT(DUK_DBLUNION_IS_NORMALIZED(&du));
+		return du.d;
+	}
+	case DUK_TAG_NULL: {
+		/* +0.0 */
+		return 0.0;
+	}
+	case DUK_TAG_BOOLEAN: {
+		if (DUK_TVAL_IS_BOOLEAN_TRUE(tv)) {
+			return 1.0;
+		}
+		return 0.0;
+	}
+	case DUK_TAG_STRING: {
+		duk_hstring *h = DUK_TVAL_GET_STRING(tv);
+		duk_push_hstring(ctx, h);
+		return duk__tonumber_string_raw(thr);
+	}
+	case DUK_TAG_OBJECT: {
+		/* Note: ToPrimitive(object,hint) == [[DefaultValue]](object,hint),
+		 * so use [[DefaultValue]] directly.
+		 */
+		duk_double_t d;
+		duk_push_tval(ctx, tv);
+		duk_to_defaultvalue(ctx, -1, DUK_HINT_NUMBER);  /* 'tv' becomes invalid */
+
+		/* recursive call for a primitive value (guaranteed not to cause second
+		 * recursion).
+		 */
+		d = duk_js_tonumber(thr, duk_require_tval(ctx, -1));
+
+		duk_pop(ctx);
+		return d;
+	}
+	case DUK_TAG_BUFFER: {
+		/* Coerce like a string.  This makes sense because addition also treats
+		 * buffers like strings.
+		 */
+		duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv);
+		duk_push_hbuffer(ctx, h);
+		duk_to_string(ctx, -1);  /* XXX: expensive, but numconv now expects to see a string */
+		return duk__tonumber_string_raw(thr);
+	}
+	case DUK_TAG_POINTER: {
+		/* Coerce like boolean.  This allows code to do something like:
+		 *
+		 *    if (ptr) { ... }
+		 */
+		void *p = DUK_TVAL_GET_POINTER(tv);
+		return (p != NULL ? 1.0 : 0.0);
+	}
+	default: {
+		/* number */
+		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
+		return DUK_TVAL_GET_NUMBER(tv);
+	}
+	}
+
+	DUK_UNREACHABLE();
+}
+
+/*
+ *  ToInteger()  (E5 Section 9.4)
+ */
+
+/* exposed, used by e.g. duk_bi_date.c */
+duk_double_t duk_js_tointeger_number(duk_double_t x) {
+	duk_small_int_t c = (duk_small_int_t) DUK_FPCLASSIFY(x);
+
+	if (c == DUK_FP_NAN) {
+		return 0.0;
+	} else if (c == DUK_FP_ZERO || c == DUK_FP_INFINITE) {
+		/* XXX: FP_ZERO check can be removed, the else clause handles it
+		 * correctly (preserving sign).
+		 */
+		return x;
+	} else {
+		duk_small_int_t s = (duk_small_int_t) DUK_SIGNBIT(x);
+		x = DUK_FLOOR(DUK_FABS(x));  /* truncate towards zero */
+		if (s) {
+			x = -x;
+		}
+		return x;
+	}
+}
+
+duk_double_t duk_js_tointeger(duk_hthread *thr, duk_tval *tv) {
+	duk_double_t d = duk_js_tonumber(thr, tv);  /* invalidates tv */
+	return duk_js_tointeger_number(d);
+}
+
+/*
+ *  ToInt32(), ToUint32(), ToUint16()  (E5 Sections 9.5, 9.6, 9.7)
+ */
+
+/* combined algorithm matching E5 Sections 9.5 and 9.6 */	
+static duk_double_t duk__toint32_touint32_helper(duk_double_t x, duk_bool_t is_toint32) {
+	duk_small_int_t c = (duk_small_int_t) DUK_FPCLASSIFY(x);
+	duk_small_int_t s;
+
+	if (c == DUK_FP_NAN || c == DUK_FP_ZERO || c == DUK_FP_INFINITE) {
+		return 0.0;
+	}
+
+
+	/* x = sign(x) * floor(abs(x)), i.e. truncate towards zero, keep sign */
+	s = (duk_small_int_t) DUK_SIGNBIT(x);
+	x = DUK_FLOOR(DUK_FABS(x));
+	if (s) {
+		x = -x;
+	}
+	
+	/* NOTE: fmod(x) result sign is same as sign of x, which
+	 * differs from what Javascript wants (see Section 9.6).
+	 */
+
+	x = DUK_FMOD(x, DUK_DOUBLE_2TO32);    /* -> x in ]-2**32, 2**32[ */
+
+	if (x < 0.0) {
+		x += DUK_DOUBLE_2TO32;
+	}
+	/* -> x in [0, 2**32[ */
+
+	if (is_toint32) {
+		if (x >= DUK_DOUBLE_2TO31) {
+			/* x in [2**31, 2**32[ */
+
+			x -= DUK_DOUBLE_2TO32;  /* -> x in [-2**31,2**31[ */
+		}
+	}
+
+	return x;
+}
+
+duk_int32_t duk_js_toint32(duk_hthread *thr, duk_tval *tv) {
+	duk_double_t d = duk_js_tonumber(thr, tv);  /* invalidates tv */
+	d = duk__toint32_touint32_helper(d, 1);
+	DUK_ASSERT(DUK_FPCLASSIFY(d) == DUK_FP_ZERO || DUK_FPCLASSIFY(d) == DUK_FP_NORMAL);
+	DUK_ASSERT(d >= -2147483648.0 && d <= 2147483647.0);  /* [-0x80000000,0x7fffffff] */
+	DUK_ASSERT(d == ((duk_double_t) ((duk_int32_t) d)));  /* whole, won't clip */
+	return (duk_int32_t) d;
+}
+
+
+duk_uint32_t duk_js_touint32(duk_hthread *thr, duk_tval *tv) {
+	duk_double_t d = duk_js_tonumber(thr, tv);  /* invalidates tv */
+	d = duk__toint32_touint32_helper(d, 0);
+	DUK_ASSERT(DUK_FPCLASSIFY(d) == DUK_FP_ZERO || DUK_FPCLASSIFY(d) == DUK_FP_NORMAL);
+	DUK_ASSERT(d >= 0.0 && d <= 4294967295.0);  /* [0x00000000, 0xffffffff] */
+	DUK_ASSERT(d == ((duk_double_t) ((duk_uint32_t) d)));  /* whole, won't clip */
+	return (duk_uint32_t) d;
+
+}
+
+duk_uint16_t duk_js_touint16(duk_hthread *thr, duk_tval *tv) {
+	/* should be a safe way to compute this */
+	return (duk_uint16_t) (duk_js_touint32(thr, tv) & 0x0000ffffU);
+}
+
+/*
+ *  ToString()  (E5 Section 9.8)
+ *
+ *  ==> implemented in the API.
+ */
+
+/*
+ *  ToObject()  (E5 Section 9.9)
+ *
+ *  ==> implemented in the API.
+ */
+
+/*
+ *  CheckObjectCoercible()  (E5 Section 9.10)
+ *
+ *  Note: no API equivalent now.
+ */
+
+#if 0  /* unused */
+void duk_js_checkobjectcoercible(duk_hthread *thr, duk_tval *tv_x) {
+	duk_small_uint_t tag = DUK_TVAL_GET_TAG(tv_x);
+
+	/* Note: this must match ToObject() behavior */
+
+	if (tag == DUK_TAG_UNDEFINED ||
+	    tag == DUK_TAG_NULL ||
+	    tag == DUK_TAG_POINTER ||
+	    tag == DUK_TAG_BUFFER) {
+		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "not object coercible");
+	}
+}
+#endif
+
+/*
+ *  IsCallable()  (E5 Section 9.11)
+ *
+ *  XXX: API equivalent is a separate implementation now, and this has
+ *  currently no callers.
+ */
+
+#if 0  /* unused */
+int duk_js_iscallable(duk_tval *tv_x) {
+	duk_hobject *obj;
+
+	if (!DUK_TVAL_IS_OBJECT(tv_x)) {
+		return 0;
+	}
+	obj = DUK_TVAL_GET_OBJECT(tv_x);
+	DUK_ASSERT(obj != NULL);
+
+	return DUK_HOBJECT_IS_CALLABLE(obj);
+}
+#endif
+
+/*
+ *  Loose equality, strict equality, and SameValue (E5 Sections 11.9.1, 11.9.4,
+ *  9.12).  These have much in common so they can share some helpers.
+ *
+ *  Future work notes:
+ *
+ *    - Current implementation (and spec definition) has recursion; this should
+ *      be fixed if possible.
+ *
+ *    - String-to-number coercion should be possible without going through the
+ *      value stack (and be more compact) if a shared helper is invoked.
+ */
+
+/* Note that this is the same operation for strict and loose equality:
+ *  - E5 Section 11.9.3, step 1.c (loose)
+ *  - E5 Section 11.9.6, step 4 (strict)
+ */
+
+static duk_bool_t duk__js_equals_number(duk_double_t x, duk_double_t y) {
+#if defined(DUK_USE_PARANOID_MATH)
+	/* Straightforward algorithm, makes fewer compiler assumptions. */
+	duk_small_int_t cx = (duk_small_int_t) DUK_FPCLASSIFY(x);
+	duk_small_int_t cy = (duk_small_int_t) DUK_FPCLASSIFY(y);
+	if (cx == DUK_FP_NAN || cy == DUK_FP_NAN) {
+		return 0;
+	}
+	if (cx == DUK_FP_ZERO && cy == DUK_FP_ZERO) {
+		return 1;
+	}
+	if (x == y) {
+		return 1;
+	}
+	return 0;
+#else  /* DUK_USE_PARANOID_MATH */
+	/* Better equivalent algorithm.  If the compiler is compliant, C and
+	 * Ecmascript semantics are identical for this particular comparison.
+	 * In particular, NaNs must never compare equal and zeroes must compare
+	 * equal regardless of sign.  Could also use a macro, but this inlines
+	 * already nicely (no difference on gcc, for instance).
+	 */
+	if (x == y) {
+		/* IEEE requires that NaNs compare false */
+		DUK_ASSERT(DUK_FPCLASSIFY(x) != DUK_FP_NAN);
+		DUK_ASSERT(DUK_FPCLASSIFY(y) != DUK_FP_NAN);
+		return 1;
+	} else {
+		/* IEEE requires that zeros compare the same regardless
+		 * of their signed, so if both x and y are zeroes, they
+		 * are caught above.
+		 */
+		DUK_ASSERT(!(DUK_FPCLASSIFY(x) == DUK_FP_ZERO && DUK_FPCLASSIFY(y) == DUK_FP_ZERO));
+		return 0;
+	}
+#endif  /* DUK_USE_PARANOID_MATH */
+}
+
+static duk_bool_t duk__js_samevalue_number(duk_double_t x, duk_double_t y) {
+#if defined(DUK_USE_PARANOID_MATH)
+	duk_small_int_t cx = (duk_small_int_t) DUK_FPCLASSIFY(x);
+	duk_small_int_t cy = (duk_small_int_t) DUK_FPCLASSIFY(y);
+
+	if (cx == DUK_FP_NAN && cy == DUK_FP_NAN) {
+		/* SameValue(NaN, NaN) = true, regardless of NaN sign or extra bits */
+		return 1;
+	}
+	if (cx == DUK_FP_ZERO && cy == DUK_FP_ZERO) {
+		/* Note: cannot assume that a non-zero return value of signbit() would
+		 * always be the same -- hence cannot (portably) use something like:
+		 *
+		 *     signbit(x) == signbit(y)
+		 */
+		duk_small_int_t sx = (DUK_SIGNBIT(x) ? 1 : 0);
+		duk_small_int_t sy = (DUK_SIGNBIT(y) ? 1 : 0);
+		return (sx == sy);
+	}
+
+	/* normal comparison; known:
+	 *   - both x and y are not NaNs (but one of them can be)
+	 *   - both x and y are not zero (but one of them can be)
+	 *   - x and y may be denormal or infinite
+	 */
+
+	return (x == y);
+#else  /* DUK_USE_PARANOID_MATH */
+	duk_small_int_t cx = (duk_small_int_t) DUK_FPCLASSIFY(x);
+	duk_small_int_t cy = (duk_small_int_t) DUK_FPCLASSIFY(y);
+
+	if (x == y) {
+		/* IEEE requires that NaNs compare false */
+		DUK_ASSERT(DUK_FPCLASSIFY(x) != DUK_FP_NAN);
+		DUK_ASSERT(DUK_FPCLASSIFY(y) != DUK_FP_NAN);
+
+		/* Using classification has smaller footprint than direct comparison. */
+		if (DUK_UNLIKELY(cx == DUK_FP_ZERO && cy == DUK_FP_ZERO)) {
+			/* Note: cannot assume that a non-zero return value of signbit() would
+			 * always be the same -- hence cannot (portably) use something like:
+			 *
+			 *     signbit(x) == signbit(y)
+			 */
+			duk_small_int_t sx = (DUK_SIGNBIT(x) ? 1 : 0);
+			duk_small_int_t sy = (DUK_SIGNBIT(y) ? 1 : 0);
+			return (sx == sy);
+		}
+		return 1;
+	} else {
+		/* IEEE requires that zeros compare the same regardless
+		 * of their signed, so if both x and y are zeroes, they
+		 * are caught above.
+		 */
+		DUK_ASSERT(!(DUK_FPCLASSIFY(x) == DUK_FP_ZERO && DUK_FPCLASSIFY(y) == DUK_FP_ZERO));
+
+		/* Difference to non-strict/strict comparison is that NaNs compare
+		 * equal and signed zero signs matter.
+		 */
+		if (DUK_UNLIKELY(cx == DUK_FP_NAN && cy == DUK_FP_NAN)) {
+			/* SameValue(NaN, NaN) = true, regardless of NaN sign or extra bits */
+			return 1;
+		}
+		return 0;
+	}
+#endif  /* DUK_USE_PARANOID_MATH */
+}
+
+duk_bool_t duk_js_equals_helper(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y, duk_small_int_t flags) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_tval *tv_tmp;
+
+	/* If flags != 0 (strict or SameValue), thr can be NULL.  For loose
+	 * equals comparison it must be != NULL.
+	 */
+	DUK_ASSERT(flags != 0 || thr != NULL);
+
+	/*
+	 *  Same type?
+	 *
+	 *  Note: since number values have no explicit tag in the 8-byte
+	 *  representation, need the awkward if + switch.
+	 */
+
+	if (DUK_TVAL_IS_NUMBER(tv_x) && DUK_TVAL_IS_NUMBER(tv_y)) {
+		if (DUK_UNLIKELY((flags & DUK_EQUALS_FLAG_SAMEVALUE) != 0)) {
+			/* SameValue */
+			return duk__js_samevalue_number(DUK_TVAL_GET_NUMBER(tv_x),
+			                                DUK_TVAL_GET_NUMBER(tv_y));
+		} else {
+			/* equals and strict equals */
+			return duk__js_equals_number(DUK_TVAL_GET_NUMBER(tv_x),
+			                             DUK_TVAL_GET_NUMBER(tv_y));
+		}
+	} else if (DUK_TVAL_GET_TAG(tv_x) == DUK_TVAL_GET_TAG(tv_y)) {
+		switch (DUK_TVAL_GET_TAG(tv_x)) {
+		case DUK_TAG_UNDEFINED:
+		case DUK_TAG_NULL: {
+			return 1;
+		}
+		case DUK_TAG_BOOLEAN: {
+			return DUK_TVAL_GET_BOOLEAN(tv_x) == DUK_TVAL_GET_BOOLEAN(tv_y);
+		}
+		case DUK_TAG_POINTER: {
+			return DUK_TVAL_GET_POINTER(tv_x) == DUK_TVAL_GET_POINTER(tv_y);
+		}
+		case DUK_TAG_STRING:
+		case DUK_TAG_OBJECT: {
+			/* heap pointer comparison suffices */
+			return DUK_TVAL_GET_HEAPHDR(tv_x) == DUK_TVAL_GET_HEAPHDR(tv_y);
+		}
+		case DUK_TAG_BUFFER: {
+			if ((flags & (DUK_EQUALS_FLAG_STRICT | DUK_EQUALS_FLAG_SAMEVALUE)) != 0) {
+				/* heap pointer comparison suffices */
+				return DUK_TVAL_GET_HEAPHDR(tv_x) == DUK_TVAL_GET_HEAPHDR(tv_y);
+			} else {
+				/* non-strict equality for buffers compares contents */
+				duk_hbuffer *h_x = DUK_TVAL_GET_BUFFER(tv_x);
+				duk_hbuffer *h_y = DUK_TVAL_GET_BUFFER(tv_y);
+				duk_size_t len_x = DUK_HBUFFER_GET_SIZE(h_x);
+				duk_size_t len_y = DUK_HBUFFER_GET_SIZE(h_y);
+				void *buf_x;
+				void *buf_y;
+				if (len_x != len_y) {
+					return 0;
+				}
+				buf_x = (void *) DUK_HBUFFER_GET_DATA_PTR(h_x);
+				buf_y = (void *) DUK_HBUFFER_GET_DATA_PTR(h_y);
+				/* if len_x == len_y == 0, buf_x and/or buf_y may
+				 * be NULL, but that's OK.
+				 */
+				DUK_ASSERT(len_x == len_y);
+				DUK_ASSERT(len_x == 0 || buf_x != NULL);
+				DUK_ASSERT(len_y == 0 || buf_y != NULL);
+				return (DUK_MEMCMP(buf_x, buf_y, len_x) == 0) ? 1 : 0;
+			}
+		}
+		default: {
+			DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_x));
+			DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_y));
+			DUK_UNREACHABLE();
+			return 0;
+		}
+		}
+	}
+
+	if ((flags & (DUK_EQUALS_FLAG_STRICT | DUK_EQUALS_FLAG_SAMEVALUE)) != 0) {
+		return 0;
+	}
+
+	DUK_ASSERT(flags == 0);  /* non-strict equality from here on */
+
+	/*
+	 *  Types are different; various cases for non-strict comparison
+	 *
+	 *  Since comparison is symmetric, we use a "swap trick" to reduce
+	 *  code size.
+	 */
+
+	/* Undefined/null are considered equal (e.g. "null == undefined" -> true). */
+	if ((DUK_TVAL_IS_UNDEFINED(tv_x) && DUK_TVAL_IS_NULL(tv_y)) ||
+	    (DUK_TVAL_IS_NULL(tv_x) && DUK_TVAL_IS_UNDEFINED(tv_y))) {
+		return 1;
+	}
+
+	/* Number/string-or-buffer -> coerce string to number (e.g. "'1.5' == 1.5" -> true). */
+	if (DUK_TVAL_IS_NUMBER(tv_x) && (DUK_TVAL_IS_STRING(tv_y) || DUK_TVAL_IS_BUFFER(tv_y))) {
+		/* the next 'if' is guaranteed to match after swap */
+		tv_tmp = tv_x;
+		tv_x = tv_y;
+		tv_y = tv_tmp;
+	}
+	if ((DUK_TVAL_IS_STRING(tv_x) || DUK_TVAL_IS_BUFFER(tv_x)) && DUK_TVAL_IS_NUMBER(tv_y)) {
+		/* XXX: this is possible without resorting to the value stack */
+		duk_double_t d1, d2;
+		d2 = DUK_TVAL_GET_NUMBER(tv_y);
+		duk_push_tval(ctx, tv_x);
+		duk_to_string(ctx, -1);  /* buffer values are coerced first to string here */
+		duk_to_number(ctx, -1);
+		d1 = duk_require_number(ctx, -1);
+		duk_pop(ctx);
+		return duk__js_equals_number(d1, d2);
+	}
+
+	/* Buffer/string -> compare contents. */
+	if (DUK_TVAL_IS_BUFFER(tv_x) && DUK_TVAL_IS_STRING(tv_y)) {
+		tv_tmp = tv_x;
+		tv_x = tv_y;
+		tv_y = tv_tmp;
+	}
+	if (DUK_TVAL_IS_STRING(tv_x) && DUK_TVAL_IS_BUFFER(tv_y)) {
+		duk_hstring *h_x = DUK_TVAL_GET_STRING(tv_x);
+		duk_hbuffer *h_y = DUK_TVAL_GET_BUFFER(tv_y);
+		duk_size_t len_x = DUK_HSTRING_GET_BYTELEN(h_x);
+		duk_size_t len_y = DUK_HBUFFER_GET_SIZE(h_y);
+		void *buf_x;
+		void *buf_y;
+		if (len_x != len_y) {
+			return 0;
+		}
+		buf_x = (void *) DUK_HSTRING_GET_DATA(h_x);
+		buf_y = (void *) DUK_HBUFFER_GET_DATA_PTR(h_y);
+		/* if len_x == len_y == 0, buf_x and/or buf_y may
+		 * be NULL, but that's OK.
+		 */
+		DUK_ASSERT(len_x == len_y);
+		DUK_ASSERT(len_x == 0 || buf_x != NULL);
+		DUK_ASSERT(len_y == 0 || buf_y != NULL);
+		return (DUK_MEMCMP(buf_x, buf_y, len_x) == 0) ? 1 : 0;
+	}
+
+	/* Boolean/any -> coerce boolean to number and try again.  If boolean is
+	 * compared to a pointer, the final comparison after coercion now always
+	 * yields false (as pointer vs. number compares to false), but this is
+	 * not special cased.
+	 */
+	if (DUK_TVAL_IS_BOOLEAN(tv_x)) {
+		tv_tmp = tv_x;
+		tv_x = tv_y;
+		tv_y = tv_tmp;
+	}
+	if (DUK_TVAL_IS_BOOLEAN(tv_y)) {
+		/* ToNumber(bool) is +1.0 or 0.0.  Tagged boolean value is always 0 or 1. */
+		duk_bool_t rc;
+		DUK_ASSERT(DUK_TVAL_GET_BOOLEAN(tv_y) == 0 || DUK_TVAL_GET_BOOLEAN(tv_y) == 1);
+		duk_push_tval(ctx, tv_x);
+		duk_push_int(ctx, DUK_TVAL_GET_BOOLEAN(tv_y));
+		rc = duk_js_equals_helper(thr, duk_get_tval(ctx, -2), duk_get_tval(ctx, -1), 0 /*flags:nonstrict*/);
+		duk_pop_2(ctx);
+		return rc;
+	}
+
+	/* String-number-buffer/object -> coerce object to primitive (apparently without hint), then try again. */
+	if ((DUK_TVAL_IS_STRING(tv_x) || DUK_TVAL_IS_NUMBER(tv_x) || DUK_TVAL_IS_BUFFER(tv_x)) &&
+	    DUK_TVAL_IS_OBJECT(tv_y)) {
+		tv_tmp = tv_x;
+		tv_x = tv_y;
+		tv_y = tv_tmp;
+	}
+	if (DUK_TVAL_IS_OBJECT(tv_x) &&
+	    (DUK_TVAL_IS_STRING(tv_y) || DUK_TVAL_IS_NUMBER(tv_y) || DUK_TVAL_IS_BUFFER(tv_y))) {
+		duk_bool_t rc;
+		duk_push_tval(ctx, tv_x);
+		duk_push_tval(ctx, tv_y);
+		duk_to_primitive(ctx, -2, DUK_HINT_NONE);  /* apparently no hint? */
+		rc = duk_js_equals_helper(thr, duk_get_tval(ctx, -2), duk_get_tval(ctx, -1), 0 /*flags:nonstrict*/);
+		duk_pop_2(ctx);
+		return rc;
+	}
+
+	/* Nothing worked -> not equal. */
+	return 0;
+}
+
+/*
+ *  Comparisons (x >= y, x > y, x <= y, x < y)
+ *
+ *  E5 Section 11.8.5: implement 'x < y' and then use negate and eval_left_first
+ *  flags to get the rest.
+ */
+
+/* XXX: this should probably just operate on the stack top, because it
+ * needs to push stuff on the stack anyway...
+ */
+
+duk_small_int_t duk_js_string_compare(duk_hstring *h1, duk_hstring *h2) {
+	/*
+	 *  String comparison (E5 Section 11.8.5, step 4), which
+	 *  needs to compare codepoint by codepoint.
+	 *
+	 *  However, UTF-8 allows us to use strcmp directly: the shared
+	 *  prefix will be encoded identically (UTF-8 has unique encoding)
+	 *  and the first differing character can be compared with a simple
+	 *  unsigned byte comparison (which strcmp does).
+	 *
+	 *  This will not work properly for non-xutf-8 strings, but this
+	 *  is not an issue for compliance.
+	 */
+
+	duk_size_t h1_len, h2_len, prefix_len;
+	duk_small_int_t rc;
+
+	DUK_ASSERT(h1 != NULL);
+	DUK_ASSERT(h2 != NULL);
+	h1_len = DUK_HSTRING_GET_BYTELEN(h1);
+	h2_len = DUK_HSTRING_GET_BYTELEN(h2);
+	prefix_len = (h1_len <= h2_len ? h1_len : h2_len);
+
+	/* XXX: this special case can now be removed with DUK_MEMCMP */
+	/* memcmp() should return zero (equal) for zero length, but avoid
+	 * it because there are some platform specific bugs.  Don't use
+	 * strncmp() because it stops comparing at a NUL.
+	 */
+
+	if (prefix_len == 0) {
+		rc = 0;
+	} else {
+		rc = DUK_MEMCMP((const char *) DUK_HSTRING_GET_DATA(h1),
+		                (const char *) DUK_HSTRING_GET_DATA(h2),
+		                prefix_len);
+	}
+
+	if (rc < 0) {
+		return -1;
+	} else if (rc > 0) {
+		return 1;
+	}
+
+	/* prefix matches, lengths matter now */
+	if (h1_len < h2_len) {
+		/* e.g. "x" < "xx" */
+		return -1;
+	} else if (h1_len > h2_len) {
+		return 1;
+	}
+
+	return 0;
+}
+
+duk_bool_t duk_js_compare_helper(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y, duk_small_int_t flags) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_double_t d1, d2;
+	duk_small_int_t c1, c2;
+	duk_small_int_t s1, s2;
+	duk_small_int_t rc;
+	duk_bool_t retval;
+
+	duk_push_tval(ctx, tv_x);
+	duk_push_tval(ctx, tv_y);
+
+	if (flags & DUK_COMPARE_FLAG_EVAL_LEFT_FIRST) {
+		duk_to_primitive(ctx, -2, DUK_HINT_NUMBER);
+		duk_to_primitive(ctx, -1, DUK_HINT_NUMBER);
+	} else {
+		duk_to_primitive(ctx, -1, DUK_HINT_NUMBER);
+		duk_to_primitive(ctx, -2, DUK_HINT_NUMBER);
+	}
+
+	/* Note: reuse variables */
+	tv_x = duk_get_tval(ctx, -2);
+	tv_y = duk_get_tval(ctx, -1);
+
+	if (DUK_TVAL_IS_STRING(tv_x) && DUK_TVAL_IS_STRING(tv_y)) {
+		duk_hstring *h1 = DUK_TVAL_GET_STRING(tv_x);
+		duk_hstring *h2 = DUK_TVAL_GET_STRING(tv_y);
+		DUK_ASSERT(h1 != NULL);
+		DUK_ASSERT(h2 != NULL);
+
+		rc = duk_js_string_compare(h1, h2);
+		if (rc < 0) {
+			goto lt_true;
+		} else {
+			goto lt_false;
+		}
+	} else {
+		/* Ordering should not matter (E5 Section 11.8.5, step 3.a) but
+		 * preserve it just in case.
+		 */
+
+		if (flags & DUK_COMPARE_FLAG_EVAL_LEFT_FIRST) {
+			d1 = duk_to_number(ctx, -2);
+			d2 = duk_to_number(ctx, -1);
+		} else {
+			d2 = duk_to_number(ctx, -1);
+			d1 = duk_to_number(ctx, -2);
+		}
+
+		c1 = (duk_small_int_t) DUK_FPCLASSIFY(d1);
+		s1 = (duk_small_int_t) DUK_SIGNBIT(d1);
+		c2 = (duk_small_int_t) DUK_FPCLASSIFY(d2);
+		s2 = (duk_small_int_t) DUK_SIGNBIT(d2);
+
+		if (c1 == DUK_FP_NAN || c2 == DUK_FP_NAN) {
+			goto lt_undefined;
+		}
+
+		if (c1 == DUK_FP_ZERO && c2 == DUK_FP_ZERO) {
+			/* For all combinations: +0 < +0, +0 < -0, -0 < +0, -0 < -0,
+			 * steps e, f, and g.
+			 */
+			goto lt_false;
+		}
+
+		if (d1 == d2) {
+			goto lt_false;
+		}
+
+		if (c1 == DUK_FP_INFINITE && s1 == 0) {
+			/* x == +Infinity */
+			goto lt_false;
+		}
+
+		if (c2 == DUK_FP_INFINITE && s2 == 0) {
+			/* y == +Infinity */
+			goto lt_true;
+		}
+
+		if (c2 == DUK_FP_INFINITE && s2 != 0) {
+			/* y == -Infinity */
+			goto lt_false;
+		}
+
+		if (c1 == DUK_FP_INFINITE && s1 != 0) {
+			/* x == -Infinity */
+			goto lt_true;
+		}
+
+		if (d1 < d2) {
+			goto lt_true;
+		}
+
+		goto lt_false;
+	}
+
+ lt_undefined:
+	/* Note: undefined from Section 11.8.5 always results in false
+	 * return (see e.g. Section 11.8.3) - hence special treatment here.
+	 */
+	retval = 0;
+	goto cleanup;
+
+ lt_true:
+	if (flags & DUK_COMPARE_FLAG_NEGATE) {
+		retval = 0;
+		goto cleanup;
+	} else {
+		retval = 1;
+		goto cleanup;
+	}
+	/* never here */
+
+ lt_false:
+	if (flags & DUK_COMPARE_FLAG_NEGATE) {
+		retval = 1;
+		goto cleanup;
+	} else {
+		retval = 0;
+		goto cleanup;
+	}
+	/* never here */
+
+ cleanup:
+	duk_pop_2(ctx);
+	return retval;
+}
+
+/*
+ *  instanceof
+ */
+
+/*
+ *  E5 Section 11.8.6 describes the main algorithm, which uses
+ *  [[HasInstance]].  [[HasInstance]] is defined for only
+ *  function objects:
+ *
+ *    - Normal functions:
+ *      E5 Section 15.3.5.3
+ *    - Functions established with Function.prototype.bind():
+ *      E5 Section 15.3.4.5.3
+ *
+ *  For other objects, a TypeError is thrown.
+ */
+
+duk_bool_t duk_js_instanceof(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_hobject *func;
+	duk_hobject *val;
+	duk_hobject *proto;
+	duk_uint_t sanity;
+
+	/*
+	 *  Get the values onto the stack first.  It would be possible to cover
+	 *  some normal cases without resorting to the value stack.
+	 */
+
+	duk_push_tval(ctx, tv_x);
+	duk_push_tval(ctx, tv_y);
+	func = duk_require_hobject(ctx, -1);
+
+	/*
+	 *  For bound objects, [[HasInstance]] just calls the target function
+	 *  [[HasInstance]].  If that is again a bound object, repeat until
+	 *  we find a non-bound Function object.
+	 */
+
+	/* XXX: this bound function resolution also happens elsewhere,
+	 * move into a shared helper.
+	 */
+
+	sanity = DUK_HOBJECT_BOUND_CHAIN_SANITY;
+	do {
+		/* check func supports [[HasInstance]] (this is checked for every function
+		 * in the bound chain, including the final one)
+		 */
+
+		if (!DUK_HOBJECT_IS_CALLABLE(func)) {
+			/*
+		 	 *  Note: of native Ecmascript objects, only Function instances
+			 *  have a [[HasInstance]] internal property.  Custom objects might
+			 *  also have it, but not in current implementation.
+			 *
+			 *  XXX: add a separate flag, DUK_HOBJECT_FLAG_ALLOW_INSTANCEOF?
+			 */
+			DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid instanceof rval");
+		}
+
+		if (!DUK_HOBJECT_HAS_BOUND(func)) {
+			break;
+		}
+
+		/* [ ... lval rval ] */
+
+		duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_TARGET);         /* -> [ ... lval rval new_rval ] */
+		duk_replace(ctx, -1);                                        /* -> [ ... lval new_rval ] */
+		func = duk_require_hobject(ctx, -1);
+
+		/* func support for [[HasInstance]] checked in the beginning of the loop */
+	} while (--sanity > 0);
+
+	if (sanity == 0) {
+		DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, DUK_STR_BOUND_CHAIN_LIMIT);
+	}
+
+	/*
+	 *  'func' is now a non-bound object which supports [[HasInstance]]
+	 *  (which here just means DUK_HOBJECT_FLAG_CALLABLE).  Move on
+	 *  to execute E5 Section 15.3.5.3.
+	 */
+
+	DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));
+	DUK_ASSERT(DUK_HOBJECT_IS_CALLABLE(func));
+
+	/* [ ... lval rval(func) ] */
+
+	val = duk_get_hobject(ctx, -2);
+	if (!val) {
+		goto pop_and_false;
+	}
+
+	duk_get_prop_stridx(ctx, -1, DUK_STRIDX_PROTOTYPE);  /* -> [ ... lval rval rval.prototype ] */
+	proto = duk_require_hobject(ctx, -1);
+	duk_pop(ctx);  /* -> [ ... lval rval ] */
+
+	sanity = DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY;
+	do {
+		/*
+		 *  Note: prototype chain is followed BEFORE first comparison.  This
+		 *  means that the instanceof lval is never itself compared to the
+		 *  rval.prototype property.  This is apparently intentional, see E5
+		 *  Section 15.3.5.3, step 4.a.
+		 *
+		 *  Also note:
+		 *
+		 *      js> (function() {}) instanceof Function
+		 *      true
+		 *      js> Function instanceof Function
+		 *      true
+		 *
+		 *  For the latter, h_proto will be Function.prototype, which is the
+		 *  built-in Function prototype.  Because Function.[[Prototype]] is
+		 *  also the built-in Function prototype, the result is true.
+		 */
+
+		val = val->prototype;
+
+		if (!val) {
+			goto pop_and_false;
+		} else if (val == proto) {
+			goto pop_and_true;
+		}
+
+		/* follow prototype chain */
+	} while (--sanity > 0);
+
+	if (sanity == 0) {
+		DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "instanceof prototype chain sanity exceeded");
+	}
+	DUK_UNREACHABLE();
+
+ pop_and_false:
+	duk_pop_2(ctx);
+	return 0;
+
+ pop_and_true:
+	duk_pop_2(ctx);
+	return 1;
+}
+
+/*
+ *  in
+ */
+
+/*
+ *  E5 Sections 11.8.7, 8.12.6.
+ *
+ *  Basically just a property existence check using [[HasProperty]].
+ */
+	
+duk_bool_t duk_js_in(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_bool_t retval;
+
+	/*
+	 *  Get the values onto the stack first.  It would be possible to cover
+	 *  some normal cases without resorting to the value stack (e.g. if
+	 *  lval is already a string).
+	 */
+
+	/* XXX: The ES5/5.1/6 specifications require that the key in 'key in obj'
+	 * must be string coerced before the internal HasProperty() algorithm is
+	 * invoked.  A fast path skipping coercion could be safely implemented for
+	 * numbers (as number-to-string coercion has no side effects).  For ES6
+	 * proxy behavior, the trap 'key' argument must be in a string coerced
+	 * form (which is a shame).
+	 */
+
+	duk_push_tval(ctx, tv_x);
+	duk_push_tval(ctx, tv_y);
+	(void) duk_require_hobject(ctx, -1);  /* TypeError if rval not object */
+	duk_to_string(ctx, -2);               /* coerce lval with ToString() */
+
+	retval = duk_hobject_hasprop(thr, duk_get_tval(ctx, -1), duk_get_tval(ctx, -2));
+
+	duk_pop_2(ctx);
+	return retval;
+}
+
+/*
+ *  typeof
+ *
+ *  E5 Section 11.4.3.
+ *
+ *  Very straightforward.  The only question is what to return for our
+ *  non-standard tag / object types.
+ *
+ *  There is an unfortunate string constant define naming problem with
+ *  typeof return values for e.g. "Object" and "object"; careful with
+ *  the built-in string defines.  The LC_XXX defines are used for the
+ *  lowercase variants now.
+ */
+
+duk_hstring *duk_js_typeof(duk_hthread *thr, duk_tval *tv_x) {
+	duk_small_int_t stridx = 0;
+
+	switch (DUK_TVAL_GET_TAG(tv_x)) {
+	case DUK_TAG_UNDEFINED: {
+		stridx = DUK_STRIDX_LC_UNDEFINED;
+		break;
+	}
+	case DUK_TAG_NULL: {
+		/* Note: not a typo, "object" is returned for a null value */
+		stridx = DUK_STRIDX_LC_OBJECT;
+		break;
+	}
+	case DUK_TAG_BOOLEAN: {
+		stridx = DUK_STRIDX_LC_BOOLEAN;
+		break;
+	}
+	case DUK_TAG_POINTER: {
+		/* implementation specific */
+		stridx = DUK_STRIDX_LC_POINTER;
+		break;
+	}
+	case DUK_TAG_STRING: {
+		stridx = DUK_STRIDX_LC_STRING;
+		break;
+	}
+	case DUK_TAG_OBJECT: {
+		duk_hobject *obj = DUK_TVAL_GET_OBJECT(tv_x);
+		DUK_ASSERT(obj != NULL);
+		if (DUK_HOBJECT_IS_CALLABLE(obj)) {
+			stridx = DUK_STRIDX_LC_FUNCTION;
+		} else {
+			stridx = DUK_STRIDX_LC_OBJECT;
+		}
+		break;
+	}
+	case DUK_TAG_BUFFER: {
+		/* implementation specific */
+		stridx = DUK_STRIDX_LC_BUFFER;
+		break;
+	}
+	default: {
+		/* number */
+		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_x));
+		stridx = DUK_STRIDX_LC_NUMBER;
+		break;
+	}
+	}
+
+	DUK_ASSERT(stridx >= 0 && stridx < DUK_HEAP_NUM_STRINGS);
+	return thr->strs[stridx];
+}
+
+/*
+ *  Array index and length
+ *
+ *  Array index: E5 Section 15.4
+ *  Array length: E5 Section 15.4.5.1 steps 3.c - 3.d (array length write)
+ *
+ *  The DUK_HSTRING_GET_ARRIDX_SLOW() and DUK_HSTRING_GET_ARRIDX_FAST() macros
+ *  call duk_js_to_arrayindex_string_helper().
+ */
+
+duk_small_int_t duk_js_to_arrayindex_raw_string(duk_uint8_t *str, duk_uint32_t blen, duk_uarridx_t *out_idx) {
+	duk_uarridx_t res, new_res;
+
+	if (blen == 0 || blen > 10) {
+		goto parse_fail;
+	}
+	if (str[0] == (duk_uint8_t) '0' && blen > 1) {
+		goto parse_fail;
+	}
+
+	/* Accept 32-bit decimal integers, no leading zeroes, signs, etc.
+	 * Leading zeroes are not accepted (zero index "0" is an exception
+	 * handled above).
+	 */
+
+	res = 0;
+	while (blen-- > 0) {
+		duk_uint8_t c = *str++;
+		if (c >= (duk_uint8_t) '0' && c <= (duk_uint8_t) '9') {
+			new_res = res * 10 + (duk_uint32_t) (c - (duk_uint8_t) '0');
+			if (new_res < res) {
+				/* overflow, more than 32 bits -> not an array index */
+				goto parse_fail;
+			}
+			res = new_res;
+		} else {
+			goto parse_fail;
+		}
+	}
+
+	*out_idx = res;
+	return 1;
+
+ parse_fail:
+	*out_idx = DUK_HSTRING_NO_ARRAY_INDEX;
+	return 0;
+}	
+
+/* Called by duk_hstring.h macros */
+duk_uarridx_t duk_js_to_arrayindex_string_helper(duk_hstring *h) {
+	duk_uarridx_t res;
+	duk_small_int_t rc;
+
+	if (!DUK_HSTRING_HAS_ARRIDX(h)) {
+		return DUK_HSTRING_NO_ARRAY_INDEX;
+	}
+
+	rc = duk_js_to_arrayindex_raw_string(DUK_HSTRING_GET_DATA(h),
+	                                     DUK_HSTRING_GET_BYTELEN(h),
+	                                     &res);
+	DUK_UNREF(rc);
+	DUK_ASSERT(rc != 0);
+	return res;
+}
+#line 1 "duk_js_var.c"
+/*
+ *  Identifier access and function closure handling.
+ *
+ *  Provides the primitives for slow path identifier accesses: GETVAR,
+ *  PUTVAR, DELVAR, etc.  The fast path, direct register accesses, should
+ *  be used for most identifier accesses.  Consequently, these slow path
+ *  primitives should be optimized for maximum compactness.
+ *
+ *  Ecmascript environment records (declarative and object) are represented
+ *  as internal objects with control keys.  Environment records have a
+ *  parent record ("outer environment reference") which is represented by
+ *  the implicit prototype for technical reasons (in other words, it is a
+ *  convenient field).  The prototype chain is not followed in the ordinary
+ *  sense for variable lookups.
+ *
+ *  See identifier-handling.txt for more details on the identifier algorithms
+ *  and the internal representation.  See function-objects.txt for details on
+ *  what function templates and instances are expected to look like.
+ *
+ *  Care must be taken to avoid duk_tval pointer invalidation caused by
+ *  e.g. value stack or object resizing.
+ *
+ *  TODO: properties for function instances could be initialized much more
+ *  efficiently by creating a property allocation for a certain size and
+ *  filling in keys and values directly (and INCREFing both with "bulk incref"
+ *  primitives.
+ *
+ *  XXX: duk_hobject_getprop() and duk_hobject_putprop() calls are a bit
+ *  awkward (especially because they follow the prototype chain); rework
+ *  if "raw" own property helpers are added.
+ */
+
+/* include removed: duk_internal.h */
+
+/*
+ *  Local result type for duk__get_identifier_reference() lookup.
+ */
+
+typedef struct {
+	duk_hobject *holder;      /* for object-bound identifiers */
+	duk_tval *value;          /* for register-bound and declarative env identifiers */
+	duk_int_t attrs;          /* property attributes for identifier (relevant if value != NULL) */
+	duk_tval *this_binding;
+	duk_hobject *env;
+} duk__id_lookup_result;
+
+/*
+ *  Create a new function object based on a "template function" which contains
+ *  compiled bytecode, constants, etc, but lacks a lexical environment.
+ *
+ *  Ecmascript requires that each created closure is a separate object, with
+ *  its own set of editable properties.  However, structured property values
+ *  (such as the formal arguments list and the variable map) are shared.
+ *  Also the bytecode, constants, and inner functions are shared.
+ *
+ *  See E5 Section 13.2 for detailed requirements on the function objects;
+ *  there are no similar requirements for function "templates" which are an
+ *  implementation dependent internal feature.  Also see function-objects.txt
+ *  for a discussion on the function instance properties provided by this
+ *  implementation.
+ *
+ *  Notes:
+ *
+ *   * Order of internal properties should match frequency of use, since the
+ *     properties will be linearly scanned on lookup (functions usually don't
+ *     have enough properties to warrant a hash part).
+ *
+ *   * The created closure is independent of its template; they do share the
+ *     same 'data' buffer object, but the template object itself can be freed
+ *     even if the closure object remains reachable.
+ */
+
+static void duk__inc_data_inner_refcounts(duk_hthread *thr, duk_hcompiledfunction *f) {
+	duk_tval *tv, *tv_end;
+	duk_hobject **funcs, **funcs_end;
+
+	DUK_ASSERT(f->data != NULL);  /* compiled functions must be created 'atomically' */
+	DUK_UNREF(thr);
+
+	tv = DUK_HCOMPILEDFUNCTION_GET_CONSTS_BASE(f);
+	tv_end = DUK_HCOMPILEDFUNCTION_GET_CONSTS_END(f);
+	while (tv < tv_end) {
+		DUK_TVAL_INCREF(thr, tv);
+		tv++;
+	}
+
+	funcs = DUK_HCOMPILEDFUNCTION_GET_FUNCS_BASE(f);
+	funcs_end = DUK_HCOMPILEDFUNCTION_GET_FUNCS_END(f);
+	while (funcs < funcs_end) {
+		DUK_HEAPHDR_INCREF(thr, (duk_heaphdr *) *funcs);
+		funcs++;
+	}
+}
+
+/* Push a new closure on the stack.
+ *
+ * Note: if fun_temp has NEWENV, i.e. a new lexical and variable declaration
+ * is created when the function is called, only outer_lex_env matters
+ * (outer_var_env is ignored and may or may not be same as outer_lex_env).
+ */
+
+static const duk_uint16_t duk__closure_copy_proplist[] = {
+	/* order: most frequent to least frequent */
+	DUK_STRIDX_INT_VARMAP,
+	DUK_STRIDX_INT_FORMALS,
+	DUK_STRIDX_NAME,
+	DUK_STRIDX_INT_PC2LINE,
+	DUK_STRIDX_FILE_NAME,
+	DUK_STRIDX_INT_SOURCE
+};
+	
+void duk_js_push_closure(duk_hthread *thr,
+                         duk_hcompiledfunction *fun_temp,
+                         duk_hobject *outer_var_env,
+                         duk_hobject *outer_lex_env) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_hcompiledfunction *fun_clos;
+	duk_small_uint_t i;
+	duk_uint_t len_value;
+
+	DUK_ASSERT(fun_temp != NULL);
+	DUK_ASSERT(fun_temp->data != NULL);
+	DUK_ASSERT(fun_temp->funcs != NULL);
+	DUK_ASSERT(fun_temp->bytecode != NULL);
+	DUK_ASSERT(outer_var_env != NULL);
+	DUK_ASSERT(outer_lex_env != NULL);
+
+	duk_push_compiledfunction(ctx);
+	duk_push_hobject(ctx, &fun_temp->obj);  /* -> [ ... closure template ] */
+
+	fun_clos = (duk_hcompiledfunction *) duk_get_hcompiledfunction(ctx, -2);
+	DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION((duk_hobject *) fun_clos));
+	DUK_ASSERT(fun_clos != NULL);
+	DUK_ASSERT(fun_clos->data == NULL);
+	DUK_ASSERT(fun_clos->funcs == NULL);
+	DUK_ASSERT(fun_clos->bytecode == NULL);
+
+	fun_clos->data = fun_temp->data;
+	fun_clos->funcs = fun_temp->funcs;
+	fun_clos->bytecode = fun_temp->bytecode;
+
+	/* Note: all references inside 'data' need to get their refcounts
+	 * upped too.  This is the case because refcounts are decreased
+	 * through every function referencing 'data' independently.
+	 */
+
+	DUK_HBUFFER_INCREF(thr, fun_clos->data);
+	duk__inc_data_inner_refcounts(thr, fun_temp);
+
+	fun_clos->nregs = fun_temp->nregs;
+	fun_clos->nargs = fun_temp->nargs;
+
+	DUK_ASSERT(fun_clos->data != NULL);
+	DUK_ASSERT(fun_clos->funcs != NULL);
+	DUK_ASSERT(fun_clos->bytecode != NULL);
+
+	/* XXX: could also copy from template, but there's no way to have any
+	 * other value here now (used code has no access to the template).
+	 */
+	DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, &fun_clos->obj, thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]);
+
+	/*
+	 *  Init/assert flags, copying them where appropriate.  Some flags
+	 *  (like NEWENV) are processed separately below.
+	 */
+
+	/* XXX: copy flags using a mask */
+
+	DUK_ASSERT(DUK_HOBJECT_HAS_EXTENSIBLE(&fun_clos->obj));
+	DUK_HOBJECT_SET_CONSTRUCTABLE(&fun_clos->obj);  /* Note: not set in template (has no "prototype") */
+	DUK_ASSERT(DUK_HOBJECT_HAS_CONSTRUCTABLE(&fun_clos->obj));
+	DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(&fun_clos->obj));
+	DUK_ASSERT(DUK_HOBJECT_HAS_COMPILEDFUNCTION(&fun_clos->obj));
+	DUK_ASSERT(!DUK_HOBJECT_HAS_NATIVEFUNCTION(&fun_clos->obj));
+	DUK_ASSERT(!DUK_HOBJECT_HAS_THREAD(&fun_clos->obj));
+	/* DUK_HOBJECT_FLAG_ARRAY_PART: don't care */
+	if (DUK_HOBJECT_HAS_STRICT(&fun_temp->obj)) {
+		DUK_HOBJECT_SET_STRICT(&fun_clos->obj);
+	}
+	if (DUK_HOBJECT_HAS_NOTAIL(&fun_temp->obj)) {
+		DUK_HOBJECT_SET_NOTAIL(&fun_clos->obj);
+	}
+	/* DUK_HOBJECT_FLAG_NEWENV: handled below */
+	DUK_ASSERT(!DUK_HOBJECT_HAS_NAMEBINDING(&fun_clos->obj));
+	if (DUK_HOBJECT_HAS_CREATEARGS(&fun_temp->obj)) {
+		DUK_HOBJECT_SET_CREATEARGS(&fun_clos->obj);
+	}
+	DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARRAY(&fun_clos->obj));
+	DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(&fun_clos->obj));
+	DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(&fun_clos->obj));
+
+	/*
+	 *  Setup environment record properties based on the template and
+	 *  its flags.
+	 *
+	 *  If DUK_HOBJECT_HAS_NEWENV(fun_temp) is true, the environment
+	 *  records represent identifiers "outside" the function; the
+	 *  "inner" environment records are created on demand.  Otherwise,
+	 *  the environment records are those that will be directly used
+	 *  (e.g. for declarations).
+	 *
+	 *  _lexenv is always set; _varenv defaults to _lexenv if missing,
+	 *  so _varenv is only set if _lexenv != _varenv.
+	 *
+	 *  This is relatively complex, see doc/identifier-handling.txt.
+	 */
+
+	if (DUK_HOBJECT_HAS_NEWENV(&fun_temp->obj)) {
+		DUK_HOBJECT_SET_NEWENV(&fun_clos->obj);
+
+		if (DUK_HOBJECT_HAS_NAMEBINDING(&fun_temp->obj)) {
+			duk_hobject *proto;
+
+			/*
+			 *  Named function expression, name needs to be bound
+			 *  in an intermediate environment record.  The "outer"
+			 *  lexical/variable environment will thus be:
+			 *
+			 *  a) { funcname: <func>, _prototype: outer_lex_env }
+			 *  b) { funcname: <func>, _prototype:  <globalenv> }  (if outer_lex_env missing)
+			 */
+
+			DUK_ASSERT(duk_has_prop_stridx(ctx, -1, DUK_STRIDX_NAME));  /* required if NAMEBINDING set */
+
+			if (outer_lex_env) {
+				proto = outer_lex_env;
+			} else {
+				proto = thr->builtins[DUK_BIDX_GLOBAL_ENV];
+			}
+
+			/* -> [ ... closure template env ] */
+			(void) duk_push_object_helper_proto(ctx,
+	   		                                    DUK_HOBJECT_FLAG_EXTENSIBLE |
+			                                    DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DECENV),
+			                                    proto);
+
+			/* It's important that duk_def_prop() is a 'raw define' so that any
+			 * properties in an ancestor are never an issue (they should never be
+			 * e.g. non-writable, but just in case).
+			 */
+			duk_get_prop_stridx(ctx, -2, DUK_STRIDX_NAME);       /* -> [ ... closure template env funcname ] */
+			duk_dup(ctx, -4);                                    /* -> [ ... closure template env funcname closure ] */
+			duk_def_prop(ctx, -3, DUK_PROPDESC_FLAGS_NONE);      /* -> [ ... closure template env ] */
+			/* env[funcname] = closure */
+
+			/* [ ... closure template env ] */
+
+			duk_def_prop_stridx(ctx, -3, DUK_STRIDX_INT_LEXENV, DUK_PROPDESC_FLAGS_WC);
+			/* since closure has NEWENV, never define DUK_STRIDX_INT_VARENV, as it
+			 * will be ignored anyway
+			 */
+
+			/* [ ... closure template ] */
+		} else {
+			/*
+			 *  Other cases (function declaration, anonymous function expression,
+			 *  strict direct eval code).  The "outer" environment will be whatever
+			 *  the caller gave us.
+			 */
+
+			duk_push_hobject(ctx, outer_lex_env);  /* -> [ ... closure template env ] */
+			duk_def_prop_stridx(ctx, -3, DUK_STRIDX_INT_LEXENV, DUK_PROPDESC_FLAGS_WC);
+			/* since closure has NEWENV, never define DUK_STRIDX_INT_VARENV, as it
+			 * will be ignored anyway
+			 */
+
+			/* [ ... closure template ] */
+		}
+	} else {
+		/*
+		 *  Function gets no new environment when called.  This is the
+		 *  case for global code, indirect eval code, and non-strict
+		 *  direct eval code.  There is no direct correspondence to the
+		 *  E5 specification, as global/eval code is not exposed as a
+		 *  function.
+		 */
+
+		DUK_ASSERT(!DUK_HOBJECT_HAS_NAMEBINDING(&fun_temp->obj));
+
+		duk_push_hobject(ctx, outer_lex_env);  /* -> [ ... closure template env ] */
+		duk_def_prop_stridx(ctx, -3, DUK_STRIDX_INT_LEXENV, DUK_PROPDESC_FLAGS_WC);
+
+		if (outer_var_env != outer_lex_env) {
+			duk_push_hobject(ctx, outer_var_env);  /* -> [ ... closure template env ] */
+			duk_def_prop_stridx(ctx, -3, DUK_STRIDX_INT_VARENV, DUK_PROPDESC_FLAGS_WC);
+		}
+	}
+#ifdef DUK_USE_DDDPRINT
+	duk_get_prop_stridx(ctx, -2, DUK_STRIDX_INT_VARENV);
+	duk_get_prop_stridx(ctx, -3, DUK_STRIDX_INT_LEXENV);
+	DUK_DDD(DUK_DDDPRINT("closure varenv -> %!ipT, lexenv -> %!ipT",
+	                     (duk_tval *) duk_get_tval(ctx, -2),
+	                     (duk_tval *) duk_get_tval(ctx, -1)));
+	duk_pop_2(ctx);
+#endif
+
+	/*
+	 *  Copy some internal properties directly
+	 *
+	 *  The properties will be writable and configurable, but not enumerable.
+	 */
+
+	/* [ ... closure template ] */
+
+	DUK_DDD(DUK_DDDPRINT("copying properties: closure=%!iT, template=%!iT",
+	                     (duk_tval *) duk_get_tval(ctx, -2),
+	                     (duk_tval *) duk_get_tval(ctx, -1)));
+
+	for (i = 0; i < (duk_small_uint_t) (sizeof(duk__closure_copy_proplist) / sizeof(duk_uint16_t)); i++) {
+		duk_small_int_t stridx = (duk_small_int_t) duk__closure_copy_proplist[i];
+		if (duk_get_prop_stridx(ctx, -1, stridx)) {
+			/* [ ... closure template val ] */
+			DUK_DDD(DUK_DDDPRINT("copying property, stridx=%ld -> found", (long) stridx));
+			duk_def_prop_stridx(ctx, -3, stridx, DUK_PROPDESC_FLAGS_WC);
+		} else {
+			DUK_DDD(DUK_DDDPRINT("copying property, stridx=%ld -> not found", (long) stridx));
+			duk_pop(ctx);
+		}
+	}
+
+	/*
+	 *  "length" maps to number of formals (E5 Section 13.2) for function
+	 *  declarations/expressions (non-bound functions).  Note that 'nargs'
+	 *  is NOT necessarily equal to the number of arguments.
+	 */
+
+	/* [ ... closure template ] */
+
+	len_value = 0;
+
+	/* XXX: use helper for size optimization */
+	if (duk_get_prop_stridx(ctx, -2, DUK_STRIDX_INT_FORMALS)) {
+		/* [ ... closure template formals ] */
+		DUK_ASSERT(duk_has_prop_stridx(ctx, -1, DUK_STRIDX_LENGTH));
+		DUK_ASSERT(duk_get_length(ctx, -1) <= DUK_UINT_MAX);  /* formal arg limits */
+		len_value = (duk_uint_t) duk_get_length(ctx, -1);
+	} else {
+		/* XXX: what to do if _formals is not empty but compiler has
+		 * optimized it away -- read length from an explicit property
+		 * then?
+		 */
+	}
+	duk_pop(ctx);
+
+	duk_push_uint(ctx, len_value);  /* [ ... closure template len_value ] */
+	duk_def_prop_stridx(ctx, -3, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_NONE);
+
+	/*
+	 *  "prototype" is, by default, a fresh object with the "constructor"
+	 *  property.
+	 *
+	 *  Note that this creates a circular reference for every function
+	 *  instance (closure) which prevents refcount-based collection of
+	 *  function instances.
+	 *
+	 *  XXX: Try to avoid creating the default prototype object, because
+	 *  many functions are not used as constructors and the default
+	 *  prototype is unnecessary.  Perhaps it could be created on-demand
+	 *  when it is first accessed?
+	 */
+
+	/* [ ... closure template ] */
+
+	duk_push_object(ctx);  /* -> [ ... closure template newobj ] */
+	duk_dup(ctx, -3);          /* -> [ ... closure template newobj closure ] */
+	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_CONSTRUCTOR, DUK_PROPDESC_FLAGS_WC);  /* -> [ ... closure template newobj ] */
+	duk_compact(ctx, -1);  /* compact the prototype */
+	duk_def_prop_stridx(ctx, -3, DUK_STRIDX_PROTOTYPE, DUK_PROPDESC_FLAGS_W);     /* -> [ ... closure template ] */
+
+	/*
+	 *  "arguments" and "caller" must be mapped to throwers for strict
+	 *  mode and bound functions (E5 Section 15.3.5).
+	 *
+	 *  XXX: This is expensive to have for every strict function instance.
+	 *  Try to implement as virtual properties or on-demand created properties.
+	 */
+
+	/* [ ... closure template ] */
+
+	if (DUK_HOBJECT_HAS_STRICT(&fun_clos->obj)) {
+		duk_def_prop_stridx_thrower(ctx, -2, DUK_STRIDX_CALLER, DUK_PROPDESC_FLAGS_NONE);
+		duk_def_prop_stridx_thrower(ctx, -2, DUK_STRIDX_LC_ARGUMENTS, DUK_PROPDESC_FLAGS_NONE);
+	} else {
+#ifdef DUK_USE_NONSTD_FUNC_CALLER_PROPERTY
+		DUK_DDD(DUK_DDDPRINT("function is non-strict and non-standard 'caller' property in use, add initial 'null' value"));
+		duk_push_null(ctx);
+		duk_def_prop_stridx(ctx, -3, DUK_STRIDX_CALLER, DUK_PROPDESC_FLAGS_NONE);
+#else
+		DUK_DDD(DUK_DDDPRINT("function is non-strict and non-standard 'caller' property not used"));
+#endif
+	}
+
+	/*
+	 *  "name" is a non-standard property found in at least V8, Rhino, smjs.
+	 *  For Rhino and smjs it is non-writable, non-enumerable, and non-configurable;
+	 *  for V8 it is writable, non-enumerable, non-configurable.  It is also defined
+	 *  for an anonymous function expression in which case the value is an empty string.
+	 *  We could also leave name 'undefined' for anonymous functions but that would
+	 *  differ from behavior of other engines, so use an empty string.
+	 *
+	 *  XXX: make optional?  costs something per function.
+	 */
+
+	/* [ ... closure template ] */
+
+	if (duk_get_prop_stridx(ctx, -1, DUK_STRIDX_NAME)) {
+		/* [ ... closure template name ] */
+		DUK_ASSERT(duk_is_string(ctx, -1));
+	} else {
+		/* [ ... closure template undefined ] */
+		duk_pop(ctx);
+		duk_push_hstring_stridx(ctx, DUK_STRIDX_EMPTY_STRING);
+	}
+	duk_def_prop_stridx(ctx, -3, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_NONE);  /* -> [ ... closure template ] */
+
+	/*
+	 *  Compact the closure, in most cases no properties will be added later.
+	 *  Also, without this the closures end up having unused property slots
+	 *  (e.g. in Duktape 0.9.0, 8 slots would be allocated and only 7 used).
+	 *  A better future solution would be to allocate the closure directly
+	 *  to correct size (and setup the properties directly without going
+	 *  through the API).
+	 */
+
+	duk_compact(ctx, -2);
+
+	/*
+	 *  Some assertions (E5 Section 13.2).
+	 */
+
+	DUK_ASSERT(DUK_HOBJECT_GET_CLASS_NUMBER(&fun_clos->obj) == DUK_HOBJECT_CLASS_FUNCTION);
+	DUK_ASSERT(fun_clos->obj.prototype == thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]);
+	DUK_ASSERT(DUK_HOBJECT_HAS_EXTENSIBLE(&fun_clos->obj));
+	DUK_ASSERT(duk_has_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH) != 0);
+	DUK_ASSERT(duk_has_prop_stridx(ctx, -2, DUK_STRIDX_PROTOTYPE) != 0);
+	DUK_ASSERT(duk_has_prop_stridx(ctx, -2, DUK_STRIDX_NAME) != 0);  /* non-standard */
+	DUK_ASSERT(!DUK_HOBJECT_HAS_STRICT(&fun_clos->obj) ||
+	           duk_has_prop_stridx(ctx, -2, DUK_STRIDX_CALLER) != 0);
+	DUK_ASSERT(!DUK_HOBJECT_HAS_STRICT(&fun_clos->obj) ||
+	           duk_has_prop_stridx(ctx, -2, DUK_STRIDX_LC_ARGUMENTS) != 0);
+
+	/*
+	 *  Finish
+	 */
+	
+	/* [ ... closure template ] */
+
+	DUK_DDD(DUK_DDDPRINT("created function instance: template=%!iT -> closure=%!iT",
+	                     (duk_tval *) duk_get_tval(ctx, -1),
+	                     (duk_tval *) duk_get_tval(ctx, -2)));
+
+	duk_pop(ctx);
+
+	/* [ ... closure ] */
+}
+
+/*
+ *  Delayed activation environment record initialization (for functions
+ *  with NEWENV).
+ *
+ *  The non-delayed initialization is handled by duk_handle_call().
+ */
+
+/* shared helper */
+duk_hobject *duk_create_activation_environment_record(duk_hthread *thr,
+                                                      duk_hobject *func,
+                                                      duk_size_t idx_bottom) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_hobject *env;
+	duk_hobject *parent;
+	duk_tval *tv;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(func != NULL);
+
+	tv = duk_hobject_find_existing_entry_tval_ptr(func, DUK_HTHREAD_STRING_INT_LEXENV(thr));
+	if (tv) {
+		DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
+		DUK_ASSERT(DUK_HOBJECT_IS_ENV(DUK_TVAL_GET_OBJECT(tv)));
+		parent = DUK_TVAL_GET_OBJECT(tv);
+	} else {
+		parent = thr->builtins[DUK_BIDX_GLOBAL_ENV];
+	}
+
+	(void) duk_push_object_helper(ctx,
+	                              DUK_HOBJECT_FLAG_EXTENSIBLE |
+	                              DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DECENV),
+	                              -1);  /* no prototype, updated below */
+	env = duk_require_hobject(ctx, -1);
+	DUK_ASSERT(env != NULL);
+	DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, env, parent);  /* parent env is the prototype */
+
+	/* open scope information, for compiled functions only */
+
+	if (DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) {
+		duk_push_hthread(ctx, thr);
+		duk_def_prop_stridx_wec(ctx, -2, DUK_STRIDX_INT_THREAD);
+		duk_push_hobject(ctx, func);
+		duk_def_prop_stridx_wec(ctx, -2, DUK_STRIDX_INT_CALLEE);
+		duk_push_size_t(ctx, idx_bottom);
+		duk_def_prop_stridx_wec(ctx, -2, DUK_STRIDX_INT_REGBASE);
+	}
+
+	return env;
+}
+
+void duk_js_init_activation_environment_records_delayed(duk_hthread *thr,
+                                                        duk_activation *act) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_hobject *func;
+	duk_hobject *env;
+
+	func = act->func;
+	DUK_ASSERT(func != NULL);
+	DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));  /* bound functions are never in act->func */
+
+	/*
+	 *  Delayed initialization only occurs for 'NEWENV' functions.
+	 */
+
+	DUK_ASSERT(DUK_HOBJECT_HAS_NEWENV(func));
+	DUK_ASSERT(act->lex_env == NULL);
+	DUK_ASSERT(act->var_env == NULL);
+
+	env = duk_create_activation_environment_record(thr, func, act->idx_bottom);
+	DUK_ASSERT(env != NULL);
+
+	DUK_DDD(DUK_DDDPRINT("created delayed fresh env: %!ipO", (duk_heaphdr *) env));
+#ifdef DUK_USE_DDDPRINT
+	{
+		duk_hobject *p = env;
+		while (p) {
+			DUK_DDD(DUK_DDDPRINT("  -> %!ipO", (duk_heaphdr *) p));
+			p = p->prototype;
+		}
+	}
+#endif
+
+	act->lex_env = env;
+	act->var_env = env;
+	DUK_HOBJECT_INCREF(thr, env);  /* XXX: incref by count (here 2 times) */
+	DUK_HOBJECT_INCREF(thr, env);
+
+	duk_pop(ctx);
+}
+
+/*
+ *  Closing environment records.
+ *
+ *  The environment record MUST be closed with the thread where its activation
+ *  is.  In other words (if 'env' is open):
+ *
+ *    - 'thr' must match _env.thread
+ *    - 'func' must match _env.callee
+ *    - 'regbase' must match _env.regbase
+ *
+ *  These are not looked up from the env to minimize code size.
+ *
+ *  XXX: should access the own properties directly instead of using the API
+ */
+
+void duk_js_close_environment_record(duk_hthread *thr, duk_hobject *env, duk_hobject *func, duk_size_t regbase) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_uint_fast32_t i;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(env != NULL);
+	DUK_ASSERT(func != NULL);
+
+	if (!DUK_HOBJECT_IS_DECENV(env) || DUK_HOBJECT_HAS_ENVRECCLOSED(env)) {
+		DUK_DDD(DUK_DDDPRINT("environment record not a declarative record, "
+		                     "or already closed: %!iO",
+		                     (duk_heaphdr *) env));
+		return;
+	}
+
+	DUK_DDD(DUK_DDDPRINT("closing environment record: %!iO, func: %!iO, regbase: %ld",
+	                     (duk_heaphdr *) env, (duk_heaphdr *) func, (long) regbase));
+
+	duk_push_hobject(ctx, env);
+
+	/* assertions: env must be closed in the same thread as where it runs */
+#ifdef DUK_USE_ASSERTIONS
+	{
+		/* [... env] */
+
+		if (duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_CALLEE)) {
+			DUK_ASSERT(duk_is_object(ctx, -1));
+			DUK_ASSERT(duk_get_hobject(ctx, -1) == (duk_hobject *) func);
+		}
+		duk_pop(ctx);
+
+		if (duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_THREAD)) {
+			DUK_ASSERT(duk_is_object(ctx, -1));
+			DUK_ASSERT(duk_get_hobject(ctx, -1) == (duk_hobject *) thr);
+		}
+		duk_pop(ctx);
+
+		if (duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_REGBASE)) {
+			DUK_ASSERT(duk_is_number(ctx, -1));
+			DUK_ASSERT(duk_get_number(ctx, -1) == (double) regbase);
+		}
+		duk_pop(ctx);
+
+		/* [... env] */
+	}
+#endif
+
+	if (DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) {
+		duk_hobject *varmap;
+		duk_hstring *key;
+		duk_tval *tv;
+		duk_uint_t regnum;
+
+		/* XXX: additional conditions when to close variables? we don't want to do it
+		 * unless the environment may have "escaped" (referenced in a function closure).
+		 * With delayed environments, the existence is probably good enough of a check.
+		 */
+
+		/* XXX: any way to detect faster whether something needs to be closed?
+		 * We now look up _callee and then skip the rest.
+		 */
+
+		/* Note: we rely on the _varmap having a bunch of nice properties, like:
+		 *  - being compacted and unmodified during this process
+		 *  - not containing an array part
+		 *  - having correct value types
+		 */
+
+		/* [... env] */
+
+		if (!duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_CALLEE)) {
+			DUK_DDD(DUK_DDDPRINT("env has no callee property, nothing to close; re-delete the control properties just in case"));
+			duk_pop(ctx);
+			goto skip_varmap;
+		}
+
+		/* [... env callee] */
+
+		if (!duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_VARMAP)) {
+			DUK_DDD(DUK_DDDPRINT("callee has no varmap property, nothing to close; delete the control properties"));
+			duk_pop_2(ctx);
+			goto skip_varmap;
+		}
+		varmap = duk_require_hobject(ctx, -1);
+		DUK_ASSERT(varmap != NULL);
+
+		DUK_DDD(DUK_DDDPRINT("varmap: %!O", (duk_heaphdr *) varmap));
+
+		/* [... env callee varmap] */
+
+		DUK_DDD(DUK_DDDPRINT("copying bound register values, %ld bound regs", (long) varmap->e_used));
+
+		for (i = 0; i < (duk_uint_fast32_t) varmap->e_used; i++) {
+			key = DUK_HOBJECT_E_GET_KEY(varmap, i);
+			DUK_ASSERT(key != NULL);   /* assume keys are compacted */
+
+			DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(varmap, i));  /* assume plain values */
+
+			tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(varmap, i);
+			DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));  /* assume value is a number */
+			regnum = (duk_uint_t) DUK_TVAL_GET_NUMBER(tv);
+			DUK_ASSERT_DISABLE(regnum >= 0);  /* unsigned */
+			DUK_ASSERT(regnum < ((duk_hcompiledfunction *) func)->nregs);  /* regnum is sane */
+			DUK_ASSERT(thr->valstack + regbase + regnum >= thr->valstack);
+			DUK_ASSERT(thr->valstack + regbase + regnum < thr->valstack_top);
+
+			/* XXX: slightly awkward */
+			duk_push_hstring(ctx, key);
+			duk_push_tval(ctx, thr->valstack + regbase + regnum);
+			DUK_DDD(DUK_DDDPRINT("closing identifier '%s' -> reg %ld, value %!T",
+			                     (const char *) duk_require_string(ctx, -2),
+			                     (long) regnum,
+			                     (duk_tval *) duk_get_tval(ctx, -1)));
+
+			/* [... env callee varmap key val] */
+
+			/* if property already exists, overwrites silently */
+			duk_def_prop(ctx, -5, DUK_PROPDESC_FLAGS_WE);  /* writable but not deletable */
+		}
+
+		duk_pop_2(ctx);
+
+		/* [... env] */
+	}
+
+ skip_varmap:
+
+	/* [... env] */
+
+	duk_del_prop_stridx(ctx, -1, DUK_STRIDX_INT_CALLEE);
+	duk_del_prop_stridx(ctx, -1, DUK_STRIDX_INT_THREAD);
+	duk_del_prop_stridx(ctx, -1, DUK_STRIDX_INT_REGBASE);
+
+	duk_pop(ctx);
+
+	DUK_HOBJECT_SET_ENVRECCLOSED(env);
+
+	DUK_DDD(DUK_DDDPRINT("environment record after being closed: %!O",
+	                     (duk_heaphdr *) env));
+}
+
+/*
+ *  GETIDREF: a GetIdentifierReference-like helper.
+ *
+ *  Provides a parent traversing lookup and a single level lookup
+ *  (for HasBinding).
+ *
+ *  Instead of returning the value, returns a bunch of values allowing
+ *  the caller to read, write, or delete the binding.  Value pointers
+ *  are duk_tval pointers which can be mutated directly as long as
+ *  refcounts are properly updated.  Note that any operation which may
+ *  reallocate valstacks or compact objects may invalidate the returned
+ *  duk_tval (but not object) pointers, so caller must be very careful.
+ *
+ *  If starting environment record 'env' is given, 'act' is ignored.
+ *  However, if 'env' is NULL, the caller may identify, in 'act', an
+ *  activation which hasn't had its declarative environment initialized
+ *  yet.  The activation registers are then looked up, and its parent
+ *  traversed normally.
+ *
+ *  The 'out' structure values are only valid if the function returns
+ *  success (non-zero).
+ */
+
+/* lookup name from an open declarative record's registers */
+static duk_bool_t duk__getid_open_decl_env_regs(duk_hthread *thr,
+                                                duk_hstring *name,
+                                                duk_hobject *env,
+                                                duk__id_lookup_result *out) {
+	duk_hthread *env_thr;
+	duk_hobject *env_func;
+	duk_size_t env_regbase;
+	duk_hobject *varmap;
+	duk_tval *tv;
+	duk_size_t reg_rel;
+	duk_size_t idx;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(name != NULL);
+	DUK_ASSERT(env != NULL);
+	DUK_ASSERT(out != NULL);
+
+	DUK_ASSERT(DUK_HOBJECT_IS_DECENV(env));
+
+	tv = duk_hobject_find_existing_entry_tval_ptr(env, DUK_HTHREAD_STRING_INT_CALLEE(thr));
+	if (!tv) {
+		/* env is closed, should be missing _callee, _thread, _regbase */
+		DUK_ASSERT(duk_hobject_find_existing_entry_tval_ptr(env, DUK_HTHREAD_STRING_INT_CALLEE(thr)) == NULL);
+		DUK_ASSERT(duk_hobject_find_existing_entry_tval_ptr(env, DUK_HTHREAD_STRING_INT_THREAD(thr)) == NULL);
+		DUK_ASSERT(duk_hobject_find_existing_entry_tval_ptr(env, DUK_HTHREAD_STRING_INT_REGBASE(thr)) == NULL);
+		return 0;
+	}
+
+	DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
+	DUK_ASSERT(DUK_TVAL_GET_OBJECT(tv) != NULL);
+	DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(DUK_TVAL_GET_OBJECT(tv)));
+	env_func = DUK_TVAL_GET_OBJECT(tv);
+	DUK_ASSERT(env_func != NULL);
+
+	tv = duk_hobject_find_existing_entry_tval_ptr(env_func, DUK_HTHREAD_STRING_INT_VARMAP(thr));
+	if (!tv) {
+		return 0;
+	}
+	DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
+	varmap = DUK_TVAL_GET_OBJECT(tv);
+	DUK_ASSERT(varmap != NULL);
+
+	tv = duk_hobject_find_existing_entry_tval_ptr(varmap, name);
+	if (!tv) {
+		return 0;
+	}
+	DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
+	reg_rel = (duk_size_t) DUK_TVAL_GET_NUMBER(tv);
+	DUK_ASSERT_DISABLE(reg_rel >= 0);  /* unsigned */
+	DUK_ASSERT(reg_rel < ((duk_hcompiledfunction *) env_func)->nregs);
+
+	tv = duk_hobject_find_existing_entry_tval_ptr(env, DUK_HTHREAD_STRING_INT_THREAD(thr));
+	DUK_ASSERT(tv != NULL);
+	DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
+	DUK_ASSERT(DUK_TVAL_GET_OBJECT(tv) != NULL);
+	DUK_ASSERT(DUK_HOBJECT_IS_THREAD(DUK_TVAL_GET_OBJECT(tv)));
+	env_thr = (duk_hthread *) DUK_TVAL_GET_OBJECT(tv);
+	DUK_ASSERT(env_thr != NULL);
+
+	/* Note: env_thr != thr is quite possible and normal, so careful
+	 * with what thread is used for valstack lookup.
+	 */
+
+	tv = duk_hobject_find_existing_entry_tval_ptr(env, DUK_HTHREAD_STRING_INT_REGBASE(thr));
+	DUK_ASSERT(tv != NULL);
+	DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
+	env_regbase = (duk_size_t) DUK_TVAL_GET_NUMBER(tv);
+
+	idx = env_regbase + reg_rel;
+	tv = env_thr->valstack + idx;
+	DUK_ASSERT(tv >= env_thr->valstack && tv < env_thr->valstack_end);  /* XXX: more accurate? */
+
+	out->value = tv;
+	out->attrs = DUK_PROPDESC_FLAGS_W;  /* registers are mutable, non-deletable */
+	out->this_binding = NULL;  /* implicit this value always undefined for
+	                            * declarative environment records.
+	                            */
+	out->env = env;
+	out->holder = NULL;
+
+	return 1;
+}
+
+/* lookup name from current activation record's functions' registers */
+static duk_bool_t duk__getid_activation_regs(duk_hthread *thr,
+                                             duk_hstring *name,
+                                             duk_activation *act,
+                                             duk__id_lookup_result *out) {
+	duk_tval *tv;
+	duk_hobject *func;
+	duk_hobject *varmap;
+	duk_size_t reg_rel;
+	duk_size_t idx;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(name != NULL);
+	DUK_ASSERT(act != NULL);
+	DUK_ASSERT(out != NULL);
+
+	func = act->func;
+	DUK_ASSERT(func != NULL);
+	DUK_ASSERT(DUK_HOBJECT_HAS_NEWENV(func));
+
+	if (!DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) {
+		return 0;
+	}
+
+	tv = duk_hobject_find_existing_entry_tval_ptr(func, DUK_HTHREAD_STRING_INT_VARMAP(thr));
+	if (!tv) {
+		return 0;
+	}
+	DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
+	varmap = DUK_TVAL_GET_OBJECT(tv);
+	DUK_ASSERT(varmap != NULL);
+
+	tv = duk_hobject_find_existing_entry_tval_ptr(varmap, name);
+	if (!tv) {
+		return 0;
+	}
+	DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
+	reg_rel = (duk_size_t) DUK_TVAL_GET_NUMBER(tv);
+	DUK_ASSERT_DISABLE(reg_rel >= 0);
+	DUK_ASSERT(reg_rel < ((duk_hcompiledfunction *) func)->nregs);
+
+	idx = act->idx_bottom + reg_rel;
+	DUK_ASSERT(idx >= act->idx_bottom);
+	tv = thr->valstack + idx;
+
+	out->value = tv;
+	out->attrs = DUK_PROPDESC_FLAGS_W;  /* registers are mutable, non-deletable */
+	out->this_binding = NULL;  /* implicit this value always undefined for
+	                            * declarative environment records.
+	                            */
+	out->env = NULL;
+	out->holder = NULL;
+
+	return 1;
+}
+
+static duk_bool_t duk__get_identifier_reference(duk_hthread *thr,
+                                                duk_hobject *env,
+                                                duk_hstring *name,
+                                                duk_activation *act,
+                                                duk_bool_t parents,
+                                                duk__id_lookup_result *out) {
+	duk_tval *tv;
+	duk_uint_t sanity;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(env != NULL || act != NULL);
+	DUK_ASSERT(name != NULL);
+	DUK_ASSERT(out != NULL);
+
+	DUK_ASSERT(!env || DUK_HOBJECT_IS_ENV(env));
+	DUK_ASSERT(!env || !DUK_HOBJECT_HAS_ARRAY_PART(env));
+
+	/*
+	 *  Conceptually, we look for the identifier binding by starting from
+	 *  'env' and following to chain of environment records (represented
+	 *  by the prototype chain).
+	 *
+	 *  If 'env' is NULL, the current activation does not yet have an
+	 *  allocated declarative environment record; this should be treated
+	 *  exactly as if the environment record existed but had no bindings
+	 *  other than register bindings.
+	 *
+	 *  Note: we assume that with the DUK_HOBJECT_FLAG_NEWENV cleared
+	 *  the environment will always be initialized immediately; hence
+	 *  a NULL 'env' should only happen with the flag set.  This is the
+	 *  case for: (1) function calls, and (2) strict, direct eval calls.
+	 */
+
+	if (env == NULL && act != NULL) {
+		duk_hobject *func;
+
+		DUK_DDD(DUK_DDDPRINT("duk__get_identifier_reference: env is NULL, activation is non-NULL -> "
+		                     "delayed env case, look up activation regs first"));
+
+		/*
+		 *  Try registers
+		 */
+
+		if (duk__getid_activation_regs(thr, name, act, out)) {
+			DUK_DDD(DUK_DDDPRINT("duk__get_identifier_reference successful: "
+			                     "name=%!O -> value=%!T, attrs=%ld, this=%!T, env=%!O, holder=%!O "
+			                     "(found from register bindings when env=NULL)",
+			                     (duk_heaphdr *) name, (duk_tval *) out->value,
+			                     (long) out->attrs, (duk_tval *) out->this_binding,
+			                     (duk_heaphdr *) out->env, (duk_heaphdr *) out->holder));
+			return 1;
+		}
+
+		DUK_DDD(DUK_DDDPRINT("not found in current activation regs"));
+
+		/*
+		 *  Not found in registers, proceed to the parent record.
+		 *  Here we need to determine what the parent would be,
+		 *  if 'env' was not NULL (i.e. same logic as when initializing
+		 *  the record).
+		 *
+		 *  Note that environment initialization is only deferred when
+		 *  DUK_HOBJECT_HAS_NEWENV is set, and this only happens for:
+		 *    - Function code
+		 *    - Strict eval code
+		 *
+		 *  We only need to check _lexenv here; _varenv exists only if it
+		 *  differs from _lexenv (and thus _lexenv will also be present).
+		 */
+
+		if (!parents) {
+			DUK_DDD(DUK_DDDPRINT("duk__get_identifier_reference failed, no parent traversal "
+			                     "(not found from register bindings when env=NULL)"));
+			goto fail_not_found;
+		}
+
+		func = act->func;
+		DUK_ASSERT(func != NULL);
+		DUK_ASSERT(DUK_HOBJECT_HAS_NEWENV(func));
+
+		tv = duk_hobject_find_existing_entry_tval_ptr(func, DUK_HTHREAD_STRING_INT_LEXENV(thr));
+		if (tv) {
+			DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
+			env = DUK_TVAL_GET_OBJECT(tv);
+		} else {
+			DUK_ASSERT(duk_hobject_find_existing_entry_tval_ptr(func, DUK_HTHREAD_STRING_INT_VARENV(thr)) == NULL);
+			env = thr->builtins[DUK_BIDX_GLOBAL_ENV];
+		}
+
+		DUK_DDD(DUK_DDDPRINT("continue lookup from env: %!iO",
+		                     (duk_heaphdr *) env));
+	}
+
+	/*
+	 *  Prototype walking starting from 'env'.
+	 *
+	 *  ('act' is not needed anywhere here.)
+	 */
+
+	sanity = DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY;
+	while (env != NULL) {
+		duk_tval *tv;
+		duk_small_int_t cl;
+		duk_int_t attrs;
+
+		DUK_DDD(DUK_DDDPRINT("duk__get_identifier_reference, name=%!O, considering env=%p -> %!iO",
+		                     (duk_heaphdr *) name,
+		                     (void *) env,
+		                     (duk_heaphdr *) env));
+
+		DUK_ASSERT(env != NULL);
+		DUK_ASSERT(DUK_HOBJECT_IS_ENV(env));
+		DUK_ASSERT(!DUK_HOBJECT_HAS_ARRAY_PART(env));
+
+		cl = DUK_HOBJECT_GET_CLASS_NUMBER(env);
+		DUK_ASSERT(cl == DUK_HOBJECT_CLASS_OBJENV || cl == DUK_HOBJECT_CLASS_DECENV);
+		if (cl == DUK_HOBJECT_CLASS_DECENV) {
+			/*
+			 *  Declarative environment record.
+			 *
+			 *  Identifiers can never be stored in ancestors and are
+			 *  always plain values, so we can use an internal helper
+			 *  and access the value directly with an duk_tval ptr.
+			 *
+			 *  A closed environment is only indicated by it missing
+			 *  the "book-keeping" properties required for accessing
+			 *  register-bound variables.
+			 */
+
+			if (DUK_HOBJECT_HAS_ENVRECCLOSED(env)) {
+				/* already closed */
+				goto skip_regs;
+			}
+
+			if (duk__getid_open_decl_env_regs(thr, name, env, out)) {
+				DUK_DDD(DUK_DDDPRINT("duk__get_identifier_reference successful: "
+				                     "name=%!O -> value=%!T, attrs=%ld, this=%!T, env=%!O, holder=%!O "
+				                     "(declarative environment record, scope open, found in regs)",
+				                     (duk_heaphdr *) name, (duk_tval *) out->value,
+				                     (long) out->attrs, (duk_tval *) out->this_binding,
+				                     (duk_heaphdr *) out->env, (duk_heaphdr *) out->holder));
+				return 1;
+			}
+		 skip_regs:
+
+			tv = duk_hobject_find_existing_entry_tval_ptr_and_attrs(env, name, &attrs);
+			if (tv) {
+				out->value = tv;
+				out->attrs = attrs;
+				out->this_binding = NULL;  /* implicit this value always undefined for
+				                            * declarative environment records.
+				                            */
+				out->env = env;
+				out->holder = env;
+
+				DUK_DDD(DUK_DDDPRINT("duk__get_identifier_reference successful: "
+				                     "name=%!O -> value=%!T, attrs=%ld, this=%!T, env=%!O, holder=%!O "
+				                     "(declarative environment record, found in properties)",
+				                     (duk_heaphdr *) name, (duk_tval *) out->value,
+				                     (long) out->attrs, (duk_tval *) out->this_binding,
+				                     (duk_heaphdr *) out->env, (duk_heaphdr *) out->holder));
+				return 1;
+			}
+		} else {
+			/*
+			 *  Object environment record.
+			 *
+			 *  Binding (target) object is an external, uncontrolled object.
+			 *  Identifier may be bound in an ancestor property, and may be
+			 *  an accessor.
+			 */
+
+			/* XXX: we could save space by using _target OR _this.  If _target, assume
+			 * this binding is undefined.  If _this, assumes this binding is _this, and
+			 * target is also _this.  One property would then be enough.
+			 */
+
+			duk_hobject *target;
+
+			DUK_ASSERT(cl == DUK_HOBJECT_CLASS_OBJENV);
+
+			tv = duk_hobject_find_existing_entry_tval_ptr(env, DUK_HTHREAD_STRING_INT_TARGET(thr));
+			DUK_ASSERT(tv != NULL);
+			DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
+			target = DUK_TVAL_GET_OBJECT(tv);
+			DUK_ASSERT(target != NULL);
+
+			/* Note: we must traverse the prototype chain, so use an actual
+			 * hasprop call here.  The property may also be an accessor, so
+			 * we can't get an duk_tval pointer here.
+			 *
+			 * out->holder is NOT set to the actual duk_hobject where the
+			 * property is found, but rather the target object.
+			 */
+
+			if (duk_hobject_hasprop_raw(thr, target, name)) {
+				out->value = NULL;  /* can't get value, may be accessor */
+				out->attrs = 0;     /* irrelevant when out->value == NULL */
+				tv = duk_hobject_find_existing_entry_tval_ptr(env, DUK_HTHREAD_STRING_INT_THIS(thr));
+				out->this_binding = tv;  /* may be NULL */
+				out->env = env;
+				out->holder = target;
+
+				DUK_DDD(DUK_DDDPRINT("duk__get_identifier_reference successful: "
+				                     "name=%!O -> value=%!T, attrs=%ld, this=%!T, env=%!O, holder=%!O "
+				                     "(object environment record)",
+				                     (duk_heaphdr *) name, (duk_tval *) out->value,
+				                     (long) out->attrs, (duk_tval *) out->this_binding,
+				                     (duk_heaphdr *) out->env, (duk_heaphdr *) out->holder));
+				return 1;
+			}
+		}
+
+		if (!parents) {
+			DUK_DDD(DUK_DDDPRINT("duk__get_identifier_reference failed, no parent traversal "
+			                     "(not found from first traversed env)"));
+			goto fail_not_found;
+		}
+
+                if (sanity-- == 0) {
+                        DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, DUK_STR_PROTOTYPE_CHAIN_LIMIT);
+                }
+		env = env->prototype;
+	};
+
+	/*
+	 *  Not found (even in global object)
+	 */
+
+ fail_not_found:
+	return 0;
+}
+
+/*
+ *  HASVAR: check identifier binding from a given environment record
+ *  without traversing its parents.
+ *
+ *  This primitive is not exposed to user code as such, but is used
+ *  internally for e.g. declaration binding instantiation.
+ *
+ *  See E5 Sections:
+ *    10.2.1.1.1 HasBinding(N)
+ *    10.2.1.2.1 HasBinding(N)
+ *
+ *  Note: strictness has no bearing on this check.  Hence we don't take
+ *  a 'strict' parameter.
+ */
+
+duk_bool_t duk_js_hasvar_envrec(duk_hthread *thr,
+                                duk_hobject *env,
+                                duk_hstring *name) {
+	duk__id_lookup_result ref;
+	duk_bool_t parents;
+
+	DUK_DDD(DUK_DDDPRINT("hasvar: thr=%p, env=%p, name=%!O "
+	                     "(env -> %!dO)",
+	                     (void *) thr, (void *) env, (duk_heaphdr *) name,
+	                     (duk_heaphdr *) env));
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(env != NULL);
+	DUK_ASSERT(name != NULL);
+
+        DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR(env);
+        DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR(name);
+
+	DUK_ASSERT(DUK_HOBJECT_IS_ENV(env));
+	DUK_ASSERT(!DUK_HOBJECT_HAS_ARRAY_PART(env));
+
+	/* lookup results is ignored */
+	parents = 0;
+	return duk__get_identifier_reference(thr, env, name, NULL, parents, &ref);
+}
+
+/*
+ *  GETVAR
+ *
+ *  See E5 Sections:
+ *    11.1.2 Identifier Reference
+ *    10.3.1 Identifier Resolution
+ *    11.13.1 Simple Assignment  [example of where the Reference is GetValue'd]
+ *    8.7.1 GetValue (V)
+ *    8.12.1 [[GetOwnProperty]] (P)
+ *    8.12.2 [[GetProperty]] (P)
+ *    8.12.3 [[Get]] (P)
+ *
+ *  If 'throw' is true, always leaves two values on top of stack: [val this].
+ *
+ *  If 'throw' is false, returns 0 if identifier cannot be resolved, and the
+ *  stack will be unaffected in this case.  If identifier is resolved, returns
+ *  1 and leaves [val this] on top of stack.
+ *
+ *  Note: the 'strict' flag of a reference returned by GetIdentifierReference
+ *  is ignored by GetValue.  Hence we don't take a 'strict' parameter.
+ *
+ *  The 'throw' flag is needed for implementing 'typeof' for an unreferenced
+ *  identifier.  An unreference identifier in other contexts generates a
+ *  ReferenceError.
+ */
+
+static duk_bool_t duk__getvar_helper(duk_hthread *thr,
+                                     duk_hobject *env,
+                                     duk_activation *act,
+                                     duk_hstring *name,
+                                     duk_bool_t throw_flag) {
+	duk_context *ctx = (duk_context *) thr;
+	duk__id_lookup_result ref;
+	duk_tval tv_tmp_obj;
+	duk_tval tv_tmp_key;
+	duk_bool_t parents;
+
+	DUK_DDD(DUK_DDDPRINT("getvar: thr=%p, env=%p, act=%p, name=%!O "
+	                     "(env -> %!dO)",
+	                     (void *) thr, (void *) env, (void *) act,
+	                     (duk_heaphdr *) name, (duk_heaphdr *) env));
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(name != NULL);
+	/* env and act may be NULL */
+
+        DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR(env);
+        DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR(name);
+
+	parents = 1;     /* follow parent chain */
+	if (duk__get_identifier_reference(thr, env, name, act, parents, &ref)) {
+		if (ref.value) {
+			DUK_ASSERT(ref.this_binding == NULL);  /* always for register bindings */
+			duk_push_tval(ctx, ref.value);
+			duk_push_undefined(ctx);
+		} else {
+			DUK_ASSERT(ref.holder != NULL);
+
+			/* Note: getprop may invoke any getter and invalidate any
+			 * duk_tval pointers, so this must be done first.
+			 */
+
+			if (ref.this_binding) {
+				duk_push_tval(ctx, ref.this_binding);
+			} else {
+				duk_push_undefined(ctx);
+			}
+
+			DUK_TVAL_SET_OBJECT(&tv_tmp_obj, ref.holder);
+			DUK_TVAL_SET_STRING(&tv_tmp_key, name);
+			(void) duk_hobject_getprop(thr, &tv_tmp_obj, &tv_tmp_key);  /* [this value] */
+
+			/* ref.value, ref.this.binding invalidated here by getprop call */
+
+			duk_insert(ctx, -2);  /* [this value] -> [value this] */
+		}
+
+		return 1;
+	} else {
+		if (throw_flag) {
+			DUK_ERROR(thr, DUK_ERR_REFERENCE_ERROR,
+			          "identifier '%s' undefined",
+			          (const char *) DUK_HSTRING_GET_DATA(name));
+		}
+
+		return 0;
+	}
+}
+
+duk_bool_t duk_js_getvar_envrec(duk_hthread *thr,
+                                duk_hobject *env,
+                                duk_hstring *name,
+                                duk_bool_t throw_flag) {
+	return duk__getvar_helper(thr, env, NULL, name, throw_flag);
+}
+
+duk_bool_t duk_js_getvar_activation(duk_hthread *thr,
+                                    duk_activation *act,
+                                    duk_hstring *name,
+                                    duk_bool_t throw_flag) {
+	DUK_ASSERT(act != NULL);
+	return duk__getvar_helper(thr, act->lex_env, act, name, throw_flag);
+}
+
+/*
+ *  PUTVAR
+ *
+ *  See E5 Sections:
+ *    11.1.2 Identifier Reference
+ *    10.3.1 Identifier Resolution
+ *    11.13.1 Simple Assignment  [example of where the Reference is PutValue'd]
+ *    8.7.2 PutValue (V,W)  [see especially step 3.b, undefined -> automatic global in non-strict mode]
+ *    8.12.4 [[CanPut]] (P)
+ *    8.12.5 [[Put]] (P)
+ *
+ *  Note: may invalidate any valstack (or object) duk_tval pointers because
+ *  putting a value may reallocate any object or any valstack.  Caller beware.
+ */
+
+static void duk__putvar_helper(duk_hthread *thr,
+                               duk_hobject *env,
+                               duk_activation *act,
+                               duk_hstring *name,
+                               duk_tval *val,
+                               duk_bool_t strict) {
+	duk__id_lookup_result ref;
+	duk_tval tv_tmp_obj;
+	duk_tval tv_tmp_key;
+	duk_bool_t parents;
+
+	DUK_DDD(DUK_DDDPRINT("putvar: thr=%p, env=%p, act=%p, name=%!O, val=%p, strict=%ld "
+	                     "(env -> %!dO, val -> %!T)",
+	                     (void *) thr, (void *) env, (void *) act,
+	                     (duk_heaphdr *) name, (void *) val, (long) strict,
+	                     (duk_heaphdr *) env, (duk_tval *) val));
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(name != NULL);
+	DUK_ASSERT(val != NULL);
+	/* env and act may be NULL */
+
+        DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR(env);
+        DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR(name);
+	DUK_ASSERT_REFCOUNT_NONZERO_TVAL(val);
+
+	/*
+	 *  In strict mode E5 protects 'eval' and 'arguments' from being
+	 *  assigned to (or even declared anywhere).  Attempt to do so
+	 *  should result in a compile time SyntaxError.  See the internal
+	 *  design documentation for details.
+	 *
+	 *  Thus, we should never come here, run-time, for strict code,
+	 *  and name 'eval' or 'arguments'.
+	 */
+
+	DUK_ASSERT(!strict ||
+	           (name != DUK_HTHREAD_STRING_EVAL(thr) &&
+	            name != DUK_HTHREAD_STRING_LC_ARGUMENTS(thr)));
+
+	/*
+	 *  Lookup variable and update in-place if found.
+	 */
+
+	parents = 1;     /* follow parent chain */
+
+	if (duk__get_identifier_reference(thr, env, name, act, parents, &ref)) {
+		if (ref.value && (ref.attrs & DUK_PROPDESC_FLAG_WRITABLE)) {
+			/* Update duk_tval in-place if pointer provided and the
+			 * property is writable.  If the property is not writable
+			 * (immutable binding), use duk_hobject_putprop() which
+			 * will respect mutability.
+			 */
+			duk_tval tv_tmp;
+			duk_tval *tv_val;
+
+			DUK_ASSERT(ref.this_binding == NULL);  /* always for register bindings */
+
+ 			tv_val = ref.value;
+			DUK_ASSERT(tv_val != NULL);
+			DUK_TVAL_SET_TVAL(&tv_tmp, tv_val);
+			DUK_TVAL_SET_TVAL(tv_val, val);
+			DUK_TVAL_INCREF(thr, val);
+			DUK_TVAL_DECREF(thr, &tv_tmp);  /* must be last */
+
+			/* ref.value and ref.this_binding invalidated here */
+		} else {
+			DUK_ASSERT(ref.holder != NULL);
+
+			DUK_TVAL_SET_OBJECT(&tv_tmp_obj, ref.holder);
+			DUK_TVAL_SET_STRING(&tv_tmp_key, name);
+			(void) duk_hobject_putprop(thr, &tv_tmp_obj, &tv_tmp_key, val, strict);
+
+			/* ref.value and ref.this_binding invalidated here */
+		}
+
+		return;
+	}
+	
+	/*
+	 *  Not found: write to global object (non-strict) or ReferenceError
+	 *  (strict); see E5 Section 8.7.2, step 3.
+	 */
+
+	if (strict) {
+		DUK_DDD(DUK_DDDPRINT("identifier binding not found, strict => reference error"));
+		DUK_ERROR(thr, DUK_ERR_REFERENCE_ERROR, "identifier not defined");
+	}
+
+	DUK_DDD(DUK_DDDPRINT("identifier binding not found, not strict => set to global"));
+
+	DUK_TVAL_SET_OBJECT(&tv_tmp_obj, thr->builtins[DUK_BIDX_GLOBAL]);
+	DUK_TVAL_SET_STRING(&tv_tmp_key, name);
+	(void) duk_hobject_putprop(thr, &tv_tmp_obj, &tv_tmp_key, val, 0);  /* 0 = no throw */
+
+	/* NB: 'val' may be invalidated here because put_value may realloc valstack,
+	 * caller beware.
+	 */
+}
+
+void duk_js_putvar_envrec(duk_hthread *thr,
+                          duk_hobject *env,
+                          duk_hstring *name,
+                          duk_tval *val,
+                          duk_bool_t strict) {
+	duk__putvar_helper(thr, env, NULL, name, val, strict);
+}
+
+void duk_js_putvar_activation(duk_hthread *thr,
+                              duk_activation *act,
+                              duk_hstring *name,
+                              duk_tval *val,
+                              duk_bool_t strict) {
+	DUK_ASSERT(act != NULL);
+	duk__putvar_helper(thr, act->lex_env, act, name, val, strict);
+}
+
+/*
+ *  DELVAR
+ *
+ *  See E5 Sections:
+ *    11.4.1 The delete operator
+ *    10.2.1.1.5 DeleteBinding (N)  [declarative environment record]
+ *    10.2.1.2.5 DeleteBinding (N)  [object environment record]
+ *
+ *  Variable bindings established inside eval() are deletable (configurable),
+ *  other bindings are not, including variables declared in global level.
+ *  Registers are always non-deletable, and the deletion of other bindings
+ *  is controlled by the configurable flag.
+ *
+ *  For strict mode code, the 'delete' operator should fail with a compile
+ *  time SyntaxError if applied to identifiers.  Hence, no strict mode
+ *  run-time deletion of identifiers should ever happen.  This function
+ *  should never be called from strict mode code!
+ */
+
+static duk_bool_t duk__delvar_helper(duk_hthread *thr,
+                                     duk_hobject *env,
+                                     duk_activation *act,
+                                     duk_hstring *name) {
+	duk__id_lookup_result ref;
+	duk_bool_t parents;
+
+	DUK_DDD(DUK_DDDPRINT("delvar: thr=%p, env=%p, act=%p, name=%!O "
+	                     "(env -> %!dO)",
+	                     (void *) thr, (void *) env, (void *) act,
+	                     (duk_heaphdr *) name, (duk_heaphdr *) env));
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(name != NULL);
+	/* env and act may be NULL */
+
+        DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR(name);
+
+	parents = 1;     /* follow parent chain */
+
+	if (duk__get_identifier_reference(thr, env, name, act, parents, &ref)) {
+		if (ref.value && !(ref.attrs & DUK_PROPDESC_FLAG_CONFIGURABLE)) {
+			/* Identifier found in registers (always non-deletable)
+			 * or declarative environment record and non-configurable.
+			 */
+			return 0;
+		}
+		DUK_ASSERT(ref.holder != NULL);
+
+		return duk_hobject_delprop_raw(thr, ref.holder, name, 0);
+	}
+
+	/*
+	 *  Not found (even in global object).
+	 *
+	 *  In non-strict mode this is a silent SUCCESS (!), see E5 Section 11.4.1,
+	 *  step 3.b.  In strict mode this case is a compile time SyntaxError so
+	 *  we should not come here.
+	 */
+
+	DUK_DDD(DUK_DDDPRINT("identifier to be deleted not found: name=%!O "
+	                     "(treated as silent success)",
+	                     (duk_heaphdr *) name));
+	return 1;
+}
+
+duk_bool_t duk_js_delvar_envrec(duk_hthread *thr,
+                                duk_hobject *env,
+                                duk_hstring *name) {
+	return duk__delvar_helper(thr, env, NULL, name);
+}
+	
+duk_bool_t duk_js_delvar_activation(duk_hthread *thr,
+                                    duk_activation *act,
+                                    duk_hstring *name) {
+	DUK_ASSERT(act != NULL);
+	return duk__delvar_helper(thr, act->lex_env, act, name);
+}
+
+/*
+ *  DECLVAR
+ *
+ *  See E5 Sections:
+ *    10.4.3 Entering Function Code
+ *    10.5 Declaration Binding Instantion
+ *    12.2 Variable Statement
+ *    11.1.2 Identifier Reference
+ *    10.3.1 Identifier Resolution
+ *
+ *  Variable declaration behavior is mainly discussed in Section 10.5,
+ *  and is not discussed in the execution semantics (Sections 11-13).
+ *
+ *  Conceptually declarations happen when code (global, eval, function)
+ *  is entered, before any user code is executed.  In practice, register-
+ *  bound identifiers are 'declared' automatically (by virtue of being
+ *  allocated to registers with the initial value 'undefined').  Other
+ *  identifiers are declared in the function prologue with this primitive.
+ *
+ *  Since non-register bindings eventually back to an internal object's
+ *  properties, the 'prop_flags' argument is used to specify binding
+ *  type:
+ *
+ *    - Immutable binding: set DUK_PROPDESC_FLAG_WRITABLE to false
+ *    - Non-deletable binding: set DUK_PROPDESC_FLAG_CONFIGURABLE to false
+ *    - The flag DUK_PROPDESC_FLAG_ENUMERABLE should be set, although it
+ *      doesn't really matter for internal objects
+ *
+ *  All bindings are non-deletable mutable bindings except:
+ *
+ *    - Declarations in eval code (mutable, deletable)
+ *    - 'arguments' binding in strict function code (immutable)
+ *    - Function name binding of a function expression (immutable)
+ *
+ *  Declarations may go to declarative environment records (always
+ *  so for functions), but may also go to object environment records
+ *  (e.g. global code).  The global object environment has special
+ *  behavior when re-declaring a function (but not a variable); see
+ *  E5.1 specification, Section 10.5, step 5.e.
+ *
+ *  Declarations always go to the 'top-most' environment record, i.e.
+ *  we never check the record chain.  It's not an error even if a
+ *  property (even an immutable or non-deletable one) of the same name
+ *  already exists.
+ *
+ *  If a declared variable already exists, its value needs to be updated
+ *  (if possible).  Returns 1 if a PUTVAR needs to be done by the caller;
+ *  otherwise returns 0.
+ */
+
+static duk_bool_t duk__declvar_helper(duk_hthread *thr,
+                                      duk_hobject *env,
+                                      duk_hstring *name,
+                                      duk_tval *val,
+                                      duk_small_int_t prop_flags,
+                                      duk_bool_t is_func_decl) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_hobject *holder;
+	duk_bool_t parents;
+	duk__id_lookup_result ref;
+	duk_tval *tv;
+
+	DUK_DDD(DUK_DDDPRINT("declvar: thr=%p, env=%p, name=%!O, val=%!T, prop_flags=0x%08lx, is_func_decl=%ld "
+	                     "(env -> %!iO)",
+	                     (void *) thr, (void *) env, (duk_heaphdr *) name,
+	                     (duk_tval *) val, (unsigned long) prop_flags,
+	                     (unsigned int) is_func_decl, (duk_heaphdr *) env));
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(env != NULL);
+	DUK_ASSERT(name != NULL);
+	DUK_ASSERT(val != NULL);
+
+	/* Note: in strict mode the compiler should reject explicit
+	 * declaration of 'eval' or 'arguments'.  However, internal
+	 * bytecode may declare 'arguments' in the function prologue.
+	 * We don't bother checking (or asserting) for these now.
+	 */
+
+	/* Note: val is a stable duk_tval pointer.  The caller makes
+	 * a value copy into its stack frame, so 'tv_val' is not subject
+	 * to side effects here.
+	 */
+
+	/*
+	 *  Check whether already declared.
+	 *
+	 *  We need to check whether the binding exists in the environment
+	 *  without walking its parents.  However, we still need to check
+	 *  register-bound identifiers and the prototype chain of an object
+	 *  environment target object.
+	 */
+
+	parents = 0;  /* just check 'env' */
+	if (duk__get_identifier_reference(thr, env, name, NULL, parents, &ref)) {
+		duk_int_t e_idx;
+		duk_int_t h_idx;
+		duk_small_int_t flags;
+
+		/*
+		 *  Variable already declared, ignore re-declaration.
+		 *  The only exception is the updated behavior of E5.1 for
+		 *  global function declarations, E5.1 Section 10.5, step 5.e.
+		 *  This behavior does not apply to global variable declarations.
+		 */
+
+		if (!(is_func_decl && env == thr->builtins[DUK_BIDX_GLOBAL_ENV])) {
+			DUK_DDD(DUK_DDDPRINT("re-declare a binding, ignoring"));
+			return 1;  /* 1 -> needs a PUTVAR */
+		}
+
+		/*
+		 *  Special behavior in E5.1.
+		 *
+		 *  Note that even though parents == 0, the conflicting property
+		 *  may be an inherited property (currently our global object's
+		 *  prototype is Object.prototype).  Step 5.e first operates on
+		 *  the existing property (which is potentially in an ancestor)
+		 *  and then defines a new property in the global object (and
+		 *  never modifies the ancestor).
+		 *
+		 *  Also note that this logic would become even more complicated
+		 *  if the conflicting property might be a virtual one.  Object
+		 *  prototype has no virtual properties, though.
+		 *
+		 *  XXX: this is now very awkward, rework.
+		 */
+
+		DUK_DDD(DUK_DDDPRINT("re-declare a function binding in global object, "
+		                     "updated E5.1 processing"));
+
+		DUK_ASSERT(ref.holder != NULL);
+		holder = ref.holder;
+
+		/* holder will be set to the target object, not the actual object
+		 * where the property was found (see duk__get_identifier_reference()).
+		 */
+		DUK_ASSERT(DUK_HOBJECT_GET_CLASS_NUMBER(holder) == DUK_HOBJECT_CLASS_GLOBAL);
+		DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARRAY(holder));  /* global object doesn't have array part */
+
+		/* XXX: use a helper for prototype traversal; no loop check here */
+		/* must be found: was found earlier, and cannot be inherited */
+		for (;;) {
+			DUK_ASSERT(holder != NULL);
+			duk_hobject_find_existing_entry(holder, name, &e_idx, &h_idx);
+			if (e_idx >= 0) {
+				break;
+			}
+			/* SCANBUILD: NULL pointer dereference, doesn't actually trigger,
+			 * asserted above.
+			 */
+			holder = holder->prototype;
+		}
+		DUK_ASSERT(holder != NULL);
+		DUK_ASSERT(e_idx >= 0);
+		/* SCANBUILD: scan-build produces a NULL pointer dereference warning
+		 * below; it never actually triggers because holder is actually never
+		 * NULL.
+		 */
+
+		/* ref.holder is global object, holder is the object with the
+		 * conflicting property.
+		 */
+
+		flags = DUK_HOBJECT_E_GET_FLAGS(holder, e_idx);
+		if (!(flags & DUK_PROPDESC_FLAG_CONFIGURABLE)) {
+			if (flags & DUK_PROPDESC_FLAG_ACCESSOR) {
+				DUK_DDD(DUK_DDDPRINT("existing property is a non-configurable "
+				                     "accessor -> reject"));
+				goto fail_existing_attributes;
+			}
+			if (!((flags & DUK_PROPDESC_FLAG_WRITABLE) &&
+			      (flags & DUK_PROPDESC_FLAG_ENUMERABLE))) {
+				DUK_DDD(DUK_DDDPRINT("existing property is a non-configurable "
+				                     "plain property which is not writable and "
+				                     "enumerable -> reject"));
+				goto fail_existing_attributes;
+			}
+
+			DUK_DDD(DUK_DDDPRINT("existing property is not configurable but "
+			                     "is plain, enumerable, and writable -> "
+			                     "allow redeclaration"));
+		}
+
+		if (holder == ref.holder) {
+			/* XXX: if duk_hobject_define_property_internal() was updated
+			 * to handle a pre-existing accessor property, this would be
+			 * a simple call (like for the ancestor case).
+			 */
+			DUK_DDD(DUK_DDDPRINT("redefine, offending property in global object itself"));
+
+			if (flags & DUK_PROPDESC_FLAG_ACCESSOR) {
+				duk_hobject *tmp;
+
+				tmp = DUK_HOBJECT_E_GET_VALUE_GETTER(holder, e_idx);
+				DUK_HOBJECT_E_SET_VALUE_GETTER(holder, e_idx, NULL);
+				DUK_HOBJECT_DECREF(thr, tmp);
+				DUK_UNREF(tmp);
+				tmp = DUK_HOBJECT_E_GET_VALUE_SETTER(holder, e_idx);
+				DUK_HOBJECT_E_SET_VALUE_SETTER(holder, e_idx, NULL);
+				DUK_HOBJECT_DECREF(thr, tmp);
+				DUK_UNREF(tmp);
+			} else {
+				duk_tval tv_tmp;
+
+				tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(holder, e_idx);
+				DUK_TVAL_SET_TVAL(&tv_tmp, tv);
+				DUK_TVAL_SET_UNDEFINED_UNUSED(tv);
+				DUK_TVAL_DECREF(thr, &tv_tmp);
+			}
+
+			/* Here val would be potentially invalid if we didn't make
+			 * a value copy at the caller.
+			 */
+
+			tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(holder, e_idx);
+			DUK_TVAL_SET_TVAL(tv, val);
+			DUK_TVAL_INCREF(thr, tv);
+			DUK_HOBJECT_E_SET_FLAGS(holder, e_idx, prop_flags);
+
+			DUK_DDD(DUK_DDDPRINT("updated global binding, final result: "
+			                     "value -> %!T, prop_flags=0x%08lx",
+			                     (duk_tval *) DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(holder, e_idx),
+			                     (unsigned long) prop_flags));
+		} else {
+			DUK_DDD(DUK_DDDPRINT("redefine, offending property in ancestor"));
+
+			DUK_ASSERT(ref.holder == thr->builtins[DUK_BIDX_GLOBAL]);
+			duk_push_tval(ctx, val);
+			duk_hobject_define_property_internal(thr, ref.holder, name, prop_flags);
+		}
+
+		return 0;
+	}
+
+	/*
+	 *  Not found (in registers or record objects).  Declare
+	 *  to current variable environment.
+	 */
+
+	/*
+	 *  Get holder object
+	 */
+
+	if (DUK_HOBJECT_IS_DECENV(env)) {
+		holder = env;
+	} else {
+		DUK_ASSERT(DUK_HOBJECT_IS_OBJENV(env));
+
+		tv = duk_hobject_find_existing_entry_tval_ptr(env, DUK_HTHREAD_STRING_INT_TARGET(thr));
+		DUK_ASSERT(tv != NULL);
+		DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
+		holder = DUK_TVAL_GET_OBJECT(tv);
+		DUK_ASSERT(holder != NULL);
+	}
+
+	/*
+	 *  Define new property
+	 *
+	 *  Note: this may fail if the holder is not extensible.
+	 */
+
+	/* XXX: this is awkward as we use an internal method which doesn't handle
+	 * extensibility etc correctly.  Basically we'd want to do a [[DefineOwnProperty]]
+	 * or Object.defineProperty() here.
+	 */
+
+	if (!DUK_HOBJECT_HAS_EXTENSIBLE(holder)) {
+		goto fail_not_extensible;
+	}
+
+	duk_push_hobject(ctx, holder);
+	duk_push_hstring(ctx, name);
+	duk_push_tval(ctx, val);
+	duk_def_prop(ctx, -3, prop_flags);  /* [holder name val] -> [holder] */
+	duk_pop(ctx);
+
+	return 0;
+
+ fail_existing_attributes:
+ fail_not_extensible:
+	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "declaration failed");
+	return 0;
+}
+
+duk_bool_t duk_js_declvar_activation(duk_hthread *thr,
+                                     duk_activation *act,
+                                     duk_hstring *name,
+                                     duk_tval *val,
+                                     duk_small_int_t prop_flags,
+                                     duk_bool_t is_func_decl) {
+	duk_hobject *env;
+	duk_tval tv_val_copy;
+
+	/*
+	 *  Make a value copy of the input val.  This ensures that
+	 *  side effects cannot invalidate the pointer.
+	 */
+
+	DUK_TVAL_SET_TVAL(&tv_val_copy, val);
+	val = &tv_val_copy;
+
+	/*
+	 *  Delayed env creation check
+	 */
+
+	if (!act->var_env) {
+		DUK_ASSERT(act->lex_env == NULL);
+		duk_js_init_activation_environment_records_delayed(thr, act);
+	}
+	DUK_ASSERT(act->lex_env != NULL);
+	DUK_ASSERT(act->var_env != NULL);
+
+	env = act->var_env;
+	DUK_ASSERT(env != NULL);
+	DUK_ASSERT(DUK_HOBJECT_IS_ENV(env));
+
+	return duk__declvar_helper(thr, env, name, val, prop_flags, is_func_decl);
+}
+#line 1 "duk_lexer.c"
+/*
+ *  Lexer for source files, ToNumber() string conversions, RegExp expressions,
+ *  and JSON.
+ *
+ *  Provides a stream of Ecmascript tokens from an UTF-8/CESU-8 buffer.  The
+ *  caller can also rewind the token stream into a certain position which is
+ *  needed by the compiler part for multi-pass scanning.  Tokens are
+ *  represented as duk_token structures, and contain line number information.
+ *  Token types are identified with DUK_TOK_* defines.
+ *
+ *  Characters are decoded into a fixed size lookup window consisting of
+ *  decoded Unicode code points, with window positions past the end of the
+ *  input filled with an invalid codepoint (-1).  The tokenizer can thus
+ *  perform multiple character lookups efficiently and with few sanity
+ *  checks (such as access outside the end of the input), which keeps the
+ *  tokenization code small at the cost of performance.
+ * 
+ *  Character data in tokens (such as identifier names and string literals)
+ *  is encoded into CESU-8 format on-the-fly while parsing the token in
+ *  question.  The string data is made reachable to garbage collection by
+ *  placing the token-related values in value stack entries allocated for
+ *  this purpose by the caller.  The characters exist in Unicode code point
+ *  form only in the fixed size lookup window, which keeps character data
+ *  expansion (of especially ASCII data) low.
+ *
+ *  Token parsing supports the full range of Unicode characters as described
+ *  in the E5 specification.  Parsing has been optimized for ASCII characters
+ *  because ordinary Ecmascript code consists almost entirely of ASCII
+ *  characters.  Matching of complex Unicode codepoint sets (such as in the
+ *  IdentifierStart and IdentifierPart productions) is optimized for size,
+ *  and is done using a linear scan of a bit-packed list of ranges.  This is
+ *  very slow, but should never be entered unless the source code actually
+ *  contains Unicode characters.
+ *
+ *  Ecmascript tokenization is partially context sensitive.  First,
+ *  additional future reserved words are recognized in strict mode (see E5
+ *  Section 7.6.1.2).  Second, a forward slash character ('/') can be
+ *  recognized either as starting a RegExp literal or as a division operator,
+ *  depending on context.  The caller must provide necessary context flags
+ *  when requesting a new token.
+ *
+ *  Future work:
+ *
+ *    * Make the input window a circular array to avoid copying.  This would
+ *      not necessarily complicate the tokenizer much, although it would make
+ *      the window fetches more expensive (one AND).
+ *
+ *    * Make line number tracking optional, as it consumes space.  Also, is
+ *      tracking end line really useful for tokens?
+ *
+ *    * Add a feature flag for disabling UTF-8 decoding of input, as most
+ *      source code is ASCII.  Because of Unicode escapes written in ASCII,
+ *      this does not allow Unicode support to be removed from e.g.
+ *      duk_unicode_is_identifier_start() nor does it allow removal of CESU-8
+ *      encoding of e.g. string literals.
+ *
+ *    * Add a feature flag for disabling Unicode compliance of e.g. identifier
+ *      names.  This allows for a build more than a kilobyte smaller, because
+ *      Unicode ranges needed by duk_unicode_is_identifier_start() and
+ *      duk_unicode_is_identifier_part() can be dropped.  String literals
+ *      should still be allowed to contain escaped Unicode, so this still does
+ *      not allow removal of CESU-8 encoding of e.g. string literals.
+ *
+ *    * Character lookup tables for codepoints above BMP could be stripped.
+ *
+ *    * Strictly speaking, E5 specification requires that source code consists
+ *      of 16-bit code units, and if not, must be conceptually converted to
+ *      that format first.  The current lexer processes Unicode code points
+ *      and allows characters outside the BMP.  These should be converted to
+ *      surrogate pairs while reading the source characters into the window,
+ *      not after tokens have been formed (as is done now).  However, the fix
+ *      is not trivial because two characters are decoded from one codepoint.
+ *
+ *    * Optimize for speed as well as size.  Large if-else ladders are slow.
+ */
+
+/* include removed: duk_internal.h */
+
+/*
+ *  Various defines and file specific helper macros
+ */
+
+#define DUK__MAX_RE_DECESC_DIGITS     9
+#define DUK__MAX_RE_QUANT_DIGITS      9   /* Does not allow e.g. 2**31-1, but one more would allow overflows of u32. */
+
+#define DUK__LOOKUP(lex_ctx,index)    ((lex_ctx)->window[(index)])
+#define DUK__ADVANCE(lex_ctx,count)   duk__advance_chars((lex_ctx), (count))
+#define DUK__INITBUFFER(lex_ctx)      duk__initbuffer((lex_ctx))
+#define DUK__APPENDBUFFER(lex_ctx,x)  duk__appendbuffer((lex_ctx), (duk_codepoint_t) (x))
+
+/* whether to use macros or helper function depends on call count */
+#define DUK__ISDIGIT(x)          ((x) >= DUK_ASC_0 && (x) <= DUK_ASC_9)
+#define DUK__ISHEXDIGIT(x)       duk__is_hex_digit((x))
+#define DUK__ISOCTDIGIT(x)       ((x) >= DUK_ASC_0 && (x) <= DUK_ASC_7)
+#define DUK__ISDIGIT03(x)        ((x) >= DUK_ASC_0 && (x) <= DUK_ASC_3)
+#define DUK__ISDIGIT47(x)        ((x) >= DUK_ASC_4 && (x) <= DUK_ASC_7)
+
+/* lookup shorthands (note: assume context variable is named 'lex_ctx') */
+#define DUK__L0()  DUK__LOOKUP(lex_ctx, 0)
+#define DUK__L1()  DUK__LOOKUP(lex_ctx, 1)
+#define DUK__L2()  DUK__LOOKUP(lex_ctx, 2)
+#define DUK__L3()  DUK__LOOKUP(lex_ctx, 3)
+#define DUK__L4()  DUK__LOOKUP(lex_ctx, 4)
+#define DUK__L5()  DUK__LOOKUP(lex_ctx, 5)
+
+/* packed advance/token number macro used by multiple functions */
+#define DUK__ADVTOK(adv,tok)  (((adv) << 8) + (tok))
+
+/*
+ *  Read a character from the window leading edge and update the line counter.
+ *
+ *  Decodes UTF-8/CESU-8 leniently with support for code points from U+0000 to
+ *  U+10FFFF, causing an error if the input is unparseable.  Leniency means:
+ *
+ *    * Unicode code point validation is intentionally not performed,
+ *      except to check that the codepoint does not exceed 0x10ffff.
+ *
+ *    * In particular, surrogate pairs are allowed and not combined, which
+ *      allows source files to represent all SourceCharacters with CESU-8.
+ *      Broken surrogate pairs are allowed, as Ecmascript does not mandate
+ *      their validation.
+ *
+ *    * Allow non-shortest UTF-8 encodings.
+ *
+ *  Leniency here causes few security concerns because all character data is
+ *  decoded into Unicode codepoints before lexer processing, and is then
+ *  re-encoded into CESU-8.  The source can be parsed as strict UTF-8 with
+ *  a compiler option.  However, Ecmascript source characters include -all-
+ *  16-bit unsigned integer codepoints, so leniency seems to be appropriate.
+ *
+ *  Note that codepoints above the BMP are not strictly SourceCharacters,
+ *  but the lexer still accepts them as such.  Before ending up in a string
+ *  or an identifier name, codepoints above BMP are converted into surrogate
+ *  pairs and then CESU-8 encoded, resulting in 16-bit Unicode data as
+ *  expected by Ecmascript.
+ *
+ *  An alternative approach to dealing with invalid or partial sequences
+ *  would be to skip them and replace them with e.g. the Unicode replacement
+ *  character U+FFFD.  This has limited utility because a replacement character
+ *  will most likely cause a parse error, unless it occurs inside a string.
+ *  Further, Ecmascript source is typically pure ASCII.
+ *
+ *  See:
+ *
+ *     http://en.wikipedia.org/wiki/UTF-8
+ *     http://en.wikipedia.org/wiki/CESU-8
+ *     http://tools.ietf.org/html/rfc3629
+ *     http://en.wikipedia.org/wiki/UTF-8#Invalid_byte_sequences
+ *
+ *  Future work:
+ *
+ *    * Reject other invalid Unicode sequences (see Wikipedia entry for examples)
+ *      in strict UTF-8 mode.
+ * 
+ *    * Size optimize.  An attempt to use a 16-byte lookup table for the first
+ *      byte resulted in a code increase though.
+ *
+ *    * Is checking against maximum 0x10ffff really useful?  4-byte encoding
+ *      imposes a certain limit anyway.
+ */
+
+static duk_codepoint_t duk__read_char(duk_lexer_ctx *lex_ctx) {
+	/* attempting to reduce size of 'len' and/or 'i' resulted in larger code */
+	duk_codepoint_t x;
+	duk_small_int_t len;
+	duk_small_int_t i;
+	const duk_uint8_t *p;
+#ifdef DUK_USE_STRICT_UTF8_SOURCE
+	duk_codepoint_t mincp;
+#endif
+	duk_size_t input_offset;
+
+	input_offset = lex_ctx->input_offset;
+	if (DUK_UNLIKELY(input_offset >= lex_ctx->input_length)) {
+		/* If input_offset were assigned a negative value, it would
+		 * result in a large positive value.  Most likely it would be
+		 * larger than input_length and be caught here.  In any case
+		 * no memory unsafe behavior would happen.
+		 */
+		return -1;
+	}
+
+	p = lex_ctx->input + input_offset;
+	x = (int) *p++;
+
+	if (x < 0x80L) {
+		/* 0xxx xxxx -> fast path */
+		len = 1;
+		goto fastpath;
+	} else if (x < 0xc0L) {
+		/* 10xx xxxx -> invalid */
+		goto error_encoding;
+	} else if (x < 0xe0L) {
+		/* 110x xxxx   10xx xxxx  */
+		len = 2;
+#ifdef DUK_USE_STRICT_UTF8_SOURCE
+		mincp = 0x80L;
+#endif
+		x = x & 0x1fL;
+	} else if (x < 0xf0L) {
+		/* 1110 xxxx   10xx xxxx   10xx xxxx */
+		len = 3;
+#ifdef DUK_USE_STRICT_UTF8_SOURCE
+		mincp = 0x800L;
+#endif
+		x = x & 0x0fL;
+	} else if (x < 0xf8L) {
+		/* 1111 0xxx   10xx xxxx   10xx xxxx   10xx xxxx */
+		len = 4;
+#ifdef DUK_USE_STRICT_UTF8_SOURCE
+		mincp = 0x10000L;
+#endif
+		x = x & 0x07;
+	} else {
+		/* no point in supporting encodings of 5 or more bytes */
+		goto error_encoding;
+	}
+
+	DUK_ASSERT(lex_ctx->input_length >= lex_ctx->input_offset);
+	if ((duk_size_t) len > (duk_size_t) (lex_ctx->input_length - lex_ctx->input_offset)) {
+		goto error_clipped;
+	}
+
+	for (i = 1; i < len; i++) {
+		duk_small_int_t y = *p++;
+		if ((y & 0xc0) != 0x80) {
+			/* check that byte has the form 10xx xxxx */
+			goto error_encoding;
+		}
+		x = x << 6;
+		x += y & 0x3f;
+	}
+
+	/* check final character validity */
+
+	if (x > 0x10ffffL) {
+		goto error_encoding;
+	}
+#ifdef DUK_USE_STRICT_UTF8_SOURCE
+	if (x < mincp || (x >= 0xd800L && x <= 0xdfffL) || x == 0xfffeL) {
+		goto error_encoding;
+	}
+#endif
+
+	/* fall through */
+
+ fastpath:
+	/* input offset tracking */
+	lex_ctx->input_offset += len;
+
+	/* line tracking */
+	if ((x == 0x000aL) ||
+	    ((x == 0x000dL) && (lex_ctx->input_offset >= lex_ctx->input_length ||
+	                        lex_ctx->input[lex_ctx->input_offset] != 0x000aL)) ||
+	    (x == 0x2028L) ||
+	    (x == 0x2029L)) {
+		/* lookup for 0x000a above assumes shortest encoding now */
+
+		/* E5 Section 7.3, treat the following as newlines:
+		 *   LF
+		 *   CR [not followed by LF]
+		 *   LS
+		 *   PS
+		 *
+		 * For CR LF, CR is ignored if it is followed by LF, and the LF will bump
+		 * the line number.
+		 */
+		lex_ctx->input_line++;
+	}
+
+	return x;
+
+ error_clipped:   /* clipped codepoint */
+ error_encoding:  /* invalid codepoint encoding or codepoint */
+	DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR, "char decode failed");
+	return 0;
+}
+
+/*
+ *  Advance lookup window by N characters.  Also used to fill the window
+ *  after position is changed (call with count == DUK_LEXER_WINDOW_SIZE).
+ *
+ *  XXX: A lot of copying now, perhaps change to circular array or at
+ *  least use memcpy().  For memcpy(), putting all elements of the
+ *  window (code point, offset, line) into a struct would allow one
+ *  memcpy() to slide the window, instead of three separate copys.
+ */
+
+static void duk__advance_chars(duk_lexer_ctx *lex_ctx, duk_small_int_t count) {
+	duk_small_int_t i, n;
+
+	DUK_ASSERT(count >= 0 && count <= DUK_LEXER_WINDOW_SIZE);
+
+	if (count == 0) {
+		/* allowing zero count makes some special caller flows easier */
+		return;
+	}
+
+	n = DUK_LEXER_WINDOW_SIZE - count;
+	for (i = 0; i < n; i++) {
+		lex_ctx->offsets[i] = lex_ctx->offsets[i + count];
+		lex_ctx->lines[i] = lex_ctx->lines[i + count];
+		lex_ctx->window[i] = lex_ctx->window[i + count];
+	}
+
+	for (; i < DUK_LEXER_WINDOW_SIZE; i++) {
+		lex_ctx->offsets[i] = lex_ctx->input_offset;
+		lex_ctx->lines[i] = lex_ctx->input_line;
+		lex_ctx->window[i] = duk__read_char(lex_ctx);
+	}
+}
+
+/*
+ *  (Re)initialize the temporary byte buffer.  May be called extra times
+ *  with little impact.
+ */
+
+static void duk__initbuffer(duk_lexer_ctx *lex_ctx) {
+	if (lex_ctx->buf->usable_size < DUK_LEXER_TEMP_BUF_LIMIT) {
+		/* Resize (zero) without realloc. */
+		lex_ctx->buf->size = 0;
+	} else {
+		duk_hbuffer_resize(lex_ctx->thr, lex_ctx->buf, 0, DUK_LEXER_TEMP_BUF_LIMIT);
+	}
+}
+
+/*
+ *  Append a Unicode codepoint to the temporary byte buffer.  Performs
+ *  CESU-8 surrogate pair encoding for codepoints above the BMP.
+ *  Existing surrogate pairs are allowed and also encoded into CESU-8.
+ */
+
+static void duk__appendbuffer(duk_lexer_ctx *lex_ctx, duk_codepoint_t x) {
+	/*
+	 *  Since character data is only generated by decoding the source or by
+	 *  the compiler itself, we rely on the input codepoints being correct
+	 *  and avoid a check here.
+	 *
+	 *  Character data can also come here through decoding of Unicode
+	 *  escapes ("\udead\ubeef") so all 16-but unsigned values can be
+	 *  present, even when the source file itself is strict UTF-8.
+	 */
+
+	DUK_ASSERT(x >= 0 && x <= 0x10ffff);
+
+	duk_hbuffer_append_cesu8(lex_ctx->thr, lex_ctx->buf, (duk_ucodepoint_t) x);
+}
+
+/*
+ *  Intern the temporary byte buffer into a valstack slot
+ *  (in practice, slot1 or slot2).
+ */
+
+static void duk__internbuffer(duk_lexer_ctx *lex_ctx, duk_idx_t valstack_idx) {
+	duk_context *ctx = (duk_context *) lex_ctx->thr;
+
+	DUK_ASSERT(valstack_idx == lex_ctx->slot1_idx || valstack_idx == lex_ctx->slot2_idx);
+
+	duk_dup(ctx, lex_ctx->buf_idx);
+	duk_to_string(ctx, -1);
+	duk_replace(ctx, valstack_idx);
+}
+
+/*
+ *  Init lexer context
+ */
+
+void duk_lexer_initctx(duk_lexer_ctx *lex_ctx) {
+	DUK_ASSERT(lex_ctx != NULL);
+
+	DUK_MEMZERO(lex_ctx, sizeof(*lex_ctx));
+#ifdef DUK_USE_EXPLICIT_NULL_INIT
+	lex_ctx->thr = NULL;
+	lex_ctx->input = NULL;
+	lex_ctx->buf = NULL;
+#endif
+}
+
+/*
+ *  Set lexer input position and reinitialize lookup window.
+ */
+
+/* NB: duk_lexer_getpoint() is a macro only */
+
+void duk_lexer_setpoint(duk_lexer_ctx *lex_ctx, duk_lexer_point *pt) {
+	DUK_ASSERT_DISABLE(pt->offset >= 0);  /* unsigned */
+	DUK_ASSERT(pt->line >= 1);
+	lex_ctx->input_offset = pt->offset;
+	lex_ctx->input_line = pt->line;
+	duk__advance_chars(lex_ctx, DUK_LEXER_WINDOW_SIZE);  /* fill window */
+}
+
+/*
+ *  Lexing helpers
+ */
+
+/* numeric value of a hex digit (also covers octal and decimal digits) */
+static duk_codepoint_t duk__hexval(duk_lexer_ctx *lex_ctx, duk_codepoint_t x) {
+	duk_small_int_t t;
+
+	/* Here 'x' is a Unicode codepoint */
+	if (DUK_LIKELY(x >= 0 && x <= 0xff)) {
+		t = duk_hex_dectab[x];
+		if (DUK_LIKELY(t >= 0)) {
+			return t;
+		}
+	}
+
+	/* Throwing an error this deep makes the error rather vague, but
+	 * saves hundreds of bytes of code.
+	 */
+	DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR, "decode error");
+	return 0;
+}
+
+/* having this as a separate function provided a size benefit */
+static duk_bool_t duk__is_hex_digit(duk_codepoint_t x) {
+	if (DUK_LIKELY(x >= 0 && x <= 0xff)) {
+		return (duk_hex_dectab[x] >= 0);
+	}
+	return 0;
+}
+
+static duk_codepoint_t duk__decode_hexesc_from_window(duk_lexer_ctx *lex_ctx, duk_small_int_t lookup_offset) {
+	/* validation performed by duk__hexval */
+	return (duk__hexval(lex_ctx, lex_ctx->window[lookup_offset]) << 4) |
+	       (duk__hexval(lex_ctx, lex_ctx->window[lookup_offset + 1]));
+}
+
+static duk_codepoint_t duk__decode_uniesc_from_window(duk_lexer_ctx *lex_ctx, duk_small_int_t lookup_offset) {
+	/* validation performed by duk__hexval */
+	return (duk__hexval(lex_ctx, lex_ctx->window[lookup_offset]) << 12) |
+	       (duk__hexval(lex_ctx, lex_ctx->window[lookup_offset + 1]) << 8) |
+	       (duk__hexval(lex_ctx, lex_ctx->window[lookup_offset + 2]) << 4) |
+	       (duk__hexval(lex_ctx, lex_ctx->window[lookup_offset + 3]));
+}
+
+/*
+ *  Eat input characters until first character of window is not
+ *  a white space (may be -1 if EOF encountered).
+ */
+static void duk__eat_whitespace(duk_lexer_ctx *lex_ctx) {
+	/* guaranteed to finish, as EOF (-1) is not a whitespace */
+	while (duk_unicode_is_whitespace(DUK__LOOKUP(lex_ctx, 0))) {
+		DUK__ADVANCE(lex_ctx, 1);
+	}
+}
+
+/*
+ *  Parse Ecmascript source InputElementDiv or InputElementRegExp
+ *  (E5 Section 7).
+ *
+ *  Possible results are:
+ *    (1) a token
+ *    (2) a line terminator
+ *    (3) a comment
+ *    (4) EOF
+ *
+ *  White space is automatically skipped from the current position (but
+ *  not after the input element).  If input has already ended, returns
+ *  DUK_TOK_EOF indefinitely.  If a parse error occurs, uses an DUK_ERROR()
+ *  macro call (and hence a longjmp through current heap longjmp context).
+ *
+ *  The input element being matched is determined by regexp_mode; if set,
+ *  parses a InputElementRegExp, otherwise a InputElementDiv.  The
+ *  difference between these are handling of productions starting with a
+ *  forward slash.
+ *
+ *  If strict_mode is set, recognizes additional future reserved words
+ *  specific to strict mode, and refuses to parse octal literals.
+ *
+ *  The matching strategy below is to (currently) use a six character
+ *  lookup window to quickly determine which production is the -longest-
+ *  matching one, and then parse that.  The top-level if-else clauses
+ *  match the first character, and the code blocks for each clause
+ *  handle -all- alternatives for that first character.  Ecmascript
+ *  specification uses the "longest match wins" semantics, so the order
+ *  of the if-clauses matters.
+ *
+ *  Misc notes:
+ *
+ *    * Ecmascript numeric literals do not accept a sign character.
+ *      Consequently e.g. "-1.0" is parsed as two tokens: a negative
+ *      sign and a positive numeric literal.  The compiler performs
+ *      the negation during compilation, so this has no adverse impact.
+ *
+ *    * There is no token for "undefined": it is just a value available
+ *      from the global object (or simply established by doing a reference
+ *      to an undefined value).
+ *
+ *    * Some contexts want Identifier tokens, which are IdentifierNames
+ *      excluding reserved words, while some contexts want IdentifierNames
+ *      directly.  In the latter case e.g. "while" is interpreted as an
+ *      identifier name, not a DUK_TOK_WHILE token.  The solution here is
+ *      to provide both token types: DUK_TOK_WHILE goes to 't' while
+ *      DUK_TOK_IDENTIFIER goes to 't_nores', and 'slot1' always contains
+ *      the identifier / keyword name.
+ *
+ *    * Directive prologue needs to identify string literals such as
+ *      "use strict" and 'use strict', which are sensitive to line
+ *      continuations and escape sequences.  For instance, "use\u0020strict"
+ *      is a valid directive but is distinct from "use strict".  The solution
+ *      here is to decode escapes while tokenizing, but to keep track of the
+ *      number of escapes.  Directive detection can then check that the
+ *      number of escapes is zero.
+ *
+ *    * Comments are expressed as DUK_TOK_COMMENT tokens, with the type
+ *      (single- or multi-line) and contents of the comments lost.
+ *      Furthermore, multi-line comments with one or more internal
+ *      LineTerminator are treated as DUK_TOK_LINETERM to comply with
+ *      automatic semicolon insertion and to avoid complicating the
+ *      tokenization process.  See E5 Section 7.4.
+ */
+
+static void duk__parse_input_element_raw(duk_lexer_ctx *lex_ctx,
+                                         duk_token *out_token,
+                                         duk_bool_t strict_mode,
+                                         duk_bool_t regexp_mode) {
+	duk_codepoint_t x, y;        /* temporaries, must be signed and 32-bit to hold Unicode code points */
+	duk_small_uint_t advtok = 0; /* (advance << 8) + token_type, updated at function end,
+	                              * init is unnecessary but suppresses "may be used uninitialized" warnings.
+	                              */
+
+	if (++lex_ctx->token_count >= lex_ctx->token_limit) {
+		DUK_ERROR(lex_ctx->thr, DUK_ERR_RANGE_ERROR, "token limit");
+		return;  /* unreachable */
+	}
+
+	duk__eat_whitespace(lex_ctx);
+
+	out_token->t = DUK_TOK_EOF;
+	out_token->t_nores = -1;	/* marker: copy t if not changed */
+	out_token->num = DUK_DOUBLE_NAN;
+	out_token->str1 = NULL;
+	out_token->str2 = NULL;
+	out_token->num_escapes = 0;
+	out_token->start_line = lex_ctx->lines[0];
+	out_token->start_offset = lex_ctx->offsets[0];
+	/* out_token->end_line set at exit */
+	/* out_token->lineterm set by caller */
+
+	duk_to_undefined((duk_context *) lex_ctx->thr, lex_ctx->slot1_idx);
+	duk_to_undefined((duk_context *) lex_ctx->thr, lex_ctx->slot2_idx);
+
+	/* 'advtok' indicates how much to advance and which token id to assign
+	 * at the end.  This shared functionality minimizes code size.  All
+	 * code paths are required to set 'advtok' to some value, so no default
+	 * init value is used.  Code paths calling DUK_ERROR() never return so
+	 * they don't need to set advtok.
+	 */
+
+	/*
+	 *  Matching order:
+	 *
+	 *    Punctuator first chars, also covers comments, regexps
+	 *    LineTerminator
+	 *    Identifier or reserved word, also covers null/true/false literals
+	 *    NumericLiteral
+	 *    StringLiteral
+	 *    EOF
+	 *
+	 *  The order does not matter as long as the longest match is
+	 *  always correctly identified.  There are order dependencies
+	 *  in the clauses, so it's not trivial to convert to a switch.
+	 *
+	 *  XXX: This is quite inefficient.  Maybe change to a switch
+	 *  statement which handles all single character cases and then
+	 *  use a followup if-else chain?  Switch matches need to use
+	 *  goto to bypass the if-else chain.
+	 */
+
+	x = DUK__L0();
+	y = DUK__L1();
+
+	if (x == '/') {
+		if (y == '/') {
+			/*
+			 *  E5 Section 7.4, allow SourceCharacter (which is any 16-bit
+			 *  code point).
+			 */
+
+			/* DUK__ADVANCE(lex_ctx, 2) would be correct here, but it unnecessary */
+			for (;;) {
+				x = DUK__L0();
+				if (x < 0 || duk_unicode_is_line_terminator(x)) {
+					break;
+				}
+				DUK__ADVANCE(lex_ctx, 1);
+			}
+			advtok = DUK__ADVTOK(0, DUK_TOK_COMMENT);
+		} else if (y == '*') {
+			/*
+			 *  E5 Section 7.4.  If the multi-line comment contains a newline,
+			 *  it is treated like a single DUK_TOK_LINETERM to facilitate
+			 *  automatic semicolon insertion.
+			 */
+
+			duk_bool_t last_asterisk = 0;
+			advtok = DUK__ADVTOK(0, DUK_TOK_COMMENT);
+			DUK__ADVANCE(lex_ctx, 2);
+			for (;;) {
+				x = DUK__L0();
+				if (x < 0) {
+					DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+					          "eof while parsing multiline comment");
+				}
+				DUK__ADVANCE(lex_ctx, 1);
+				if (last_asterisk && x == '/') {
+					break;
+				}
+				if (duk_unicode_is_line_terminator(x)) {
+					advtok = DUK__ADVTOK(0, DUK_TOK_LINETERM);
+				}
+				last_asterisk = (x == '*');
+			}
+		} else if (regexp_mode) {
+#ifdef DUK_USE_REGEXP_SUPPORT
+			/*
+			 *  "/" followed by something in regexp mode.  See E5 Section 7.8.5.
+			 *
+			 *  RegExp parsing is a bit complex.  First, the regexp body is delimited
+			 *  by forward slashes, but the body may also contain forward slashes as
+			 *  part of an escape sequence or inside a character class (delimited by
+			 *  square brackets).  A mini state machine is used to implement these.
+			 *
+			 *  Further, an early (parse time) error must be thrown if the regexp
+			 *  would cause a run-time error when used in the expression new RegExp(...).
+			 *  Parsing here simply extracts the (candidate) regexp, and also accepts
+			 *  invalid regular expressions (which are delimited properly).  The caller
+			 *  (compiler) must perform final validation and regexp compilation.
+			 *
+			 *  RegExp first char may not be '/' (single line comment) or '*' (multi-
+			 *  line comment).  These have already been checked above, so there is no
+			 *  need below for special handling of the first regexp character as in
+			 *  the E5 productions.
+			 *
+			 *  About unicode escapes within regexp literals:
+			 *
+			 *      E5 Section 7.8.5 grammar does NOT accept \uHHHH escapes.
+			 *      However, Section 6 states that regexps accept the escapes,
+			 *      see paragraph starting with "In string literals...".
+			 *      The regexp grammar, which sees the decoded regexp literal
+			 *      (after lexical parsing) DOES have a \uHHHH unicode escape.
+			 *      So, for instance:
+			 *
+			 *          /\u1234/
+			 *
+			 *      should first be parsed by the lexical grammar as:
+			 *
+			 *          '\' 'u'		RegularExpressionBackslashSequence
+			 *          '1'			RegularExpressionNonTerminator
+			 *          '2'			RegularExpressionNonTerminator
+			 *          '3'			RegularExpressionNonTerminator
+			 *          '4'			RegularExpressionNonTerminator
+			 *
+			 *      and the escape itself is then parsed by the regexp engine.
+			 *      This is the current implementation. 
+			 *
+			 *  Minor spec inconsistency:
+			 *
+			 *      E5 Section 7.8.5 RegularExpressionBackslashSequence is:
+			 *
+			 *         \ RegularExpressionNonTerminator
+			 *
+			 *      while Section A.1 RegularExpressionBackslashSequence is:
+			 *
+			 *         \ NonTerminator
+			 * 
+			 *      The latter is not normative and a typo.
+			 * 
+			 */
+
+			/* first, parse regexp body roughly */
+
+			duk_small_int_t state = 0;  /* 0=base, 1=esc, 2=class, 3=class+esc */
+
+			DUK__INITBUFFER(lex_ctx);
+			for (;;) {
+				DUK__ADVANCE(lex_ctx, 1);	/* skip opening slash on first loop */
+				x = DUK__L0();
+				if (x < 0 || duk_unicode_is_line_terminator(x)) {
+					DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+					          "eof or line terminator while parsing regexp");
+				}
+				x = DUK__L0();	/* re-read to avoid spill / fetch */
+				if (state == 0) {
+					if (x == '/') {
+						DUK__ADVANCE(lex_ctx, 1);	/* eat closing slash */
+						break;
+					} else if (x == '\\') {
+						state = 1;
+					} else if (x == '[') {
+						state = 2;
+					}
+				} else if (state == 1) {
+					state = 0;
+				} else if (state == 2) {
+					if (x == ']') {
+						state = 0;
+					} else if (x == '\\') {
+						state = 3;
+					}
+				} else { /* state == 3 */
+					state = 2;
+				}
+				DUK__APPENDBUFFER(lex_ctx, x);
+			}
+			duk__internbuffer(lex_ctx, lex_ctx->slot1_idx);
+			out_token->str1 = duk_get_hstring((duk_context *) lex_ctx->thr, lex_ctx->slot1_idx);
+
+			/* second, parse flags */
+
+			DUK__INITBUFFER(lex_ctx);
+			for (;;) {
+				x = DUK__L0();
+				if (!duk_unicode_is_identifier_part(x)) {
+					break;
+				}
+				x = DUK__L0();	/* re-read to avoid spill / fetch */
+				DUK__APPENDBUFFER(lex_ctx, x);
+				DUK__ADVANCE(lex_ctx, 1);
+			}
+			duk__internbuffer(lex_ctx, lex_ctx->slot2_idx);
+			out_token->str2 = duk_get_hstring((duk_context *) lex_ctx->thr, lex_ctx->slot2_idx);
+
+			DUK__INITBUFFER(lex_ctx);	/* free some memory */
+
+			/* validation of the regexp is caller's responsibility */
+
+			advtok = DUK__ADVTOK(0, DUK_TOK_REGEXP);
+#else
+			DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR, "regexp support disabled");
+#endif
+		} else if (y == '=') {
+			/* "/=" and not in regexp mode */
+			advtok = DUK__ADVTOK(2, DUK_TOK_DIV_EQ);
+		} else {
+			/* "/" and not in regexp mode */
+			advtok = DUK__ADVTOK(1, DUK_TOK_DIV);
+		}
+	} else if (x == '{') {
+		advtok = DUK__ADVTOK(1, DUK_TOK_LCURLY);
+	} else if (x == '}') {
+		advtok = DUK__ADVTOK(1, DUK_TOK_RCURLY);
+	} else if (x == '(') {
+		advtok = DUK__ADVTOK(1, DUK_TOK_LPAREN);
+	} else if (x == ')') {
+		advtok = DUK__ADVTOK(1, DUK_TOK_RPAREN);
+	} else if (x == '[') {
+		advtok = DUK__ADVTOK(1, DUK_TOK_LBRACKET);
+	} else if (x == ']') {
+		advtok = DUK__ADVTOK(1, DUK_TOK_RBRACKET);
+	} else if (x == '.' && !DUK__ISDIGIT(y)) {
+		/* Note: period followed by a digit can only start DecimalLiteral (captured below) */
+		advtok = DUK__ADVTOK(1, DUK_TOK_PERIOD);
+	} else if (x == ';') {
+		advtok = DUK__ADVTOK(1, DUK_TOK_SEMICOLON);
+	} else if (x == ',') {
+		advtok = DUK__ADVTOK(1, DUK_TOK_COMMA);
+	} else if (x == '<') {
+		if (y == '<' && DUK__L2() == '=') {
+			advtok = DUK__ADVTOK(3, DUK_TOK_ALSHIFT_EQ);
+		} else if (y == '=') {
+			advtok = DUK__ADVTOK(2, DUK_TOK_LE);
+		} else if (y == '<') {
+			advtok = DUK__ADVTOK(2, DUK_TOK_ALSHIFT);
+		} else {
+			advtok = DUK__ADVTOK(1, DUK_TOK_LT);
+		}
+	} else if (x == '>') {
+		if (y == '>' && DUK__L2() == '>' && DUK__L3() == '=') {
+			advtok = DUK__ADVTOK(4, DUK_TOK_RSHIFT_EQ);
+		} else if (y == '>' && DUK__L2() == '>') {
+			advtok = DUK__ADVTOK(3, DUK_TOK_RSHIFT);
+		} else if (y == '>' && DUK__L2() == '=') {
+			advtok = DUK__ADVTOK(3, DUK_TOK_ARSHIFT_EQ);
+		} else if (y == '=') {
+			advtok = DUK__ADVTOK(2, DUK_TOK_GE);
+		} else if (y == '>') {
+			advtok = DUK__ADVTOK(2, DUK_TOK_ARSHIFT);
+		} else {
+			advtok = DUK__ADVTOK(1, DUK_TOK_GT);
+		}
+	} else if (x == '=') {
+		if (y == '=' && DUK__L2() == '=') {
+			advtok = DUK__ADVTOK(3, DUK_TOK_SEQ);
+		} else if (y == '=') {
+			advtok = DUK__ADVTOK(2, DUK_TOK_EQ);
+		} else {
+			advtok = DUK__ADVTOK(1, DUK_TOK_EQUALSIGN);
+		}
+	} else if (x == '!') {
+		if (y == '=' && DUK__L2() == '=') {
+			advtok = DUK__ADVTOK(3, DUK_TOK_SNEQ);
+		} else if (y == '=') {
+			advtok = DUK__ADVTOK(2, DUK_TOK_NEQ);
+		} else {
+			advtok = DUK__ADVTOK(1, DUK_TOK_LNOT);
+		}
+	} else if (x == '+') {
+		if (y == '+') {
+			advtok = DUK__ADVTOK(2, DUK_TOK_INCREMENT);
+		} else if (y == '=') {
+			advtok = DUK__ADVTOK(2, DUK_TOK_ADD_EQ);
+		} else {
+			advtok = DUK__ADVTOK(1, DUK_TOK_ADD);
+		}
+	} else if (x == '-') {
+		if (y == '-') {
+			advtok = DUK__ADVTOK(2, DUK_TOK_DECREMENT);
+		} else if (y == '=') {
+			advtok = DUK__ADVTOK(2, DUK_TOK_SUB_EQ);
+		} else {
+			advtok = DUK__ADVTOK(1, DUK_TOK_SUB);
+		}
+	} else if (x == '*') {
+		if (y == '=') {
+			advtok = DUK__ADVTOK(2, DUK_TOK_MUL_EQ);
+		} else {
+			advtok = DUK__ADVTOK(1, DUK_TOK_MUL);
+		}
+	} else if (x == '%') {
+		if (y == '=') {
+			advtok = DUK__ADVTOK(2, DUK_TOK_MOD_EQ);
+		} else {
+			advtok = DUK__ADVTOK(1, DUK_TOK_MOD);
+		}
+	} else if (x == '&') {
+		if (y == '&') {
+			advtok = DUK__ADVTOK(2, DUK_TOK_LAND);
+		} else if (y == '=') {
+			advtok = DUK__ADVTOK(2, DUK_TOK_BAND_EQ);
+		} else {
+			advtok = DUK__ADVTOK(1, DUK_TOK_BAND);
+		}
+	} else if (x == '|') {
+		if (y == '|') {
+			advtok = DUK__ADVTOK(2, DUK_TOK_LOR);
+		} else if (y == '=') {
+			advtok = DUK__ADVTOK(2, DUK_TOK_BOR_EQ);
+		} else {
+			advtok = DUK__ADVTOK(1, DUK_TOK_BOR);
+		}
+	} else if (x == '^') {
+		if (y == '=') {
+			advtok = DUK__ADVTOK(2, DUK_TOK_BXOR_EQ);
+		} else {
+			advtok = DUK__ADVTOK(1, DUK_TOK_BXOR);
+		}
+	} else if (x == '~') {
+		advtok = DUK__ADVTOK(1, DUK_TOK_BNOT);
+	} else if (x == '?') {
+		advtok = DUK__ADVTOK(1, DUK_TOK_QUESTION);
+	} else if (x == ':') {
+		advtok = DUK__ADVTOK(1, DUK_TOK_COLON);
+	} else if (duk_unicode_is_line_terminator(x)) {
+		if (x == 0x000d && y == 0x000a) {
+			/*
+			 *  E5 Section 7.3: CR LF is detected as a single line terminator for
+			 *  line numbers.  Here we also detect it as a single line terminator
+			 *  token.
+			 */
+			advtok = DUK__ADVTOK(2, DUK_TOK_LINETERM);
+		} else {
+			advtok = DUK__ADVTOK(1, DUK_TOK_LINETERM);
+		}
+	} else if (duk_unicode_is_identifier_start(x) || x == '\\') {
+		/*
+		 *  Parse an identifier and then check whether it is:
+		 *    - reserved word (keyword or other reserved word)
+		 *    - "null"  (NullLiteral)
+		 *    - "true"  (BooleanLiteral)
+		 *    - "false" (BooleanLiteral)
+		 *    - anything else => identifier
+		 *
+		 *  This does not follow the E5 productions cleanly, but is
+		 *  useful and compact.
+		 *
+		 *  Note that identifiers may contain Unicode escapes,
+		 *  see E5 Sections 6 and 7.6.  They must be decoded first,
+		 *  and the result checked against allowed characters.
+		 *  The above if-clause accepts an identifier start and an
+		 *  '\' character -- no other token can begin with a '\'.
+		 *
+		 *  Note that "get" and "set" are not reserved words in E5
+		 *  specification so they are recognized as plain identifiers
+		 *  (the tokens DUK_TOK_GET and DUK_TOK_SET are actually not
+		 *  used now).  The compiler needs to work around this.
+		 *
+		 *  Strictly speaking, following Ecmascript longest match
+		 *  specification, an invalid escape for the first character
+		 *  should cause a syntax error.  However, an invalid escape
+		 *  for IdentifierParts should just terminate the identifier
+		 *  early (longest match), and let the next tokenization
+		 *  fail.  For instance Rhino croaks with 'foo\z' when
+		 *  parsing the identifier.  This has little practical impact.
+		 */
+
+		duk_small_int_t i, i_end;
+		duk_bool_t first = 1;
+		duk_hstring *str;
+
+		DUK__INITBUFFER(lex_ctx);
+		for (;;) {
+			/* re-lookup first char on first loop */
+			if (DUK__L0() == '\\') {
+				duk_codepoint_t ch;
+				if (DUK__L1() != 'u') {
+					DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+					          "invalid unicode escape while parsing identifier");
+				}
+
+				ch = duk__decode_uniesc_from_window(lex_ctx, 2);
+
+				/* IdentifierStart is stricter than IdentifierPart, so if the first
+				 * character is escaped, must have a stricter check here.
+				 */
+				if (!(first ? duk_unicode_is_identifier_start(ch) : duk_unicode_is_identifier_part(ch))) {
+					DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+					          "invalid unicode escaped character while parsing identifier");
+				}
+				DUK__APPENDBUFFER(lex_ctx, ch);
+				DUK__ADVANCE(lex_ctx, 6);
+
+				/* Track number of escapes: necessary for proper keyword
+				 * detection.
+				 */
+				out_token->num_escapes++;
+			} else {
+				/* Note: first character is checked against this.  But because
+				 * IdentifierPart includes all IdentifierStart characters, and
+				 * the first character (if unescaped) has already been checked
+				 * in the if condition, this is OK.
+				 */
+				if (!duk_unicode_is_identifier_part(DUK__L0())) {
+					break;
+				}
+				DUK__APPENDBUFFER(lex_ctx, DUK__L0());
+				DUK__ADVANCE(lex_ctx, 1);
+			}
+			first = 0;
+		}
+
+		duk__internbuffer(lex_ctx, lex_ctx->slot1_idx);
+		out_token->str1 = duk_get_hstring((duk_context *) lex_ctx->thr, lex_ctx->slot1_idx);
+		str = out_token->str1;
+		DUK_ASSERT(str != NULL);
+		out_token->t_nores = DUK_TOK_IDENTIFIER;
+
+		DUK__INITBUFFER(lex_ctx);	/* free some memory */
+
+		/*
+		 *  Interned identifier is compared against reserved words, which are
+		 *  currently interned into the heap context.  See genstrings.py.
+		 *
+		 *  Note that an escape in the identifier disables recognition of
+		 *  keywords; e.g. "\u0069f = 1;" is a valid statement (assigns to
+		 *  identifier named "if").  This is not necessarily compliant,
+		 *  see test-dec-escaped-char-in-keyword.js.
+		 *
+		 *  Note: "get" and "set" are awkward.  They are not officially
+		 *  ReservedWords (and indeed e.g. "var set = 1;" is valid), and
+		 *  must come out as DUK_TOK_IDENTIFIER.  The compiler needs to
+		 *  work around this a bit.
+		 */
+
+		/* XXX: optimize by adding the token numbers directly into the
+		 * always interned duk_hstring objects (there should be enough
+		 * flag bits free for that)?
+		 */
+
+		i_end = (strict_mode ? DUK_STRIDX_END_RESERVED : DUK_STRIDX_START_STRICT_RESERVED);
+
+		advtok = DUK__ADVTOK(0, DUK_TOK_IDENTIFIER);
+		if (out_token->num_escapes == 0) {
+			for (i = DUK_STRIDX_START_RESERVED; i < i_end; i++) {
+				DUK_ASSERT(i >= 0 && i < DUK_HEAP_NUM_STRINGS);
+				if (lex_ctx->thr->strs[i] == str) {
+					advtok = DUK__ADVTOK(0, DUK_STRIDX_TO_TOK(i));
+					break;
+				}
+			}
+		}
+	} else if (DUK__ISDIGIT(x) || (x == '.')) {
+		/* Note: decimal number may start with a period, but must be followed by a digit */
+
+		/*
+		 *  DecimalLiteral, HexIntegerLiteral, OctalIntegerLiteral
+		 *  "pre-parsing", followed by an actual, accurate parser step.
+		 *
+		 *  Note: the leading sign character ('+' or '-') is -not- part of
+		 *  the production in E5 grammar, and that the a DecimalLiteral
+		 *  starting with a '0' must be followed by a non-digit.  Leading
+		 *  zeroes are syntax errors and must be checked for.
+		 *
+		 *  XXX: the two step parsing process is quite awkward, it would
+		 *  be more straightforward to allow numconv to parse the longest
+		 *  valid prefix (it already does that, it only needs to indicate
+		 *  where the input ended).  However, the lexer decodes characters
+		 *  using a lookup window, so this is not a trivial change.
+		 */
+
+		/* XXX: because of the final check below (that the literal is not
+		 * followed by a digit), this could maybe be simplified, if we bail
+		 * out early from a leading zero (and if there are no periods etc).
+		 * Maybe too complex.
+		 */
+
+		duk_double_t val;
+		duk_bool_t int_only = 0;
+		duk_bool_t allow_hex = 0;
+		duk_small_int_t state;  /* 0=before period/exp,
+		                         * 1=after period, before exp
+		                         * 2=after exp, allow '+' or '-'
+		                         * 3=after exp and exp sign
+		                         */
+		duk_small_uint_t s2n_flags;
+
+		DUK__INITBUFFER(lex_ctx);
+		if (x == '0' && (y == 'x' || y == 'X')) {
+			DUK__APPENDBUFFER(lex_ctx, x);
+			DUK__APPENDBUFFER(lex_ctx, y);
+			DUK__ADVANCE(lex_ctx, 2);
+			int_only = 1;
+			allow_hex = 1;
+#ifdef DUK_USE_OCTAL_SUPPORT
+		} else if (!strict_mode && x == '0' && DUK__ISDIGIT(y)) {
+			/* Note: if DecimalLiteral starts with a '0', it can only be
+			 * followed by a period or an exponent indicator which starts
+			 * with 'e' or 'E'.  Hence the if-check above ensures that
+			 * OctalIntegerLiteral is the only valid NumericLiteral
+			 * alternative at this point (even if y is, say, '9').
+			 */
+	
+			DUK__APPENDBUFFER(lex_ctx, x);
+			DUK__ADVANCE(lex_ctx, 1);
+			int_only = 1;
+#endif
+		}
+
+		state = 0;
+		for (;;) {
+			x = DUK__L0();	/* re-lookup curr char on first round */
+			if (DUK__ISDIGIT(x)) {
+				/* Note: intentionally allow leading zeroes here, as the
+				 * actual parser will check for them.
+				 */
+				if (state == 2) {
+					state = 3;
+				}
+			} else if (allow_hex && DUK__ISHEXDIGIT(x)) {
+				/* Note: 'e' and 'E' are also accepted here. */
+				;
+			} else if (x == '.') {
+				if (state >= 1 || int_only) {
+					break;
+				} else {
+					state = 1;
+				}
+			} else if (x == 'e' || x == 'E') {
+				if (state >= 2 || int_only) {
+					break;
+				} else {
+					state = 2;
+				}
+			} else if (x == '-' || x == '+') {
+				if (state != 2) {
+					break;
+				} else {
+					state = 3;
+				}
+			} else {
+				break;
+			}
+			DUK__APPENDBUFFER(lex_ctx, x);
+			DUK__ADVANCE(lex_ctx, 1);
+		}
+
+		/* XXX: better coercion */
+		duk__internbuffer(lex_ctx, lex_ctx->slot1_idx);
+
+		s2n_flags = DUK_S2N_FLAG_ALLOW_EXP |
+		            DUK_S2N_FLAG_ALLOW_FRAC |
+		            DUK_S2N_FLAG_ALLOW_NAKED_FRAC |
+		            DUK_S2N_FLAG_ALLOW_EMPTY_FRAC |
+#ifdef DUK_USE_OCTAL_SUPPORT
+		            (strict_mode ? 0 : DUK_S2N_FLAG_ALLOW_AUTO_OCT_INT) |
+#endif
+		            DUK_S2N_FLAG_ALLOW_AUTO_HEX_INT;
+
+		duk_dup((duk_context *) lex_ctx->thr, lex_ctx->slot1_idx);
+		duk_numconv_parse((duk_context *) lex_ctx->thr, 10 /*radix*/, s2n_flags);
+		val = duk_to_number((duk_context *) lex_ctx->thr, -1);
+		if (DUK_ISNAN(val)) {
+			DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR, "invalid numeric literal");
+		}
+		duk_replace((duk_context *) lex_ctx->thr, lex_ctx->slot1_idx);  /* could also just pop? */
+
+		DUK__INITBUFFER(lex_ctx);	/* free some memory */
+
+		/* Section 7.8.3 (note): NumericLiteral must be followed by something other than
+		 * IdentifierStart or DecimalDigit.
+		 */
+
+		if (DUK__ISDIGIT(DUK__L0()) || duk_unicode_is_identifier_start(DUK__L0())) {
+			DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR, "invalid numeric literal");
+		}
+
+		out_token->num = val;
+		advtok = DUK__ADVTOK(0, DUK_TOK_NUMBER);
+	} else if (x == '"' || x == '\'') {
+		duk_small_int_t quote = x;    /* Note: duk_uint8_t type yields larger code */
+		duk_small_int_t adv;
+
+		DUK__INITBUFFER(lex_ctx);
+		for (;;) {
+			DUK__ADVANCE(lex_ctx, 1);	/* eat opening quote on first loop */
+			x = DUK__L0();
+			if (x < 0 || duk_unicode_is_line_terminator(x)) {
+				DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+				          "eof or line terminator while parsing string literal");
+			}
+			if (x == quote) {
+				DUK__ADVANCE(lex_ctx, 1);	/* eat closing quote */
+				break;
+			}
+			if (x == '\\') {
+				/* DUK__L0        -> '\' char
+				 * DUK__L1 ... DUK__L5 -> more lookup
+				 */
+
+				x = DUK__L1();
+				y = DUK__L2();
+
+				/* How much to advance before next loop; note that next loop
+				 * will advance by 1 anyway, so -1 from the total escape
+				 * length (e.g. len('\uXXXX') - 1 = 6 - 1).  As a default,
+				 * 1 is good.
+				 */
+				adv = 2 - 1;	/* note: long live range */
+
+				if (x < 0) {
+					DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+					          "eof while parsing string literal");
+				}
+				if (duk_unicode_is_line_terminator(x)) {
+					/* line continuation */
+					if (x == 0x000d && y == 0x000a) {
+						/* CR LF again a special case */
+						adv = 3 - 1;
+					}
+				} else if (x == '\'') {
+					DUK__APPENDBUFFER(lex_ctx, 0x0027);
+				} else if (x == '"') {
+					DUK__APPENDBUFFER(lex_ctx, 0x0022);
+				} else if (x == '\\') {
+					DUK__APPENDBUFFER(lex_ctx, 0x005c);
+				} else if (x == 'b') {
+					DUK__APPENDBUFFER(lex_ctx, 0x0008);
+				} else if (x == 'f') {
+					DUK__APPENDBUFFER(lex_ctx, 0x000c);
+				} else if (x == 'n') {
+					DUK__APPENDBUFFER(lex_ctx, 0x000a);
+				} else if (x == 'r') {
+					DUK__APPENDBUFFER(lex_ctx, 0x000d);
+				} else if (x == 't') {
+					DUK__APPENDBUFFER(lex_ctx, 0x0009);
+				} else if (x == 'v') {
+					DUK__APPENDBUFFER(lex_ctx, 0x000b);
+				} else if (x == 'x') {
+					adv = 4 - 1;
+					DUK__APPENDBUFFER(lex_ctx, duk__decode_hexesc_from_window(lex_ctx, 2));
+				} else if (x == 'u') {
+					adv = 6 - 1;
+					DUK__APPENDBUFFER(lex_ctx, duk__decode_uniesc_from_window(lex_ctx, 2));
+				} else if (DUK__ISDIGIT(x)) {
+					duk_codepoint_t ch = 0;  /* initialized to avoid warnings of unused var */
+
+					/*
+					 *  Octal escape or zero escape:
+					 *    \0                                     (lookahead not DecimalDigit)
+					 *    \1 ... \7                              (lookahead not DecimalDigit)
+					 *    \ZeroToThree OctalDigit                (lookahead not DecimalDigit)
+					 *    \FourToSeven OctalDigit                (no lookahead restrictions)
+					 *    \ZeroToThree OctalDigit OctalDigit     (no lookahead restrictions)
+					 *
+					 *  Zero escape is part of the standard syntax.  Octal escapes are
+					 *  defined in E5 Section B.1.2, and are only allowed in non-strict mode.
+					 *  Any other productions starting with a decimal digit are invalid.
+					 */
+
+					if (x == '0' && !DUK__ISDIGIT(y)) {
+						/* Zero escape (also allowed in non-strict mode) */
+						ch = 0;
+						/* adv = 2 - 1 default OK */
+#ifdef DUK_USE_OCTAL_SUPPORT
+					} else if (strict_mode) {
+						/* No other escape beginning with a digit in strict mode */
+						DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+						          "invalid escape while parsing string literal");
+					} else if (DUK__ISDIGIT03(x) && DUK__ISOCTDIGIT(y) && DUK__ISOCTDIGIT(DUK__L3())) {
+						/* Three digit octal escape, digits validated. */
+						adv = 4 - 1;
+						ch = (duk__hexval(lex_ctx, x) << 6) +
+						     (duk__hexval(lex_ctx, y) << 3) +
+						     duk__hexval(lex_ctx, DUK__L3());
+					} else if (((DUK__ISDIGIT03(x) && !DUK__ISDIGIT(DUK__L3())) || DUK__ISDIGIT47(x)) &&
+					           DUK__ISOCTDIGIT(y)) {
+						/* Two digit octal escape, digits validated.
+						 * 
+						 * The if-condition is a bit tricky.  We could catch e.g.
+						 * '\039' in the three-digit escape and fail it there (by
+					         * validating the digits), but we want to avoid extra
+						 * additional validation code.
+						 */
+						adv = 3 - 1;
+						ch = (duk__hexval(lex_ctx, x) << 3) +
+						     duk__hexval(lex_ctx, y);
+					} else if (DUK__ISDIGIT(x) && !DUK__ISDIGIT(y)) {
+						/* One digit octal escape, digit validated. */
+						/* adv = 2 default OK */
+						ch = duk__hexval(lex_ctx, x);
+#else
+					/* fall through to error */
+#endif
+					} else {
+						DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+						          "invalid escape while parsing string literal");
+					}
+
+					DUK__APPENDBUFFER(lex_ctx, ch);
+				} else {
+					/* escaped NonEscapeCharacter */
+					DUK__APPENDBUFFER(lex_ctx, x);
+				}
+				DUK__ADVANCE(lex_ctx, adv);
+
+				/* Track number of escapes; count not really needed but directive
+				 * prologues need to detect whether there were any escapes or line
+				 * continuations or not.
+				 */
+				out_token->num_escapes++;
+			} else {
+				/* part of string */
+				DUK__APPENDBUFFER(lex_ctx, x);
+			}
+		}
+
+		duk__internbuffer(lex_ctx, lex_ctx->slot1_idx);
+		out_token->str1 = duk_get_hstring((duk_context *) lex_ctx->thr, lex_ctx->slot1_idx);
+
+		DUK__INITBUFFER(lex_ctx);	/* free some memory */
+
+		advtok = DUK__ADVTOK(0, DUK_TOK_STRING);
+	} else if (x < 0) {
+		advtok = DUK__ADVTOK(0, DUK_TOK_EOF);
+	} else {
+		DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR, "error parsing token");
+	}
+
+	/*
+	 *  Shared exit path
+	 */
+
+	DUK__ADVANCE(lex_ctx, advtok >> 8);
+	out_token->t = advtok & 0xff;
+	if (out_token->t_nores < 0) {
+		out_token->t_nores = out_token->t;
+	}
+	out_token->end_line = lex_ctx->lines[0];
+}
+
+/*
+ *  Tokenize input until a non-whitespace, non-lineterm token is found.
+ *  Note in the output token whether a lineterm token preceded the starting
+ *  point (inclusive) and the result token.  This information is needed for
+ *  automatic semicolon insertion.
+ *
+ *  Future work:
+ *
+ *    * Merge with duk__parse_input_element_raw() because only this function is
+ *      called in practice.
+ */
+
+/* XXX: change mode flags into one flags argument? */
+
+void duk_lexer_parse_js_input_element(duk_lexer_ctx *lex_ctx,
+                                      duk_token *out_token,
+                                      duk_bool_t strict_mode,
+                                      duk_bool_t regexp_mode) {
+	duk_small_int_t tok;
+	duk_bool_t got_lineterm = 0;  /* got lineterm preceding non-whitespace, non-lineterm token */
+
+	for (;;) {
+		duk__parse_input_element_raw(lex_ctx, out_token, strict_mode, regexp_mode);
+		tok = out_token->t;
+
+		DUK_DDD(DUK_DDDPRINT("RAWTOKEN: %ld (line %ld-%ld)",
+		                     (long) tok, (long) out_token->start_line, (long) out_token->end_line));
+
+		if (tok == DUK_TOK_COMMENT) {
+			/* single-line comment or multi-line comment without an internal lineterm */
+			continue;
+		} else if (tok == DUK_TOK_LINETERM) {
+			/* lineterm or multi-line comment with an internal lineterm */
+			got_lineterm = 1;
+			continue;
+		} else {
+			break;
+		}
+	}
+
+	out_token->lineterm = got_lineterm;
+
+	/* Automatic semicolon insertion is allowed if a token is preceded
+	 * by line terminator(s), or terminates a statement list (right curly
+	 * or EOF).
+	 */
+	if (got_lineterm || tok == DUK_TOK_RCURLY || tok == DUK_TOK_EOF) {
+		out_token->allow_auto_semi = 1;
+	} else {
+		out_token->allow_auto_semi = 0;
+	}
+}
+
+#ifdef DUK_USE_REGEXP_SUPPORT
+
+/*
+ *  Parse a RegExp token.  The grammar is described in E5 Section 15.10.
+ *  Terminal constructions (such as quantifiers) are parsed directly here.
+ *
+ *  0xffffffffU is used as a marker for "infinity" in quantifiers.  Further,
+ *  DUK__MAX_RE_QUANT_DIGITS limits the maximum number of digits that
+ *  will be accepted for a quantifier.
+ */
+
+void duk_lexer_parse_re_token(duk_lexer_ctx *lex_ctx, duk_re_token *out_token) {
+	duk_small_int_t advtok = 0;  /* init is unnecessary but suppresses "may be used uninitialized" warnings */
+	duk_codepoint_t x, y;
+
+	if (++lex_ctx->token_count >= lex_ctx->token_limit) {
+		DUK_ERROR(lex_ctx->thr, DUK_ERR_RANGE_ERROR, "token limit");
+		return;  /* unreachable */
+	}
+
+	DUK_MEMZERO(out_token, sizeof(*out_token));
+
+	x = DUK__L0();
+	y = DUK__L1();
+
+	DUK_DDD(DUK_DDDPRINT("parsing regexp token, L0=%ld, L1=%ld", (long) x, (long) y));
+
+	switch (x) {
+	case '|': {
+		advtok = DUK__ADVTOK(1, DUK_RETOK_DISJUNCTION);
+		break;
+	}
+	case '^': {
+		advtok = DUK__ADVTOK(1, DUK_RETOK_ASSERT_START);
+		break;
+	}
+	case '$': {
+		advtok = DUK__ADVTOK(1, DUK_RETOK_ASSERT_END);
+		break;
+	}
+	case '?': {
+		out_token->qmin = 0;
+		out_token->qmax = 1;	
+		if (y == '?') {
+			advtok = DUK__ADVTOK(2, DUK_RETOK_QUANTIFIER);
+			out_token->greedy = 0;
+		} else {
+			advtok = DUK__ADVTOK(1, DUK_RETOK_QUANTIFIER);
+			out_token->greedy = 1;
+		}
+		break;
+	}
+	case '*': {
+		out_token->qmin = 0;
+		out_token->qmax = DUK_RE_QUANTIFIER_INFINITE;
+		if (y == '?') {
+			advtok = DUK__ADVTOK(2, DUK_RETOK_QUANTIFIER);
+			out_token->greedy = 0;
+		} else {
+			advtok = DUK__ADVTOK(1, DUK_RETOK_QUANTIFIER);
+			out_token->greedy = 1;
+		}
+		break;
+	}
+	case '+': {
+		out_token->qmin = 1;
+		out_token->qmax = DUK_RE_QUANTIFIER_INFINITE;
+		if (y == '?') {
+			advtok = DUK__ADVTOK(2, DUK_RETOK_QUANTIFIER);
+			out_token->greedy = 0;
+		} else {
+			advtok = DUK__ADVTOK(1, DUK_RETOK_QUANTIFIER);
+			out_token->greedy = 1;
+		}
+		break;
+	}
+	case '{': {
+		/* Production allows 'DecimalDigits', including leading zeroes */
+		duk_uint_fast32_t val1 = 0;
+		duk_uint_fast32_t val2 = DUK_RE_QUANTIFIER_INFINITE;
+		duk_small_int_t digits = 0;
+		for (;;) {
+			DUK__ADVANCE(lex_ctx, 1);	/* eat '{' on entry */
+			x = DUK__L0();
+			if (DUK__ISDIGIT(x)) {
+				if (digits >= DUK__MAX_RE_QUANT_DIGITS) {
+					DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+					          "invalid regexp quantifier (too many digits)");
+				}
+				digits++;
+				val1 = val1 * 10 + (duk_uint_fast32_t) duk__hexval(lex_ctx, x);
+			} else if (x == ',') {
+				if (val2 != DUK_RE_QUANTIFIER_INFINITE) {
+					DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+					          "invalid regexp quantifier (double comma)");
+				}
+				if (DUK__L1() == '}') {
+					/* form: { DecimalDigits , }, val1 = min count */
+					if (digits == 0) {
+						DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+						          "invalid regexp quantifier (missing digits)");
+					}
+					out_token->qmin = val1;
+					out_token->qmax = DUK_RE_QUANTIFIER_INFINITE;
+					DUK__ADVANCE(lex_ctx, 2);
+					break;
+				}
+				val2 = val1;
+				val1 = 0;
+				digits = 0;	/* not strictly necessary because of lookahead '}' above */
+			} else if (x == '}') {
+				if (digits == 0) {
+					DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+					          "invalid regexp quantifier (missing digits)");
+				}
+				if (val2 != DUK_RE_QUANTIFIER_INFINITE) {
+					/* val2 = min count, val1 = max count */
+					out_token->qmin = val2;
+					out_token->qmax = val1;
+				} else {
+					/* val1 = count */
+					out_token->qmin = val1;
+					out_token->qmax = val1;
+				}
+				DUK__ADVANCE(lex_ctx, 1);
+				break;
+			} else {
+				DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+				          "invalid regexp quantifier (unknown char)");
+			}
+		}
+		if (DUK__L0() == '?') {
+			out_token->greedy = 0;
+			DUK__ADVANCE(lex_ctx, 1);
+		} else {
+			out_token->greedy = 1;
+		}
+		advtok = DUK__ADVTOK(0, DUK_RETOK_QUANTIFIER);
+		break;
+	}
+	case '.': {
+		advtok = DUK__ADVTOK(1, DUK_RETOK_ATOM_PERIOD);
+		break;
+	}
+	case '\\': {
+		/* The E5.1 specification does not seem to allow IdentifierPart characters
+		 * to be used as identity escapes.  Unfortunately this includes '$', which
+		 * cannot be escaped as '\$'; it needs to be escaped e.g. as '\u0024'.
+		 * Many other implementations (including V8 and Rhino, for instance) do
+		 * accept '\$' as a valid identity escape, which is quite pragmatic.
+		 * See: test-regexp-identity-escape-dollar.js.
+		 */
+
+		advtok = DUK__ADVTOK(2, DUK_RETOK_ATOM_CHAR);	/* default: char escape (two chars) */
+		if (y == 'b') {
+			advtok = DUK__ADVTOK(2, DUK_RETOK_ASSERT_WORD_BOUNDARY);
+		} else if (y == 'B') {
+			advtok = DUK__ADVTOK(2, DUK_RETOK_ASSERT_NOT_WORD_BOUNDARY);
+		} else if (y == 'f') {
+			out_token->num = 0x000c;
+		} else if (y == 'n') {
+			out_token->num = 0x000a;
+		} else if (y == 't') {
+			out_token->num = 0x0009;
+		} else if (y == 'r') {
+			out_token->num = 0x000d;
+		} else if (y == 'v') {
+			out_token->num = 0x000b;
+		} else if (y == 'c') {
+			x = DUK__L2();
+			if ((x >= 'a' && x <= 'z') ||
+			    (x >= 'A' && x <= 'Z')) {
+				out_token->num = (x % 32);
+				advtok = DUK__ADVTOK(3, DUK_RETOK_ATOM_CHAR);
+			} else {
+				DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+				          "invalid regexp control escape");
+			}
+		} else if (y == 'x') {
+			out_token->num = duk__decode_hexesc_from_window(lex_ctx, 2);
+			advtok = DUK__ADVTOK(4, DUK_RETOK_ATOM_CHAR);
+		} else if (y == 'u') {
+			out_token->num = duk__decode_uniesc_from_window(lex_ctx, 2);
+			advtok = DUK__ADVTOK(6, DUK_RETOK_ATOM_CHAR);
+		} else if (y == 'd') {
+			advtok = DUK__ADVTOK(2, DUK_RETOK_ATOM_DIGIT);
+		} else if (y == 'D') {
+			advtok = DUK__ADVTOK(2, DUK_RETOK_ATOM_NOT_DIGIT);
+		} else if (y == 's') {
+			advtok = DUK__ADVTOK(2, DUK_RETOK_ATOM_WHITE);
+		} else if (y == 'S') {
+			advtok = DUK__ADVTOK(2, DUK_RETOK_ATOM_NOT_WHITE);
+		} else if (y == 'w') {
+			advtok = DUK__ADVTOK(2, DUK_RETOK_ATOM_WORD_CHAR);
+		} else if (y == 'W') {
+			advtok = DUK__ADVTOK(2, DUK_RETOK_ATOM_NOT_WORD_CHAR);
+		} else if (DUK__ISDIGIT(y)) {
+			/* E5 Section 15.10.2.11 */
+			if (y == '0') {
+				if (DUK__ISDIGIT(DUK__L2())) {
+					DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+					          "invalid regexp escape");
+				}
+				out_token->num = 0x0000;
+				advtok = DUK__ADVTOK(2, DUK_RETOK_ATOM_CHAR);
+			} else {
+				/* XXX: shared parsing? */
+				duk_uint_fast32_t val = 0;
+				duk_small_int_t i;
+				for (i = 0; ; i++) {
+					if (i >= DUK__MAX_RE_DECESC_DIGITS) {
+						DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+						          "invalid regexp escape (decimal escape too long)");
+					}
+					DUK__ADVANCE(lex_ctx, 1);	/* eat backslash on entry */
+					x = DUK__L0();
+					if (!DUK__ISDIGIT(x)) {
+						break;
+					}
+					val = val * 10 + (duk_uint_fast32_t) duk__hexval(lex_ctx, x);
+				}
+				/* DUK__L0() cannot be a digit, because the loop doesn't terminate if it is */
+				advtok = DUK__ADVTOK(0, DUK_RETOK_ATOM_BACKREFERENCE);
+				out_token->num = val;
+			}
+		} else if ((y >= 0 && !duk_unicode_is_identifier_part(y)) ||
+#if defined(DUK_USE_NONSTD_REGEXP_DOLLAR_ESCAPE)
+		           y == '$' ||
+#endif
+		           y == DUK_UNICODE_CP_ZWNJ ||
+		           y == DUK_UNICODE_CP_ZWJ) {
+			/* IdentityEscape, with dollar added as a valid additional
+			 * non-standard escape (see test-regexp-identity-escape-dollar.js).
+			 * Careful not to match end-of-buffer (<0) here.
+			 */
+			out_token->num = y;
+		} else {
+			DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+			          "invalid regexp escape");
+		}
+		break;
+	}
+	case '(': {
+		/* XXX: naming is inconsistent: ATOM_END_GROUP ends an ASSERT_START_LOOKAHEAD */
+
+		if (y == '?') {
+			if (DUK__L2() == '=') {
+				/* (?= */
+				advtok = DUK__ADVTOK(3, DUK_RETOK_ASSERT_START_POS_LOOKAHEAD);
+			} else if (DUK__L2() == '!') {
+				/* (?! */
+				advtok = DUK__ADVTOK(3, DUK_RETOK_ASSERT_START_NEG_LOOKAHEAD);
+			} else if (DUK__L2() == ':') {
+				/* (?: */
+				advtok = DUK__ADVTOK(3, DUK_RETOK_ATOM_START_NONCAPTURE_GROUP);
+			}
+		} else {
+			/* ( */
+			advtok = DUK__ADVTOK(1, DUK_RETOK_ATOM_START_CAPTURE_GROUP);
+		}
+		break;
+	}
+	case ')': {
+		advtok = DUK__ADVTOK(1, DUK_RETOK_ATOM_END_GROUP);
+		break;
+	}
+	case '[': {
+		/*
+		 *  To avoid creating a heavy intermediate value for the list of ranges,
+		 *  only the start token ('[' or '[^') is parsed here.  The regexp
+		 *  compiler parses the ranges itself.
+		 */
+		advtok = DUK__ADVTOK(1, DUK_RETOK_ATOM_START_CHARCLASS);
+		if (y == '^') {
+			advtok = DUK__ADVTOK(2, DUK_RETOK_ATOM_START_CHARCLASS_INVERTED);
+		}
+		break;
+	}
+	case ']':
+	case '}': {
+		/* Although these could be parsed as PatternCharacters unambiguously (here),
+		 * E5 Section 15.10.1 grammar explicitly forbids these as PatternCharacters.
+		 */
+		DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+		          "invalid regexp character");
+		break;
+	}
+	case -1: {
+		/* EOF */
+		advtok = DUK__ADVTOK(0, DUK_TOK_EOF);
+		break;
+	}
+	default: {
+		/* PatternCharacter, all excluded characters are matched by cases above */
+		advtok = DUK__ADVTOK(1, DUK_RETOK_ATOM_CHAR);
+		out_token->num = x;
+		break;
+	}
+	}
+
+	/*
+	 *  Shared exit path
+	 */
+
+	DUK__ADVANCE(lex_ctx, advtok >> 8);
+	out_token->t = advtok & 0xff;
+}
+
+/*
+ *  Special parser for character classes; calls callback for every
+ *  range parsed and returns the number of ranges present.
+ */
+
+/* XXX: this duplicates functionality in duk_regexp.c where a similar loop is
+ * required anyway.  We could use that BUT we need to update the regexp compiler
+ * 'nranges' too.  Work this out a bit more cleanly to save space.
+ */
+
+/* XXX: the handling of character range detection is a bit convoluted.
+ * Try to simplify and make smaller.
+ */
+
+/* XXX: logic for handling character ranges is now incorrect, it will accept
+ * e.g. [\d-z] whereas it should croak from it?  SMJS accepts this too, though.
+ *
+ * Needs a read through and a lot of additional tests.
+ */
+
+static void duk__emit_u16_direct_ranges(duk_lexer_ctx *lex_ctx,
+                                        duk_re_range_callback gen_range,
+                                        void *userdata,
+                                        duk_uint16_t *ranges,
+                                        int num) {
+	duk_uint16_t *ranges_end;
+
+	DUK_UNREF(lex_ctx);
+
+	ranges_end = ranges + num;
+	while (ranges < ranges_end) {
+		/* mark range 'direct', bypass canonicalization (see Wiki) */
+		gen_range(userdata, (duk_codepoint_t) ranges[0], (duk_codepoint_t) ranges[1], 1);
+		ranges += 2;
+	}
+}
+
+void duk_lexer_parse_re_ranges(duk_lexer_ctx *lex_ctx, duk_re_range_callback gen_range, void *userdata) {
+	duk_codepoint_t start = -1;
+	duk_codepoint_t ch;
+	duk_codepoint_t x;
+	duk_bool_t dash = 0;
+
+	DUK_DD(DUK_DDPRINT("parsing regexp ranges"));
+
+	for (;;) {
+		x = DUK__L0();
+		DUK__ADVANCE(lex_ctx, 1);
+
+		ch = -1;  /* not strictly necessary, but avoids "uninitialized variable" warnings */
+		DUK_UNREF(ch);
+
+		if (x < 0) {
+			DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+			          "eof while parsing character class");
+		} else if (x == ']') {
+			DUK_ASSERT(!dash);	/* lookup should prevent this */
+			if (start >= 0) {
+				gen_range(userdata, start, start, 0);
+			}
+			break;
+		} else if (x == '-') {
+			if (start >= 0 && !dash && DUK__L0() != ']') {
+				/* '-' as a range indicator */
+				dash = 1;
+				continue;
+			} else {
+				/* '-' verbatim */
+				ch = x;
+			}
+		} else if (x == '\\') {
+			/*
+			 *  The escapes are same as outside a character class, except that \b has a
+			 *  different meaning, and \B and backreferences are prohibited (see E5
+			 *  Section 15.10.2.19).  However, it's difficult to share code because we
+			 *  handle e.g. "\n" very differently: here we generate a single character
+			 *  range for it.
+			 */
+
+			x = DUK__L0();
+			DUK__ADVANCE(lex_ctx, 1);
+
+			if (x == 'b') {
+				/* Note: '\b' in char class is different than outside (assertion),
+				 * '\B' is not allowed and is caught by the duk_unicode_is_identifier_part()
+				 * check below.
+				 */
+				ch = 0x0008;
+			} else if (x == 'f') {
+				ch = 0x000c;
+			} else if (x == 'n') {
+				ch = 0x000a;
+			} else if (x == 't') {
+				ch = 0x0009;
+			} else if (x == 'r') {
+				ch = 0x000d;
+			} else if (x == 'v') {
+				ch = 0x000b;
+			} else if (x == 'c') {
+				x = DUK__L0();
+				DUK__ADVANCE(lex_ctx, 1);
+				if ((x >= 'a' && x <= 'z') ||
+				    (x >= 'A' && x <= 'Z')) {
+					ch = (x % 32);
+				} else {
+					DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+					          "invalid regexp control escape");
+					return;  /* never reached, but avoids warnings of
+					          * potentially unused variables.
+					          */
+				}
+			} else if (x == 'x') {
+				ch = duk__decode_hexesc_from_window(lex_ctx, 0);
+				DUK__ADVANCE(lex_ctx, 2);
+			} else if (x == 'u') {
+				ch = duk__decode_uniesc_from_window(lex_ctx, 0);
+				DUK__ADVANCE(lex_ctx, 4);
+			} else if (x == 'd') {
+				duk__emit_u16_direct_ranges(lex_ctx,
+				                            gen_range,
+				                            userdata,
+				                            duk_unicode_re_ranges_digit,
+				                            sizeof(duk_unicode_re_ranges_digit) / sizeof(duk_uint16_t));
+				ch = -1;
+			} else if (x == 'D') {
+				duk__emit_u16_direct_ranges(lex_ctx,
+				                            gen_range,
+				                            userdata,
+				                            duk_unicode_re_ranges_not_digit,
+				                            sizeof(duk_unicode_re_ranges_not_digit) / sizeof(duk_uint16_t));
+				ch = -1;
+			} else if (x == 's') {
+				duk__emit_u16_direct_ranges(lex_ctx,
+				                            gen_range,
+				                            userdata,
+				                            duk_unicode_re_ranges_white,
+				                            sizeof(duk_unicode_re_ranges_white) / sizeof(duk_uint16_t));
+				ch = -1;
+			} else if (x == 'S') {
+				duk__emit_u16_direct_ranges(lex_ctx,
+				                            gen_range,
+				                            userdata,
+				                            duk_unicode_re_ranges_not_white,
+				                            sizeof(duk_unicode_re_ranges_not_white) / sizeof(duk_uint16_t));
+				ch = -1;
+			} else if (x == 'w') {
+				duk__emit_u16_direct_ranges(lex_ctx,
+				                            gen_range,
+				                            userdata,
+				                            duk_unicode_re_ranges_wordchar,
+				                            sizeof(duk_unicode_re_ranges_wordchar) / sizeof(duk_uint16_t));
+				ch = -1;
+			} else if (x == 'W') {
+				duk__emit_u16_direct_ranges(lex_ctx,
+				                            gen_range,
+				                            userdata,
+				                            duk_unicode_re_ranges_not_wordchar,
+				                            sizeof(duk_unicode_re_ranges_not_wordchar) / sizeof(duk_uint16_t));
+				ch = -1;
+			} else if (DUK__ISDIGIT(x)) {
+				/* DecimalEscape, only \0 is allowed, no leading zeroes are allowed */
+				if (x == 0 && !DUK__ISDIGIT(DUK__L0())) {
+					ch = 0x0000;
+				} else {
+					DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+					          "invalid decimal escape");
+				}
+			} else if (!duk_unicode_is_identifier_part(x)
+#if defined(DUK_USE_NONSTD_REGEXP_DOLLAR_ESCAPE)
+			           || x == '$'
+#endif
+			          ) {
+				/* IdentityEscape */
+				ch = x;
+			} else {
+				DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+				          "invalid regexp escape");
+			}
+		} else {
+			/* character represents itself */
+			ch = x;
+		}
+
+		/* ch is a literal character here or -1 if parsed entity was
+		 * an escape such as "\s".
+		 */
+
+		if (ch < 0) {
+			/* multi-character sets not allowed as part of ranges, see
+			 * E5 Section 15.10.2.15, abstract operation CharacterRange.
+			 */
+			if (start >= 0) {
+				if (dash) {
+					DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+					          "invalid range");
+				} else {
+					gen_range(userdata, start, start, 0);
+					start = -1;
+					/* dash is already 0 */
+				}
+			}
+		} else {
+			if (start >= 0) {
+				if (dash) {
+					if (start > ch) {
+						DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+						          "invalid range");
+					}
+					gen_range(userdata, start, ch, 0);
+					start = -1;
+					dash = 0;
+				} else {
+					gen_range(userdata, start, start, 0);
+					start = ch;
+					/* dash is already 0 */
+				}
+			} else {
+				start = ch;
+			}
+		}
+	}
+
+	return;
+}
+
+#endif  /* DUK_USE_REGEXP_SUPPORT */
+#line 1 "duk_numconv.c"
+/*
+ *  Number-to-string and string-to-number conversions.
+ *
+ *  Slow path number-to-string and string-to-number conversion is based on
+ *  a Dragon4 variant, with fast paths for small integers.  Big integer
+ *  arithmetic is needed for guaranteeing that the conversion is correct
+ *  and uses a minimum number of digits.  The big number arithmetic has a
+ *  fixed maximum size and does not require dynamic allocations.
+ *
+ *  See: doc/number-conversion.txt.
+ */
+
+/* include removed: duk_internal.h */
+
+#define DUK__IEEE_DOUBLE_EXP_BIAS  1023
+#define DUK__IEEE_DOUBLE_EXP_MIN   (-1022)   /* biased exp == 0 -> denormal, exp -1022 */
+
+#define DUK__DIGITCHAR(x)  duk_lc_digits[(x)]
+
+/*
+ *  Tables generated with src/gennumdigits.py.
+ *
+ *  duk__str2num_digits_for_radix indicates, for each radix, how many input
+ *  digits should be considered significant for string-to-number conversion.
+ *  The input is also padded to this many digits to give the Dragon4
+ *  conversion enough (apparent) precision to work with.
+ *
+ *  duk__str2num_exp_limits indicates, for each radix, the radix-specific
+ *  minimum/maximum exponent values (for a Dragon4 integer mantissa)
+ *  below and above which the number is guaranteed to underflow to zero
+ *  or overflow to Infinity.  This allows parsing to keep bigint values
+ *  bounded.
+ */
+
+static const duk_uint8_t duk__str2num_digits_for_radix[] = {
+	69, 44, 35, 30, 27, 25, 23, 22, 20, 20,    /* 2 to 11 */
+	20, 19, 19, 18, 18, 17, 17, 17, 16, 16,    /* 12 to 21 */
+	16, 16, 16, 15, 15, 15, 15, 15, 15, 14,    /* 22 to 31 */
+	14, 14, 14, 14, 14                         /* 31 to 36 */
+};
+
+typedef struct {
+	duk_int16_t upper;
+	duk_int16_t lower;
+} duk__exp_limits;
+
+static const duk__exp_limits duk__str2num_exp_limits[] = {
+	{ 957, -1147 }, { 605, -725 },  { 479, -575 },  { 414, -496 },
+	{ 372, -446 },  { 342, -411 },  { 321, -384 },  { 304, -364 },
+	{ 291, -346 },  { 279, -334 },  { 268, -323 },  { 260, -312 },
+	{ 252, -304 },  { 247, -296 },  { 240, -289 },  { 236, -283 },
+	{ 231, -278 },  { 227, -273 },  { 223, -267 },  { 220, -263 },
+	{ 216, -260 },  { 213, -256 },  { 210, -253 },  { 208, -249 },
+	{ 205, -246 },  { 203, -244 },  { 201, -241 },  { 198, -239 },
+	{ 196, -237 },  { 195, -234 },  { 193, -232 },  { 191, -230 },
+	{ 190, -228 },  { 188, -226 },  { 187, -225 },
+};
+
+/*
+ *  Limited functionality bigint implementation.
+ *
+ *  Restricted to non-negative numbers with less than 32 * DUK__BI_MAX_PARTS bits,
+ *  with the caller responsible for ensuring this is never exceeded.  No memory
+ *  allocation (except stack) is needed for bigint computation.  Operations
+ *  have been tailored for number conversion needs.
+ *
+ *  Argument order is "assignment order", i.e. target first, then arguments:
+ *  x <- y * z  -->  duk__bi_mul(x, y, z);
+ */
+
+/* This upper value has been experimentally determined; debug build will check
+ * bigint size with assertions.
+ */
+#define DUK__BI_MAX_PARTS  37  /* 37x32 = 1184 bits */
+
+#ifdef DUK_USE_DDDPRINT
+#define DUK__BI_PRINT(name,x)  duk__bi_print((name),(x))
+#else
+#define DUK__BI_PRINT(name,x)
+#endif
+
+/* Current size is about 152 bytes. */
+typedef struct {
+	duk_small_int_t n;
+	duk_uint32_t v[DUK__BI_MAX_PARTS];  /* low to high */
+} duk__bigint;
+
+#ifdef DUK_USE_DDDPRINT
+static void duk__bi_print(const char *name, duk__bigint *x) {
+	/* Overestimate required size; debug code so not critical to be tight. */
+	char buf[DUK__BI_MAX_PARTS * 9 + 64];
+	char *p = buf;
+	duk_small_int_t i;
+
+	/* No NUL term checks in this debug code. */
+	p += DUK_SPRINTF(p, "%p n=%ld", (void *) x, (long) x->n);
+	if (x->n == 0) {
+		p += DUK_SPRINTF(p, " 0");
+	}
+	for (i = x->n - 1; i >= 0; i--) {
+		p += DUK_SPRINTF(p, " %08lx", (unsigned long) x->v[i]);
+	}
+
+	DUK_DDD(DUK_DDDPRINT("%s: %s", (const char *) name, (const char *) buf));
+}
+#endif
+
+#ifdef DUK_USE_ASSERTIONS
+static duk_small_int_t duk__bi_is_valid(duk__bigint *x) {
+	return (duk_small_int_t) 
+	       ( ((x->n >= 0) && (x->n <= DUK__BI_MAX_PARTS)) /* is valid size */ &&
+	         ((x->n == 0) || (x->v[x->n - 1] != 0)) /* is normalized */ );
+}
+#endif
+
+static void duk__bi_normalize(duk__bigint *x) {
+	duk_small_int_t i;
+
+	for (i = x->n - 1; i >= 0; i--) {
+		if (x->v[i] != 0) {
+			break;
+		}
+	}
+
+	/* Note: if 'x' is zero, x->n becomes 0 here */
+	x->n = i + 1;
+	DUK_ASSERT(duk__bi_is_valid(x));
+}
+
+/* x <- y */
+static void duk__bi_copy(duk__bigint *x, duk__bigint *y) {
+	duk_small_int_t n;
+
+	n = y->n;
+	x->n = n;
+	if (n == 0) {
+		return;
+	}
+	DUK_MEMCPY((void *) x->v, (void *) y->v, (size_t) (sizeof(duk_uint32_t) * n));
+}
+
+static void duk__bi_set_small(duk__bigint *x, duk_uint32_t v) {
+	if (v == 0U) {
+		x->n = 0;
+	} else {
+		x->n = 1;
+		x->v[0] = v;
+	}
+	DUK_ASSERT(duk__bi_is_valid(x));
+}
+
+/* Return value: <0  <=>  x < y
+ *                0  <=>  x == y
+ *               >0  <=>  x > y
+ */
+static int duk__bi_compare(duk__bigint *x, duk__bigint *y) {
+	duk_small_int_t i, nx, ny;
+	duk_uint32_t tx, ty;
+
+	DUK_ASSERT(duk__bi_is_valid(x));
+	DUK_ASSERT(duk__bi_is_valid(y));
+
+	nx = x->n;
+	ny = y->n;
+	if (nx > ny) {
+		goto ret_gt;
+	}
+	if (nx < ny) {
+		goto ret_lt;
+	}
+	for (i = nx - 1; i >= 0; i--) {
+		tx = x->v[i];
+		ty = y->v[i];
+
+		if (tx > ty) {
+			goto ret_gt;
+		}
+		if (tx < ty) {
+			goto ret_lt;
+		}
+	}
+
+	return 0;
+
+ ret_gt:
+	return 1;
+
+ ret_lt:
+	return -1;
+}
+
+/* x <- y + z */
+#ifdef DUK_USE_64BIT_OPS
+static void duk__bi_add(duk__bigint *x, duk__bigint *y, duk__bigint *z) {
+	duk_uint64_t tmp;
+	duk_small_int_t i, ny, nz;
+
+	DUK_ASSERT(duk__bi_is_valid(y));
+	DUK_ASSERT(duk__bi_is_valid(z));
+
+	if (z->n > y->n) {
+		duk__bigint *t;
+		t = y; y = z; z = t;
+	}
+	DUK_ASSERT(y->n >= z->n);
+
+	ny = y->n; nz = z->n;
+	tmp = 0U;
+	for (i = 0; i < ny; i++) {
+		DUK_ASSERT(i < DUK__BI_MAX_PARTS);
+		tmp += y->v[i];
+		if (i < nz) {
+			tmp += z->v[i];
+		}
+		x->v[i] = (duk_uint32_t) (tmp & 0xffffffffUL);
+		tmp = tmp >> 32;
+	}
+	if (tmp != 0U) {
+		DUK_ASSERT(i < DUK__BI_MAX_PARTS);
+		x->v[i++] = (duk_uint32_t) tmp;
+	}
+	x->n = i;
+	DUK_ASSERT(x->n <= DUK__BI_MAX_PARTS);
+
+	/* no need to normalize */
+	DUK_ASSERT(duk__bi_is_valid(x));
+}
+#else  /* DUK_USE_64BIT_OPS */
+static void duk__bi_add(duk__bigint *x, duk__bigint *y, duk__bigint *z) {
+	duk_uint32_t carry, tmp1, tmp2;
+	duk_small_int_t i, ny, nz;
+
+	DUK_ASSERT(duk__bi_is_valid(y));
+	DUK_ASSERT(duk__bi_is_valid(z));
+
+	if (z->n > y->n) {
+		duk__bigint *t;
+		t = y; y = z; z = t;
+	}
+	DUK_ASSERT(y->n >= z->n);
+
+	ny = y->n; nz = z->n;
+	carry = 0U;
+	for (i = 0; i < ny; i++) {
+		/* Carry is detected based on wrapping which relies on exact 32-bit
+		 * types.
+		 */
+		DUK_ASSERT(i < DUK__BI_MAX_PARTS);
+		tmp1 = y->v[i];
+		tmp2 = tmp1;
+		if (i < nz) {
+			tmp2 += z->v[i];
+		}
+
+		/* Careful with carry condition:
+		 *  - If carry not added: 0x12345678 + 0 + 0xffffffff = 0x12345677 (< 0x12345678)
+		 *  - If carry added:     0x12345678 + 1 + 0xffffffff = 0x12345678 (== 0x12345678)
+		 */
+		if (carry) {
+			tmp2++;
+			carry = (tmp2 <= tmp1 ? 1U : 0U);
+		} else {
+			carry = (tmp2 < tmp1 ? 1U : 0U);
+		}
+
+		x->v[i] = tmp2;
+	}
+	if (carry) {
+		DUK_ASSERT(i < DUK__BI_MAX_PARTS);
+		DUK_ASSERT(carry == 1U);
+		x->v[i++] = carry;
+	}
+	x->n = i;
+	DUK_ASSERT(x->n <= DUK__BI_MAX_PARTS);
+
+	/* no need to normalize */
+	DUK_ASSERT(duk__bi_is_valid(x));
+}
+#endif  /* DUK_USE_64BIT_OPS */
+
+/* x <- y + z */
+static void duk__bi_add_small(duk__bigint *x, duk__bigint *y, duk_uint32_t z) {
+	duk__bigint tmp;
+
+	DUK_ASSERT(duk__bi_is_valid(y));
+
+	/* XXX: this could be optimized; there is only one call site now though */
+	duk__bi_set_small(&tmp, z);
+	duk__bi_add(x, y, &tmp);
+
+	DUK_ASSERT(duk__bi_is_valid(x));
+}
+
+#if 0  /* unused */
+/* x <- x + y, use t as temp */
+static void duk__bi_add_copy(duk__bigint *x, duk__bigint *y, duk__bigint *t) {
+	duk__bi_add(t, x, y);
+	duk__bi_copy(x, t);
+}
+#endif
+
+/* x <- y - z, require x >= y => z >= 0, i.e. y >= z */
+#ifdef DUK_USE_64BIT_OPS
+static void duk__bi_sub(duk__bigint *x, duk__bigint *y, duk__bigint *z) {
+	duk_small_int_t i, ny, nz;
+	duk_uint32_t ty, tz;
+	duk_int64_t tmp;
+
+	DUK_ASSERT(duk__bi_is_valid(y));
+	DUK_ASSERT(duk__bi_is_valid(z));
+	DUK_ASSERT(duk__bi_compare(y, z) >= 0);
+	DUK_ASSERT(y->n >= z->n);
+
+	ny = y->n; nz = z->n;
+	tmp = 0;
+	for (i = 0; i < ny; i++) {
+		ty = y->v[i];
+		if (i < nz) {
+			tz = z->v[i];
+		} else {
+			tz = 0;
+		}
+		tmp = (duk_int64_t) ty - (duk_int64_t) tz + tmp;
+		x->v[i] = (duk_uint32_t) (tmp & 0xffffffffUL);
+		tmp = tmp >> 32;  /* 0 or -1 */
+	}
+	DUK_ASSERT(tmp == 0);
+
+	x->n = i;
+	duk__bi_normalize(x);  /* need to normalize, may even cancel to 0 */
+	DUK_ASSERT(duk__bi_is_valid(x));
+}
+#else
+static void duk__bi_sub(duk__bigint *x, duk__bigint *y, duk__bigint *z) {
+	duk_small_int_t i, ny, nz;
+	duk_uint32_t tmp1, tmp2, borrow;
+
+	DUK_ASSERT(duk__bi_is_valid(y));
+	DUK_ASSERT(duk__bi_is_valid(z));
+	DUK_ASSERT(duk__bi_compare(y, z) >= 0);
+	DUK_ASSERT(y->n >= z->n);
+
+	ny = y->n; nz = z->n;
+	borrow = 0U;
+	for (i = 0; i < ny; i++) {
+		/* Borrow is detected based on wrapping which relies on exact 32-bit
+		 * types.
+		 */
+		tmp1 = y->v[i];
+		tmp2 = tmp1;
+		if (i < nz) {
+			tmp2 -= z->v[i];
+		}
+
+		/* Careful with borrow condition:
+		 *  - If borrow not subtracted: 0x12345678 - 0 - 0xffffffff = 0x12345679 (> 0x12345678)
+		 *  - If borrow subtracted:     0x12345678 - 1 - 0xffffffff = 0x12345678 (== 0x12345678)
+		 */
+		if (borrow) {
+			tmp2--;
+			borrow = (tmp2 >= tmp1 ? 1U : 0U);
+		} else {
+			borrow = (tmp2 > tmp1 ? 1U : 0U);
+		}
+
+		x->v[i] = tmp2;
+	}
+	DUK_ASSERT(borrow == 0U);
+
+	x->n = i;
+	duk__bi_normalize(x);  /* need to normalize, may even cancel to 0 */
+	DUK_ASSERT(duk__bi_is_valid(x));
+}
+#endif
+
+#if 0  /* unused */
+/* x <- y - z */
+static void duk__bi_sub_small(duk__bigint *x, duk__bigint *y, duk_uint32_t z) {
+	duk__bigint tmp;
+
+	DUK_ASSERT(duk__bi_is_valid(y));
+
+	/* XXX: this could be optimized */
+	duk__bi_set_small(&tmp, z);
+	duk__bi_sub(x, y, &tmp);
+
+	DUK_ASSERT(duk__bi_is_valid(x));
+}
+#endif
+
+/* x <- x - y, use t as temp */
+static void duk__bi_sub_copy(duk__bigint *x, duk__bigint *y, duk__bigint *t) {
+	duk__bi_sub(t, x, y);
+	duk__bi_copy(x, t);
+}
+
+/* x <- y * z */
+static void duk__bi_mul(duk__bigint *x, duk__bigint *y, duk__bigint *z) {
+	duk_small_int_t i, j, nx, nz;
+
+	DUK_ASSERT(duk__bi_is_valid(y));
+	DUK_ASSERT(duk__bi_is_valid(z));
+
+	nx = y->n + z->n;  /* max possible */
+	DUK_ASSERT(nx <= DUK__BI_MAX_PARTS);
+
+	if (nx == 0) {
+		/* Both inputs are zero; cases where only one is zero can go
+		 * through main algorithm.
+		 */
+		x->n = 0;
+		return;
+	}
+
+	DUK_MEMZERO((void *) x->v, (size_t) (sizeof(duk_uint32_t) * nx));
+	x->n = nx;
+
+	nz = z->n;
+	for (i = 0; i < y->n; i++) {
+#ifdef DUK_USE_64BIT_OPS
+		duk_uint64_t tmp = 0U;
+		for (j = 0; j < nz; j++) {
+			tmp += (duk_uint64_t) y->v[i] * (duk_uint64_t) z->v[j] + x->v[i+j];
+			x->v[i+j] = (duk_uint32_t) (tmp & 0xffffffffUL);
+			tmp = tmp >> 32;
+		}
+		if (tmp > 0) {
+			DUK_ASSERT(i + j < nx);
+			DUK_ASSERT(i + j < DUK__BI_MAX_PARTS);
+			DUK_ASSERT(x->v[i+j] == 0U);
+			x->v[i+j] = (duk_uint32_t) tmp;
+		}
+#else
+		/*
+		 *  Multiply + add + carry for 32-bit components using only 16x16->32
+		 *  multiplies and carry detection based on unsigned overflow.
+		 *
+		 *    1st mult, 32-bit: (A*2^16 + B)
+		 *    2nd mult, 32-bit: (C*2^16 + D)
+		 *    3rd add, 32-bit: E
+		 *    4th add, 32-bit: F
+		 *
+		 *      (AC*2^16 + B) * (C*2^16 + D) + E + F
+		 *    = AC*2^32 + AD*2^16 + BC*2^16 + BD + E + F
+		 *    = AC*2^32 + (AD + BC)*2^16 + (BD + E + F)
+		 *    = AC*2^32 + AD*2^16 + BC*2^16 + (BD + E + F)
+		 */
+		duk_uint32_t a, b, c, d, e, f;
+		duk_uint32_t r, s, t;
+
+		a = y->v[i]; b = a & 0xffffUL; a = a >> 16;
+
+		f = 0;
+		for (j = 0; j < nz; j++) {
+			c = z->v[j]; d = c & 0xffffUL; c = c >> 16;
+			e = x->v[i+j];
+
+			/* build result as: (r << 32) + s: start with (BD + E + F) */
+			r = 0;
+			s = b * d;
+
+			/* add E */
+			t = s + e;
+			if (t < s) { r++; }  /* carry */
+			s = t;
+
+			/* add F */
+			t = s + f;
+			if (t < s) { r++; }  /* carry */
+			s = t;
+
+			/* add BC*2^16 */
+			t = b * c;
+			r += (t >> 16);
+			t = s + ((t & 0xffffUL) << 16);
+			if (t < s) { r++; }  /* carry */
+			s = t;
+
+			/* add AD*2^16 */
+			t = a * d;
+			r += (t >> 16);
+			t = s + ((t & 0xffffUL) << 16);
+			if (t < s) { r++; }  /* carry */
+			s = t;
+
+			/* add AC*2^32 */
+			t = a * c;
+			r += t;
+
+			DUK_DDD(DUK_DDDPRINT("ab=%08lx cd=%08lx ef=%08lx -> rs=%08lx %08lx",
+			                     (unsigned long) y->v[i], (unsigned long) z->v[j],
+			                     (unsigned long) x->v[i+j], (unsigned long) r,
+			                     (unsigned long) s));
+
+			x->v[i+j] = s;
+			f = r;
+		}
+		if (f > 0U) {
+			DUK_ASSERT(i + j < nx);
+			DUK_ASSERT(i + j < DUK__BI_MAX_PARTS);
+			DUK_ASSERT(x->v[i+j] == 0U);
+			x->v[i+j] = (duk_uint32_t) f;
+		}
+#endif  /* DUK_USE_64BIT_OPS */
+	}
+
+	duk__bi_normalize(x);
+	DUK_ASSERT(duk__bi_is_valid(x));
+}
+
+/* x <- y * z */
+static void duk__bi_mul_small(duk__bigint *x, duk__bigint *y, duk_uint32_t z) {
+	duk__bigint tmp;
+
+	DUK_ASSERT(duk__bi_is_valid(y));
+
+	/* XXX: this could be optimized */
+	duk__bi_set_small(&tmp, z);
+	duk__bi_mul(x, y, &tmp);
+
+	DUK_ASSERT(duk__bi_is_valid(x));
+}
+
+/* x <- x * y, use t as temp */
+static void duk__bi_mul_copy(duk__bigint *x, duk__bigint *y, duk__bigint *t) {
+	duk__bi_mul(t, x, y);
+	duk__bi_copy(x, t);
+}
+
+/* x <- x * y, use t as temp */
+static void duk__bi_mul_small_copy(duk__bigint *x, duk_uint32_t y, duk__bigint *t) {
+	duk__bi_mul_small(t, x, y);
+	duk__bi_copy(x, t);
+}
+
+static int duk__bi_is_even(duk__bigint *x) {
+	DUK_ASSERT(duk__bi_is_valid(x));
+	return (x->n == 0) || ((x->v[0] & 0x01) == 0);
+}
+
+static int duk__bi_is_zero(duk__bigint *x) {
+	DUK_ASSERT(duk__bi_is_valid(x));
+	return (x->n == 0);  /* this is the case for normalized numbers */
+}
+
+/* Bigint is 2^52.  Used to detect normalized IEEE double mantissa values
+ * which are at the lowest edge (next floating point value downwards has
+ * a different exponent).  The lowest mantissa has the form:
+ *
+ *     1000........000    (52 zeroes; only "hidden bit" is set)
+ */
+static duk_small_int_t duk__bi_is_2to52(duk__bigint *x) {
+	DUK_ASSERT(duk__bi_is_valid(x));
+	return (duk_small_int_t)
+	        (x->n == 2) && (x->v[0] == 0U) && (x->v[1] == (1U << (52-32)));
+}
+
+/* x <- (1<<y) */
+static void duk__bi_twoexp(duk__bigint *x, duk_small_int_t y) {
+	duk_small_int_t n, r;
+
+	n = (y / 32) + 1;
+	DUK_ASSERT(n > 0);
+	r = y % 32;
+	DUK_MEMZERO((void *) x->v, sizeof(duk_uint32_t) * n);
+	x->n = n;
+	x->v[n - 1] = (((duk_uint32_t) 1) << r);
+}
+
+/* x <- b^y; use t1 and t2 as temps */
+static void duk__bi_exp_small(duk__bigint *x, duk_small_int_t b, duk_small_int_t y, duk__bigint *t1, duk__bigint *t2) {
+	/* Fast path the binary case */
+
+	DUK_ASSERT(x != t1 && x != t2 && t1 != t2);  /* distinct bignums, easy mistake to make */
+	DUK_ASSERT(b >= 0);
+	DUK_ASSERT(y >= 0);
+
+	if (b == 2) {
+		duk__bi_twoexp(x, y);
+		return;
+	}
+
+	/* http://en.wikipedia.org/wiki/Exponentiation_by_squaring */
+
+	DUK_DDD(DUK_DDDPRINT("exp_small: b=%ld, y=%ld", (long) b, (long) y));
+
+	duk__bi_set_small(x, 1);
+	duk__bi_set_small(t1, b);
+	for (;;) {
+		/* Loop structure ensures that we don't compute t1^2 unnecessarily
+		 * on the final round, as that might create a bignum exceeding the
+		 * current DUK__BI_MAX_PARTS limit.
+		 */
+		if (y & 0x01) {
+			duk__bi_mul_copy(x, t1, t2);
+		}
+		y = y >> 1;
+		if (y == 0) {
+			break;
+		}
+		duk__bi_mul_copy(t1, t1, t2);
+	}
+
+	DUK__BI_PRINT("exp_small result", x);
+}
+
+/*
+ *  A Dragon4 number-to-string variant, based on:
+ *
+ *    Guy L. Steele Jr., Jon L. White: "How to Print Floating-Point Numbers
+ *    Accurately"
+ *
+ *    Robert G. Burger, R. Kent Dybvig: "Printing Floating-Point Numbers
+ *    Quickly and Accurately"
+ *
+ *  The current algorithm is based on Figure 1 of the Burger-Dybvig paper,
+ *  i.e. the base implementation without logarithm estimation speedups
+ *  (these would increase code footprint considerably).  Fixed-format output
+ *  does not follow the suggestions in the paper; instead, we generate an
+ *  extra digit and round-with-carry.
+ *
+ *  The same algorithm is used for number parsing (with b=10 and B=2)
+ *  by generating one extra digit and doing rounding manually.
+ *
+ *  See doc/number-conversion.txt for limitations.
+ */
+
+/* Maximum number of digits generated. */
+#define DUK__MAX_OUTPUT_DIGITS          1040  /* (Number.MAX_VALUE).toString(2).length == 1024, + spare */
+
+/* Maximum number of characters in formatted value. */
+#define DUK__MAX_FORMATTED_LENGTH       1040  /* (-Number.MAX_VALUE).toString(2).length == 1025, + spare */
+
+/* Number and (minimum) size of bigints in the nc_ctx structure. */
+#define DUK__NUMCONV_CTX_NUM_BIGINTS    7
+#define DUK__NUMCONV_CTX_BIGINTS_SIZE   (sizeof(duk__bigint) * DUK__NUMCONV_CTX_NUM_BIGINTS)
+
+typedef struct {
+	/* Currently about 7*152 = 1064 bytes.  The space for these
+	 * duk__bigints is used also as a temporary buffer for generating
+	 * the final string.  This is a bit awkard; a union would be
+	 * more correct.
+	 */
+	duk__bigint f, r, s, mp, mm, t1, t2;
+
+	duk_small_int_t is_s2n;        /* if 1, doing a string-to-number; else doing a number-to-string */
+	duk_small_int_t is_fixed;      /* if 1, doing a fixed format output (not free format) */
+	duk_small_int_t req_digits;    /* requested number of output digits; 0 = free-format */
+	duk_small_int_t abs_pos;       /* digit position is absolute, not relative */
+	duk_small_int_t e;             /* exponent for 'f' */
+	duk_small_int_t b;             /* input radix */
+	duk_small_int_t B;             /* output radix */
+	duk_small_int_t k;             /* see algorithm */
+	duk_small_int_t low_ok;        /* see algorithm */
+	duk_small_int_t high_ok;       /* see algorithm */
+	duk_small_int_t unequal_gaps;  /* m+ != m- (very rarely) */
+
+	/* Buffer used for generated digits, values are in the range [0,B-1]. */
+	duk_uint8_t digits[DUK__MAX_OUTPUT_DIGITS];
+	duk_small_int_t count;  /* digit count */
+} duk__numconv_stringify_ctx;
+
+/* Note: computes with 'idx' in assertions, so caller beware.
+ * 'idx' is preincremented, i.e. '1' on first call, because it
+ * is more convenient for the caller.
+ */
+#define DUK__DRAGON4_OUTPUT_PREINC(nc_ctx,preinc_idx,x)  do { \
+		DUK_ASSERT((preinc_idx) - 1 >= 0); \
+		DUK_ASSERT((preinc_idx) - 1 < DUK__MAX_OUTPUT_DIGITS); \
+		((nc_ctx)->digits[(preinc_idx) - 1]) = (duk_uint8_t) (x); \
+	} while (0)
+
+static duk_size_t duk__dragon4_format_uint32(duk_uint8_t *buf, duk_uint32_t x, duk_small_int_t radix) {
+	duk_uint8_t *p;
+	duk_size_t len;
+	duk_small_int_t dig;
+	duk_small_int_t t;
+
+	DUK_ASSERT(radix >= 2 && radix <= 36);
+
+	/* A 32-bit unsigned integer formats to at most 32 digits (the
+	 * worst case happens with radix == 2).  Output the digits backwards,
+	 * and use a memmove() to get them in the right place.
+	 */
+
+	p = buf + 32;
+	for (;;) {
+		t = x / radix;
+		dig = x - t * radix;
+		x = t;
+
+		DUK_ASSERT(dig >= 0 && dig < 36);
+		*(--p) = DUK__DIGITCHAR(dig);
+
+		if (x == 0) {
+			break;
+		}
+	}
+	len = (duk_size_t) ((buf + 32) - p);
+
+	DUK_MEMMOVE((void *) buf, (void *) p, (size_t) len);
+
+	return len;
+}
+
+static void duk__dragon4_prepare(duk__numconv_stringify_ctx *nc_ctx) {
+	duk_small_int_t lowest_mantissa;
+
+#if 1
+	/* Assume IEEE round-to-even, so that shorter encoding can be used
+	 * when round-to-even would produce correct result.  By removing
+	 * this check (and having low_ok == high_ok == 0) the results would
+	 * still be accurate but in some cases longer than necessary.
+	 */
+	if (duk__bi_is_even(&nc_ctx->f)) {
+		DUK_DDD(DUK_DDDPRINT("f is even"));
+		nc_ctx->low_ok = 1;
+		nc_ctx->high_ok = 1;
+	} else {
+		DUK_DDD(DUK_DDDPRINT("f is odd"));
+		nc_ctx->low_ok = 0;
+		nc_ctx->high_ok = 0;
+	}
+#else
+	/* Note: not honoring round-to-even should work but now generates incorrect
+	 * results.  For instance, 1e23 serializes to "a000...", i.e. the first digit
+	 * equals the radix (10).  Scaling stops one step too early in this case.
+	 * Don't know why this is the case, but since this code path is unused, it
+	 * doesn't matter.
+	 */
+	nc_ctx->low_ok = 0;
+	nc_ctx->high_ok = 0;
+#endif
+
+	/* For string-to-number, pretend we never have the lowest mantissa as there
+	 * is no natural "precision" for inputs.  Having lowest_mantissa == 0, we'll
+	 * fall into the base cases for both e >= 0 and e < 0.
+	 */
+	if (nc_ctx->is_s2n) {
+		lowest_mantissa = 0;
+	} else {
+		lowest_mantissa = duk__bi_is_2to52(&nc_ctx->f);
+	}
+
+	nc_ctx->unequal_gaps = 0;
+	if (nc_ctx->e >= 0) {
+		/* exponent non-negative (and thus not minimum exponent) */
+
+		if (lowest_mantissa) {
+			/* (>= e 0) AND (= f (expt b (- p 1)))
+			 *
+			 * be <- (expt b e) == b^e
+			 * be1 <- (* be b) == (expt b (+ e 1)) == b^(e+1)
+			 * r <- (* f be1 2) == 2 * f * b^(e+1)    [if b==2 -> f * b^(e+2)]
+			 * s <- (* b 2)                           [if b==2 -> 4]
+			 * m+ <- be1 == b^(e+1)
+			 * m- <- be == b^e
+			 * k <- 0
+			 * B <- B
+			 * low_ok <- round
+			 * high_ok <- round
+			 */
+
+			DUK_DDD(DUK_DDDPRINT("non-negative exponent (not smallest exponent); "
+			                     "lowest mantissa value for this exponent -> "
+			                     "unequal gaps"));
+
+			duk__bi_exp_small(&nc_ctx->mm, nc_ctx->b, nc_ctx->e, &nc_ctx->t1, &nc_ctx->t2);  /* mm <- b^e */
+			duk__bi_mul_small(&nc_ctx->mp, &nc_ctx->mm, nc_ctx->b);  /* mp <- b^(e+1) */
+			duk__bi_mul_small(&nc_ctx->t1, &nc_ctx->f, 2);
+			duk__bi_mul(&nc_ctx->r, &nc_ctx->t1, &nc_ctx->mp);       /* r <- (2 * f) * b^(e+1) */
+			duk__bi_set_small(&nc_ctx->s, nc_ctx->b * 2);            /* s <- 2 * b */
+			nc_ctx->unequal_gaps = 1;
+		} else {
+			/* (>= e 0) AND (not (= f (expt b (- p 1))))
+			 *
+			 * be <- (expt b e) == b^e
+			 * r <- (* f be 2) == 2 * f * b^e    [if b==2 -> f * b^(e+1)]
+			 * s <- 2
+			 * m+ <- be == b^e
+			 * m- <- be == b^e
+			 * k <- 0
+			 * B <- B
+			 * low_ok <- round
+			 * high_ok <- round
+			 */
+
+			DUK_DDD(DUK_DDDPRINT("non-negative exponent (not smallest exponent); "
+			                     "not lowest mantissa for this exponent -> "
+			                     "equal gaps"));
+
+			duk__bi_exp_small(&nc_ctx->mm, nc_ctx->b, nc_ctx->e, &nc_ctx->t1, &nc_ctx->t2);  /* mm <- b^e */
+			duk__bi_copy(&nc_ctx->mp, &nc_ctx->mm);                /* mp <- b^e */
+			duk__bi_mul_small(&nc_ctx->t1, &nc_ctx->f, 2);
+			duk__bi_mul(&nc_ctx->r, &nc_ctx->t1, &nc_ctx->mp);     /* r <- (2 * f) * b^e */
+			duk__bi_set_small(&nc_ctx->s, 2);                      /* s <- 2 */
+		}
+	} else {
+		/* When doing string-to-number, lowest_mantissa is always 0 so
+		 * the exponent check, while incorrect, won't matter.
+		 */
+		if (nc_ctx->e > DUK__IEEE_DOUBLE_EXP_MIN /*not minimum exponent*/ &&
+		    lowest_mantissa /* lowest mantissa for this exponent*/) {
+			/* r <- (* f b 2)                                [if b==2 -> (* f 4)]
+			 * s <- (* (expt b (- 1 e)) 2) == b^(1-e) * 2    [if b==2 -> b^(2-e)]
+			 * m+ <- b == 2
+			 * m- <- 1
+			 * k <- 0
+			 * B <- B
+			 * low_ok <- round
+			 * high_ok <- round
+			 */
+
+			DUK_DDD(DUK_DDDPRINT("negative exponent; not minimum exponent and "
+			                     "lowest mantissa for this exponent -> "
+			                     "unequal gaps"));
+
+			duk__bi_mul_small(&nc_ctx->r, &nc_ctx->f, nc_ctx->b * 2);  /* r <- (2 * b) * f */
+			duk__bi_exp_small(&nc_ctx->t1, nc_ctx->b, 1 - nc_ctx->e, &nc_ctx->s, &nc_ctx->t2);  /* NB: use 's' as temp on purpose */
+			duk__bi_mul_small(&nc_ctx->s, &nc_ctx->t1, 2);             /* s <- b^(1-e) * 2 */
+			duk__bi_set_small(&nc_ctx->mp, 2);
+			duk__bi_set_small(&nc_ctx->mm, 1);
+			nc_ctx->unequal_gaps = 1;
+		} else {
+			/* r <- (* f 2)
+			 * s <- (* (expt b (- e)) 2) == b^(-e) * 2    [if b==2 -> b^(1-e)]
+			 * m+ <- 1
+			 * m- <- 1
+			 * k <- 0
+			 * B <- B
+			 * low_ok <- round
+			 * high_ok <- round
+			 */
+
+			DUK_DDD(DUK_DDDPRINT("negative exponent; minimum exponent or not "
+			                     "lowest mantissa for this exponent -> "
+			                     "equal gaps"));
+
+			duk__bi_mul_small(&nc_ctx->r, &nc_ctx->f, 2);            /* r <- 2 * f */
+			duk__bi_exp_small(&nc_ctx->t1, nc_ctx->b, -nc_ctx->e, &nc_ctx->s, &nc_ctx->t2);  /* NB: use 's' as temp on purpose */
+			duk__bi_mul_small(&nc_ctx->s, &nc_ctx->t1, 2);           /* s <- b^(-e) * 2 */
+			duk__bi_set_small(&nc_ctx->mp, 1);
+			duk__bi_set_small(&nc_ctx->mm, 1);
+		}
+	}
+}
+
+static void duk__dragon4_scale(duk__numconv_stringify_ctx *nc_ctx) {
+	duk_small_int_t k = 0;
+
+	/* This is essentially the 'scale' algorithm, with recursion removed.
+	 * Note that 'k' is either correct immediately, or will move in one
+	 * direction in the loop.  There's no need to do the low/high checks
+	 * on every round (like the Scheme algorithm does).
+	 *
+	 * The scheme algorithm finds 'k' and updates 's' simultaneously,
+	 * while the logical algorithm finds 'k' with 's' having its initial
+	 * value, after which 's' is updated separately (see the Burger-Dybvig
+	 * paper, Section 3.1, steps 2 and 3).
+	 *
+	 * The case where m+ == m- (almost always) is optimized for, because
+	 * it reduces the bigint operations considerably and almost always
+	 * applies.  The scale loop only needs to work with m+, so this works.
+	 */
+
+	/* XXX: this algorithm could be optimized quite a lot by using e.g.
+	 * a logarithm based estimator for 'k' and performing B^n multiplication
+	 * using a lookup table or using some bit-representation based exp
+	 * algorithm.  Currently we just loop, with significant performance
+	 * impact for very large and very small numbers.
+	 */
+
+	DUK_DDD(DUK_DDDPRINT("scale: B=%ld, low_ok=%ld, high_ok=%ld",
+	                     (long) nc_ctx->B, (long) nc_ctx->low_ok, (long) nc_ctx->high_ok));
+	DUK__BI_PRINT("r(init)", &nc_ctx->r);
+	DUK__BI_PRINT("s(init)", &nc_ctx->s);
+	DUK__BI_PRINT("mp(init)", &nc_ctx->mp);
+	DUK__BI_PRINT("mm(init)", &nc_ctx->mm);
+
+	for (;;) {
+		DUK_DDD(DUK_DDDPRINT("scale loop (inc k), k=%ld", (long) k));
+		DUK__BI_PRINT("r", &nc_ctx->r);
+		DUK__BI_PRINT("s", &nc_ctx->s);
+		DUK__BI_PRINT("m+", &nc_ctx->mp);
+		DUK__BI_PRINT("m-", &nc_ctx->mm);
+
+		duk__bi_add(&nc_ctx->t1, &nc_ctx->r, &nc_ctx->mp);  /* t1 = (+ r m+) */
+		if (duk__bi_compare(&nc_ctx->t1, &nc_ctx->s) >= (nc_ctx->high_ok ? 0 : 1)) {
+			DUK_DDD(DUK_DDDPRINT("k is too low"));
+			/* r <- r
+			 * s <- (* s B)
+			 * m+ <- m+
+			 * m- <- m-
+			 * k <- (+ k 1)
+			 */
+
+			duk__bi_mul_small_copy(&nc_ctx->s, nc_ctx->B, &nc_ctx->t1);
+			k++;
+		} else {
+			break;
+		}
+	}
+
+	/* k > 0 -> k was too low, and cannot be too high */
+	if (k > 0) {
+		goto skip_dec_k;
+	}
+
+	for (;;) {
+		DUK_DDD(DUK_DDDPRINT("scale loop (dec k), k=%ld", (long) k));
+		DUK__BI_PRINT("r", &nc_ctx->r);
+		DUK__BI_PRINT("s", &nc_ctx->s);
+		DUK__BI_PRINT("m+", &nc_ctx->mp);
+		DUK__BI_PRINT("m-", &nc_ctx->mm);
+
+		duk__bi_add(&nc_ctx->t1, &nc_ctx->r, &nc_ctx->mp);  /* t1 = (+ r m+) */
+		duk__bi_mul_small(&nc_ctx->t2, &nc_ctx->t1, nc_ctx->B);   /* t2 = (* (+ r m+) B) */
+		if (duk__bi_compare(&nc_ctx->t2, &nc_ctx->s) <= (nc_ctx->high_ok ? -1 : 0)) {
+			DUK_DDD(DUK_DDDPRINT("k is too high"));
+			/* r <- (* r B)
+			 * s <- s
+			 * m+ <- (* m+ B)
+			 * m- <- (* m- B)
+			 * k <- (- k 1)
+			 */
+			duk__bi_mul_small_copy(&nc_ctx->r, nc_ctx->B, &nc_ctx->t1);
+			duk__bi_mul_small_copy(&nc_ctx->mp, nc_ctx->B, &nc_ctx->t1);
+			if (nc_ctx->unequal_gaps) {
+				DUK_DDD(DUK_DDDPRINT("m+ != m- -> need to update m- too"));
+				duk__bi_mul_small_copy(&nc_ctx->mm, nc_ctx->B, &nc_ctx->t1);
+			}
+			k--;
+		} else {
+			break;
+		}
+	}
+
+ skip_dec_k:
+
+	if (!nc_ctx->unequal_gaps) {
+		DUK_DDD(DUK_DDDPRINT("equal gaps, copy m- from m+"));
+		duk__bi_copy(&nc_ctx->mm, &nc_ctx->mp);  /* mm <- mp */
+	}
+	nc_ctx->k = k;
+
+	DUK_DDD(DUK_DDDPRINT("final k: %ld", (long) k));
+	DUK__BI_PRINT("r(final)", &nc_ctx->r);
+	DUK__BI_PRINT("s(final)", &nc_ctx->s);
+	DUK__BI_PRINT("mp(final)", &nc_ctx->mp);
+	DUK__BI_PRINT("mm(final)", &nc_ctx->mm);
+}
+
+static void duk__dragon4_generate(duk__numconv_stringify_ctx *nc_ctx) {
+	duk_small_int_t tc1, tc2;    /* terminating conditions */
+	duk_small_int_t d;           /* current digit */
+	duk_small_int_t count = 0;   /* digit count */
+
+	/*
+	 *  Digit generation loop.
+	 *
+	 *  Different termination conditions:
+	 *
+	 *    1. Free format output.  Terminate when shortest accurate
+	 *       representation found.
+	 *
+	 *    2. Fixed format output, with specific number of digits.
+	 *       Ignore termination conditions, terminate when digits
+	 *       generated.  Caller requests an extra digit and rounds.
+	 *
+	 *    3. Fixed format output, with a specific absolute cut-off
+	 *       position (e.g. 10 digits after decimal point).  Note
+	 *       that we always generate at least one digit, even if
+	 *       the digit is below the cut-off point already.
+	 */
+
+	for (;;) {
+		DUK_DDD(DUK_DDDPRINT("generate loop, count=%ld, k=%ld, B=%ld, low_ok=%ld, high_ok=%ld",
+		                     (long) count, (long) nc_ctx->k, (long) nc_ctx->B,
+		                     (long) nc_ctx->low_ok, (long) nc_ctx->high_ok));
+		DUK__BI_PRINT("r", &nc_ctx->r);
+		DUK__BI_PRINT("s", &nc_ctx->s);
+		DUK__BI_PRINT("m+", &nc_ctx->mp);
+		DUK__BI_PRINT("m-", &nc_ctx->mm);
+
+		/* (quotient-remainder (* r B) s) using a dummy subtraction loop */
+		duk__bi_mul_small(&nc_ctx->t1, &nc_ctx->r, nc_ctx->B);       /* t1 <- (* r B) */
+		d = 0;
+		for (;;) {
+			if (duk__bi_compare(&nc_ctx->t1, &nc_ctx->s) < 0) {
+				break;
+			}
+			duk__bi_sub_copy(&nc_ctx->t1, &nc_ctx->s, &nc_ctx->t2);  /* t1 <- t1 - s */
+			d++;
+		}
+		duk__bi_copy(&nc_ctx->r, &nc_ctx->t1);  /* r <- (remainder (* r B) s) */
+		                                        /* d <- (quotient (* r B) s)   (in range 0...B-1) */
+		DUK_DDD(DUK_DDDPRINT("-> d(quot)=%ld", (long) d));
+		DUK__BI_PRINT("r(rem)", &nc_ctx->r);
+
+		duk__bi_mul_small_copy(&nc_ctx->mp, nc_ctx->B, &nc_ctx->t2); /* m+ <- (* m+ B) */
+		duk__bi_mul_small_copy(&nc_ctx->mm, nc_ctx->B, &nc_ctx->t2); /* m- <- (* m- B) */
+		DUK__BI_PRINT("mp(upd)", &nc_ctx->mp);
+		DUK__BI_PRINT("mm(upd)", &nc_ctx->mm);
+
+		/* Terminating conditions.  For fixed width output, we just ignore the
+		 * terminating conditions (and pretend that tc1 == tc2 == false).  The
+		 * the current shortcut for fixed-format output is to generate a few
+		 * extra digits and use rounding (with carry) to finish the output.
+		 */
+
+		if (nc_ctx->is_fixed == 0) {
+			/* free-form */
+			tc1 = (duk__bi_compare(&nc_ctx->r, &nc_ctx->mm) <= (nc_ctx->low_ok ? 0 : -1));
+
+			duk__bi_add(&nc_ctx->t1, &nc_ctx->r, &nc_ctx->mp);  /* t1 <- (+ r m+) */
+			tc2 = (duk__bi_compare(&nc_ctx->t1, &nc_ctx->s) >= (&nc_ctx->high_ok ? 0 : 1));
+
+			DUK_DDD(DUK_DDDPRINT("tc1=%ld, tc2=%ld", (long) tc1, (long) tc2));
+		} else {
+			/* fixed-format */
+			tc1 = 0;
+			tc2 = 0;
+		}
+
+		/* Count is incremented before DUK__DRAGON4_OUTPUT_PREINC() call
+		 * on purpose, which is taken into account by the macro.
+		 */
+		count++;
+
+		if (tc1) {
+			if (tc2) {
+				/* tc1 = true, tc2 = true */
+				duk__bi_mul_small(&nc_ctx->t1, &nc_ctx->r, 2);
+				if (duk__bi_compare(&nc_ctx->t1, &nc_ctx->s) < 0) {  /* (< (* r 2) s) */
+					DUK_DDD(DUK_DDDPRINT("tc1=true, tc2=true, 2r > s: output d --> %ld (k=%ld)",
+					                     (long) d, (long) nc_ctx->k));
+					DUK__DRAGON4_OUTPUT_PREINC(nc_ctx, count, d);
+				} else {
+					DUK_DDD(DUK_DDDPRINT("tc1=true, tc2=true, 2r <= s: output d+1 --> %ld (k=%ld)",
+					                     (long) (d + 1), (long) nc_ctx->k));
+					DUK__DRAGON4_OUTPUT_PREINC(nc_ctx, count, d + 1);
+				}
+				break;
+			} else {
+				/* tc1 = true, tc2 = false */
+				DUK_DDD(DUK_DDDPRINT("tc1=true, tc2=false: output d --> %ld (k=%ld)",
+				                     (long) d, (long) nc_ctx->k));
+				DUK__DRAGON4_OUTPUT_PREINC(nc_ctx, count, d);
+				break;
+			}
+		} else {
+			if (tc2) {
+				/* tc1 = false, tc2 = true */
+				DUK_DDD(DUK_DDDPRINT("tc1=false, tc2=true: output d+1 --> %ld (k=%ld)",
+				                     (long) (d + 1), (long) nc_ctx->k));
+				DUK__DRAGON4_OUTPUT_PREINC(nc_ctx, count, d + 1);
+				break;
+			} else {
+				/* tc1 = false, tc2 = false */
+				DUK_DDD(DUK_DDDPRINT("tc1=false, tc2=false: output d --> %ld (k=%ld)",
+				                     (long) d, (long) nc_ctx->k));
+				DUK__DRAGON4_OUTPUT_PREINC(nc_ctx, count, d);
+
+				/* r <- r    (updated above: r <- (remainder (* r B) s)
+				 * s <- s
+				 * m+ <- m+  (updated above: m+ <- (* m+ B)
+				 * m- <- m-  (updated above: m- <- (* m- B)
+				 * B, low_ok, high_ok are fixed
+				 */
+
+				/* fall through and continue for-loop */
+			}
+		}
+
+		/* fixed-format termination conditions */
+		if (nc_ctx->is_fixed) {
+			if (nc_ctx->abs_pos) {
+				int pos = nc_ctx->k - count + 1;  /* count is already incremented, take into account */
+				DUK_DDD(DUK_DDDPRINT("fixed format, absolute: abs pos=%ld, k=%ld, count=%ld, req=%ld",
+				                     (long) pos, (long) nc_ctx->k, (long) count, (long) nc_ctx->req_digits));
+				if (pos <= nc_ctx->req_digits) {
+					DUK_DDD(DUK_DDDPRINT("digit position reached req_digits, end generate loop"));
+					break;
+				}
+			} else {
+				DUK_DDD(DUK_DDDPRINT("fixed format, relative: k=%ld, count=%ld, req=%ld",
+				                     (long) nc_ctx->k, (long) count, (long) nc_ctx->req_digits));
+				if (count >= nc_ctx->req_digits) {
+					DUK_DDD(DUK_DDDPRINT("digit count reached req_digits, end generate loop"));
+					break;
+				}
+			}
+		}
+	}  /* for */
+
+	nc_ctx->count = count;
+
+	DUK_DDD(DUK_DDDPRINT("generate finished"));
+
+#ifdef DUK_USE_DDDPRINT
+	{
+		duk_uint8_t buf[2048];
+		duk_small_int_t i, t;
+		DUK_MEMZERO(buf, sizeof(buf));
+		for (i = 0; i < nc_ctx->count; i++) {
+			t = nc_ctx->digits[i];
+			if (t < 0 || t > 36) {
+				buf[i] = (duk_uint8_t) '?';
+			} else {
+				buf[i] = (duk_uint8_t) DUK__DIGITCHAR(t);
+			}
+		}
+		DUK_DDD(DUK_DDDPRINT("-> generated digits; k=%ld, digits='%s'",
+		                     (long) nc_ctx->k, (const char *) buf));
+	}
+#endif
+}
+
+/* Round up digits to a given position.  If position is out-of-bounds,
+ * does nothing.  If carry propagates over the first digit, a '1' is
+ * prepended to digits and 'k' will be updated.  Return value indicates
+ * whether carry propagated over the first digit.
+ *
+ * Note that nc_ctx->count is NOT updated based on the rounding position
+ * (it is updated only if carry overflows over the first digit and an
+ * extra digit is prepended).
+ */
+static duk_small_int_t duk__dragon4_fixed_format_round(duk__numconv_stringify_ctx *nc_ctx, duk_small_int_t round_idx) {
+	duk_small_int_t t;
+	duk_uint8_t *p;
+	duk_uint8_t roundup_limit;
+	duk_small_int_t ret = 0;
+
+	/*
+	 *  round_idx points to the digit which is considered for rounding; the
+	 *  digit to its left is the final digit of the rounded value.  If round_idx
+	 *  is zero, rounding will be performed; the result will either be an empty
+	 *  rounded value or if carry happens a '1' digit is generated.
+	 */
+
+	if (round_idx >= nc_ctx->count) {
+		DUK_DDD(DUK_DDDPRINT("round_idx out of bounds (%ld >= %ld (count)) -> no rounding",
+		                     (long) round_idx, (long) nc_ctx->count));
+		return 0;
+	} else if (round_idx < 0) {
+		DUK_DDD(DUK_DDDPRINT("round_idx out of bounds (%ld < 0) -> no rounding",
+		                     (long) round_idx));
+		return 0;
+	}
+
+	/*
+	 *  Round-up limit.
+	 *
+	 *  For even values, divides evenly, e.g. 10 -> roundup_limit=5.
+	 *
+	 *  For odd values, rounds up, e.g. 3 -> roundup_limit=2.
+	 *  If radix is 3, 0/3 -> down, 1/3 -> down, 2/3 -> up.
+	 */
+	roundup_limit = (duk_uint8_t) ((nc_ctx->B + 1) / 2);
+
+	p = &nc_ctx->digits[round_idx];
+	if (*p >= roundup_limit) {
+		DUK_DDD(DUK_DDDPRINT("fixed-format rounding carry required"));
+		/* carry */
+		for (;;) {
+			*p = 0;
+			if (p == &nc_ctx->digits[0]) {
+				DUK_DDD(DUK_DDDPRINT("carry propagated to first digit -> special case handling"));
+				DUK_MEMMOVE((void *) (&nc_ctx->digits[1]),
+				            (void *) (&nc_ctx->digits[0]),
+				            (size_t) (sizeof(char) * nc_ctx->count));
+				nc_ctx->digits[0] = 1;  /* don't increase 'count' */
+				nc_ctx->k++;  /* position of highest digit changed */
+				nc_ctx->count++;  /* number of digits changed */
+				ret = 1;
+				break;
+			}
+
+			DUK_DDD(DUK_DDDPRINT("fixed-format rounding carry: B=%ld, roundup_limit=%ld, p=%p, digits=%p",
+			                     (long) nc_ctx->B, (long) roundup_limit, (void *) p, (void *) nc_ctx->digits));
+			p--;
+			t = *p;
+			DUK_DDD(DUK_DDDPRINT("digit before carry: %ld", (long) t));
+			if (++t < nc_ctx->B) {
+				DUK_DDD(DUK_DDDPRINT("rounding carry terminated"));
+				*p = t;
+				break;
+			}
+
+			DUK_DDD(DUK_DDDPRINT("wraps, carry to next digit"));
+		}
+	}
+
+	return ret;
+}
+
+#define DUK__NO_EXP  (65536)  /* arbitrary marker, outside valid exp range */
+
+static void duk__dragon4_convert_and_push(duk__numconv_stringify_ctx *nc_ctx,
+                                          duk_context *ctx,
+                                          duk_small_int_t radix,
+                                          duk_small_int_t digits,
+                                          duk_small_uint_t flags,
+                                          duk_small_int_t neg) {
+	duk_small_int_t k;
+	duk_small_int_t pos, pos_end;
+	duk_small_int_t exp;
+	duk_small_int_t dig;
+	duk_uint8_t *q;
+	duk_uint8_t *buf;
+
+	/*
+	 *  The string conversion here incorporates all the necessary Ecmascript
+	 *  semantics without attempting to be generic.  nc_ctx->digits contains
+	 *  nc_ctx->count digits (>= 1), with the topmost digit's 'position'
+	 *  indicated by nc_ctx->k as follows:
+	 *
+	 *    digits="123" count=3 k=0   -->   0.123
+	 *    digits="123" count=3 k=1   -->   1.23
+	 *    digits="123" count=3 k=5   -->   12300
+	 *    digits="123" count=3 k=-1  -->   0.0123
+	 *
+	 *  Note that the identifier names used for format selection are different
+	 *  in Burger-Dybvig paper and Ecmascript specification (quite confusingly
+	 *  so, because e.g. 'k' has a totally different meaning in each).  See
+	 *  documentation for discussion.
+	 *
+	 *  Ecmascript doesn't specify any specific behavior for format selection
+	 *  (e.g. when to use exponent notation) for non-base-10 numbers.
+	 *
+	 *  The bigint space in the context is reused for string output, as there
+	 *  is more than enough space for that (>1kB at the moment), and we avoid
+	 *  allocating even more stack.
+	 */
+
+	DUK_ASSERT(DUK__NUMCONV_CTX_BIGINTS_SIZE >= DUK__MAX_FORMATTED_LENGTH);
+	DUK_ASSERT(nc_ctx->count >= 1);
+
+	k = nc_ctx->k;
+	buf = (duk_uint8_t *) &nc_ctx->f;  /* XXX: union would be more correct */
+	q = buf;
+
+	/* Exponent handling: if exponent format is used, record exponent value and
+	 * fake k such that one leading digit is generated (e.g. digits=123 -> "1.23").
+	 *
+	 * toFixed() prevents exponent use; otherwise apply a set of criteria to
+	 * match the other API calls (toString(), toPrecision, etc).
+	 */
+
+	exp = DUK__NO_EXP;
+	if (!nc_ctx->abs_pos /* toFixed() */) {
+		if ((flags & DUK_N2S_FLAG_FORCE_EXP) ||             /* exponential notation forced */
+		    ((flags & DUK_N2S_FLAG_NO_ZERO_PAD) &&          /* fixed precision and zero padding would be required */
+	             (k - digits >= 1)) ||                          /* (e.g. k=3, digits=2 -> "12X") */
+		    ((k > 21 || k <= -6) && (radix == 10))) {       /* toString() conditions */
+			DUK_DDD(DUK_DDDPRINT("use exponential notation: k=%ld -> exp=%ld",
+			                     (long) k, (long) (k - 1)));
+			exp = k - 1;  /* e.g. 12.3 -> digits="123" k=2 -> 1.23e1 */
+			k = 1;  /* generate mantissa with a single leading whole number digit */
+		}
+	}
+
+	if (neg) {
+		*q++ = '-';
+	}
+
+	/* Start position (inclusive) and end position (exclusive) */
+	pos = (k >= 1 ? k : 1);
+	if (nc_ctx->is_fixed) {
+		if (nc_ctx->abs_pos) {
+			/* toFixed() */
+			pos_end = -digits;
+		} else {
+			pos_end = k - digits;
+		}
+	} else {
+		pos_end = k - nc_ctx->count;
+	}
+	if (pos_end > 0) {
+		pos_end = 0;
+	}
+
+	DUK_DDD(DUK_DDDPRINT("exp=%ld, k=%ld, count=%ld, pos=%ld, pos_end=%ld, is_fixed=%ld, "
+	                     "digits=%ld, abs_pos=%ld",
+	                     (long) exp, (long) k, (long) nc_ctx->count, (long) pos, (long) pos_end,
+	                     (long) nc_ctx->is_fixed, (long) digits, (long) nc_ctx->abs_pos));
+
+	/* Digit generation */
+	while (pos > pos_end) {
+		DUK_DDD(DUK_DDDPRINT("digit generation: pos=%ld, pos_end=%ld",
+		                     (long) pos, (long) pos_end));
+		if (pos == 0) {
+			*q++ = (duk_uint8_t) '.';
+		}
+		if (pos > k) {
+			*q++ = (duk_uint8_t) '0';
+		} else if (pos <= k - nc_ctx->count) {
+			*q++ = (duk_uint8_t) '0';
+		} else {
+			dig = nc_ctx->digits[k - pos];
+			DUK_ASSERT(dig >= 0 && dig < nc_ctx->B);
+			*q++ = (duk_uint8_t) DUK__DIGITCHAR(dig);
+		} 
+
+		pos--;
+	}
+	DUK_ASSERT(pos <= 1);
+
+	/* Exponent */
+	if (exp != DUK__NO_EXP) {
+		/*
+		 *  Exponent notation for non-base-10 numbers isn't specified in Ecmascript
+		 *  specification, as it never explicitly turns up: non-decimal numbers can
+		 *  only be formatted with Number.prototype.toString([radix]) and for that,
+		 *  behavior is not explicitly specified.
+		 *
+		 *  Logical choices include formatting the exponent as decimal (e.g. binary
+		 *  100000 as 1e+5) or in current radix (e.g. binary 100000 as 1e+101).
+		 *  The Dragon4 algorithm (in the original paper) prints the exponent value
+		 *  in the target radix B.  However, for radix values 15 and above, the
+		 *  exponent separator 'e' is no longer easily parseable.  Consider, for
+		 *  instance, the number "1.faecee+1c".
+		 */
+
+		duk_size_t len;
+		char exp_sign;
+
+		*q++ = 'e';
+		if (exp >= 0) {
+			exp_sign = '+';
+		} else {
+			exp_sign = '-';
+			exp = -exp;
+		}
+		*q++ = (duk_uint8_t) exp_sign;
+		len = duk__dragon4_format_uint32(q, (duk_uint32_t) exp, radix);
+		q += len;
+	}
+
+	duk_push_lstring(ctx, (const char *) buf, (size_t) (q - buf));
+}
+
+/*
+ *  Conversion helpers
+ */
+
+static void duk__dragon4_double_to_ctx(duk__numconv_stringify_ctx *nc_ctx, duk_double_t x) {
+	duk_double_union u;
+	duk_uint32_t tmp;
+	duk_small_int_t exp;
+
+	/*
+	 *    seeeeeee eeeeffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff
+	 *       A        B        C        D        E        F        G        H
+	 *
+	 *    s       sign bit
+	 *    eee...  exponent field
+	 *    fff...  fraction
+	 *
+	 *    ieee value = 1.ffff... * 2^(e - 1023)  (normal)
+	 *               = 0.ffff... * 2^(-1022)     (denormal)
+	 *
+	 *    algorithm v = f * b^e
+	 */
+
+	DUK_DBLUNION_SET_DOUBLE(&u, x);
+
+	nc_ctx->f.n = 2;
+
+	tmp = DUK_DBLUNION_GET_LOW32(&u);
+	nc_ctx->f.v[0] = tmp;
+	tmp = DUK_DBLUNION_GET_HIGH32(&u);
+	nc_ctx->f.v[1] = tmp & 0x000fffffUL;
+	exp = (duk_small_int_t) ((tmp >> 20) & 0x07ffUL);
+
+	if (exp == 0) {
+		/* denormal */
+		exp = DUK__IEEE_DOUBLE_EXP_MIN - 52;
+		duk__bi_normalize(&nc_ctx->f);
+	} else {
+		/* normal: implicit leading 1-bit */
+		nc_ctx->f.v[1] |= 0x00100000UL;
+		exp = exp - DUK__IEEE_DOUBLE_EXP_BIAS - 52;
+		DUK_ASSERT(duk__bi_is_valid(&nc_ctx->f));  /* true, because v[1] has at least one bit set */
+	}
+
+	DUK_ASSERT(duk__bi_is_valid(&nc_ctx->f));
+
+	nc_ctx->e = exp;
+}
+
+void duk__dragon4_ctx_to_double(duk__numconv_stringify_ctx *nc_ctx, duk_double_t *x) {
+	duk_double_union u;
+	duk_small_int_t exp;
+	duk_small_int_t i;
+	duk_small_int_t bitstart;
+	duk_small_int_t bitround;
+	duk_small_int_t bitidx;
+	duk_small_int_t skip_round;
+	duk_uint32_t t, v;
+
+	DUK_ASSERT(nc_ctx->count == 53 + 1);
+
+	/* Sometimes this assert is not true right now; it will be true after
+	 * rounding.  See: test-bug-numconv-mantissa-assert.js.
+	 */
+	DUK_ASSERT_DISABLE(nc_ctx->digits[0] == 1);  /* zero handled by caller */
+
+	/* Should not be required because the code below always sets both high
+	 * and low parts, but at least gcc-4.4.5 fails to deduce this correctly
+	 * (perhaps because the low part is set (seemingly) conditionally in a
+	 * loop), so this is here to avoid the bogus warning.
+	 */
+	DUK_MEMZERO((void *) &u, sizeof(u));
+
+	/*
+	 *  Figure out how generated digits match up with the mantissa,
+	 *  and then perform rounding.  If mantissa overflows, need to
+	 *  recompute the exponent (it is bumped and may overflow to
+	 *  infinity).
+	 *
+	 *  For normal numbers the leading '1' is hidden and ignored,
+	 *  and the last bit is used for rounding:
+	 *
+	 *                          rounding pt
+	 *       <--------52------->|
+	 *     1 x x x x ... x x x x|y  ==>  x x x x ... x x x x
+	 *
+	 *  For denormals, the leading '1' is included in the number,
+	 *  and the rounding point is different:
+	 *
+	 *                      rounding pt
+	 *     <--52 or less--->|
+	 *     1 x x x x ... x x|x x y  ==>  0 0 ... 1 x x ... x x
+	 *
+	 *  The largest denormals will have a mantissa beginning with
+	 *  a '1' (the explicit leading bit); smaller denormals will
+	 *  have leading zero bits.
+	 *
+	 *  If the exponent would become too high, the result becomes
+	 *  Infinity.  If the exponent is so small that the entire
+	 *  mantissa becomes zero, the result becomes zero.
+	 *
+	 *  Note: the Dragon4 'k' is off-by-one with respect to the IEEE
+	 *  exponent.  For instance, k==0 indicates that the leading '1'
+	 *  digit is at the first binary fraction position (0.1xxx...);
+	 *  the corresponding IEEE exponent would be -1.
+	 */
+
+	skip_round = 0;
+
+ recheck_exp:
+
+	exp = nc_ctx->k - 1;   /* IEEE exp without bias */
+	if (exp > 1023) {
+		/* Infinity */
+		bitstart = -255;  /* needed for inf: causes mantissa to become zero,
+		                   * and rounding to be skipped.
+		                   */
+		exp = 2047;
+	} else if (exp >= -1022) {
+		/* normal */
+		bitstart = 1;  /* skip leading digit */
+		exp += DUK__IEEE_DOUBLE_EXP_BIAS;
+		DUK_ASSERT(exp >= 1 && exp <= 2046);
+	} else {
+		/* denormal or zero */
+		bitstart = 1023 + exp;  /* exp==-1023 -> bitstart=0 (leading 1);
+		                         * exp==-1024 -> bitstart=-1 (one left of leading 1), etc
+		                         */
+		exp = 0;
+	}
+	bitround = bitstart + 52;
+
+	DUK_DDD(DUK_DDDPRINT("ieee exp=%ld, bitstart=%ld, bitround=%ld",
+	                     (long) exp, (long) bitstart, (long) bitround));
+
+	if (!skip_round) {
+		if (duk__dragon4_fixed_format_round(nc_ctx, bitround)) {
+			/* Corner case: see test-numconv-parse-mant-carry.js.  We could
+			 * just bump the exponent and update bitstart, but it's more robust
+			 * to recompute (but avoid rounding twice).
+			 */
+			DUK_DDD(DUK_DDDPRINT("rounding caused exponent to be bumped, recheck exponent"));
+			skip_round = 1;
+			goto recheck_exp;
+		}
+	}
+
+	/*
+	 *  Create mantissa
+	 */
+
+	t = 0;
+	for (i = 0; i < 52; i++) {
+		bitidx = bitstart + 52 - 1 - i;
+		if (bitidx >= nc_ctx->count) {
+			v = 0;
+		} else if (bitidx < 0) {
+			v = 0;
+		} else {
+			v = nc_ctx->digits[bitidx];
+		}
+		DUK_ASSERT(v == 0 || v == 1);
+		t += v << (i % 32);
+		if (i == 31) {
+			/* low 32 bits is complete */
+			DUK_DBLUNION_SET_LOW32(&u, t);
+			t = 0;
+		}
+	}
+	/* t has high mantissa */
+
+	DUK_DDD(DUK_DDDPRINT("mantissa is complete: %08lx %08lx",
+	                     (unsigned long) t,
+	                     (unsigned long) DUK_DBLUNION_GET_LOW32(&u)));
+
+	DUK_ASSERT(exp >= 0 && exp <= 0x7ffL);
+	t += exp << 20;
+#if 0  /* caller handles sign change */
+	if (negative) {
+		t |= 0x80000000U;
+	}
+#endif
+	DUK_DBLUNION_SET_HIGH32(&u, t);
+
+	DUK_DDD(DUK_DDDPRINT("number is complete: %08lx %08lx",
+	                     (unsigned long) DUK_DBLUNION_GET_HIGH32(&u),
+	                     (unsigned long) DUK_DBLUNION_GET_LOW32(&u)));
+
+	*x = DUK_DBLUNION_GET_DOUBLE(&u);
+}
+
+/*
+ *  Exposed number-to-string API
+ *
+ *  Input: [ number ]
+ *  Output: [ string ]
+ */
+
+void duk_numconv_stringify(duk_context *ctx, duk_small_int_t radix, duk_small_int_t digits, duk_small_uint_t flags) {
+	duk_double_t x;
+	duk_small_int_t c;
+	duk_small_int_t neg;
+	duk_uint32_t uval;
+	duk__numconv_stringify_ctx nc_ctx_alloc;  /* large context; around 2kB now */
+	duk__numconv_stringify_ctx *nc_ctx = &nc_ctx_alloc;
+
+	x = (duk_double_t) duk_require_number(ctx, -1);
+	duk_pop(ctx);
+
+	/*
+	 *  Handle special cases (NaN, infinity, zero).
+	 */
+
+	c = (duk_small_int_t) DUK_FPCLASSIFY(x);
+	if (DUK_SIGNBIT((double) x)) {
+		x = -x;
+		neg = 1;
+	} else {
+		neg = 0;
+	}
+
+	/* NaN sign bit is platform specific with unpacked, un-normalized NaNs */
+	DUK_ASSERT(c == DUK_FP_NAN || DUK_SIGNBIT((double) x) == 0);
+
+	if (c == DUK_FP_NAN) {
+		duk_push_hstring_stridx(ctx, DUK_STRIDX_NAN);
+		return;
+	} else if (c == DUK_FP_INFINITE) {
+		if (neg) {
+			/* -Infinity */
+			duk_push_hstring_stridx(ctx, DUK_STRIDX_MINUS_INFINITY);
+		} else {
+			/* Infinity */
+			duk_push_hstring_stridx(ctx, DUK_STRIDX_INFINITY);
+		}
+		return;
+	} else if (c == DUK_FP_ZERO) {
+		/* We can't shortcut zero here if it goes through special formatting
+		 * (such as forced exponential notation).
+		 */
+		;
+	}
+
+	/*
+	 *  Handle integers in 32-bit range (that is, [-(2**32-1),2**32-1])
+	 *  specially, as they're very likely for embedded programs.  This
+	 *  is now done for all radix values.  We must be careful not to use
+	 *  the fast path when special formatting (e.g. forced exponential)
+	 *  is in force.
+	 *
+	 *  XXX: could save space by supporting radix 10 only and using
+	 *  sprintf "%lu" for the fast path and for exponent formatting.
+	 */
+
+	uval = (unsigned int) x;
+	if (((double) uval) == x &&  /* integer number in range */
+	    flags == 0) {            /* no special formatting */
+		/* use bigint area as a temp */
+		duk_uint8_t *buf = (duk_uint8_t *) (&nc_ctx->f);
+		duk_uint8_t *p = buf;
+
+		DUK_ASSERT(DUK__NUMCONV_CTX_BIGINTS_SIZE >= 32 + 1);  /* max size: radix=2 + sign */
+		if (neg && uval != 0) {
+			/* no negative sign for zero */
+			*p++ = (duk_uint8_t) '-';
+		}
+		p += duk__dragon4_format_uint32(p, uval, radix);
+		duk_push_lstring(ctx, (const char *) buf, (duk_size_t) (p - buf));
+		return;
+	}
+
+	/*
+	 *  Dragon4 setup.
+	 *
+	 *  Convert double from IEEE representation for conversion;
+	 *  normal finite values have an implicit leading 1-bit.  The
+	 *  slow path algorithm doesn't handle zero, so zero is special
+	 *  cased here but still creates a valid nc_ctx, and goes
+	 *  through normal formatting in case special formatting has
+	 *  been requested (e.g. forced exponential format: 0 -> "0e+0").
+	 */
+
+	/* Would be nice to bulk clear the allocation, but the context
+	 * is 1-2 kilobytes and nothing should rely on it being zeroed.
+	 */
+#if 0
+	DUK_MEMZERO((void *) nc_ctx, sizeof(*nc_ctx));  /* slow init, do only for slow path cases */
+#endif
+
+	nc_ctx->is_s2n = 0;
+	nc_ctx->b = 2;
+	nc_ctx->B = radix;
+	nc_ctx->abs_pos = 0;
+	if (flags & DUK_N2S_FLAG_FIXED_FORMAT) {
+		nc_ctx->is_fixed = 1;
+		if (flags & DUK_N2S_FLAG_FRACTION_DIGITS) {
+			/* absolute req_digits; e.g. digits = 1 -> last digit is 0,
+			 * but add an extra digit for rounding.
+			 */
+			nc_ctx->abs_pos = 1;
+			nc_ctx->req_digits = (-digits + 1) - 1;
+		} else {
+			nc_ctx->req_digits = digits + 1;
+		}
+	} else {
+		nc_ctx->is_fixed = 0;
+		nc_ctx->req_digits = 0;
+	}
+
+	if (c == DUK_FP_ZERO) {
+		/* Zero special case: fake requested number of zero digits; ensure
+		 * no sign bit is printed.  Relative and absolute fixed format
+		 * require separate handling.
+		 */
+		duk_small_int_t count;
+		if (nc_ctx->is_fixed) {
+			if (nc_ctx->abs_pos) {
+				count = digits + 2;  /* lead zero + 'digits' fractions + 1 for rounding */
+			} else {
+				count = digits + 1;  /* + 1 for rounding */
+			}
+		} else {
+			count = 1;
+		}
+		DUK_DDD(DUK_DDDPRINT("count=%ld", (long) count));
+		DUK_ASSERT(count >= 1);
+		DUK_MEMZERO((void *) nc_ctx->digits, count);
+		nc_ctx->count = count;
+		nc_ctx->k = 1;  /* 0.000... */
+		neg = 0;
+		goto zero_skip;
+	}
+
+	duk__dragon4_double_to_ctx(nc_ctx, x);   /* -> sets 'f' and 'e' */
+	DUK__BI_PRINT("f", &nc_ctx->f);
+	DUK_DDD(DUK_DDDPRINT("e=%ld", (long) nc_ctx->e));
+
+	/*
+	 *  Dragon4 slow path digit generation.
+	 */
+
+	duk__dragon4_prepare(nc_ctx);  /* setup many variables in nc_ctx */
+
+	DUK_DDD(DUK_DDDPRINT("after prepare:"));
+	DUK__BI_PRINT("r", &nc_ctx->r);
+	DUK__BI_PRINT("s", &nc_ctx->s);
+	DUK__BI_PRINT("mp", &nc_ctx->mp);
+	DUK__BI_PRINT("mm", &nc_ctx->mm);
+
+	duk__dragon4_scale(nc_ctx);
+
+	DUK_DDD(DUK_DDDPRINT("after scale; k=%ld", (long) nc_ctx->k));
+	DUK__BI_PRINT("r", &nc_ctx->r);
+	DUK__BI_PRINT("s", &nc_ctx->s);
+	DUK__BI_PRINT("mp", &nc_ctx->mp);
+	DUK__BI_PRINT("mm", &nc_ctx->mm);
+
+	duk__dragon4_generate(nc_ctx);
+
+	/*
+	 *  Convert and push final string.
+	 */
+
+ zero_skip:
+
+	if (flags & DUK_N2S_FLAG_FIXED_FORMAT) {
+		/* Perform fixed-format rounding. */
+		duk_small_int_t roundpos;
+		if (flags & DUK_N2S_FLAG_FRACTION_DIGITS) {
+			/* 'roundpos' is relative to nc_ctx->k and increases to the right
+			 * (opposite of how 'k' changes).
+			 */
+			roundpos = -digits;  /* absolute position for digit considered for rounding */
+			roundpos = nc_ctx->k - roundpos;
+			
+		} else {
+			roundpos = digits;
+		}
+		DUK_DDD(DUK_DDDPRINT("rounding: k=%ld, count=%ld, digits=%ld, roundpos=%ld",
+		                     (long) nc_ctx->k, (long) nc_ctx->count, (long) digits, (long) roundpos));
+		(void) duk__dragon4_fixed_format_round(nc_ctx, roundpos);
+
+		/* Note: 'count' is currently not adjusted by rounding (i.e. the
+		 * digits are not "chopped off".  That shouldn't matter because
+		 * the digit position (absolute or relative) is passed on to the
+		 * convert-and-push function.
+		 */
+	}
+
+	duk__dragon4_convert_and_push(nc_ctx, ctx, radix, digits, flags, neg);
+}
+
+/*
+ *  Exposed string-to-number API
+ *
+ *  Input: [ string ]
+ *  Output: [ number ]
+ *
+ *  If number parsing fails, a NaN is pushed as the result.  If number parsing
+ *  fails due to an internal error, an InternalError is thrown.
+ */
+
+void duk_numconv_parse(duk_context *ctx, duk_small_int_t radix, duk_small_uint_t flags) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk__numconv_stringify_ctx nc_ctx_alloc;  /* large context; around 2kB now */
+	duk__numconv_stringify_ctx *nc_ctx = &nc_ctx_alloc;
+	duk_double_t res;
+	duk_hstring *h_str;
+	duk_small_int_t exp;
+	duk_small_int_t exp_neg;
+	duk_small_int_t exp_adj;
+	duk_small_int_t neg;
+	duk_small_int_t dig;
+	duk_small_int_t dig_whole;
+	duk_small_int_t dig_lzero;
+	duk_small_int_t dig_frac;
+	duk_small_int_t dig_exp;
+	duk_small_int_t dig_prec;
+	const duk__exp_limits *explim;
+	const duk_uint8_t *p;
+	duk_small_int_t ch;
+
+	/* This seems to waste a lot of stack frame entries, but good compilers
+	 * will compute these as needed below.  Some of these initial flags are
+	 * also modified in the code below, so they can't all be removed.
+	 */
+	duk_small_int_t trim_white = (flags & DUK_S2N_FLAG_TRIM_WHITE);
+	duk_small_int_t allow_exp = (flags & DUK_S2N_FLAG_ALLOW_EXP);
+	duk_small_int_t allow_garbage = (flags & DUK_S2N_FLAG_ALLOW_GARBAGE);
+	duk_small_int_t allow_plus = (flags & DUK_S2N_FLAG_ALLOW_PLUS);
+	duk_small_int_t allow_minus = (flags & DUK_S2N_FLAG_ALLOW_MINUS);
+	duk_small_int_t allow_infinity = (flags & DUK_S2N_FLAG_ALLOW_INF);
+	duk_small_int_t allow_frac = (flags & DUK_S2N_FLAG_ALLOW_FRAC);
+	duk_small_int_t allow_naked_frac = (flags & DUK_S2N_FLAG_ALLOW_NAKED_FRAC);
+	duk_small_int_t allow_empty_frac = (flags & DUK_S2N_FLAG_ALLOW_EMPTY_FRAC);
+	duk_small_int_t allow_empty = (flags & DUK_S2N_FLAG_ALLOW_EMPTY_AS_ZERO);
+	duk_small_int_t allow_leading_zero = (flags & DUK_S2N_FLAG_ALLOW_LEADING_ZERO);
+	duk_small_int_t allow_auto_hex_int = (flags & DUK_S2N_FLAG_ALLOW_AUTO_HEX_INT);
+	duk_small_int_t allow_auto_oct_int = (flags & DUK_S2N_FLAG_ALLOW_AUTO_OCT_INT);
+
+	DUK_DDD(DUK_DDDPRINT("parse number: %!T, radix=%ld, flags=0x%08lx",
+	                     (duk_tval *) duk_get_tval(ctx, -1),
+	                     (long) radix, (unsigned long) flags));
+
+	DUK_ASSERT(radix >= 2 && radix <= 36);
+	DUK_ASSERT(radix - 2 < (duk_small_int_t) sizeof(duk__str2num_digits_for_radix));
+
+	/*
+	 *  Preliminaries: trim, sign, Infinity check
+	 *
+	 *  We rely on the interned string having a NUL terminator, which will
+	 *  cause a parse failure wherever it is encountered.  As a result, we
+	 *  don't need separate pointer checks.
+	 *
+	 *  There is no special parsing for 'NaN' in the specification although
+	 *  'Infinity' (with an optional sign) is allowed in some contexts.
+	 *  Some contexts allow plus/minus sign, while others only allow the
+	 *  minus sign (like JSON.parse()).
+	 *
+	 *  Automatic hex number detection (leading '0x' or '0X') and octal
+	 *  number detection (leading '0' followed by at least one octal digit)
+	 *  is done here too.
+	 */
+
+	if (trim_white) {
+		/* Leading / trailing whitespace is sometimes accepted and
+		 * sometimes not.  After white space trimming, all valid input
+		 * characters are pure ASCII.
+		 */
+		duk_trim(ctx, -1);
+	}
+	h_str = duk_require_hstring(ctx, -1);
+	DUK_ASSERT(h_str != NULL);
+	p = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h_str);
+
+	neg = 0;
+	ch = *p;
+	if (ch == (duk_small_int_t) '+') {
+		if (!allow_plus) {
+			DUK_DDD(DUK_DDDPRINT("parse failed: leading plus sign not allowed"));
+			goto parse_fail;
+		}
+		p++;
+	} else if (ch == (duk_small_int_t) '-') {
+		if (!allow_minus) {
+			DUK_DDD(DUK_DDDPRINT("parse failed: leading minus sign not allowed"));
+			goto parse_fail;
+		}
+		p++;
+		neg = 1;
+	}
+
+	ch = *p;
+	if (allow_infinity && ch == (duk_small_int_t) 'I') {
+		/* Don't check for Infinity unless the context allows it.
+		 * 'Infinity' is a valid integer literal in e.g. base-36:
+		 *
+		 *   parseInt('Infinity', 36)
+		 *   1461559270678
+		 */
+
+		const duk_uint8_t *q;
+
+		/* borrow literal Infinity from builtin string */
+		q = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(DUK_HTHREAD_STRING_INFINITY(thr));
+		if (DUK_STRNCMP((const char *) p, (const char *) q, 8) == 0) {
+			if (!allow_garbage && (p[8] != (duk_uint8_t) 0)) {
+				DUK_DDD(DUK_DDDPRINT("parse failed: trailing garbage after matching 'Infinity' not allowed"));
+				goto parse_fail;
+			} else {
+				res = DUK_DOUBLE_INFINITY;
+				goto negcheck_and_ret;
+			}
+		}
+	}
+	if (ch == (duk_small_int_t) '0') {
+		duk_small_int_t detect_radix = 0;
+		ch = p[1];
+		if (allow_auto_hex_int && (ch == (duk_small_int_t) 'x' || ch == (duk_small_int_t) 'X')) {
+			DUK_DDD(DUK_DDDPRINT("detected 0x/0X hex prefix, changing radix and preventing fractions and exponent"));
+			detect_radix = 16;
+			allow_empty = 0;  /* interpret e.g. '0x' and '0xg' as a NaN (= parse error) */
+			p += 2;
+		} else if (allow_auto_oct_int && (ch >= (duk_small_int_t) '0' && ch <= (duk_small_int_t) '9')) {
+			DUK_DDD(DUK_DDDPRINT("detected 0n oct prefix, changing radix and preventing fractions and exponent"));
+			detect_radix = 8;
+			allow_empty = 1;  /* interpret e.g. '09' as '0', not NaN */
+			p += 1;
+		}
+		if (detect_radix > 0) {
+			radix = detect_radix;
+			allow_exp = 0;
+			allow_frac = 0;
+			allow_naked_frac = 0;
+			allow_empty_frac = 0;
+			allow_leading_zero = 1;  /* allow e.g. '0x0009' and '00077' */
+		}
+	}
+
+	/*
+	 *  Scan number and setup for Dragon4.
+	 *
+	 *  The fast path case is detected during setup: an integer which
+	 *  can be converted without rounding, no net exponent.  The fast
+	 *  path could be implemented as a separate scan, but may not really
+	 *  be worth it: the multiplications for building 'f' are not
+	 *  expensive when 'f' is small.
+	 *
+	 *  The significand ('f') must contain enough bits of (apparent)
+	 *  accuracy, so that Dragon4 will generate enough binary output digits.
+	 *  For decimal numbers, this means generating a 20-digit significand,
+	 *  which should yield enough practical accuracy to parse IEEE doubles.
+	 *  In fact, the Ecmascript specification explicitly allows an
+	 *  implementation to treat digits beyond 20 as zeroes (and even
+	 *  to round the 20th digit upwards).  For non-decimal numbers, the
+	 *  appropriate number of digits has been precomputed for comparable
+	 *  accuracy.
+	 *
+	 *  Digit counts:
+	 *
+	 *    [ dig_lzero ]
+	 *      |
+	 *     .+-..---[ dig_prec ]----.
+	 *     |  ||                   |
+	 *     0000123.456789012345678901234567890e+123456
+	 *     |     | |                         |  |    |
+	 *     `--+--' `------[ dig_frac ]-------'  `-+--'
+	 *        |                                   |
+	 *    [ dig_whole ]                       [ dig_exp ]
+	 *
+	 *    dig_frac and dig_exp are -1 if not present
+	 *    dig_lzero is only computed for whole number part
+	 *
+	 *  Parsing state
+	 *
+	 *     Parsing whole part      dig_frac < 0 AND dig_exp < 0
+	 *     Parsing fraction part   dig_frac >= 0 AND dig_exp < 0
+	 *     Parsing exponent part   dig_exp >= 0   (dig_frac may be < 0 or >= 0)
+	 * 
+	 *  Note: in case we hit an implementation limit (like exponent range),
+	 *  we should throw an error, NOT return NaN or Infinity.  Even with
+	 *  very large exponent (or significand) values the final result may be
+	 *  finite, so NaN/Infinity would be incorrect.
+	 */
+
+	duk__bi_set_small(&nc_ctx->f, 0);
+	dig_prec = 0;
+	dig_lzero = 0;
+	dig_whole = 0;
+	dig_frac = -1;
+	dig_exp = -1;
+	exp = 0;
+	exp_adj = 0;  /* essentially tracks digit position of lowest 'f' digit */
+	exp_neg = 0;
+	for (;;) {
+		ch = *p++;
+
+		DUK_DDD(DUK_DDDPRINT("parse digits: p=%p, ch='%c' (%ld), exp=%ld, exp_adj=%ld, "
+		                     "dig_whole=%ld, dig_frac=%ld, dig_exp=%ld, dig_lzero=%ld, dig_prec=%ld",
+		                     (void *) p, (int) ((ch >= 0x20 && ch <= 0x7e) ? ch : '?'), (long) ch,
+		                     (long) exp, (long) exp_adj, (long) dig_whole, (long) dig_frac,
+		                     (long) dig_exp, (long) dig_lzero, (long) dig_prec));
+		DUK__BI_PRINT("f", &nc_ctx->f);
+
+		/* Most common cases first. */
+		if (ch >= (duk_small_int_t) '0' && ch <= (duk_small_int_t) '9') {
+			dig = (int) ch - '0' + 0;
+		} else if (ch == (duk_small_int_t) '.') {
+			/* A leading digit is not required in some cases, e.g. accept ".123".
+			 * In other cases (JSON.parse()) a leading digit is required.  This
+			 * is checked for after the loop.
+			 */
+			if (dig_frac >= 0 || dig_exp >= 0) {
+				if (allow_garbage) {
+					DUK_DDD(DUK_DDDPRINT("garbage termination (invalid period)"));
+					break;
+				} else {
+					DUK_DDD(DUK_DDDPRINT("parse failed: period not allowed"));
+					goto parse_fail;
+				}
+			}
+
+			if (!allow_frac) {
+				/* Some contexts don't allow fractions at all; this can't be a
+				 * post-check because the state ('f' and exp) would be incorrect.
+				 */
+				if (allow_garbage) {
+					DUK_DDD(DUK_DDDPRINT("garbage termination (invalid first period)"));
+					break;
+				} else {
+					DUK_DDD(DUK_DDDPRINT("parse failed: fraction part not allowed"));
+				}
+			}
+
+			DUK_DDD(DUK_DDDPRINT("start fraction part"));
+			dig_frac = 0;
+			continue;
+		} else if (ch == (duk_small_int_t) 0) {
+			DUK_DDD(DUK_DDDPRINT("NUL termination"));
+			break;
+		} else if (allow_exp && dig_exp < 0 && (ch == (duk_small_int_t) 'e' || ch == (duk_small_int_t) 'E')) {
+			/* Note: we don't parse back exponent notation for anything else
+			 * than radix 10, so this is not an ambiguous check (e.g. hex
+			 * exponent values may have 'e' either as a significand digit
+			 * or as an exponent separator).
+			 *
+			 * If the exponent separator occurs twice, 'e' will be interpreted
+			 * as a digit (= 14) and will be rejected as an invalid decimal
+			 * digit.
+			 */
+
+			DUK_DDD(DUK_DDDPRINT("start exponent part"));
+
+			/* Exponent without a sign or with a +/- sign is accepted
+			 * by all call sites (even JSON.parse()).
+			 */
+			ch = *p;
+			if (ch == (duk_small_int_t) '-') {
+				exp_neg = 1;
+				p++;
+			} else if (ch == (duk_small_int_t) '+') {
+				p++;
+			}
+			dig_exp = 0;
+			continue;
+		} else if (ch >= (duk_small_int_t) 'a' && ch <= (duk_small_int_t) 'z') {
+			dig = (duk_small_int_t) (ch - (duk_small_int_t) 'a' + 0x0a);
+		} else if (ch >= (duk_small_int_t) 'A' && ch <= (duk_small_int_t) 'Z') {
+			dig = (duk_small_int_t) (ch - (duk_small_int_t) 'A' + 0x0a);
+		} else {
+			dig = 255;  /* triggers garbage digit check below */
+		}
+		DUK_ASSERT((dig >= 0 && dig <= 35) || dig == 255);
+
+		if (dig >= radix) {
+			if (allow_garbage) {
+				DUK_DDD(DUK_DDDPRINT("garbage termination"));
+				break;
+			} else {
+				DUK_DDD(DUK_DDDPRINT("parse failed: trailing garbage or invalid digit"));
+				goto parse_fail;
+			}
+		}
+
+		if (dig_exp < 0) {
+			/* whole or fraction digit */
+
+			if (dig_prec < duk__str2num_digits_for_radix[radix - 2]) {
+				/* significant from precision perspective */
+
+				duk_small_int_t f_zero = duk__bi_is_zero(&nc_ctx->f);
+				if (f_zero && dig == 0) {
+					/* Leading zero is not counted towards precision digits; not
+					 * in the integer part, nor in the fraction part.
+					 */
+					if (dig_frac < 0) {
+						dig_lzero++;
+					}
+				} else {
+					/* XXX: join these ops (multiply-accumulate), but only if
+					 * code footprint decreases.
+					 */
+					duk__bi_mul_small(&nc_ctx->t1, &nc_ctx->f, radix);
+					duk__bi_add_small(&nc_ctx->f, &nc_ctx->t1, dig);
+					dig_prec++;
+				}
+			} else {
+				/* Ignore digits beyond a radix-specific limit, but note them
+				 * in exp_adj.
+				 */
+				exp_adj++;
+			}
+	
+			if (dig_frac >= 0) {
+				dig_frac++;
+				exp_adj--;
+			} else {
+				dig_whole++;
+			}
+		} else {
+			/* exponent digit */
+
+			exp = exp * radix + dig;
+			if (exp > DUK_S2N_MAX_EXPONENT) {
+				/* impose a reasonable exponent limit, so that exp
+				 * doesn't need to get tracked using a bigint.
+				 */
+				DUK_DDD(DUK_DDDPRINT("parse failed: exponent too large"));
+				goto parse_int_error;
+			}
+			dig_exp++;
+		}
+	}
+
+	/* Leading zero. */
+
+	if (dig_lzero > 0 && dig_whole > 1) {
+		if (!allow_leading_zero) {
+			DUK_DDD(DUK_DDDPRINT("parse failed: leading zeroes not allowed in integer part"));
+			goto parse_fail;
+		}
+	}
+
+	/* Validity checks for various fraction formats ("0.1", ".1", "1.", "."). */
+
+	if (dig_whole == 0) {
+		if (dig_frac == 0) {
+			/* "." is not accepted in any format */
+			DUK_DDD(DUK_DDDPRINT("parse failed: plain period without leading or trailing digits"));
+			goto parse_fail;
+		} else if (dig_frac > 0) {
+			/* ".123" */
+			if (!allow_naked_frac) {
+				DUK_DDD(DUK_DDDPRINT("parse failed: fraction part not allowed without "
+				                     "leading integer digit(s)"));
+				goto parse_fail;
+			}
+		} else {
+			/* empty ("") is allowed in some formats (e.g. Number(''), as zero */
+			if (!allow_empty) {
+				DUK_DDD(DUK_DDDPRINT("parse failed: empty string not allowed (as zero)"));
+				goto parse_fail;
+			}
+		}
+	} else {
+		if (dig_frac == 0) {
+			/* "123." is allowed in some formats */
+			if (!allow_empty_frac) {
+				DUK_DDD(DUK_DDDPRINT("parse failed: empty fractions"));
+				goto parse_fail;
+			}
+		} else if (dig_frac > 0) {
+			/* "123.456" */
+			;
+		} else {
+			/* "123" */
+			;
+		}
+	}
+
+	/* Exponent without digits (e.g. "1e" or "1e+").  If trailing garbage is
+	 * allowed, ignore exponent part as garbage (= parse as "1", i.e. exp 0).
+	 */
+
+	if (dig_exp == 0) {
+		if (!allow_garbage) {
+			DUK_DDD(DUK_DDDPRINT("parse failed: empty exponent"));
+			goto parse_fail;
+		}
+		DUK_ASSERT(exp == 0);
+	}
+
+	if (exp_neg) {
+		exp = -exp;
+	}
+	DUK_DDD(DUK_DDDPRINT("exp=%ld, exp_adj=%ld, net exponent -> %ld",
+	                     (long) exp, (long) exp_adj, (long) (exp + exp_adj)));
+	exp += exp_adj;
+
+	/* Fast path check. */
+
+	if (nc_ctx->f.n <= 1 &&   /* 32-bit value */
+	    exp == 0    /* no net exponent */) {
+		/* Fast path is triggered for no exponent and also for balanced exponent
+		 * and fraction parts, e.g. for "1.23e2" == "123".  Remember to respect
+		 * zero sign.
+		 */
+
+		/* XXX: could accept numbers larger than 32 bits, e.g. up to 53 bits? */
+		DUK_DDD(DUK_DDDPRINT("fast path number parse"));
+		if (nc_ctx->f.n == 1) {
+			res = (double) nc_ctx->f.v[0];
+		} else {
+			res = 0.0;
+		}
+		goto negcheck_and_ret;
+	}
+
+	/* Significand ('f') padding. */
+
+	while (dig_prec < duk__str2num_digits_for_radix[radix - 2]) {
+		/* Pad significand with "virtual" zero digits so that Dragon4 will
+		 * have enough (apparent) precision to work with.
+		 */
+		DUK_DDD(DUK_DDDPRINT("dig_prec=%ld, pad significand with zero", (long) dig_prec));
+		duk__bi_mul_small_copy(&nc_ctx->f, radix, &nc_ctx->t1);
+		DUK__BI_PRINT("f", &nc_ctx->f);
+		exp--;
+		dig_prec++;
+	}
+
+	DUK_DDD(DUK_DDDPRINT("final exponent: %ld", (long) exp));
+
+	/* Detect zero special case. */
+
+	if (nc_ctx->f.n == 0) {
+		/* This may happen even after the fast path check, if exponent is
+		 * not balanced (e.g. "0e1").  Remember to respect zero sign.
+		 */
+		DUK_DDD(DUK_DDDPRINT("significand is zero"));
+		res = 0.0;
+		goto negcheck_and_ret;
+	}
+
+
+	/* Quick reject of too large or too small exponents.  This check
+	 * would be incorrect for zero (e.g. "0e1000" is zero, not Infinity)
+	 * so zero check must be above.
+	 */
+
+	explim = &duk__str2num_exp_limits[radix - 2];
+	if (exp > explim->upper) {
+		DUK_DDD(DUK_DDDPRINT("exponent too large -> infinite"));
+		res = (duk_double_t) DUK_DOUBLE_INFINITY;
+		goto negcheck_and_ret;
+	} else if (exp < explim->lower) {
+		DUK_DDD(DUK_DDDPRINT("exponent too small -> zero"));
+		res = (duk_double_t) 0.0;
+		goto negcheck_and_ret;
+	}
+
+	nc_ctx->is_s2n = 1;
+	nc_ctx->e = exp;
+	nc_ctx->b = radix;
+	nc_ctx->B = 2;
+	nc_ctx->is_fixed = 1;
+	nc_ctx->abs_pos = 0;
+	nc_ctx->req_digits = 53 + 1;
+
+	DUK__BI_PRINT("f", &nc_ctx->f);
+	DUK_DDD(DUK_DDDPRINT("e=%ld", (long) nc_ctx->e));
+
+	/*
+	 *  Dragon4 slow path (binary) digit generation.
+	 *  An extra digit is generated for rounding.
+	 */
+
+	duk__dragon4_prepare(nc_ctx);  /* setup many variables in nc_ctx */
+
+	DUK_DDD(DUK_DDDPRINT("after prepare:"));
+	DUK__BI_PRINT("r", &nc_ctx->r);
+	DUK__BI_PRINT("s", &nc_ctx->s);
+	DUK__BI_PRINT("mp", &nc_ctx->mp);
+	DUK__BI_PRINT("mm", &nc_ctx->mm);
+
+	duk__dragon4_scale(nc_ctx);
+
+	DUK_DDD(DUK_DDDPRINT("after scale; k=%ld", (long) nc_ctx->k));
+	DUK__BI_PRINT("r", &nc_ctx->r);
+	DUK__BI_PRINT("s", &nc_ctx->s);
+	DUK__BI_PRINT("mp", &nc_ctx->mp);
+	DUK__BI_PRINT("mm", &nc_ctx->mm);
+
+	duk__dragon4_generate(nc_ctx);
+
+	DUK_ASSERT(nc_ctx->count == 53 + 1);
+
+	/*
+	 *  Convert binary digits into an IEEE double.  Need to handle
+	 *  denormals and rounding correctly.
+	 */
+
+	duk__dragon4_ctx_to_double(nc_ctx, &res);
+	goto negcheck_and_ret;
+
+ negcheck_and_ret:
+	if (neg) {
+		res = -res;
+	}
+	duk_pop(ctx);
+	duk_push_number(ctx, (double) res);
+	DUK_DDD(DUK_DDDPRINT("result: %!T", (duk_tval *) duk_get_tval(ctx, -1)));
+	return;
+
+ parse_fail:
+	DUK_DDD(DUK_DDDPRINT("parse failed"));
+	duk_pop(ctx);
+	duk_push_nan(ctx);
+	return;
+
+ parse_int_error:
+	DUK_DDD(DUK_DDDPRINT("parse failed, internal error, can't return a value"));
+	DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "number parse error");
+	return;
+}
+
+#line 1 "duk_regexp_compiler.c"
+/*
+ *  Regexp compilation.
+ *
+ *  See doc/regexp.txt for a discussion of the compilation approach and
+ *  current limitations.
+ *
+ *  Regexp bytecode assumes jumps can be expressed with signed 32-bit
+ *  integers.  Consequently the bytecode size must not exceed 0x7fffffffL.
+ *  The implementation casts duk_size_t (buffer size) to duk_(u)int32_t
+ *  in many places.  Although this could be changed, the bytecode format
+ *  limit would still prevent regexps exceeding the signed 32-bit limit
+ *  from working.
+ *
+ *  XXX: The implementation does not prevent bytecode from exceeding the
+ *  maximum supported size.  This could be done by limiting the maximum
+ *  input string size (assuming an upper bound can be computed for number
+ *  of bytecode bytes emitted per input byte) or checking buffer maximum
+ *  size when emitting bytecode (slower).
+ */
+
+/* include removed: duk_internal.h */
+
+#ifdef DUK_USE_REGEXP_SUPPORT
+
+/*
+ *  Helper macros
+ */
+
+#ifdef DUK__BUFLEN
+#undef DUK__BUFLEN
+#endif
+
+#define DUK__BUFLEN(re_ctx)   DUK_HBUFFER_GET_SIZE((duk_hbuffer *) re_ctx->buf)
+
+/*
+ *  Disjunction struct: result of parsing a disjunction
+ */
+
+typedef struct {
+	/* Number of characters that the atom matches (e.g. 3 for 'abc'),
+	 * -1 if atom is complex and number of matched characters either
+	 * varies or is not known.
+	 */
+	duk_int32_t charlen;
+
+#if 0
+	/* These are not needed to implement quantifier capture handling,
+	 * but might be needed at some point.
+	 */
+
+	/* re_ctx->captures at start and end of atom parsing.
+	 * Since 'captures' indicates highest capture number emitted
+	 * so far in a DUK_REOP_SAVE, the captures numbers saved by
+	 * the atom are: ]start_captures,end_captures].
+	 */
+	duk_uint32_t start_captures;
+	duk_uint32_t end_captures;
+#endif
+} duk__re_disjunction_info;
+
+/*
+ *  Encoding helpers
+ *
+ *  Some of the typing is bytecode based, e.g. slice sizes are unsigned 32-bit
+ *  even though the buffer operations will use duk_size_t.
+ */
+
+/* XXX: the insert helpers should ensure that the bytecode result is not
+ * larger than expected (or at least assert for it).  Many things in the
+ * bytecode, like skip offsets, won't work correctly if the bytecode is
+ * larger than say 2G.
+ */
+
+static duk_uint32_t duk__encode_i32(duk_int32_t x) {
+	if (x < 0) {
+		return ((duk_uint32_t) (-x)) * 2 + 1;
+	} else {
+		return ((duk_uint32_t) x) * 2;
+	}
+}
+
+/* XXX: return type should probably be duk_size_t, or explicit checks are needed for
+ * maximum size.
+ */
+static duk_uint32_t duk__insert_u32(duk_re_compiler_ctx *re_ctx, duk_uint32_t offset, duk_uint32_t x) {
+	return (duk_uint32_t) duk_hbuffer_insert_xutf8(re_ctx->thr, re_ctx->buf, offset, x);
+}
+
+static duk_uint32_t duk__append_u32(duk_re_compiler_ctx *re_ctx, duk_uint32_t x) {
+	return (duk_uint32_t) duk_hbuffer_append_xutf8(re_ctx->thr, re_ctx->buf, x);
+}
+
+static duk_uint32_t duk__insert_i32(duk_re_compiler_ctx *re_ctx, duk_uint32_t offset, duk_int32_t x) {
+	return (duk_uint32_t) duk_hbuffer_insert_xutf8(re_ctx->thr, re_ctx->buf, offset, duk__encode_i32(x));
+}
+
+#if 0  /* unused */
+static duk_uint32_t duk__append_i32(duk_re_compiler_ctx *re_ctx, duk_int32_t x) {
+	return duk_hbuffer_append_xutf8(re_ctx->thr, re_ctx->buf, duk__encode_i32(x));
+}
+#endif
+
+/* special helper for emitting u16 lists (used for character ranges for built-in char classes) */
+static void duk__append_u16_list(duk_re_compiler_ctx *re_ctx, duk_uint16_t *values, duk_uint32_t count) {
+	/* Call sites don't need the result length so it's not accumulated. */
+	while (count > 0) {
+		(void) duk__append_u32(re_ctx, (duk_uint32_t) (*values++));
+		count--;
+	}
+}
+
+static void duk__insert_slice(duk_re_compiler_ctx *re_ctx, duk_uint32_t offset, duk_uint32_t data_offset, duk_uint32_t data_length) {
+	duk_hbuffer_insert_slice(re_ctx->thr, re_ctx->buf, offset, data_offset, (duk_size_t) data_length);
+}
+
+static void duk__append_slice(duk_re_compiler_ctx *re_ctx, duk_uint32_t data_offset, duk_uint32_t data_length) {
+	duk_hbuffer_append_slice(re_ctx->thr, re_ctx->buf, data_offset, (duk_size_t) data_length);
+}
+
+static void duk__remove_slice(duk_re_compiler_ctx *re_ctx, duk_uint32_t offset, duk_uint32_t length) {
+	duk_hbuffer_remove_slice(re_ctx->thr, re_ctx->buf, offset, (duk_size_t) length);
+}
+
+/*
+ *  Insert a jump offset at 'offset' to complete an instruction
+ *  (the jump offset is always the last component of an instruction).
+ *  The 'skip' argument must be computed relative to 'offset',
+ *  -without- taking into account the skip field being inserted.
+ *
+ *       ... A B C ins X Y Z ...   (ins may be a JUMP, SPLIT1/SPLIT2, etc)
+ *   =>  ... A B C ins SKIP X Y Z
+ *
+ *  Computing the final (adjusted) skip value, which is relative to the
+ *  first byte of the next instruction, is a bit tricky because of the
+ *  variable length UTF-8 encoding.  See doc/regexp.txt for discussion.
+ */
+static duk_uint32_t duk__insert_jump_offset(duk_re_compiler_ctx *re_ctx, duk_uint32_t offset, duk_int32_t skip) {
+	duk_small_int_t len;
+
+	/* XXX: solve into closed form (smaller code) */
+
+	if (skip < 0) {
+		/* two encoding attempts suffices */
+		len = duk_unicode_get_xutf8_length((duk_codepoint_t) duk__encode_i32(skip));
+		len = duk_unicode_get_xutf8_length((duk_codepoint_t) duk__encode_i32(skip - (duk_int32_t) len));
+		DUK_ASSERT(duk_unicode_get_xutf8_length(duk__encode_i32(skip - (duk_int32_t) len)) == len);  /* no change */
+		skip -= (duk_int32_t) len;
+	}
+	return duk__insert_i32(re_ctx, offset, skip);
+}
+
+static duk_uint32_t duk__append_jump_offset(duk_re_compiler_ctx *re_ctx, duk_int32_t skip) {
+	return (duk_uint32_t) duk__insert_jump_offset(re_ctx, (duk_uint32_t) DUK__BUFLEN(re_ctx), skip);
+}
+
+/*
+ *  duk_re_range_callback for generating character class ranges.
+ *
+ *  When ignoreCase is false, the range is simply emitted as is.
+ *  We don't, for instance, eliminate duplicates or overlapping
+ *  ranges in a character class.
+ *
+ *  When ignoreCase is true, the range needs to be normalized through
+ *  canonicalization.  Unfortunately a canonicalized version of a
+ *  continuous range is not necessarily continuous (e.g. [x-{] is
+ *  continuous but [X-{] is not).  The current algorithm creates the
+ *  canonicalized range(s) space efficiently at the cost of compile
+ *  time execution time (see doc/regexp.txt for discussion).
+ *
+ *  Note that the ctx->nranges is a context-wide temporary value
+ *  (this is OK because there cannot be multiple character classes
+ *  being parsed simultaneously).
+ */
+
+static void duk__generate_ranges(void *userdata, duk_codepoint_t r1, duk_codepoint_t r2, duk_bool_t direct) {
+	duk_re_compiler_ctx *re_ctx = (duk_re_compiler_ctx *) userdata;
+
+	DUK_DD(DUK_DDPRINT("duk__generate_ranges(): re_ctx=%p, range=[%ld,%ld] direct=%ld",
+	                   (void *) re_ctx, (long) r1, (long) r2, (long) direct));
+
+	if (!direct && (re_ctx->re_flags & DUK_RE_FLAG_IGNORE_CASE)) {
+		/*
+		 *  Canonicalize a range, generating result ranges as necessary.
+		 *  Needs to exhaustively scan the entire range (at most 65536
+		 *  code points).  If 'direct' is set, caller (lexer) has ensured
+		 *  that the range is already canonicalization compatible (this
+		 *  is used to avoid unnecessary canonicalization of built-in
+		 *  ranges like \W, which are not affected by canonicalization).
+		 *
+		 *  NOTE: here is one place where we don't want to support chars
+		 *  outside the BMP, because the exhaustive search would be
+		 *  massively larger.
+		 */
+
+		duk_codepoint_t i;
+		duk_codepoint_t t;
+		duk_codepoint_t r_start, r_end;
+
+		r_start = duk_unicode_re_canonicalize_char(re_ctx->thr, r1);
+		r_end = r_start;
+		for (i = r1 + 1; i <= r2; i++) {
+			t = duk_unicode_re_canonicalize_char(re_ctx->thr, i);
+			if (t == r_end + 1) {
+				r_end = t;
+			} else {
+				DUK_DD(DUK_DDPRINT("canonicalized, emit range: [%ld,%ld]", (long) r_start, (long) r_end));
+				duk__append_u32(re_ctx, (duk_uint32_t) r_start);
+				duk__append_u32(re_ctx, (duk_uint32_t) r_end);
+				re_ctx->nranges++;
+				r_start = t;
+				r_end = t;
+			}
+		}
+		DUK_DD(DUK_DDPRINT("canonicalized, emit range: [%ld,%ld]", (long) r_start, (long) r_end));
+		duk__append_u32(re_ctx, (duk_uint32_t) r_start);
+		duk__append_u32(re_ctx, (duk_uint32_t) r_end);
+		re_ctx->nranges++;
+	} else {
+		DUK_DD(DUK_DDPRINT("direct, emit range: [%ld,%ld]", (long) r1, (long) r2));
+		duk__append_u32(re_ctx, (duk_uint32_t) r1);
+		duk__append_u32(re_ctx, (duk_uint32_t) r2);
+		re_ctx->nranges++;
+	}
+}
+
+/*
+ *  Parse regexp Disjunction.  Most of regexp compilation happens here.
+ *
+ *  Handles Disjunction, Alternative, and Term productions directly without
+ *  recursion.  The only constructs requiring recursion are positive/negative
+ *  lookaheads, capturing parentheses, and non-capturing parentheses.
+ *
+ *  The function determines whether the entire disjunction is a 'simple atom'
+ *  (see doc/regexp.txt discussion on 'simple quantifiers') and if so,
+ *  returns the atom character length which is needed by the caller to keep
+ *  track of its own atom character length.  A disjunction with more than one
+ *  alternative is never considered a simple atom (although in some cases
+ *  that might be the case).
+ *
+ *  Return value: simple atom character length or < 0 if not a simple atom.
+ *  Appends the bytecode for the disjunction matcher to the end of the temp
+ *  buffer.
+ *
+ *  Regexp top level structure is:
+ *
+ *    Disjunction = Term*
+ *                | Term* | Disjunction
+ *
+ *    Term = Assertion
+ *         | Atom
+ *         | Atom Quantifier
+ *
+ *  An empty Term sequence is a valid disjunction alternative (e.g. /|||c||/).
+ *
+ *  Notes:
+ *
+ *    * Tracking of the 'simple-ness' of the current atom vs. the entire
+ *      disjunction are separate matters.  For instance, the disjunction
+ *      may be complex, but individual atoms may be simple.  Furthermore,
+ *      simple quantifiers are used whenever possible, even if the
+ *      disjunction as a whole is complex.
+ *
+ *    * The estimate of whether an atom is simple is conservative now,
+ *      and it would be possible to expand it.  For instance, captures
+ *      cause the disjunction to be marked complex, even though captures
+ *      -can- be handled by simple quantifiers with some minor modifications.
+ *
+ *    * Disjunction 'tainting' as 'complex' is handled at the end of the
+ *      main for loop collectively for atoms.  Assertions, quantifiers,
+ *      and '|' tokens need to taint the result manually if necessary.
+ *      Assertions cannot add to result char length, only atoms (and
+ *      quantifiers) can; currently quantifiers will taint the result
+ *      as complex though.
+ */
+
+static void duk__parse_disjunction(duk_re_compiler_ctx *re_ctx, duk_bool_t expect_eof, duk__re_disjunction_info *out_atom_info) {
+	duk_int32_t atom_start_offset = -1;                   /* negative -> no atom matched on previous round */
+	duk_int32_t atom_char_length = 0;                     /* negative -> complex atom */
+	duk_uint32_t atom_start_captures = re_ctx->captures;  /* value of re_ctx->captures at start of atom */
+	duk_int32_t unpatched_disjunction_split = -1;
+	duk_int32_t unpatched_disjunction_jump = -1;
+	duk_uint32_t entry_offset = (duk_uint32_t) DUK__BUFLEN(re_ctx);
+	duk_int32_t res_charlen = 0;  /* -1 if disjunction is complex, char length if simple */
+	duk__re_disjunction_info tmp_disj;
+
+	DUK_ASSERT(out_atom_info != NULL);
+
+	if (re_ctx->recursion_depth >= re_ctx->recursion_limit) {
+		DUK_ERROR(re_ctx->thr, DUK_ERR_INTERNAL_ERROR,
+		          "regexp compiler recursion limit reached");
+	}
+	re_ctx->recursion_depth++;
+
+#if 0
+	out_atom_info->start_captures = re_ctx->captures;
+#endif
+
+	for (;;) {
+		/* atom_char_length, atom_start_offset, atom_start_offset reflect the
+		 * atom matched on the previous loop.  If a quantifier is encountered
+		 * on this loop, these are needed to handle the quantifier correctly.
+		 * new_atom_char_length etc are for the atom parsed on this round;
+		 * they're written to atom_char_length etc at the end of the round.
+		 */
+		duk_int32_t new_atom_char_length;   /* char length of the atom parsed in this loop */
+		duk_int32_t new_atom_start_offset;  /* bytecode start offset of the atom parsed in this loop
+		                                     * (allows quantifiers to copy the atom bytecode)
+		                                     */
+		duk_uint32_t new_atom_start_captures;  /* re_ctx->captures at the start of the atom parsed in this loop */
+
+		duk_lexer_parse_re_token(&re_ctx->lex, &re_ctx->curr_token);
+
+		DUK_DD(DUK_DDPRINT("re token: %ld (num=%ld, char=%c)",
+		                   (long) re_ctx->curr_token.t,
+		                   (long) re_ctx->curr_token.num,
+		                   (re_ctx->curr_token.num >= 0x20 && re_ctx->curr_token.num <= 0x7e) ?
+		                   (int) re_ctx->curr_token.num : (int) '?'));
+
+		/* set by atom case clauses */
+		new_atom_start_offset = -1;
+		new_atom_char_length = -1;
+		new_atom_start_captures = re_ctx->captures;
+
+		switch (re_ctx->curr_token.t) {
+		case DUK_RETOK_DISJUNCTION: {
+			/*
+			 *  The handling here is a bit tricky.  If a previous '|' has been processed,
+			 *  we have a pending split1 and a pending jump (for a previous match).  These
+			 *  need to be back-patched carefully.  See docs for a detailed example.
+			 */
+
+			/* patch pending jump and split */
+			if (unpatched_disjunction_jump >= 0) {
+				duk_uint32_t offset;
+
+				DUK_ASSERT(unpatched_disjunction_split >= 0);
+				offset = unpatched_disjunction_jump;
+				offset += duk__insert_jump_offset(re_ctx,
+				                                  offset,
+				                                  (duk_int32_t) (DUK__BUFLEN(re_ctx) - offset));
+				/* offset is now target of the pending split (right after jump) */
+				duk__insert_jump_offset(re_ctx,
+				                        unpatched_disjunction_split,
+				                        offset - unpatched_disjunction_split);
+			}
+
+			/* add a new pending split to the beginning of the entire disjunction */
+			(void) duk__insert_u32(re_ctx,
+			                       entry_offset,
+			                       DUK_REOP_SPLIT1);   /* prefer direct execution */
+			unpatched_disjunction_split = entry_offset + 1;   /* +1 for opcode */
+
+			/* add a new pending match jump for latest finished alternative */
+			duk__append_u32(re_ctx, DUK_REOP_JUMP);
+			unpatched_disjunction_jump = (duk_int32_t) DUK__BUFLEN(re_ctx);
+
+			/* 'taint' result as complex */
+			res_charlen = -1;
+			break;
+		}
+		case DUK_RETOK_QUANTIFIER: {
+			if (atom_start_offset < 0) {
+				DUK_ERROR(re_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+				          "quantifier without preceding atom");
+			}
+			if (re_ctx->curr_token.qmin > re_ctx->curr_token.qmax) {
+				DUK_ERROR(re_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+				          "quantifier values invalid (qmin > qmax)");
+			}
+			if (atom_char_length >= 0) {
+				/*
+				 *  Simple atom
+				 *
+				 *  If atom_char_length is zero, we'll have unbounded execution time for e.g.
+			 	 *  /()*x/.exec('x').  We can't just skip the match because it might have some
+				 *  side effects (for instance, if we allowed captures in simple atoms, the
+				 *  capture needs to happen).  The simple solution below is to force the
+				 *  quantifier to match at most once, since the additional matches have no effect.
+				 *
+				 *  With a simple atom there can be no capture groups, so no captures need
+				 *  to be reset.
+				 */
+				duk_int32_t atom_code_length;
+				duk_uint32_t offset;
+				duk_uint32_t qmin, qmax;
+
+				qmin = re_ctx->curr_token.qmin;
+				qmax = re_ctx->curr_token.qmax;
+				if (atom_char_length == 0) {
+					/* qmin and qmax will be 0 or 1 */
+					if (qmin > 1) {
+						qmin = 1;
+					}
+					if (qmax > 1) {
+						qmax = 1;
+					}
+				}
+
+				duk__append_u32(re_ctx, DUK_REOP_MATCH);   /* complete 'sub atom' */
+				atom_code_length = (duk_int32_t) (DUK__BUFLEN(re_ctx) - atom_start_offset);
+
+				offset = atom_start_offset;
+				if (re_ctx->curr_token.greedy) {
+					offset += duk__insert_u32(re_ctx, offset, DUK_REOP_SQGREEDY);
+					offset += duk__insert_u32(re_ctx, offset, qmin);
+					offset += duk__insert_u32(re_ctx, offset, qmax);
+					offset += duk__insert_u32(re_ctx, offset, atom_char_length);
+					offset += duk__insert_jump_offset(re_ctx, offset, atom_code_length);
+				} else {
+					offset += duk__insert_u32(re_ctx, offset, DUK_REOP_SQMINIMAL);
+					offset += duk__insert_u32(re_ctx, offset, qmin);
+					offset += duk__insert_u32(re_ctx, offset, qmax);
+					offset += duk__insert_jump_offset(re_ctx, offset, atom_code_length);
+				}
+				DUK_UNREF(offset);  /* silence scan-build warning */
+			} else {
+				/*
+				 *  Complex atom
+				 *
+				 *  The original code is used as a template, and removed at the end
+				 *  (this differs from the handling of simple quantifiers).
+				 *
+				 *  NOTE: there is no current solution for empty atoms in complex
+				 *  quantifiers.  This would need some sort of a 'progress' instruction.
+				 *
+				 *  XXX: impose limit on maximum result size, i.e. atom_code_len * atom_copies?
+				 */
+				duk_int32_t atom_code_length;
+				duk_uint32_t atom_copies;
+				duk_uint32_t tmp_qmin, tmp_qmax;
+
+				/* pre-check how many atom copies we're willing to make (atom_copies not needed below) */
+				atom_copies = (re_ctx->curr_token.qmax == DUK_RE_QUANTIFIER_INFINITE) ?
+				              re_ctx->curr_token.qmin : re_ctx->curr_token.qmax;
+				if (atom_copies > DUK_RE_MAX_ATOM_COPIES) {
+					DUK_ERROR(re_ctx->thr, DUK_ERR_INTERNAL_ERROR,
+					          "quantifier expansion requires too many atom copies");
+				}
+
+				/* wipe the capture range made by the atom (if any) */
+				DUK_ASSERT(atom_start_captures <= re_ctx->captures);
+				if (atom_start_captures != re_ctx->captures) {
+					DUK_ASSERT(atom_start_captures < re_ctx->captures);
+					DUK_DDD(DUK_DDDPRINT("must wipe ]atom_start_captures,re_ctx->captures]: ]%ld,%ld]",
+					                     (long) atom_start_captures, (long) re_ctx->captures));
+
+					/* insert (DUK_REOP_WIPERANGE, start, count) in reverse order so the order ends up right */
+					duk__insert_u32(re_ctx, atom_start_offset, (re_ctx->captures - atom_start_captures) * 2);
+					duk__insert_u32(re_ctx, atom_start_offset, (atom_start_captures + 1) * 2);
+					duk__insert_u32(re_ctx, atom_start_offset, DUK_REOP_WIPERANGE);
+				} else {
+					DUK_DDD(DUK_DDDPRINT("no need to wipe captures: atom_start_captures == re_ctx->captures == %ld",
+					                     (long) atom_start_captures));
+				}
+
+				atom_code_length = (duk_int32_t) DUK__BUFLEN(re_ctx) - atom_start_offset;
+
+				/* insert the required matches (qmin) by copying the atom */
+				tmp_qmin = re_ctx->curr_token.qmin;
+				tmp_qmax = re_ctx->curr_token.qmax;
+				while (tmp_qmin > 0) {
+					duk__append_slice(re_ctx, atom_start_offset, atom_code_length);
+					tmp_qmin--;
+					if (tmp_qmax != DUK_RE_QUANTIFIER_INFINITE) {
+						tmp_qmax--;
+					}
+				}
+				DUK_ASSERT(tmp_qmin == 0);
+
+				/* insert code for matching the remainder - infinite or finite */
+				if (tmp_qmax == DUK_RE_QUANTIFIER_INFINITE) {
+					/* reuse last emitted atom for remaining 'infinite' quantifier */
+
+					if (re_ctx->curr_token.qmin == 0) {
+						/* Special case: original qmin was zero so there is nothing
+						 * to repeat.  Emit an atom copy but jump over it here.
+						 */
+						duk__append_u32(re_ctx, DUK_REOP_JUMP);
+						duk__append_jump_offset(re_ctx, atom_code_length);
+						duk__append_slice(re_ctx, atom_start_offset, atom_code_length);
+					}
+					if (re_ctx->curr_token.greedy) {
+						duk__append_u32(re_ctx, DUK_REOP_SPLIT2);   /* prefer jump */
+					} else {
+						duk__append_u32(re_ctx, DUK_REOP_SPLIT1);   /* prefer direct */
+					}
+					duk__append_jump_offset(re_ctx, -atom_code_length - 1);  /* -1 for opcode */
+				} else {
+					/*
+					 *  The remaining matches are emitted as sequence of SPLITs and atom
+					 *  copies; the SPLITs skip the remaining copies and match the sequel.
+					 *  This sequence needs to be emitted starting from the last copy
+					 *  because the SPLITs are variable length due to the variable length
+					 *  skip offset.  This causes a lot of memory copying now.
+					 *
+					 *  Example structure (greedy, match maximum # atoms):
+					 *
+					 *      SPLIT1 LSEQ
+					 *      (atom)
+					 *      SPLIT1 LSEQ    ; <- the byte length of this instruction is needed
+					 *      (atom)	       ; to encode the above SPLIT1 correctly
+					 *      ...
+					 *   LSEQ:
+					 */
+					duk_uint32_t offset = (duk_uint32_t) DUK__BUFLEN(re_ctx);
+					while (tmp_qmax > 0) {
+						duk__insert_slice(re_ctx, offset, atom_start_offset, atom_code_length);
+						if (re_ctx->curr_token.greedy) {
+							duk__insert_u32(re_ctx, offset, DUK_REOP_SPLIT1);   /* prefer direct */
+						} else {
+							duk__insert_u32(re_ctx, offset, DUK_REOP_SPLIT2);   /* prefer jump */
+						}
+						duk__insert_jump_offset(re_ctx,
+						                        offset + 1,   /* +1 for opcode */
+						                        (duk_int32_t) (DUK__BUFLEN(re_ctx) - (offset + 1)));
+						tmp_qmax--;
+					}
+				}
+
+				/* remove the original 'template' atom */
+				duk__remove_slice(re_ctx, atom_start_offset, atom_code_length);
+			}
+
+			/* 'taint' result as complex */
+			res_charlen = -1;
+			break;
+		}
+		case DUK_RETOK_ASSERT_START: {
+			duk__append_u32(re_ctx, DUK_REOP_ASSERT_START);
+			break;
+		}
+		case DUK_RETOK_ASSERT_END: {
+			duk__append_u32(re_ctx, DUK_REOP_ASSERT_END);
+			break;
+		}
+		case DUK_RETOK_ASSERT_WORD_BOUNDARY: {
+			duk__append_u32(re_ctx, DUK_REOP_ASSERT_WORD_BOUNDARY);
+			break;
+		}
+		case DUK_RETOK_ASSERT_NOT_WORD_BOUNDARY: {
+			duk__append_u32(re_ctx, DUK_REOP_ASSERT_NOT_WORD_BOUNDARY);
+			break;
+		}
+		case DUK_RETOK_ASSERT_START_POS_LOOKAHEAD:
+		case DUK_RETOK_ASSERT_START_NEG_LOOKAHEAD: {
+			duk_uint32_t offset;
+			duk_uint32_t opcode = (re_ctx->curr_token.t == DUK_RETOK_ASSERT_START_POS_LOOKAHEAD) ?
+			                      DUK_REOP_LOOKPOS : DUK_REOP_LOOKNEG;
+
+			offset = (duk_uint32_t) DUK__BUFLEN(re_ctx);
+			duk__parse_disjunction(re_ctx, 0, &tmp_disj);
+			duk__append_u32(re_ctx, DUK_REOP_MATCH);
+
+			(void) duk__insert_u32(re_ctx, offset, opcode);
+			(void) duk__insert_jump_offset(re_ctx,
+			                               offset + 1,   /* +1 for opcode */
+			                               (duk_int32_t) (DUK__BUFLEN(re_ctx) - (offset + 1)));
+
+			/* 'taint' result as complex -- this is conservative,
+			 * as lookaheads do not backtrack.
+			 */
+			res_charlen = -1;
+			break;
+		}
+		case DUK_RETOK_ATOM_PERIOD: {
+			new_atom_char_length = 1;
+			new_atom_start_offset = (duk_int32_t) DUK__BUFLEN(re_ctx);
+			duk__append_u32(re_ctx, DUK_REOP_PERIOD);
+			break;
+		}
+		case DUK_RETOK_ATOM_CHAR: {
+			/* Note: successive characters could be joined into string matches
+			 * but this is not trivial (consider e.g. '/xyz+/); see docs for
+			 * more discussion.
+			 */
+			duk_uint32_t ch;
+
+			new_atom_char_length = 1;
+			new_atom_start_offset = (duk_int32_t) DUK__BUFLEN(re_ctx);
+			duk__append_u32(re_ctx, DUK_REOP_CHAR);
+			ch = re_ctx->curr_token.num;
+			if (re_ctx->re_flags & DUK_RE_FLAG_IGNORE_CASE) {
+				ch = duk_unicode_re_canonicalize_char(re_ctx->thr, ch);
+			}
+			duk__append_u32(re_ctx, ch);
+			break;
+		}
+		case DUK_RETOK_ATOM_DIGIT:
+		case DUK_RETOK_ATOM_NOT_DIGIT: {
+			new_atom_char_length = 1;
+			new_atom_start_offset = (duk_int32_t) DUK__BUFLEN(re_ctx);
+			duk__append_u32(re_ctx,
+			                (re_ctx->curr_token.t == DUK_RETOK_ATOM_DIGIT) ?
+			                DUK_REOP_RANGES : DUK_REOP_INVRANGES);
+			duk__append_u32(re_ctx, sizeof(duk_unicode_re_ranges_digit) / (2 * sizeof(duk_uint16_t)));
+			duk__append_u16_list(re_ctx, duk_unicode_re_ranges_digit, sizeof(duk_unicode_re_ranges_digit) / sizeof(duk_uint16_t));
+			break;
+		}
+		case DUK_RETOK_ATOM_WHITE:
+		case DUK_RETOK_ATOM_NOT_WHITE: {
+			new_atom_char_length = 1;
+			new_atom_start_offset = (duk_int32_t) DUK__BUFLEN(re_ctx);
+			duk__append_u32(re_ctx,
+			                (re_ctx->curr_token.t == DUK_RETOK_ATOM_WHITE) ?
+			                DUK_REOP_RANGES : DUK_REOP_INVRANGES);
+			duk__append_u32(re_ctx, sizeof(duk_unicode_re_ranges_white) / (2 * sizeof(duk_uint16_t)));
+			duk__append_u16_list(re_ctx, duk_unicode_re_ranges_white, sizeof(duk_unicode_re_ranges_white) / sizeof(duk_uint16_t));
+			break;
+		}
+		case DUK_RETOK_ATOM_WORD_CHAR:
+		case DUK_RETOK_ATOM_NOT_WORD_CHAR: {
+			new_atom_char_length = 1;
+			new_atom_start_offset = (duk_int32_t) DUK__BUFLEN(re_ctx);
+			duk__append_u32(re_ctx,
+			                (re_ctx->curr_token.t == DUK_RETOK_ATOM_WORD_CHAR) ?
+			                DUK_REOP_RANGES : DUK_REOP_INVRANGES);
+			duk__append_u32(re_ctx, sizeof(duk_unicode_re_ranges_wordchar) / (2 * sizeof(duk_uint16_t)));
+			duk__append_u16_list(re_ctx, duk_unicode_re_ranges_wordchar, sizeof(duk_unicode_re_ranges_wordchar) / sizeof(duk_uint16_t));
+			break;
+		}
+		case DUK_RETOK_ATOM_BACKREFERENCE: {
+			duk_uint32_t backref = (duk_uint32_t) re_ctx->curr_token.num;
+			if (backref > re_ctx->highest_backref) {
+				re_ctx->highest_backref = backref;
+			}
+			new_atom_char_length = -1;   /* mark as complex */
+			new_atom_start_offset = (duk_int32_t) DUK__BUFLEN(re_ctx);
+			duk__append_u32(re_ctx, DUK_REOP_BACKREFERENCE);
+			duk__append_u32(re_ctx, backref);
+			break;
+		}
+		case DUK_RETOK_ATOM_START_CAPTURE_GROUP: {
+			duk_uint32_t cap;
+
+			new_atom_char_length = -1;   /* mark as complex (capture handling) */
+			new_atom_start_offset = (duk_int32_t) DUK__BUFLEN(re_ctx);
+			cap = ++re_ctx->captures;
+			duk__append_u32(re_ctx, DUK_REOP_SAVE);
+			duk__append_u32(re_ctx, cap * 2);
+			duk__parse_disjunction(re_ctx, 0, &tmp_disj);  /* retval (sub-atom char length) unused, tainted as complex above */
+			duk__append_u32(re_ctx, DUK_REOP_SAVE);
+			duk__append_u32(re_ctx, cap * 2 + 1);
+			break;
+		}
+		case DUK_RETOK_ATOM_START_NONCAPTURE_GROUP: {
+			new_atom_start_offset = (duk_int32_t) DUK__BUFLEN(re_ctx);
+			duk__parse_disjunction(re_ctx, 0, &tmp_disj);
+			new_atom_char_length = tmp_disj.charlen;
+			break;
+		}
+		case DUK_RETOK_ATOM_START_CHARCLASS:
+		case DUK_RETOK_ATOM_START_CHARCLASS_INVERTED: {
+			/*
+			 *  Range parsing is done with a special lexer function which calls
+			 *  us for every range parsed.  This is different from how rest of
+			 *  the parsing works, but avoids a heavy, arbitrary size intermediate
+			 *  value type to hold the ranges.
+			 *
+			 *  Another complication is the handling of character ranges when
+			 *  case insensitive matching is used (see docs for discussion).
+			 *  The range handler callback given to the lexer takes care of this
+			 *  as well.
+			 *
+			 *  Note that duplicate ranges are not eliminated when parsing character
+			 *  classes, so that canonicalization of
+			 *
+			 *    [0-9a-fA-Fx-{]
+			 *
+			 *  creates the result (note the duplicate ranges):
+			 *
+			 *    [0-9A-FA-FX-Z{-{]
+			 *
+			 *  where [x-{] is split as a result of canonicalization.  The duplicate
+			 *  ranges are not a semantics issue: they work correctly.
+			 */
+
+			duk_uint32_t offset;
+
+			DUK_DD(DUK_DDPRINT("character class"));
+
+			/* insert ranges instruction, range count patched in later */
+			new_atom_char_length = 1;
+			new_atom_start_offset = (duk_int32_t) DUK__BUFLEN(re_ctx);
+			duk__append_u32(re_ctx,
+			                (re_ctx->curr_token.t == DUK_RETOK_ATOM_START_CHARCLASS) ?
+			                DUK_REOP_RANGES : DUK_REOP_INVRANGES);
+			offset = (duk_uint32_t) DUK__BUFLEN(re_ctx);    /* patch in range count later */
+
+			/* parse ranges until character class ends */
+			re_ctx->nranges = 0;    /* note: ctx-wide temporary */
+			duk_lexer_parse_re_ranges(&re_ctx->lex, duk__generate_ranges, (void *) re_ctx);
+
+			/* insert range count */
+			duk__insert_u32(re_ctx, offset, re_ctx->nranges);
+			break;
+		}
+		case DUK_RETOK_ATOM_END_GROUP: {
+			if (expect_eof) {
+				DUK_ERROR(re_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+				          "unexpected closing parenthesis");
+			}
+			goto done;
+		}
+		case DUK_RETOK_EOF: {
+			if (!expect_eof) {
+				DUK_ERROR(re_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+				          "unexpected end of pattern");
+			}
+			goto done;
+		}
+		default: {
+			DUK_ERROR(re_ctx->thr, DUK_ERR_SYNTAX_ERROR,
+			          "unexpected token in regexp");
+		}
+		}
+
+		/* a complex (new) atom taints the result */
+		if (new_atom_start_offset >= 0) {
+			if (new_atom_char_length < 0) {
+				res_charlen = -1;
+			} else if (res_charlen >= 0) {
+				/* only advance if not tainted */
+				res_charlen += new_atom_char_length;
+			}
+		}
+
+		/* record previous atom info in case next token is a quantifier */
+		atom_start_offset = new_atom_start_offset;
+		atom_char_length = new_atom_char_length;
+		atom_start_captures = new_atom_start_captures;
+	}
+
+ done:
+
+	/* finish up pending jump and split for last alternative */
+	if (unpatched_disjunction_jump >= 0) {
+		duk_uint32_t offset;
+
+		DUK_ASSERT(unpatched_disjunction_split >= 0);
+		offset = unpatched_disjunction_jump;
+		offset += duk__insert_jump_offset(re_ctx,
+		                                  offset,
+		                                  (duk_int32_t) (DUK__BUFLEN(re_ctx) - offset));
+		/* offset is now target of the pending split (right after jump) */
+		duk__insert_jump_offset(re_ctx,
+		                        unpatched_disjunction_split,
+		                        offset - unpatched_disjunction_split);
+	}
+
+#if 0
+	out_atom_info->end_captures = re_ctx->captures;
+#endif
+	out_atom_info->charlen = res_charlen;
+	DUK_DDD(DUK_DDDPRINT("parse disjunction finished: charlen=%ld",
+	                     (long) out_atom_info->charlen));
+
+	re_ctx->recursion_depth--;
+}
+
+/*
+ *  Flags parsing (see E5 Section 15.10.4.1).
+ */
+
+static duk_uint32_t duk__parse_regexp_flags(duk_hthread *thr, duk_hstring *h) {
+	duk_uint8_t *p;
+	duk_uint8_t *p_end;
+	duk_uint32_t flags = 0;
+
+	p = DUK_HSTRING_GET_DATA(h);
+	p_end = p + DUK_HSTRING_GET_BYTELEN(h);
+
+	/* Note: can be safely scanned as bytes (undecoded) */
+
+	while (p < p_end) {
+		duk_uint8_t c = *p++;
+		switch ((int) c) {
+		case (int) 'g': {
+			if (flags & DUK_RE_FLAG_GLOBAL) {
+				goto error;
+			}
+			flags |= DUK_RE_FLAG_GLOBAL;
+			break;
+		}
+		case (int) 'i': {
+			if (flags & DUK_RE_FLAG_IGNORE_CASE) {
+				goto error;
+			}
+			flags |= DUK_RE_FLAG_IGNORE_CASE;
+			break;
+		}
+		case (int) 'm': {
+			if (flags & DUK_RE_FLAG_MULTILINE) {
+				goto error;
+			}
+			flags |= DUK_RE_FLAG_MULTILINE;
+			break;
+		}
+		default: {
+			goto error;
+		}
+		}
+	}
+
+	return flags;
+
+ error:
+	DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "invalid regexp flags");
+	return 0;  /* never here */
+}
+
+/*
+ *  Create escaped RegExp source (E5 Section 15.10.3).
+ *
+ *  The current approach is to special case the empty RegExp
+ *  ('' -> '(?:)') and otherwise replace unescaped '/' characters
+ *  with '\/' regardless of where they occur in the regexp.
+ *
+ *  Note that normalization does not seem to be necessary for
+ *  RegExp literals (e.g. '/foo/') because to be acceptable as
+ *  a RegExp literal, the text between forward slashes must
+ *  already match the escaping requirements (e.g. must not contain
+ *  unescaped forward slashes or be empty).  Escaping IS needed
+ *  for expressions like 'new Regexp("...", "")' however.
+ *  Currently, we re-escape in either case.
+ *
+ *  Also note that we process the source here in UTF-8 encoded
+ *  form.  This is correct, because any non-ASCII characters are
+ *  passed through without change.
+ */
+
+static void duk__create_escaped_source(duk_hthread *thr, int idx_pattern) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_hstring *h;
+	duk_hbuffer_dynamic *buf;
+	const duk_uint8_t *p;
+	duk_size_t i, n;
+	duk_uint_fast8_t c_prev, c;
+
+	h = duk_get_hstring(ctx, idx_pattern);
+	DUK_ASSERT(h != NULL);
+	p = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h);
+	n = (duk_size_t) DUK_HSTRING_GET_BYTELEN(h);
+
+	if (n == 0) {
+		/* return '(?:)' */
+		duk_push_hstring_stridx(ctx, DUK_STRIDX_ESCAPED_EMPTY_REGEXP);
+		return;
+	}
+
+	duk_push_dynamic_buffer(ctx, 0);
+	buf = (duk_hbuffer_dynamic *) duk_get_hbuffer(ctx, -1);
+	DUK_ASSERT(buf != NULL);
+
+	c_prev = (duk_uint_fast8_t) 0;
+
+	for (i = 0; i < n; i++) {
+		c = p[i];
+
+		if (c == (duk_uint_fast8_t) '/' && c_prev != (duk_uint_fast8_t) '\\') {
+			/* Unescaped '/' ANYWHERE in the regexp (in disjunction,
+			 * inside a character class, ...) => same escape works.
+			 */
+			duk_hbuffer_append_byte(thr, buf, (duk_uint8_t) '\\');
+		}
+		duk_hbuffer_append_byte(thr, buf, (duk_uint8_t) c);
+
+		c_prev = c;
+	}
+
+	duk_to_string(ctx, -1);  /* -> [ ... escaped_source ] */
+}
+
+/*
+ *  Exposed regexp compilation primitive.
+ *
+ *  Sets up a regexp compilation context, and calls duk__parse_disjunction() to do the
+ *  actual parsing.  Handles generation of the compiled regexp header and the
+ *  "boilerplate" capture of the matching substring (save 0 and 1).  Also does some
+ *  global level regexp checks after recursive compilation has finished.
+ *
+ *  An escaped version of the regexp source, suitable for use as a RegExp instance
+ *  'source' property (see E5 Section 15.10.3), is also left on the stack.
+ *
+ *  Input stack:  [ pattern flags ]
+ *  Output stack: [ bytecode escaped_source ]  (both as strings)
+ */
+
+void duk_regexp_compile(duk_hthread *thr) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_re_compiler_ctx re_ctx;
+	duk_lexer_point lex_point;
+	duk_hstring *h_pattern;
+	duk_hstring *h_flags;
+	duk_hbuffer_dynamic *h_buffer;
+	duk__re_disjunction_info ign_disj;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(ctx != NULL);
+
+	/*
+	 *  Args validation
+	 */
+
+	/* TypeError if fails */
+	h_pattern = duk_require_hstring(ctx, -2);
+	h_flags = duk_require_hstring(ctx, -1);
+
+	/*
+	 *  Create normalized 'source' property (E5 Section 15.10.3).
+	 */
+
+	/* [ ... pattern flags ] */
+
+	duk__create_escaped_source(thr, -2);
+
+	/* [ ... pattern flags escaped_source ] */
+
+	/*
+	 *  Init compilation context
+	 */
+
+	duk_push_dynamic_buffer(ctx, 0);
+	h_buffer = (duk_hbuffer_dynamic *) duk_require_hbuffer(ctx, -1);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(h_buffer));
+
+	/* [ ... pattern flags escaped_source buffer ] */
+
+	DUK_MEMZERO(&re_ctx, sizeof(re_ctx));
+	DUK_LEXER_INITCTX(&re_ctx.lex);  /* duplicate zeroing, expect for (possible) NULL inits */
+	re_ctx.thr = thr;
+	re_ctx.lex.thr = thr;
+	re_ctx.lex.input = DUK_HSTRING_GET_DATA(h_pattern);
+	re_ctx.lex.input_length = DUK_HSTRING_GET_BYTELEN(h_pattern);
+	re_ctx.lex.token_limit = DUK_RE_COMPILE_TOKEN_LIMIT;
+	re_ctx.buf = h_buffer;
+	re_ctx.recursion_limit = DUK_RE_COMPILE_RECURSION_LIMIT;
+	re_ctx.re_flags = duk__parse_regexp_flags(thr, h_flags);
+
+	DUK_DD(DUK_DDPRINT("regexp compiler ctx initialized, flags=0x%08lx, recursion_limit=%ld",
+	                   (unsigned long) re_ctx.re_flags, (long) re_ctx.recursion_limit));
+
+	/*
+	 *  Init lexer
+	 */
+
+	lex_point.offset = 0;		/* expensive init, just want to fill window */
+	lex_point.line = 1;
+	DUK_LEXER_SETPOINT(&re_ctx.lex, &lex_point);
+
+	/*
+	 *  Compilation
+	 */
+
+	DUK_D(DUK_DPRINT("starting regexp compilation"));
+
+	duk__append_u32(&re_ctx, DUK_REOP_SAVE);
+	duk__append_u32(&re_ctx, 0);
+	duk__parse_disjunction(&re_ctx, 1 /*expect_eof*/, &ign_disj);
+	duk__append_u32(&re_ctx, DUK_REOP_SAVE);
+	duk__append_u32(&re_ctx, 1);
+	duk__append_u32(&re_ctx, DUK_REOP_MATCH);
+
+	DUK_D(DUK_DPRINT("regexp bytecode size (before header) is %ld bytes",
+	                 (long) DUK_HBUFFER_GET_SIZE(re_ctx.buf)));
+
+	/*
+	 *  Check for invalid backreferences; note that it is NOT an error
+	 *  to back-reference a capture group which has not yet been introduced
+	 *  in the pattern (as in /\1(foo)/); in fact, the backreference will
+	 *  always match!  It IS an error to back-reference a capture group
+	 *  which will never be introduced in the pattern.  Thus, we can check
+	 *  for such references only after parsing is complete.
+	 */
+
+	if (re_ctx.highest_backref > re_ctx.captures) {
+		DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "invalid backreference(s)");
+	}
+
+	/*
+	 *  Emit compiled regexp header: flags, ncaptures
+	 *  (insertion order inverted on purpose)
+	 */
+
+	duk__insert_u32(&re_ctx, 0, (re_ctx.captures + 1) * 2);
+	duk__insert_u32(&re_ctx, 0, re_ctx.re_flags);
+
+	DUK_D(DUK_DPRINT("regexp bytecode size (after header) is %ld bytes",
+	                 (long) DUK_HBUFFER_GET_SIZE(re_ctx.buf)));
+	DUK_DDD(DUK_DDDPRINT("compiled regexp: %!xO", (duk_heaphdr *) re_ctx.buf));
+
+	/* [ ... pattern flags escaped_source buffer ] */
+
+	duk_to_string(ctx, -1);  /* coerce to string */
+
+	/* [ ... pattern flags escaped_source bytecode ] */
+
+	/*
+	 *  Finalize stack
+	 */
+
+	duk_remove(ctx, -4);     /* -> [ ... flags escaped_source bytecode ] */
+	duk_remove(ctx, -3);     /* -> [ ... escaped_source bytecode ] */
+
+	DUK_D(DUK_DPRINT("regexp compilation successful, bytecode: %!T, escaped source: %!T",
+	                 (duk_tval *) duk_get_tval(ctx, -1), (duk_tval *) duk_get_tval(ctx, -2)));
+}
+
+/*
+ *  Create a RegExp instance (E5 Section 15.10.7).
+ *
+ *  Note: the output stack left by duk_regexp_compile() is directly compatible
+ *  with the input here.
+ *
+ *  Input stack:  [ escaped_source bytecode ]  (both as strings)
+ *  Output stack: [ RegExp ]
+ */
+ 
+void duk_regexp_create_instance(duk_hthread *thr) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_hobject *h;
+	duk_hstring *h_bc;
+	duk_small_int_t re_flags;
+
+	/* [ ... escape_source bytecode ] */
+
+	h_bc = duk_get_hstring(ctx, -1);
+	DUK_ASSERT(h_bc != NULL);
+	DUK_ASSERT(DUK_HSTRING_GET_BYTELEN(h_bc) >= 1);          /* always at least the header */
+	DUK_ASSERT(DUK_HSTRING_GET_CHARLEN(h_bc) >= 1);
+	DUK_ASSERT((duk_small_int_t) DUK_HSTRING_GET_DATA(h_bc)[0] < 0x80);  /* flags always encodes to 1 byte */
+	re_flags = (duk_small_int_t) DUK_HSTRING_GET_DATA(h_bc)[0];
+
+	/* [ ... escaped_source bytecode ] */
+
+	duk_push_object(ctx);
+	h = duk_get_hobject(ctx, -1);
+	DUK_ASSERT(h != NULL);
+	duk_insert(ctx, -3);
+
+	/* [ ... regexp_object escaped_source bytecode ] */
+
+	DUK_HOBJECT_SET_CLASS_NUMBER(h, DUK_HOBJECT_CLASS_REGEXP);
+	DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, h, thr->builtins[DUK_BIDX_REGEXP_PROTOTYPE]);
+
+	duk_def_prop_stridx(ctx, -3, DUK_STRIDX_INT_BYTECODE, DUK_PROPDESC_FLAGS_NONE);
+
+	/* [ ... regexp_object escaped_source ] */
+
+	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_SOURCE, DUK_PROPDESC_FLAGS_NONE);
+
+	/* [ ... regexp_object ] */
+
+	duk_push_boolean(ctx, (re_flags & DUK_RE_FLAG_GLOBAL));
+	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_GLOBAL, DUK_PROPDESC_FLAGS_NONE);
+
+	duk_push_boolean(ctx, (re_flags & DUK_RE_FLAG_IGNORE_CASE));
+	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_IGNORE_CASE, DUK_PROPDESC_FLAGS_NONE);
+
+	duk_push_boolean(ctx, (re_flags & DUK_RE_FLAG_MULTILINE));
+	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_MULTILINE, DUK_PROPDESC_FLAGS_NONE);
+
+	duk_push_int(ctx, 0);
+	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_LAST_INDEX, DUK_PROPDESC_FLAGS_W);
+
+	/* [ ... regexp_object ] */
+}
+
+#undef DUK__BUFLEN
+
+#else  /* DUK_USE_REGEXP_SUPPORT */
+
+/* regexp support disabled */
+
+#endif  /* DUK_USE_REGEXP_SUPPORT */
+
+#line 1 "duk_regexp_executor.c"
+/*
+ *  Regexp executor.
+ *
+ *  Safety: the Ecmascript executor should prevent user from reading and
+ *  replacing regexp bytecode.  Even so, the executor must validate all
+ *  memory accesses etc.  When an invalid access is detected (e.g. a 'save'
+ *  opcode to invalid, unallocated index) it should fail with an internal
+ *  error but not cause a segmentation fault.
+ *
+ *  Notes:
+ *
+ *    - Backtrack counts are limited to unsigned 32 bits but should
+ *      technically be duk_size_t for strings longer than 4G chars.
+ *      This also requires a regexp bytecode change.
+ */
+
+/* include removed: duk_internal.h */
+
+#ifdef DUK_USE_REGEXP_SUPPORT
+
+/*
+ *  Helpers for UTF-8 handling
+ *
+ *  For bytecode readers the duk_uint32_t and duk_int32_t types are correct
+ *  because they're used for more than just codepoints.
+ */
+
+static duk_uint32_t duk__bc_get_u32(duk_re_matcher_ctx *re_ctx, duk_uint8_t **pc) {
+	return (duk_uint32_t) duk_unicode_decode_xutf8_checked(re_ctx->thr, pc, re_ctx->bytecode, re_ctx->bytecode_end);
+}
+
+static duk_int32_t duk__bc_get_i32(duk_re_matcher_ctx *re_ctx, duk_uint8_t **pc) {
+	duk_uint32_t t;
+
+	/* signed integer encoding needed to work with UTF-8 */
+	t = (duk_uint32_t) duk_unicode_decode_xutf8_checked(re_ctx->thr, pc, re_ctx->bytecode, re_ctx->bytecode_end);
+	if (t & 1) {
+		return -((duk_int32_t) (t >> 1));
+	} else {
+		return (duk_int32_t) (t >> 1);
+	}
+}
+
+static duk_uint8_t *duk__utf8_backtrack(duk_hthread *thr, duk_uint8_t **ptr, duk_uint8_t *ptr_start, duk_uint8_t *ptr_end, duk_uint_fast32_t count) {
+	duk_uint8_t *p;
+
+	/* Note: allow backtracking from p == ptr_end */
+	p = *ptr;
+	if (p < ptr_start || p > ptr_end) {
+		goto fail;
+	}
+
+	while (count > 0) {
+		for (;;) {
+			p--;
+			if (p < ptr_start) {
+				goto fail;
+			}
+			if ((*p & 0xc0) != 0x80) {
+				/* utf-8 continuation bytes have the form 10xx xxxx */
+				break;
+			}
+		}
+		count--;
+	}
+	*ptr = p;
+	return p;
+
+ fail:
+	DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "regexp backtrack failed");
+	return NULL;  /* never here */
+}
+
+static duk_uint8_t *duk__utf8_advance(duk_hthread *thr, duk_uint8_t **ptr, duk_uint8_t *ptr_start, duk_uint8_t *ptr_end, duk_uint_fast32_t count) {
+	duk_uint8_t *p;
+
+	p = *ptr;
+	if (p < ptr_start || p >= ptr_end) {
+		goto fail;
+	}
+
+	while (count > 0) {
+		for (;;) {
+			p++;
+
+			/* Note: if encoding ends by hitting end of input, we don't check that
+			 * the encoding is valid, we just assume it is.
+			 */
+			if (p >= ptr_end || ((*p & 0xc0) != 0x80)) {
+				/* utf-8 continuation bytes have the form 10xx xxxx */
+				break;
+			}
+		}
+		count--;
+	}
+
+	*ptr = p;
+	return p;
+
+ fail:
+	DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "regexp advance failed");
+	return NULL;  /* never here */
+}
+
+/*
+ *  Helpers for dealing with the input string
+ */
+
+/* Get a (possibly canonicalized) input character from current sp.  The input
+ * itself is never modified, and captures always record non-canonicalized
+ * characters even in case-insensitive matching.
+ */
+static duk_codepoint_t duk__inp_get_cp(duk_re_matcher_ctx *re_ctx, duk_uint8_t **sp) {
+	duk_codepoint_t res = (duk_codepoint_t) duk_unicode_decode_xutf8_checked(re_ctx->thr, sp, re_ctx->input, re_ctx->input_end);
+	if (re_ctx->re_flags & DUK_RE_FLAG_IGNORE_CASE) {
+		res = duk_unicode_re_canonicalize_char(re_ctx->thr, res);
+	}
+	return res;
+}
+
+static duk_uint8_t *duk__inp_backtrack(duk_re_matcher_ctx *re_ctx, duk_uint8_t **sp, duk_uint_fast32_t count) {
+	return duk__utf8_backtrack(re_ctx->thr, sp, re_ctx->input, re_ctx->input_end, count);
+}
+
+/* Backtrack utf-8 input and return a (possibly canonicalized) input character. */
+static duk_codepoint_t duk__inp_get_prev_cp(duk_re_matcher_ctx *re_ctx, duk_uint8_t *sp) {
+	/* note: caller 'sp' is intentionally not updated here */
+	(void) duk__inp_backtrack(re_ctx, &sp, (duk_uint_fast32_t) 1);
+	return duk__inp_get_cp(re_ctx, &sp);
+}
+	
+/*
+ *  Regexp recursive matching function.
+ *
+ *  Returns 'sp' on successful match (points to character after last matched one),
+ *  NULL otherwise.
+ *
+ *  The C recursion depth limit check is only performed in this function, this
+ *  suffices because the function is present in all true recursion required by
+ *  regexp execution.
+ */
+
+static duk_uint8_t *duk__match_regexp(duk_re_matcher_ctx *re_ctx, duk_uint8_t *pc, duk_uint8_t *sp) {
+	if (re_ctx->recursion_depth >= re_ctx->recursion_limit) {
+		DUK_ERROR(re_ctx->thr, DUK_ERR_RANGE_ERROR, "regexp executor recursion limit");
+	}
+	re_ctx->recursion_depth++;
+
+	for (;;) {
+		duk_small_int_t op;
+
+		if (re_ctx->steps_count >= re_ctx->steps_limit) {
+			DUK_ERROR(re_ctx->thr, DUK_ERR_RANGE_ERROR, "regexp step limit");
+		}
+		re_ctx->steps_count++;
+
+		op = (duk_small_int_t) duk__bc_get_u32(re_ctx, &pc);
+
+		DUK_DDD(DUK_DDDPRINT("match: rec=%ld, steps=%ld, pc (after op)=%ld, sp=%ld, op=%ld",
+		                     (long) re_ctx->recursion_depth,
+		                     (long) re_ctx->steps_count,
+		                     (long) (pc - re_ctx->bytecode),
+		                     (long) (sp - re_ctx->input),
+		                     (long) op));
+
+		switch (op) {
+		case DUK_REOP_MATCH: {
+			goto match;
+		}
+		case DUK_REOP_CHAR: {
+			/*
+			 *  Byte-based matching would be possible for case-sensitive
+			 *  matching but not for case-insensitive matching.  So, we
+			 *  match by decoding the input and bytecode character normally.
+			 *
+			 *  Bytecode characters are assumed to be already canonicalized.
+			 *  Input characters are canonicalized automatically by
+			 *  duk__inp_get_cp() if necessary.
+			 *
+			 *  There is no opcode for matching multiple characters.  The
+			 *  regexp compiler has trouble joining strings efficiently
+			 *  during compilation.  See doc/regexp.txt for more discussion.
+			 */
+			duk_codepoint_t c1, c2;
+
+			c1 = (duk_codepoint_t) duk__bc_get_u32(re_ctx, &pc);
+			DUK_ASSERT(!(re_ctx->re_flags & DUK_RE_FLAG_IGNORE_CASE) ||
+			           c1 == duk_unicode_re_canonicalize_char(re_ctx->thr, c1));  /* canonicalized by compiler */
+			if (sp >= re_ctx->input_end) {
+				goto fail;
+			}
+			c2 = duk__inp_get_cp(re_ctx, &sp);
+			DUK_DDD(DUK_DDDPRINT("char match, c1=%ld, c2=%ld", (long) c1, (long) c2));
+			if (c1 != c2) {
+				goto fail;
+			}
+			break;
+		}
+		case DUK_REOP_PERIOD: {
+			duk_codepoint_t c;
+
+			if (sp >= re_ctx->input_end) {
+				goto fail;
+			}
+			c = duk__inp_get_cp(re_ctx, &sp);
+			if (duk_unicode_is_line_terminator(c)) {
+				/* E5 Sections 15.10.2.8, 7.3 */
+				goto fail;
+			}
+			break;
+		}
+		case DUK_REOP_RANGES:
+		case DUK_REOP_INVRANGES: {
+			duk_uint32_t n;
+			duk_codepoint_t c;
+			duk_small_int_t match;
+	
+			n = duk__bc_get_u32(re_ctx, &pc);
+			if (sp >= re_ctx->input_end) {
+				goto fail;
+			}
+			c = duk__inp_get_cp(re_ctx, &sp);
+
+			match = 0;
+			while (n) {
+				duk_codepoint_t r1, r2;
+				r1 = (duk_codepoint_t) duk__bc_get_u32(re_ctx, &pc);
+				r2 = (duk_codepoint_t) duk__bc_get_u32(re_ctx, &pc);
+				DUK_DDD(DUK_DDDPRINT("matching ranges/invranges, n=%ld, r1=%ld, r2=%ld, c=%ld",
+				                     (long) n, (long) r1, (long) r2, (long) c));
+				if (c >= r1 && c <= r2) {
+					/* Note: don't bail out early, we must read all the ranges from
+					 * bytecode.  Another option is to skip them efficiently after
+					 * breaking out of here.  Prefer smallest code.
+					 */
+					match = 1;
+				}
+				n--;
+			}
+
+			if (op == DUK_REOP_RANGES) {
+				if (!match) {
+					goto fail;
+				}
+			} else {
+				DUK_ASSERT(op == DUK_REOP_INVRANGES);
+				if (match) {
+					goto fail;
+				}
+			}
+			break;
+		}
+		case DUK_REOP_ASSERT_START: {
+			duk_codepoint_t c;
+
+			if (sp <= re_ctx->input) {
+				break;
+			}
+			if (!(re_ctx->re_flags & DUK_RE_FLAG_MULTILINE)) {
+				goto fail;
+			}
+			c = duk__inp_get_prev_cp(re_ctx, sp);
+			if (duk_unicode_is_line_terminator(c)) {
+				/* E5 Sections 15.10.2.8, 7.3 */
+				break;
+			}
+			goto fail;
+		}
+		case DUK_REOP_ASSERT_END: {
+			duk_codepoint_t c;
+			duk_uint8_t *temp_sp;
+
+			if (sp >= re_ctx->input_end) {
+				break;
+			}
+			if (!(re_ctx->re_flags & DUK_RE_FLAG_MULTILINE)) {
+				goto fail;
+			}
+			temp_sp = sp;
+			c = duk__inp_get_cp(re_ctx, &temp_sp);
+			if (duk_unicode_is_line_terminator(c)) {
+				/* E5 Sections 15.10.2.8, 7.3 */
+				break;
+			}
+			goto fail;
+		}
+		case DUK_REOP_ASSERT_WORD_BOUNDARY:
+		case DUK_REOP_ASSERT_NOT_WORD_BOUNDARY: {
+			/*
+			 *  E5 Section 15.10.2.6.  The previous and current character
+			 *  should -not- be canonicalized as they are now.  However,
+			 *  canonicalization does not affect the result of IsWordChar()
+			 *  (which depends on Unicode characters never canonicalizing
+			 *  into ASCII characters) so this does not matter.
+			 */
+			duk_small_int_t w1, w2;
+
+			if (sp <= re_ctx->input) {
+				w1 = 0;  /* not a wordchar */
+			} else {
+				duk_codepoint_t c;
+				c = duk__inp_get_prev_cp(re_ctx, sp);
+				w1 = duk_unicode_re_is_wordchar(c);
+			}
+			if (sp >= re_ctx->input_end) {
+				w2 = 0;  /* not a wordchar */
+			} else {
+				duk_uint8_t *tmp_sp = sp;  /* dummy so sp won't get updated */
+				duk_codepoint_t c;
+				c = duk__inp_get_cp(re_ctx, &tmp_sp);
+				w2 = duk_unicode_re_is_wordchar(c);
+			}
+
+			if (op == DUK_REOP_ASSERT_WORD_BOUNDARY) {
+				if (w1 == w2) {
+					goto fail;
+				}
+			} else {
+				DUK_ASSERT(op == DUK_REOP_ASSERT_NOT_WORD_BOUNDARY);
+				if (w1 != w2) {
+					goto fail;
+				}
+			}
+			break;
+		}
+		case DUK_REOP_JUMP: {
+			duk_int32_t skip;
+
+			skip = duk__bc_get_i32(re_ctx, &pc);
+			pc += skip;
+			break;
+		}
+		case DUK_REOP_SPLIT1: {
+			/* split1: prefer direct execution (no jump) */
+			duk_uint8_t *sub_sp;
+			duk_int32_t skip;
+
+			skip = duk__bc_get_i32(re_ctx, &pc);
+			sub_sp = duk__match_regexp(re_ctx, pc, sp);
+			if (sub_sp) {
+				sp = sub_sp;
+				goto match;
+			}
+			pc += skip;
+			break;
+		}
+		case DUK_REOP_SPLIT2: {
+			/* split2: prefer jump execution (not direct) */
+			duk_uint8_t *sub_sp;
+			duk_int32_t skip;
+
+			skip = duk__bc_get_i32(re_ctx, &pc);
+			sub_sp = duk__match_regexp(re_ctx, pc + skip, sp);
+			if (sub_sp) {
+				sp = sub_sp;
+				goto match;
+			}
+			break;
+		}
+		case DUK_REOP_SQMINIMAL: {
+			duk_uint32_t q, qmin, qmax;
+			duk_int32_t skip;
+			duk_uint8_t *sub_sp;
+
+			qmin = duk__bc_get_u32(re_ctx, &pc);
+			qmax = duk__bc_get_u32(re_ctx, &pc);
+			skip = duk__bc_get_i32(re_ctx, &pc);
+			DUK_DDD(DUK_DDDPRINT("minimal quantifier, qmin=%lu, qmax=%lu, skip=%ld",
+			                     (unsigned long) qmin, (unsigned long) qmax, (long) skip));
+
+			q = 0;
+			while (q <= qmax) {
+				if (q >= qmin) {
+					sub_sp = duk__match_regexp(re_ctx, pc + skip, sp);
+					if (sub_sp) {
+						sp = sub_sp;
+						goto match;
+					}
+				}
+				sub_sp = duk__match_regexp(re_ctx, pc, sp);
+				if (!sub_sp) {
+					break;
+				}
+				sp = sub_sp;
+				q++;
+			}
+			goto fail;
+		}
+		case DUK_REOP_SQGREEDY: {
+			duk_uint32_t q, qmin, qmax, atomlen;
+			duk_int32_t skip;
+			duk_uint8_t *sub_sp;
+
+			qmin = duk__bc_get_u32(re_ctx, &pc);
+			qmax = duk__bc_get_u32(re_ctx, &pc);
+			atomlen = duk__bc_get_u32(re_ctx, &pc);
+			skip = duk__bc_get_i32(re_ctx, &pc);
+			DUK_DDD(DUK_DDDPRINT("greedy quantifier, qmin=%lu, qmax=%lu, atomlen=%lu, skip=%ld",
+			                     (unsigned long) qmin, (unsigned long) qmax, (unsigned long) atomlen, (long) skip));
+
+			q = 0;
+			while (q < qmax) {
+				sub_sp = duk__match_regexp(re_ctx, pc, sp);
+				if (!sub_sp) {
+					break;
+				}
+				sp = sub_sp;
+				q++;
+			}
+			while (q >= qmin) {
+				sub_sp = duk__match_regexp(re_ctx, pc + skip, sp);
+				if (sub_sp) {
+					sp = sub_sp;
+					goto match;
+				}
+				if (q == qmin) {
+					break;
+				}
+
+				/* Note: if atom were to contain e.g. captures, we would need to
+				 * re-match the atom to get correct captures.  Simply quantifiers
+				 * do not allow captures in their atom now, so this is not an issue.
+				 */
+
+				DUK_DDD(DUK_DDDPRINT("greedy quantifier, backtrack %ld characters (atomlen)",
+				                     (long) atomlen));
+				sp = duk__inp_backtrack(re_ctx, &sp, (duk_uint_fast32_t) atomlen);
+				q--;
+			}
+			goto fail;
+		}
+		case DUK_REOP_SAVE: {
+			duk_uint32_t idx;
+			duk_uint8_t *old;
+			duk_uint8_t *sub_sp;
+
+			idx = duk__bc_get_u32(re_ctx, &pc);
+			if (idx >= re_ctx->nsaved) {
+				/* idx is unsigned, < 0 check is not necessary */
+				DUK_D(DUK_DPRINT("internal error, regexp save index insane: idx=%ld", (long) idx));
+				goto internal_error;
+			}
+			old = re_ctx->saved[idx];
+			re_ctx->saved[idx] = sp;
+			sub_sp = duk__match_regexp(re_ctx, pc, sp);
+			if (sub_sp) {
+				sp = sub_sp;
+				goto match;
+			}
+			re_ctx->saved[idx] = old;
+			goto fail;
+		}
+		case DUK_REOP_WIPERANGE: {
+			/* Wipe capture range and save old values for backtracking.
+			 *
+			 * XXX: this typically happens with a relatively small idx_count.
+			 * It might be useful to handle cases where the count is small
+			 * (say <= 8) by saving the values in stack instead.  This would
+			 * reduce memory churn and improve performance, at the cost of a
+			 * slightly higher code footprint.
+			 */
+			duk_uint32_t idx_start, idx_count;
+#ifdef DUK_USE_EXPLICIT_NULL_INIT
+			duk_uint32_t idx_end, idx;
+#endif
+			duk_uint8_t **range_save;
+			duk_uint8_t *sub_sp;
+
+			idx_start = duk__bc_get_u32(re_ctx, &pc);
+			idx_count = duk__bc_get_u32(re_ctx, &pc);
+			DUK_DDD(DUK_DDDPRINT("wipe saved range: start=%ld, count=%ld -> [%ld,%ld] (captures [%ld,%ld])",
+			                     (long) idx_start, (long) idx_count,
+			                     (long) idx_start, (long) (idx_start + idx_count - 1),
+			                     (long) (idx_start / 2), (long) ((idx_start + idx_count - 1) / 2)));
+			if (idx_start + idx_count > re_ctx->nsaved || idx_count == 0) {
+				/* idx is unsigned, < 0 check is not necessary */
+				DUK_D(DUK_DPRINT("internal error, regexp wipe indices insane: idx_start=%ld, idx_count=%ld",
+				                 (long) idx_start, (long) idx_count));
+				goto internal_error;
+			}
+			DUK_ASSERT(idx_count > 0);
+
+			duk_require_stack((duk_context *) re_ctx->thr, 1);
+			range_save = (duk_uint8_t **) duk_push_fixed_buffer((duk_context *) re_ctx->thr,
+			                                                    sizeof(duk_uint8_t *) * idx_count);
+			DUK_ASSERT(range_save != NULL);
+			DUK_MEMCPY(range_save, re_ctx->saved + idx_start, sizeof(duk_uint8_t *) * idx_count);
+#ifdef DUK_USE_EXPLICIT_NULL_INIT
+			idx_end = idx_start + idx_count;
+			for (idx = idx_start; idx < idx_end; idx++) {
+				re_ctx->saved[idx] = NULL;
+			}
+#else
+			DUK_MEMZERO(re_ctx->saved + idx_start, sizeof(duk_uint8_t *) * idx_count);
+#endif
+
+			sub_sp = duk__match_regexp(re_ctx, pc, sp);
+			if (sub_sp) {
+				/* match: keep wiped/resaved values */
+				DUK_DDD(DUK_DDDPRINT("match: keep wiped/resaved values [%ld,%ld] (captures [%ld,%ld])",
+				                     (long) idx_start, (long) (idx_start + idx_count - 1),
+			                             (long) (idx_start / 2), (long) ((idx_start + idx_count - 1) / 2)));
+				duk_pop((duk_context *) re_ctx->thr);
+				sp = sub_sp;
+				goto match;
+			}
+
+			/* fail: restore saves */
+			DUK_DDD(DUK_DDDPRINT("fail: restore wiped/resaved values [%ld,%ld] (captures [%ld,%ld])",
+			                     (long) idx_start, (long) (idx_start + idx_count - 1),
+			                     (long) (idx_start / 2), (long) ((idx_start + idx_count - 1) / 2)));
+			DUK_MEMCPY(re_ctx->saved + idx_start, range_save, sizeof(duk_uint8_t *) * idx_count);
+			duk_pop((duk_context *) re_ctx->thr);
+			goto fail;
+		}
+		case DUK_REOP_LOOKPOS:
+		case DUK_REOP_LOOKNEG: {
+			/*
+			 *  Needs a save of multiple saved[] entries depending on what range
+			 *  may be overwritten.  Because the regexp parser does no such analysis,
+			 *  we currently save the entire saved array here.  Lookaheads are thus
+			 *  a bit expensive.  Note that the saved array is not needed for just
+			 *  the lookahead sub-match, but for the matching of the entire sequel.
+			 *
+			 *  The temporary save buffer is pushed on to the valstack to handle
+			 *  errors correctly.  Each lookahead causes a C recursion and pushes
+			 *  more stuff on the value stack.  If the C recursion limit is less
+			 *  than the value stack spare, there is no need to check the stack.
+			 *  We do so regardless, just in case.
+			 */
+
+			duk_int32_t skip;
+			duk_uint8_t **full_save;
+			duk_uint8_t *sub_sp;
+
+			DUK_ASSERT(re_ctx->nsaved > 0);
+
+			duk_require_stack((duk_context *) re_ctx->thr, 1);
+			full_save = (duk_uint8_t **) duk_push_fixed_buffer((duk_context *) re_ctx->thr,
+			                                                   sizeof(duk_uint8_t *) * re_ctx->nsaved);
+			DUK_ASSERT(full_save != NULL);
+			DUK_MEMCPY(full_save, re_ctx->saved, sizeof(duk_uint8_t *) * re_ctx->nsaved);
+
+			skip = duk__bc_get_i32(re_ctx, &pc);
+			sub_sp = duk__match_regexp(re_ctx, pc, sp);
+			if (op == DUK_REOP_LOOKPOS) {
+				if (!sub_sp) {
+					goto lookahead_fail;
+				}
+			} else {
+				if (sub_sp) {
+					goto lookahead_fail;
+				}
+			}
+			sub_sp = duk__match_regexp(re_ctx, pc + skip, sp);
+			if (sub_sp) {
+				/* match: keep saves */
+				duk_pop((duk_context *) re_ctx->thr);
+				sp = sub_sp;
+				goto match;
+			}
+
+			/* fall through */
+
+		 lookahead_fail:
+			/* fail: restore saves */
+			DUK_MEMCPY(re_ctx->saved, full_save, sizeof(duk_uint8_t *) * re_ctx->nsaved);
+			duk_pop((duk_context *) re_ctx->thr);
+			goto fail;
+		}
+		case DUK_REOP_BACKREFERENCE: {
+			/*
+			 *  Byte matching for back-references would be OK in case-
+			 *  sensitive matching.  In case-insensitive matching we need
+			 *  to canonicalize characters, so back-reference matching needs
+			 *  to be done with codepoints instead.  So, we just decode
+			 *  everything normally here, too.
+			 *
+			 *  Note: back-reference index which is 0 or higher than
+			 *  NCapturingParens (= number of capturing parens in the
+			 *  -entire- regexp) is a compile time error.  However, a
+			 *  backreference referring to a valid capture which has
+			 *  not matched anything always succeeds!  See E5 Section
+			 *  15.10.2.9, step 5, sub-step 3.
+			 */
+			duk_uint32_t idx;
+			duk_uint8_t *p;
+
+			idx = duk__bc_get_u32(re_ctx, &pc);
+			idx = idx << 1;		/* backref n -> saved indices [n*2, n*2+1] */
+			if (idx < 2 || idx + 1 >= re_ctx->nsaved) {
+				/* regexp compiler should catch these */
+				DUK_D(DUK_DPRINT("internal error, backreference index insane"));
+				goto internal_error;
+			}
+			if (!re_ctx->saved[idx] || !re_ctx->saved[idx+1]) {
+				/* capture is 'undefined', always matches! */
+				DUK_DDD(DUK_DDDPRINT("backreference: saved[%ld,%ld] not complete, always match",
+				                     (long) idx, (long) (idx + 1)));
+				break;
+			}
+			DUK_DDD(DUK_DDDPRINT("backreference: match saved[%ld,%ld]", (long) idx, (long) (idx + 1)));
+
+			p = re_ctx->saved[idx];
+			while (p < re_ctx->saved[idx+1]) {
+				duk_codepoint_t c1, c2;
+
+				/* Note: not necessary to check p against re_ctx->input_end:
+				 * the memory access is checked by duk__inp_get_cp(), while
+				 * valid compiled regexps cannot write a saved[] entry
+				 * which points to outside the string.
+				 */
+				if (sp >= re_ctx->input_end) {
+					goto fail;
+				}
+				c1 = duk__inp_get_cp(re_ctx, &p);
+				c2 = duk__inp_get_cp(re_ctx, &sp);
+				if (c1 != c2) {
+					goto fail;
+				}
+			}
+			break;
+		}
+		default: {
+			DUK_D(DUK_DPRINT("internal error, regexp opcode error: %ld", (long) op));
+			goto internal_error;
+		}
+		}
+	}
+
+ match:
+	re_ctx->recursion_depth--;
+	return sp;
+
+ fail:
+	re_ctx->recursion_depth--;
+	return NULL;
+
+ internal_error:
+	DUK_ERROR(re_ctx->thr, DUK_ERR_INTERNAL_ERROR, "regexp internal error");
+	return NULL;  /* never here */
+}
+
+/*
+ *  Exposed matcher function which provides the semantics of RegExp.prototype.exec().
+ *
+ *  RegExp.prototype.test() has the same semantics as exec() but does not return the
+ *  result object (which contains the matching string and capture groups).  Currently
+ *  there is no separate test() helper, so a temporary result object is created and
+ *  discarded if test() is needed.  This is intentional, to save code space.
+ *
+ *  Input stack:  [ ... re_obj input ]
+ *  Output stack: [ ... result ]
+ */
+
+static void duk__regexp_match_helper(duk_hthread *thr, duk_small_int_t force_global) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_re_matcher_ctx re_ctx;
+	duk_hobject *h_regexp;
+	duk_hstring *h_bytecode;
+	duk_hstring *h_input;
+	duk_uint8_t *pc;
+	duk_uint8_t *sp;
+	duk_small_int_t match = 0;
+	duk_small_int_t global;
+	duk_uint_fast32_t i;
+	double d;
+	duk_uint32_t char_offset;
+
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(ctx != NULL);
+
+	DUK_DD(DUK_DDPRINT("regexp match: regexp=%!T, input=%!T",
+	                   (duk_tval *) duk_get_tval(ctx, -2),
+	                   (duk_tval *) duk_get_tval(ctx, -1)));
+
+	/*
+	 *  Regexp instance check, bytecode check, input coercion.
+	 *
+	 *  See E5 Section 15.10.6.
+	 */
+
+	/* TypeError if wrong; class check, see E5 Section 15.10.6 */
+	h_regexp = duk_require_hobject_with_class(ctx, -2, DUK_HOBJECT_CLASS_REGEXP);
+	DUK_ASSERT(h_regexp != NULL);
+	DUK_ASSERT(DUK_HOBJECT_GET_CLASS_NUMBER(h_regexp) == DUK_HOBJECT_CLASS_REGEXP);
+	DUK_UNREF(h_regexp);
+
+	duk_to_string(ctx, -1);
+	h_input = duk_get_hstring(ctx, -1);
+	DUK_ASSERT(h_input != NULL);
+
+	duk_get_prop_stridx(ctx, -2, DUK_STRIDX_INT_BYTECODE);  /* [ ... re_obj input ] -> [ ... re_obj input bc ] */
+	h_bytecode = duk_require_hstring(ctx, -1);  /* no regexp instance should exist without a non-configurable bytecode property */
+	DUK_ASSERT(h_bytecode != NULL);
+
+	/*
+	 *  Basic context initialization.
+	 *
+	 *  Some init values are read from the bytecode header
+	 *  whose format is (UTF-8 codepoints):
+	 *
+	 *    uint   flags
+	 *    uint   nsaved (even, 2n+2 where n = num captures)
+	 */
+
+	/* [ ... re_obj input bc ] */
+
+	DUK_MEMZERO(&re_ctx, sizeof(re_ctx));
+
+	re_ctx.thr = thr;
+	re_ctx.input = (duk_uint8_t *) DUK_HSTRING_GET_DATA(h_input);
+	re_ctx.input_end = re_ctx.input + DUK_HSTRING_GET_BYTELEN(h_input);
+	re_ctx.bytecode = (duk_uint8_t *) DUK_HSTRING_GET_DATA(h_bytecode);
+	re_ctx.bytecode_end = re_ctx.bytecode + DUK_HSTRING_GET_BYTELEN(h_bytecode);
+	re_ctx.saved = NULL;
+	re_ctx.recursion_limit = DUK_RE_EXECUTE_RECURSION_LIMIT;
+	re_ctx.steps_limit = DUK_RE_EXECUTE_STEPS_LIMIT;
+
+	/* read header */
+	pc = re_ctx.bytecode;
+	re_ctx.re_flags = duk__bc_get_u32(&re_ctx, &pc);
+	re_ctx.nsaved = duk__bc_get_u32(&re_ctx, &pc);
+	re_ctx.bytecode = pc;
+
+	DUK_ASSERT(DUK_RE_FLAG_GLOBAL < 0x10000UL);  /* must fit into duk_small_int_t */
+	global = (duk_small_int_t) (force_global | (re_ctx.re_flags & DUK_RE_FLAG_GLOBAL));
+
+	DUK_ASSERT(re_ctx.nsaved >= 2);
+	DUK_ASSERT((re_ctx.nsaved % 2) == 0);
+
+	duk_push_fixed_buffer(ctx, sizeof(duk_uint8_t *) * re_ctx.nsaved);
+	re_ctx.saved = (duk_uint8_t **) duk_get_buffer(ctx, -1, NULL);
+	DUK_ASSERT(re_ctx.saved != NULL);
+
+	/* [ ... re_obj input bc saved_buf ] */
+
+	/* buffer is automatically zeroed */
+#ifdef DUK_USE_EXPLICIT_NULL_INIT
+	for (i = 0; i < re_ctx.nsaved; i++) {
+		re_ctx.saved[i] = (duk_uint8_t *) NULL;
+	}
+#endif
+
+	DUK_DDD(DUK_DDDPRINT("regexp ctx initialized, flags=0x%08lx, nsaved=%ld, recursion_limit=%ld, steps_limit=%ld",
+	                     (unsigned long) re_ctx.re_flags, (long) re_ctx.nsaved, (long) re_ctx.recursion_limit,
+	                     (long) re_ctx.steps_limit));
+
+	/*
+	 *  Get starting character offset for match, and initialize 'sp' based on it.
+	 *
+	 *  Note: lastIndex is non-configurable so it must be present (we check the
+	 *  internal class of the object above, so we know it is).  User code can set
+	 *  its value to an arbitrary (garbage) value though; E5 requires that lastIndex
+	 *  be coerced to a number before using.  The code below works even if the
+	 *  property is missing: the value will then be coerced to zero.
+	 *
+	 *  Note: lastIndex may be outside Uint32 range even after ToInteger() coercion.
+	 *  For instance, ToInteger(+Infinity) = +Infinity.  We track the match offset
+	 *  as an integer, but pre-check it to be inside the 32-bit range before the loop.
+	 *  If not, the check in E5 Section 15.10.6.2, step 9.a applies.
+	 */
+
+	/* XXX: lastIndex handling produces a lot of asm */
+
+	/* [ ... re_obj input bc saved_buf ] */
+
+	duk_get_prop_stridx(ctx, -4, DUK_STRIDX_LAST_INDEX);  /* -> [ ... re_obj input bc saved_buf lastIndex ] */
+	(void) duk_to_int(ctx, -1);  /* ToInteger(lastIndex) */
+	d = duk_get_number(ctx, -1);  /* integer, but may be +/- Infinite, +/- zero (not NaN, though) */
+	duk_pop(ctx);
+
+	if (global) {
+		if (d < 0.0 || d > (double) DUK_HSTRING_GET_CHARLEN(h_input)) {
+			/* match fail */
+			char_offset = 0;   /* not really necessary */
+			DUK_ASSERT(match == 0);
+			goto match_over;
+		}
+		char_offset = (duk_uint32_t) d;
+	} else {
+		/* lastIndex must be ignored for non-global regexps, but get the
+		 * value for (theoretical) side effects.  No side effects can
+		 * really occur, because lastIndex is a normal property and is
+		 * always non-configurable for RegExp instances.
+		 */
+		char_offset = (duk_uint32_t) 0;
+	}
+
+	sp = re_ctx.input + duk_heap_strcache_offset_char2byte(thr, h_input, char_offset);
+
+	/*
+	 *  Match loop.
+	 *
+	 *  Try matching at different offsets until match found or input exhausted.
+	 */
+
+	/* [ ... re_obj input bc saved_buf ] */
+
+	DUK_ASSERT(match == 0);
+
+	for (;;) {
+		/* char offset in [0, h_input->clen] (both ends inclusive), checked before entry */
+		DUK_ASSERT_DISABLE(char_offset >= 0);
+		DUK_ASSERT(char_offset <= DUK_HSTRING_GET_CHARLEN(h_input));
+
+		/* Note: ctx.steps is intentionally not reset, it applies to the entire unanchored match */
+		DUK_ASSERT(re_ctx.recursion_depth == 0);
+
+		DUK_DDD(DUK_DDDPRINT("attempt match at char offset %ld; %p [%p,%p]",
+		                     (long) char_offset, (void *) sp, (void *) re_ctx.input,
+		                     (void *) re_ctx.input_end));
+
+		/*
+		 *  Note:
+		 *
+		 *    - duk__match_regexp() is required not to longjmp() in ordinary "non-match"
+		 *      conditions; a longjmp() will terminate the entire matching process.
+		 *
+		 *    - Clearing saved[] is not necessary because backtracking does it
+		 *
+		 *    - Backtracking also rewinds ctx.recursion back to zero, unless an
+		 *      internal/limit error occurs (which causes a longjmp())
+		 *
+		 *    - If we supported anchored matches, we would break out here
+		 *      unconditionally; however, Ecmascript regexps don't have anchored
+		 *      matches.  It might make sense to implement a fast bail-out if
+		 *      the regexp begins with '^' and sp is not 0: currently we'll just
+		 *      run through the entire input string, trivially failing the match
+		 *      at every non-zero offset.
+		 */
+
+		if (duk__match_regexp(&re_ctx, re_ctx.bytecode, sp) != NULL) {
+			DUK_DDD(DUK_DDDPRINT("match at offset %ld", (long) char_offset));
+			match = 1;
+			break;
+		}
+
+		/* advance by one character (code point) and one char_offset */
+		char_offset++;
+		if (char_offset > DUK_HSTRING_GET_CHARLEN(h_input)) {
+			/*
+			 *  Note:
+			 *
+			 *    - Intentionally attempt (empty) match at char_offset == k_input->clen
+			 *
+			 *    - Negative char_offsets have been eliminated and char_offset is duk_uint32_t
+			 *      -> no need or use for a negative check
+			 */
+
+			DUK_DDD(DUK_DDDPRINT("no match after trying all sp offsets"));
+			break;
+		}
+
+		/* avoid calling at end of input, will DUK_ERROR (above check suffices to avoid this) */
+		(void) duk__utf8_advance(thr, &sp, re_ctx.input, re_ctx.input_end, (duk_uint_fast32_t) 1);
+	}
+
+ match_over:
+
+	/*
+	 *  Matching complete, create result array or return a 'null'.  Update lastIndex
+	 *  if necessary.  See E5 Section 15.10.6.2.
+	 *
+	 *  Because lastIndex is a character (not byte) offset, we need the character
+	 *  length of the match which we conveniently get as a side effect of interning
+	 *  the matching substring (0th index of result array).
+	 *
+	 *  saved[0]         start pointer (~ byte offset) of current match
+	 *  saved[1]         end pointer (~ byte offset) of current match (exclusive)
+	 *  char_offset      start character offset of current match (-> .index of result)
+	 *  char_end_offset  end character offset (computed below)
+	 */
+
+	/* [ ... re_obj input bc saved_buf ] */
+
+	if (match) {
+#ifdef DUK_USE_ASSERTIONS
+		duk_hobject *h_res;
+#endif
+		duk_uint32_t char_end_offset = 0;
+
+		DUK_DDD(DUK_DDDPRINT("regexp matches at char_offset %ld", (long) char_offset));
+
+		DUK_ASSERT(re_ctx.nsaved >= 2);        /* must have start and end */
+		DUK_ASSERT((re_ctx.nsaved % 2) == 0);  /* and even number */
+
+		/* XXX: Array size is known before and (2 * re_ctx.nsaved) but not taken
+		 * advantage of now.  The array is not compacted either, as regexp match
+		 * objects are usually short lived.
+		 */
+
+		duk_push_array(ctx);
+
+#ifdef DUK_USE_ASSERTIONS
+		h_res = duk_require_hobject(ctx, -1);
+		DUK_ASSERT(DUK_HOBJECT_HAS_EXTENSIBLE(h_res));
+		DUK_ASSERT(DUK_HOBJECT_HAS_EXOTIC_ARRAY(h_res));
+		DUK_ASSERT(DUK_HOBJECT_GET_CLASS_NUMBER(h_res) == DUK_HOBJECT_CLASS_ARRAY);
+#endif
+
+		/* [ ... re_obj input bc saved_buf res_obj ] */
+
+		duk_push_number(ctx, (double) char_offset);
+		duk_def_prop_stridx_wec(ctx, -2, DUK_STRIDX_INDEX);
+
+		duk_dup(ctx, -4);
+		duk_def_prop_stridx_wec(ctx, -2, DUK_STRIDX_INPUT);
+
+		for (i = 0; i < re_ctx.nsaved; i += 2) {
+			/* Captures which are undefined have NULL pointers and are returned
+			 * as 'undefined'.  The same is done when saved[] pointers are insane
+			 * (this should, of course, never happen in practice).
+			 */
+			if (re_ctx.saved[i] && re_ctx.saved[i+1] && re_ctx.saved[i+1] >= re_ctx.saved[i]) {
+				duk_hstring *h_saved;
+
+				duk_push_lstring(ctx,
+				                 (char *) re_ctx.saved[i],
+				                 (duk_size_t) (re_ctx.saved[i+1] - re_ctx.saved[i]));
+				h_saved = duk_get_hstring(ctx, -1);
+				DUK_ASSERT(h_saved != NULL);
+
+				if (i == 0) {
+					/* Assumes that saved[0] and saved[1] are always
+					 * set by regexp bytecode (if not, char_end_offset
+					 * will be zero).  Also assumes clen reflects the
+					 * correct char length.
+					 */
+					char_end_offset = char_offset + DUK_HSTRING_GET_CHARLEN(h_saved);
+				}
+			} else {
+				duk_push_undefined(ctx);
+			}
+
+			/* [ ... re_obj input bc saved_buf res_obj val ] */
+			duk_put_prop_index(ctx, -2, i / 2);
+		}
+
+		/* [ ... re_obj input bc saved_buf res_obj ] */
+
+		/* NB: 'length' property is automatically updated by the array setup loop */
+
+		if (global) {
+			/* global regexp: lastIndex updated on match */
+			duk_push_number(ctx, (double) char_end_offset);
+			duk_put_prop_stridx(ctx, -6, DUK_STRIDX_LAST_INDEX);
+		} else {
+			/* non-global regexp: lastIndex never updated on match */
+			;
+		}
+	} else {
+		/*
+		 *  No match, E5 Section 15.10.6.2, step 9.a.i - 9.a.ii apply, regardless
+		 *  of 'global' flag of the RegExp.  In particular, if lastIndex is invalid
+		 *  initially, it is reset to zero.
+		 */
+
+		DUK_DDD(DUK_DDDPRINT("regexp does not match"));
+
+		duk_push_null(ctx);
+
+		/* [ ... re_obj input bc saved_buf res_obj ] */
+
+		duk_push_int(ctx, 0);
+		duk_put_prop_stridx(ctx, -6, DUK_STRIDX_LAST_INDEX);
+	}
+
+	/* [ ... re_obj input bc saved_buf res_obj ] */
+
+	duk_insert(ctx, -5);
+
+	/* [ ... res_obj re_obj input bc saved_buf ] */
+
+	duk_pop_n(ctx, 4);
+
+	/* [ ... res_obj ] */
+
+	/* XXX: these last tricks are unnecessary if the function is made
+	 * a genuine native function.
+	 */
+}
+
+void duk_regexp_match(duk_hthread *thr) {
+	duk__regexp_match_helper(thr, 0 /*force_global*/);
+}
+
+/* This variant is needed by String.prototype.split(); it needs to perform
+ * global-style matching on a cloned RegExp which is potentially non-global.
+ */
+void duk_regexp_match_force_global(duk_hthread *thr) {
+	duk__regexp_match_helper(thr, 1 /*force_global*/);
+}
+
+#else  /* DUK_USE_REGEXP_SUPPORT */
+
+/* regexp support disabled */
+
+#endif  /* DUK_USE_REGEXP_SUPPORT */
+
+#line 1 "duk_replacements.c"
+/*
+ *  Replacements for missing platform functions.
+ *
+ *  Unlike the originals, fpclassify() and signbit() replacements don't
+ *  work on any floating point types, only doubles.  The C typing here
+ *  mimics the standard prototypes.
+ */
+
+/* include removed: duk_internal.h */
+
+#ifdef DUK_USE_COMPUTED_NAN
+double duk_computed_nan;
+#endif
+
+#ifdef DUK_USE_COMPUTED_INFINITY
+double duk_computed_infinity;
+#endif
+
+#ifdef DUK_USE_REPL_FPCLASSIFY
+int duk_repl_fpclassify(double x) {
+	duk_double_union u;
+	duk_uint_fast16_t exp;
+	duk_small_int_t mzero;
+
+	u.d = x;
+	exp = (duk_uint_fast16_t) (u.us[DUK_DBL_IDX_US0] & 0x7ff0UL);
+	if (exp > 0x0000UL && exp < 0x7ff0UL) {
+		/* exp values [0x001,0x7fe] = normal */
+		return DUK_FP_NORMAL;
+	}
+
+	mzero = (u.ui[DUK_DBL_IDX_UI1] == 0 && (u.ui[DUK_DBL_IDX_UI0] & 0x000fffffUL) == 0);
+	if (exp == 0x0000UL) {
+		/* exp 0x000 is zero/subnormal */
+		if (mzero) {
+			return DUK_FP_ZERO;
+		} else {
+			return DUK_FP_SUBNORMAL;
+		}
+	} else {
+		/* exp 0xfff is infinite/nan */
+		if (mzero) {
+			return DUK_FP_INFINITE;
+		} else {
+			return DUK_FP_NAN;
+		}
+	}
+}
+#endif
+
+#ifdef DUK_USE_REPL_SIGNBIT
+int duk_repl_signbit(double x) {
+	duk_double_union u;
+	u.d = x;
+	return (int) (u.uc[DUK_DBL_IDX_UC0] & 0x80UL);
+}
+#endif
+
+#ifdef DUK_USE_REPL_ISFINITE
+int duk_repl_isfinite(double x) {
+	int c = DUK_FPCLASSIFY(x);
+	if (c == DUK_FP_NAN || c == DUK_FP_INFINITE) {
+		return 0;
+	} else {
+		return 1;
+	}
+}
+#endif
+
+#ifdef DUK_USE_REPL_ISNAN
+int duk_repl_isnan(double x) {
+	int c = DUK_FPCLASSIFY(x);
+	return (c == DUK_FP_NAN);
+}
+#endif
+
+#ifdef DUK_USE_REPL_ISINF
+int duk_repl_isinf(double x) {
+	int c = DUK_FPCLASSIFY(x);
+	return (c == DUK_FP_INFINITE);
+}
+#endif
+
+#line 1 "duk_selftest.c"
+/*
+ *  Self tests to ensure execution environment is sane.  Intended to catch
+ *  compiler/platform problems which cannot be detected at compile time.
+ */
+
+/* include removed: duk_internal.h */
+
+#if defined(DUK_USE_SELF_TESTS)
+
+/*
+ *  Unions and structs for self tests
+ */
+
+typedef union {
+	double d;
+	duk_uint8_t c[8];
+} duk__test_double_union;
+
+#define DUK__DBLUNION_CMP_TRUE(a,b)  do { \
+		if (DUK_MEMCMP((void *) (a), (void *) (b), sizeof(duk__test_double_union)) != 0) { \
+			DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: double union compares false (expected true)"); \
+		} \
+	} while (0)
+
+#define DUK__DBLUNION_CMP_FALSE(a,b)  do { \
+		if (DUK_MEMCMP((void *) (a), (void *) (b), sizeof(duk__test_double_union)) == 0) { \
+			DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: double union compares true (expected false)"); \
+		} \
+	} while (0)
+
+typedef union {
+	duk_uint32_t i;
+	duk_uint8_t c[8];
+} duk__test_u32_union;
+
+/*
+ *  Various sanity checks for typing
+ */
+
+static void duk__selftest_types(void) {
+	if (!(sizeof(duk_int8_t) == 1 &&
+	      sizeof(duk_uint8_t) == 1 &&
+	      sizeof(duk_int16_t) == 2 &&
+	      sizeof(duk_uint16_t) == 2 &&
+	      sizeof(duk_int32_t) == 4 &&
+	      sizeof(duk_uint32_t) == 4)) {
+		DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: duk_(u)int{8,16,32}_t size");
+	}
+#if defined(DUK_USE_64BIT_OPS)
+	if (!(sizeof(duk_int64_t) == 8 &&
+	      sizeof(duk_uint64_t) == 8)) {
+		DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: duk_(u)int64_t size");
+	}
+#endif
+
+	if (!(sizeof(duk_size_t) >= sizeof(duk_uint_t))) {
+		/* Some internal code now assumes that all duk_uint_t values
+		 * can be expressed with a duk_size_t.
+		 */
+		DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: duk_size_t is smaller than duk_uint_t");
+	}
+	if (!(sizeof(duk_int_t) >= 4)) {
+		DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: duk_int_t is not 32 bits");
+	}
+}
+
+/*
+ *  Packed tval sanity
+ */
+
+static void duk__selftest_packed_tval(void) {
+#if defined(DUK_USE_PACKED_TVAL)
+	if (sizeof(void *) > 4) {
+		DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: packed duk_tval in use but sizeof(void *) > 4");
+	}
+#endif
+}
+
+/*
+ *  Two's complement arithmetic.
+ */
+
+static void duk__selftest_twos_complement(void) {
+	volatile int test;
+	test = -1;
+	if (((duk_uint8_t *) &test)[0] != (duk_uint8_t) 0xff) {
+		DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: two's complement arithmetic");
+	}
+}
+
+/*
+ *  Byte order.  Important to self check, because on some exotic platforms
+ *  there is no actual detection but rather assumption based on platform
+ *  defines.
+ */
+
+static void duk__selftest_byte_order(void) {
+	duk__test_u32_union u1;
+	duk__test_double_union u2;
+
+	/*
+	 *  >>> struct.pack('>d', 102030405060).encode('hex')
+	 *  '4237c17c6dc40000'
+	 */
+#if defined(DUK_USE_INTEGER_LE)
+	u1.c[0] = 0xef; u1.c[1] = 0xbe; u1.c[2] = 0xad; u1.c[3] = 0xde;
+#elif defined(DUK_USE_INTEGER_ME)
+#error integer mixed endian not supported now
+#elif defined(DUK_USE_INTEGER_BE)
+	u1.c[0] = 0xde; u1.c[1] = 0xad; u1.c[2] = 0xbe; u1.c[3] = 0xef;
+#else
+#error unknown integer endianness
+#endif
+
+#if defined(DUK_USE_DOUBLE_LE)
+	u2.c[0] = 0x00; u2.c[1] = 0x00; u2.c[2] = 0xc4; u2.c[3] = 0x6d;
+	u2.c[4] = 0x7c; u2.c[5] = 0xc1; u2.c[6] = 0x37; u2.c[7] = 0x42;
+#elif defined(DUK_USE_DOUBLE_ME)
+	u2.c[0] = 0x7c; u2.c[1] = 0xc1; u2.c[2] = 0x37; u2.c[3] = 0x42;
+	u2.c[4] = 0x00; u2.c[5] = 0x00; u2.c[6] = 0xc4; u2.c[7] = 0x6d;
+#elif defined(DUK_USE_DOUBLE_BE)
+	u2.c[0] = 0x42; u2.c[1] = 0x37; u2.c[2] = 0xc1; u2.c[3] = 0x7c;
+	u2.c[4] = 0x6d; u2.c[5] = 0xc4; u2.c[6] = 0x00; u2.c[7] = 0x00;
+#else
+#error unknown double endianness
+#endif
+
+	if (u1.i != (duk_uint32_t) 0xdeadbeefUL) {
+		DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: duk_uint32_t byte order");
+	}
+
+	if (u2.d != (double) 102030405060.0) {
+		DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: double byte order");
+	}
+}
+
+/*
+ *  Basic double / byte union memory layout.
+ */
+
+static void duk__selftest_double_union_size(void) {
+	if (sizeof(duk__test_double_union) != 8) {
+		DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: invalid union size");
+	}
+}
+
+/*
+ *  Union aliasing, see misc/clang_aliasing.c.
+ */
+
+static void duk__selftest_double_aliasing(void) {
+	duk__test_double_union a, b;
+
+	/* This testcase fails when Emscripten-generated code runs on Firefox.
+	 * It's not an issue because the failure should only affect packed
+	 * duk_tval representation, which is not used with Emscripten.
+	 */
+#if defined(DUK_USE_NO_DOUBLE_ALIASING_SELFTEST)
+#if defined(DUK_USE_PACKED_TVAL)
+#error inconsistent defines: skipping double aliasing selftest when using packed duk_tval
+#endif
+	return;
+#endif
+
+	/* Test signaling NaN and alias assignment in all
+	 * endianness combinations.
+	 */
+
+	/* little endian */
+	a.c[0] = 0x11; a.c[1] = 0x22; a.c[2] = 0x33; a.c[3] = 0x44;
+	a.c[4] = 0x00; a.c[5] = 0x00; a.c[6] = 0xf1; a.c[7] = 0xff;
+	b = a;
+	DUK__DBLUNION_CMP_TRUE(&a, &b);
+
+	/* big endian */
+	a.c[0] = 0xff; a.c[1] = 0xf1; a.c[2] = 0x00; a.c[3] = 0x00;
+	a.c[4] = 0x44; a.c[5] = 0x33; a.c[6] = 0x22; a.c[7] = 0x11;
+	b = a;
+	DUK__DBLUNION_CMP_TRUE(&a, &b);
+
+	/* mixed endian */
+	a.c[0] = 0x00; a.c[1] = 0x00; a.c[2] = 0xf1; a.c[3] = 0xff;
+	a.c[4] = 0x11; a.c[5] = 0x22; a.c[6] = 0x33; a.c[7] = 0x44;
+	b = a;
+	DUK__DBLUNION_CMP_TRUE(&a, &b);
+}
+
+/*
+ *  Zero sign, see misc/tcc_zerosign2.c.
+ */
+
+static void duk__selftest_double_zero_sign(void) {
+	volatile duk__test_double_union a, b;
+
+	a.d = 0.0;
+	b.d = -a.d;
+	DUK__DBLUNION_CMP_FALSE(&a, &b);
+}
+
+/*
+ *  Struct size/alignment if platform requires it
+ *
+ *  There are some compiler specific struct padding pragmas etc in use, this
+ *  selftest ensures they're correctly detected and used.
+ */
+
+static void duk__selftest_struct_align(void) {
+#if defined(DUK_USE_ALIGN_4)
+	if ((sizeof(duk_hbuffer_fixed) % 4) != 0) {
+		DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: sizeof(duk_hbuffer_fixed) not aligned to 4");
+	}
+#elif defined(DUK_USE_ALIGN_8)
+	if ((sizeof(duk_hbuffer_fixed) % 8) != 0) {
+		DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "self test failed: sizeof(duk_hbuffer_fixed) not aligned to 8");
+	}
+#else
+	/* no check */
+#endif
+}
+
+/*
+ *  Self test main
+ */
+
+void duk_selftest_run_tests(void) {
+	duk__selftest_types();
+	duk__selftest_packed_tval();
+	duk__selftest_twos_complement();
+	duk__selftest_byte_order();
+	duk__selftest_double_union_size();
+	duk__selftest_double_aliasing();
+	duk__selftest_double_zero_sign();
+	duk__selftest_struct_align();
+}
+
+#undef DUK__DBLUNION_CMP_TRUE
+#undef DUK__DBLUNION_CMP_FALSE
+
+#endif  /* DUK_USE_SELF_TESTS */
+#line 1 "duk_strings.c"
+/*
+ *  Shared error message strings
+ *
+ *  To minimize code footprint, try to share error messages inside Duktape
+ *  code.
+ */
+
+/* include removed: duk_internal.h */
+
+/* Mostly API related */
+const char *duk_str_internal_error = "internal error";
+const char *duk_str_invalid_count = "invalid count";
+const char *duk_str_invalid_call_args = "invalid call args";
+const char *duk_str_not_constructable = "not constructable";
+const char *duk_str_not_callable = "not callable";
+const char *duk_str_not_extensible = "not extensible";
+const char *duk_str_not_writable = "not writable";
+const char *duk_str_not_configurable = "not configurable";
+
+const char *duk_str_invalid_index = "invalid index";
+const char *duk_str_push_beyond_alloc_stack = "attempt to push beyond currently allocated stack";
+const char *duk_str_src_stack_not_enough = "source stack does not contain enough elements";
+const char *duk_str_not_undefined = "not undefined";
+const char *duk_str_not_null = "not null";
+const char *duk_str_not_boolean = "not boolean";
+const char *duk_str_not_number = "not number";
+const char *duk_str_not_string = "not string";
+const char *duk_str_not_pointer = "not pointer";
+const char *duk_str_not_buffer = "not buffer";
+const char *duk_str_not_object = "not object";
+const char *duk_str_unexpected_type = "unexpected type";
+const char *duk_str_not_thread = "not thread";
+const char *duk_str_not_compiledfunction = "not compiledfunction";
+const char *duk_str_not_nativefunction = "not nativefunction";
+const char *duk_str_not_c_function = "not c function";
+const char *duk_str_defaultvalue_coerce_failed = "[[DefaultValue]] coerce failed";
+const char *duk_str_number_outside_range = "number outside range";
+const char *duk_str_not_object_coercible = "not object coercible";
+const char *duk_str_string_too_long = "string too long";
+const char *duk_str_buffer_too_long = "buffer too long";
+const char *duk_str_sprintf_too_long = "sprintf message too long";
+const char *duk_str_object_alloc_failed = "object alloc failed";
+const char *duk_str_thread_alloc_failed = "thread alloc failed";
+const char *duk_str_func_alloc_failed = "func alloc failed";
+const char *duk_str_buffer_alloc_failed = "buffer alloc failed";
+const char *duk_str_pop_too_many = "attempt to pop too many entries";
+
+/* JSON */
+const char *duk_str_fmt_ptr = "%p";
+const char *duk_str_invalid_json = "invalid json";
+const char *duk_str_invalid_number = "invalid number";
+const char *duk_str_jsondec_reclimit = "json decode recursion limit";
+const char *duk_str_jsonenc_reclimit = "json encode recursion limit";
+const char *duk_str_cyclic_input = "cyclic input";
+
+/* Object property access */
+const char *duk_str_proxy_revoked = "proxy revoked";
+const char *duk_str_object_resize_failed = "object resize failed";
+const char *duk_str_invalid_base = "invalid base value";
+const char *duk_str_strict_caller_read = "attempt to read strict 'caller'";
+const char *duk_str_proxy_rejected = "proxy rejected";
+const char *duk_str_invalid_array_length = "invalid array length";
+const char *duk_str_array_length_write_failed = "array length write failed";
+const char *duk_str_array_length_not_writable = "array length non-writable";
+const char *duk_str_setter_undefined = "setter undefined";
+const char *duk_str_redefine_virt_prop = "attempt to redefine virtual property";
+const char *duk_str_invalid_descriptor = "invalid descriptor";
+const char *duk_str_property_is_virtual = "property is virtual";
+
+/* Compiler */
+const char *duk_str_parse_error = "parse error";
+const char *duk_str_duplicate_label = "duplicate label";
+const char *duk_str_invalid_label = "invalid label";
+const char *duk_str_invalid_array_literal = "invalid array literal";
+const char *duk_str_invalid_object_literal = "invalid object literal";
+const char *duk_str_invalid_var_declaration = "invalid variable declaration";
+const char *duk_str_cannot_delete_identifier = "cannot delete identifier";
+const char *duk_str_invalid_expression = "invalid expression";
+const char *duk_str_invalid_lvalue = "invalid lvalue";
+const char *duk_str_expected_identifier = "expected identifier";
+const char *duk_str_empty_expr_not_allowed = "empty expression not allowed";
+const char *duk_str_invalid_for = "invalid for statement";
+const char *duk_str_invalid_switch = "invalid switch statement";
+const char *duk_str_invalid_break_cont_label = "invalid break/continue label";
+const char *duk_str_invalid_return = "invalid return";
+const char *duk_str_invalid_try = "invalid try";
+const char *duk_str_with_in_strict_mode = "with in strict mode";
+const char *duk_str_func_stmt_not_allowed = "function statement not allowed";
+const char *duk_str_unterminated_stmt = "unterminated statement";
+const char *duk_str_invalid_arg_name = "invalid argument name";
+const char *duk_str_invalid_func_name = "invalid function name";
+const char *duk_str_invalid_getset_name = "invalid getter/setter name";
+const char *duk_str_func_name_required = "function name required";
+
+/* Executor */
+const char *duk_str_internal_error_exec_longjmp = "internal error in bytecode executor longjmp handler";
+
+/* Limits */
+const char *duk_str_valstack_limit = "valstack limit";
+const char *duk_str_object_property_limit = "object property limit";
+const char *duk_str_prototype_chain_limit = "prototype chain limit";
+const char *duk_str_bound_chain_limit = "function call bound chain limit";
+const char *duk_str_c_callstack_limit = "C call stack depth limit";
+const char *duk_str_compiler_recursion_limit = "compiler recursion limit";
+const char *duk_str_bytecode_limit = "bytecode limit";
+const char *duk_str_reg_limit = "register limit";
+const char *duk_str_temp_limit = "temp limit";
+const char *duk_str_const_limit = "const limit";
+const char *duk_str_func_limit = "function limit";
+#line 1 "duk_unicode_support.c"
+/*
+ *  Various Unicode help functions for character classification predicates,
+ *  case conversion, decoding, etc.
+ */
+
+/* include removed: duk_internal.h */
+
+/*
+ *  XUTF-8 and CESU-8 encoding/decoding
+ */
+
+duk_small_int_t duk_unicode_get_xutf8_length(duk_ucodepoint_t cp) {
+	duk_uint_fast32_t x = (duk_uint_fast32_t) cp;
+	if (x < 0x80UL) {
+		/* 7 bits */
+		return 1;
+	} else if (x < 0x800UL) {
+		/* 11 bits */
+		return 2;
+	} else if (x < 0x10000UL) {
+		/* 16 bits */
+		return 3;
+	} else if (x < 0x200000UL) {
+		/* 21 bits */
+		return 4;
+	} else if (x < 0x4000000UL) {
+		/* 26 bits */
+		return 5;
+	} else if (x < (duk_ucodepoint_t) 0x80000000UL) {
+		/* 31 bits */
+		return 6;
+	} else {
+		/* 36 bits */
+		return 7;
+	}
+}
+
+duk_uint8_t duk_unicode_xutf8_markers[7] = {
+	0x00, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe
+};
+
+/* Encode to extended UTF-8; 'out' must have space for at least
+ * DUK_UNICODE_MAX_XUTF8_LENGTH bytes.  Allows encoding of any
+ * 32-bit (unsigned) codepoint.
+ */
+duk_small_int_t duk_unicode_encode_xutf8(duk_ucodepoint_t cp, duk_uint8_t *out) {
+	duk_uint_fast32_t x = (duk_uint_fast32_t) cp;
+	duk_small_int_t len;
+	duk_uint8_t marker;
+	duk_small_int_t i;
+
+	len = duk_unicode_get_xutf8_length(cp);
+	DUK_ASSERT(len > 0);
+
+	marker = duk_unicode_xutf8_markers[len - 1];  /* 64-bit OK because always >= 0 */
+
+	i = len;
+	DUK_ASSERT(i > 0);
+	do {
+		i--;
+		if (i > 0) {
+			out[i] = (duk_uint8_t) (0x80 + (x & 0x3f));
+			x >>= 6;
+		} else {
+			/* Note: masking of 'x' is not necessary because of
+			 * range check and shifting -> no bits overlapping
+			 * the marker should be set.
+			 */
+			out[0] = (duk_uint8_t) (marker + x);
+		}
+	} while (i > 0);
+
+	return len;
+}
+
+/* Encode to CESU-8; 'out' must have space for at least
+ * DUK_UNICODE_MAX_CESU8_LENGTH bytes; codepoints above U+10FFFF
+ * will encode to garbage but won't overwrite the output buffer.
+ */
+duk_small_int_t duk_unicode_encode_cesu8(duk_ucodepoint_t cp, duk_uint8_t *out) {
+	duk_uint_fast32_t x = (duk_uint_fast32_t) cp;
+	duk_small_int_t len;
+
+	if (x < 0x80UL) {
+		out[0] = (duk_uint8_t) x;
+		len = 1;
+	} else if (x < 0x800UL) {
+		out[0] = (duk_uint8_t) (0xc0 + ((x >> 6) & 0x1f));
+		out[1] = (duk_uint8_t) (0x80 + (x & 0x3f));
+		len = 2;
+	} else if (x < 0x10000UL) {
+		/* surrogate pairs get encoded here */
+		out[0] = (duk_uint8_t) (0xe0 + ((x >> 12) & 0x0f));
+		out[1] = (duk_uint8_t) (0x80 + ((x >> 6) & 0x3f));
+		out[2] = (duk_uint8_t) (0x80 + (x & 0x3f));
+		len = 3;
+	} else {
+		/*
+		 *  Unicode codepoints above U+FFFF are encoded as surrogate
+		 *  pairs here.  This ensures that all CESU-8 codepoints are
+		 *  16-bit values as expected in Ecmascript.  The surrogate
+		 *  pairs always get a 3-byte encoding (each) in CESU-8.
+		 *  See: http://en.wikipedia.org/wiki/Surrogate_pair
+		 *
+		 *  20-bit codepoint, 10 bits (A and B) per surrogate pair:
+		 * 
+		 *    x = 0b00000000 0000AAAA AAAAAABB BBBBBBBB
+		 *  sp1 = 0b110110AA AAAAAAAA  (0xd800 + ((x >> 10) & 0x3ff))
+		 *  sp2 = 0b110111BB BBBBBBBB  (0xdc00 + (x & 0x3ff))
+		 *
+		 *  Encoded into CESU-8:
+		 *
+		 *  sp1 -> 0b11101101  (0xe0 + ((sp1 >> 12) & 0x0f))
+		 *      -> 0b1010AAAA  (0x80 + ((sp1 >> 6) & 0x3f))
+		 *      -> 0b10AAAAAA  (0x80 + (sp1 & 0x3f))
+		 *  sp2 -> 0b11101101  (0xe0 + ((sp2 >> 12) & 0x0f))
+		 *      -> 0b1011BBBB  (0x80 + ((sp2 >> 6) & 0x3f))
+		 *      -> 0b10BBBBBB  (0x80 + (sp2 & 0x3f))
+		 *
+		 *  Note that 0x10000 must be subtracted first.  The code below
+		 *  avoids the sp1, sp2 temporaries which saves around 20 bytes
+		 *  of code.
+		 */
+
+		x -= 0x10000UL;
+
+		out[0] = (duk_uint8_t) (0xed);
+		out[1] = (duk_uint8_t) (0xa0 + ((x >> 16) & 0x0f));
+		out[2] = (duk_uint8_t) (0x80 + ((x >> 10) & 0x3f));
+		out[3] = (duk_uint8_t) (0xed);
+		out[4] = (duk_uint8_t) (0xb0 + ((x >> 6) & 0x0f));
+		out[5] = (duk_uint8_t) (0x80 + (x & 0x3f));
+		len = 6;
+	}
+
+	return len;
+}
+
+/* Decode helper.  Return zero on error. */
+duk_small_int_t duk_unicode_decode_xutf8(duk_hthread *thr, duk_uint8_t **ptr, duk_uint8_t *ptr_start, duk_uint8_t *ptr_end, duk_ucodepoint_t *out_cp) {
+	duk_uint8_t *p;
+	duk_uint32_t res;
+	duk_uint_fast8_t ch;
+	duk_small_int_t n;
+
+	DUK_UNREF(thr);
+
+	p = *ptr;
+	if (p < ptr_start || p >= ptr_end) {
+		goto fail;
+	}
+
+	/*
+	 *  UTF-8 decoder which accepts longer than standard byte sequences.
+	 *  This allows full 32-bit code points to be used.
+	 */
+
+	ch = (duk_uint_fast8_t) (*p++);
+	if (ch < 0x80) {
+		/* 0xxx xxxx   [7 bits] */
+		res = (duk_uint32_t) (ch & 0x7f);
+		n = 0;
+	} else if (ch < 0xc0) {
+		/* 10xx xxxx -> invalid */
+		goto fail;
+	} else if (ch < 0xe0) {
+		/* 110x xxxx   10xx xxxx   [11 bits] */
+		res = (duk_uint32_t) (ch & 0x1f);
+		n = 1;
+	} else if (ch < 0xf0) {
+		/* 1110 xxxx   10xx xxxx   10xx xxxx   [16 bits] */
+		res = (duk_uint32_t) (ch & 0x0f);
+		n = 2;
+	} else if (ch < 0xf8) {
+		/* 1111 0xxx   10xx xxxx   10xx xxxx   10xx xxxx   [21 bits] */
+		res = (duk_uint32_t) (ch & 0x07);
+		n = 3;
+	} else if (ch < 0xfc) {
+		/* 1111 10xx   10xx xxxx   10xx xxxx   10xx xxxx   10xx xxxx   [26 bits] */
+		res = (duk_uint32_t) (ch & 0x03);
+		n = 4;
+	} else if (ch < 0xfe) {
+		/* 1111 110x   10xx xxxx   10xx xxxx   10xx xxxx   10xx xxxx   10xx xxxx   [31 bits] */
+		res = (duk_uint32_t) (ch & 0x01);
+		n = 5;
+	} else if (ch < 0xff) {
+		/* 1111 1110   10xx xxxx   10xx xxxx   10xx xxxx   10xx xxxx   10xx xxxx   10xx xxxx   [36 bits] */
+		res = (duk_uint32_t) (0);
+		n = 6;
+	} else {
+		/* 8-byte format could be:
+		 * 1111 1111   10xx xxxx   10xx xxxx   10xx xxxx   10xx xxxx   10xx xxxx   10xx xxxx   10xx xxxx   [41 bits]
+		 *
+		 * However, this format would not have a zero bit following the
+		 * leading one bits and would not allow 0xFF to be used as an
+		 * "invalid xutf-8" marker for internal keys.  Further, 8-byte
+		 * encodings (up to 41 bit code points) are not currently needed.
+		 */
+		goto fail;
+	}
+
+	DUK_ASSERT(p >= ptr_start);  /* verified at beginning */
+	if (p + n > ptr_end) {
+		/* check pointer at end */
+		goto fail;
+	}
+
+	while (n > 0) {
+		DUK_ASSERT(p >= ptr_start && p < ptr_end);
+		res = res << 6;
+		res += (duk_uint32_t) ((*p++) & 0x3f);
+		n--;
+	}
+
+	*ptr = p;
+	*out_cp = res;
+	return 1;
+
+ fail:
+	return 0;
+}
+
+/* used by e.g. duk_regexp_executor.c, string built-ins */
+duk_ucodepoint_t duk_unicode_decode_xutf8_checked(duk_hthread *thr, duk_uint8_t **ptr, duk_uint8_t *ptr_start, duk_uint8_t *ptr_end) {
+	duk_ucodepoint_t cp;
+
+	if (duk_unicode_decode_xutf8(thr, ptr, ptr_start, ptr_end, &cp)) {
+		return cp;
+	}
+	DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "utf-8 decode failed");
+	DUK_UNREACHABLE();
+	return 0;
+}
+
+/* (extended) utf-8 length without codepoint encoding validation, used
+ * for string interning (should probably be inlined).
+ */
+duk_size_t duk_unicode_unvalidated_utf8_length(duk_uint8_t *data, duk_size_t blen) {
+	duk_uint8_t *p = data;
+	duk_uint8_t *p_end = data + blen;
+	duk_size_t clen = 0;
+
+	while (p < p_end) {
+		duk_uint8_t x = *p++;
+		if (x < 0x80 || x >= 0xc0) {
+			/* 10xxxxxx = continuation chars (0x80...0xbf), above
+			 * and below that initial bytes.
+			 */
+			clen++;
+		}
+	}
+
+	return clen;
+}
+
+/*
+ *  Unicode range matcher
+ *
+ *  Matches a codepoint against a packed bitstream of character ranges.
+ *  Used for slow path Unicode matching.
+ */
+
+/* Must match src/extract_chars.py, generate_match_table3(). */
+static duk_uint32_t duk__uni_decode_value(duk_bitdecoder_ctx *bd_ctx) {
+	duk_uint32_t t;
+
+	t = (duk_uint32_t) duk_bd_decode(bd_ctx, 4);
+	if (t <= 0x0eU) {
+		return t;
+	}
+	t = (duk_uint32_t) duk_bd_decode(bd_ctx, 8);
+	if (t <= 0xfdU) {
+		return t + 0x0f;
+	}
+	if (t == 0xfeU) {
+		t = (duk_uint32_t) duk_bd_decode(bd_ctx, 12);
+		return t + 0x0fU + 0xfeU;
+	} else {
+		t = (duk_uint32_t) duk_bd_decode(bd_ctx, 24);
+		return t + 0x0fU + 0xfeU + 0x1000UL;
+	}
+}
+
+static duk_small_int_t duk__uni_range_match(const duk_uint8_t *unitab, duk_size_t unilen, duk_codepoint_t cp) {
+	duk_bitdecoder_ctx bd_ctx;
+	duk_codepoint_t prev_re;
+
+	DUK_MEMZERO(&bd_ctx, sizeof(bd_ctx));
+	bd_ctx.data = (duk_uint8_t *) unitab;
+	bd_ctx.length = (duk_size_t) unilen;
+
+	prev_re = 0;
+	for (;;) {
+		duk_codepoint_t r1, r2;
+		r1 = (duk_codepoint_t) duk__uni_decode_value(&bd_ctx);
+		if (r1 == 0) {
+			break;
+		}
+		r2 = (duk_codepoint_t) duk__uni_decode_value(&bd_ctx);
+
+		r1 = prev_re + r1;
+		r2 = r1 + r2;
+		prev_re = r2;
+
+		/* [r1,r2] is the range */
+
+		DUK_DDD(DUK_DDDPRINT("duk__uni_range_match: cp=%06lx range=[0x%06lx,0x%06lx]",
+		                     (unsigned long) cp, (unsigned long) r1, (unsigned long) r2));
+		if (cp >= r1 && cp <= r2) {
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ *  "WhiteSpace" production check.
+ */
+
+duk_small_int_t duk_unicode_is_whitespace(duk_codepoint_t cp) {
+	/*
+	 *  E5 Section 7.2 specifies six characters specifically as
+	 *  white space:
+	 *
+	 *    0009;<control>;Cc;0;S;;;;;N;CHARACTER TABULATION;;;;
+	 *    000B;<control>;Cc;0;S;;;;;N;LINE TABULATION;;;;
+	 *    000C;<control>;Cc;0;WS;;;;;N;FORM FEED (FF);;;;
+	 *    0020;SPACE;Zs;0;WS;;;;;N;;;;;
+	 *    00A0;NO-BREAK SPACE;Zs;0;CS;<noBreak> 0020;;;;N;NON-BREAKING SPACE;;;;
+	 *    FEFF;ZERO WIDTH NO-BREAK SPACE;Cf;0;BN;;;;;N;BYTE ORDER MARK;;;;
+	 *
+	 *  It also specifies any Unicode category 'Zs' characters as white
+	 *  space.  These can be extracted with the "src/extract_chars.py" script.
+	 *  Current result:
+	 *  
+	 *    RAW OUTPUT:
+	 *    ===========
+	 *    0020;SPACE;Zs;0;WS;;;;;N;;;;;
+	 *    00A0;NO-BREAK SPACE;Zs;0;CS;<noBreak> 0020;;;;N;NON-BREAKING SPACE;;;;
+	 *    1680;OGHAM SPACE MARK;Zs;0;WS;;;;;N;;;;;
+	 *    180E;MONGOLIAN VOWEL SEPARATOR;Zs;0;WS;;;;;N;;;;;
+	 *    2000;EN QUAD;Zs;0;WS;2002;;;;N;;;;;
+	 *    2001;EM QUAD;Zs;0;WS;2003;;;;N;;;;;
+	 *    2002;EN SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;
+	 *    2003;EM SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;
+	 *    2004;THREE-PER-EM SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;
+	 *    2005;FOUR-PER-EM SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;
+	 *    2006;SIX-PER-EM SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;
+	 *    2007;FIGURE SPACE;Zs;0;WS;<noBreak> 0020;;;;N;;;;;
+	 *    2008;PUNCTUATION SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;
+	 *    2009;THIN SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;
+	 *    200A;HAIR SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;
+	 *    202F;NARROW NO-BREAK SPACE;Zs;0;CS;<noBreak> 0020;;;;N;;;;;
+	 *    205F;MEDIUM MATHEMATICAL SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;
+	 *    3000;IDEOGRAPHIC SPACE;Zs;0;WS;<wide> 0020;;;;N;;;;;
+	 *  
+	 *    RANGES:
+	 *    =======
+	 *    0x0020
+	 *    0x00a0
+	 *    0x1680
+	 *    0x180e
+	 *    0x2000 ... 0x200a
+	 *    0x202f
+	 *    0x205f
+	 *    0x3000
+	 *
+	 *  A manual decoder (below) is probably most compact for this.
+	 */
+
+	duk_uint_fast8_t lo;
+	duk_uint_fast32_t hi;
+
+	/* cp == -1 (EOF) never matches and causes return value 0 */
+
+	lo = (duk_uint_fast8_t) (cp & 0xff);
+	hi = (duk_uint_fast32_t) (cp >> 8);  /* does not fit into an uchar */
+
+	if (hi == 0x0000UL) {
+		if (lo == 0x09U || lo == 0x0bU || lo == 0x0cU ||
+		    lo == 0x20U || lo == 0xa0U) {
+			return 1;
+		}
+	} else if (hi == 0x0020UL) {
+		if (lo <= 0x0aU || lo == 0x2fU || lo == 0x5fU) {
+			return 1;
+		}
+	} else if (cp == 0x1680L || cp == 0x180eL || cp == 0x3000L ||
+	           cp == 0xfeffL) {
+		return 1;
+	}
+
+	return 0;
+}
+
+/*
+ *  "LineTerminator" production check.
+ */
+
+duk_small_int_t duk_unicode_is_line_terminator(duk_codepoint_t cp) {
+	/*
+	 *  E5 Section 7.3
+	 *
+	 *  A LineTerminatorSequence essentially merges <CR> <LF> sequences
+	 *  into a single line terminator.  This must be handled by the caller.
+	 */
+
+	if (cp == 0x000aL || cp == 0x000dL || cp == 0x2028L ||
+	    cp == 0x2029L) {
+		return 1;
+	}
+
+	return 0;
+}
+
+/*
+ *  "IdentifierStart" production check.
+ */
+
+duk_small_int_t duk_unicode_is_identifier_start(duk_codepoint_t cp) {
+	/*
+	 *  E5 Section 7.6:
+	 *
+	 *    IdentifierStart:
+	 *      UnicodeLetter
+	 *      $
+	 *      _
+	 *      \ UnicodeEscapeSequence
+	 *
+	 *  IdentifierStart production has one multi-character production:
+	 *
+	 *    \ UnicodeEscapeSequence
+	 *
+	 *  The '\' character is -not- matched by this function.  Rather, the caller
+	 *  should decode the escape and then call this function to check whether the
+	 *  decoded character is acceptable (see discussion in E5 Section 7.6).
+	 *
+	 *  The "UnicodeLetter" alternative of the production allows letters
+	 *  from various Unicode categories.  These can be extracted with the
+	 *  "src/extract_chars.py" script.
+	 *
+	 *  Because the result has hundreds of Unicode codepoint ranges, matching
+	 *  for any values >= 0x80 are done using a very slow range-by-range scan
+	 *  and a packed range format.
+	 *
+	 *  The ASCII portion (codepoints 0x00 ... 0x7f) is fast-pathed below because
+	 *  it matters the most.  The ASCII related ranges of IdentifierStart are:
+	 *
+	 *    0x0041 ... 0x005a		['A' ... 'Z']
+	 *    0x0061 ... 0x007a		['a' ... 'z']
+	 *    0x0024			['$']
+	 *    0x005f			['_']
+	 */
+
+	/* ASCII (and EOF) fast path -- quick accept and reject */
+	if (cp <= 0x7fL) {
+		if ((cp >= 'a' && cp <= 'z') ||
+		    (cp >= 'A' && cp <= 'Z') ||
+		    cp == '_' || cp == '$') {
+			return 1;
+		}
+		return 0;
+	}
+
+	/* Non-ASCII slow path (range-by-range linear comparison), very slow */
+
+#ifdef DUK_USE_SOURCE_NONBMP
+	if (duk__uni_range_match(duk_unicode_ids_noa,
+	                         (duk_size_t) sizeof(duk_unicode_ids_noa),
+	                         (duk_codepoint_t) cp)) {
+		return 1;
+	}
+	return 0;
+#else
+	if (cp < 0x10000L) {
+		if (duk__uni_range_match(duk_unicode_ids_noabmp,
+		                         sizeof(duk_unicode_ids_noabmp),
+		                         (duk_codepoint_t) cp)) {
+			return 1;
+		}
+		return 0;
+	} else {
+		/* without explicit non-BMP support, assume non-BMP characters
+		 * are always accepted as identifier characters.
+		 */
+		return 1;
+	}
+#endif
+}
+
+/*
+ *  "IdentifierPart" production check.
+ */
+
+duk_small_int_t duk_unicode_is_identifier_part(duk_codepoint_t cp) {
+	/*
+	 *  E5 Section 7.6:
+	 *
+	 *    IdentifierPart:
+	 *      IdentifierStart
+	 *      UnicodeCombiningMark
+	 *      UnicodeDigit
+	 *      UnicodeConnectorPunctuation
+	 *      <ZWNJ>	[U+200C]
+	 *      <ZWJ>	[U+200D]
+	 *
+	 *  IdentifierPart production has one multi-character production
+	 *  as part of its IdentifierStart alternative.  The '\' character
+	 *  of an escape sequence is not matched here, see discussion in
+	 *  duk_unicode_is_identifier_start().
+	 *
+	 *  To match non-ASCII characters (codepoints >= 0x80), a very slow
+	 *  linear range-by-range scan is used.  The codepoint is first compared
+	 *  to the IdentifierStart ranges, and if it doesn't match, then to a
+	 *  set consisting of code points in IdentifierPart but not in
+	 *  IdentifierStart.  This is done to keep the unicode range data small,
+	 *  at the expense of speed.
+	 *
+	 *  The ASCII fast path consists of:
+	 *
+	 *    0x0030 ... 0x0039		['0' ... '9', UnicodeDigit]
+	 *    0x0041 ... 0x005a		['A' ... 'Z', IdentifierStart]
+	 *    0x0061 ... 0x007a		['a' ... 'z', IdentifierStart]
+	 *    0x0024			['$', IdentifierStart]
+	 *    0x005f			['_', IdentifierStart and
+	 *                               UnicodeConnectorPunctuation]
+	 *
+	 *  UnicodeCombiningMark has no code points <= 0x7f.
+	 *
+	 *  The matching code reuses the "identifier start" tables, and then
+	 *  consults a separate range set for characters in "identifier part"
+	 *  but not in "identifier start".  These can be extracted with the
+	 *  "src/extract_chars.py" script.
+	 *
+	 *  UnicodeCombiningMark -> categories Mn, Mc
+	 *  UnicodeDigit -> categories Nd
+	 *  UnicodeConnectorPunctuation -> categories Pc
+	 */
+
+	/* ASCII (and EOF) fast path -- quick accept and reject */
+	if (cp <= 0x7fL) {
+		if ((cp >= 'a' && cp <= 'z') ||
+		    (cp >= 'A' && cp <= 'Z') ||
+		    (cp >= '0' && cp <= '9') ||
+		    cp == '_' || cp == '$') {
+			return 1;
+		}
+		return 0;
+	}
+
+	/* Non-ASCII slow path (range-by-range linear comparison), very slow */
+
+#ifdef DUK_USE_SOURCE_NONBMP
+	if (duk__uni_range_match(duk_unicode_ids_noa,
+	                         sizeof(duk_unicode_ids_noa),
+	                         (duk_codepoint_t) cp) ||
+	    duk__uni_range_match(duk_unicode_idp_m_ids_noa,
+	                         sizeof(duk_unicode_idp_m_ids_noa),
+	                         (duk_codepoint_t) cp)) {
+		return 1;
+	}
+	return 0;
+#else
+	if (cp < 0x10000L) {
+		if (duk__uni_range_match(duk_unicode_ids_noabmp,
+		                         sizeof(duk_unicode_ids_noabmp),
+		                         (duk_codepoint_t) cp) ||
+		    duk__uni_range_match(duk_unicode_idp_m_ids_noabmp,
+		                         sizeof(duk_unicode_idp_m_ids_noabmp),
+		                         (duk_codepoint_t) cp)) {
+			return 1;
+		}
+		return 0;
+	} else {
+		/* without explicit non-BMP support, assume non-BMP characters
+		 * are always accepted as identifier characters.
+		 */
+		return 1;
+	}
+#endif
+}
+
+/*
+ *  Unicode letter check.
+ */
+
+duk_small_int_t duk_unicode_is_letter(duk_codepoint_t cp) {
+	/*
+	 *  Unicode letter is now taken to be the categories:
+	 *
+	 *    Lu, Ll, Lt, Lm, Lo
+	 *
+	 *  (Not sure if this is exactly correct.)
+	 *
+	 *  The ASCII fast path consists of:
+	 *
+	 *    0x0041 ... 0x005a		['A' ... 'Z']
+	 *    0x0061 ... 0x007a		['a' ... 'z']
+	 */
+
+	/* ASCII (and EOF) fast path -- quick accept and reject */
+	if (cp <= 0x7fL) {
+		if ((cp >= 'a' && cp <= 'z') ||
+		    (cp >= 'A' && cp <= 'Z')) {
+			return 1;
+		}
+		return 0;
+	}
+
+	/* Non-ASCII slow path (range-by-range linear comparison), very slow */
+
+#ifdef DUK_USE_SOURCE_NONBMP
+	if (duk__uni_range_match(duk_unicode_ids_noa,
+	                         sizeof(duk_unicode_ids_noa),
+	                         (duk_codepoint_t) cp) &&
+	    !duk__uni_range_match(duk_unicode_ids_m_let_noa,
+	                          sizeof(duk_unicode_ids_m_let_noa),
+	                          (duk_codepoint_t) cp)) {
+		return 1;
+	}
+	return 0;
+#else
+	if (cp < 0x10000L) {
+		if (duk__uni_range_match(duk_unicode_ids_noabmp,
+		                         sizeof(duk_unicode_ids_noabmp),
+		                         (duk_codepoint_t) cp) &&
+		    !duk__uni_range_match(duk_unicode_ids_m_let_noabmp,
+		                          sizeof(duk_unicode_ids_m_let_noabmp),
+		                          (duk_codepoint_t) cp)) {
+			return 1;
+		}
+		return 0;
+	} else {
+		/* without explicit non-BMP support, assume non-BMP characters
+		 * are always accepted as letters.
+		 */
+		return 1;
+	}
+#endif
+}
+
+/*
+ *  Complex case conversion helper which decodes a bit-packed conversion
+ *  control stream generated by unicode/extract_caseconv.py.  The conversion
+ *  is very slow because it runs through the conversion data in a linear
+ *  fashion to save space (which is why ASCII characters have a special
+ *  fast path before arriving here).
+ * 
+ *  The particular bit counts etc have been determined experimentally to
+ *  be small but still sufficient, and must match the Python script
+ *  (src/extract_caseconv.py).
+ *
+ *  The return value is the case converted codepoint or -1 if the conversion
+ *  results in multiple characters (this is useful for regexp Canonicalization
+ *  operation).  If 'buf' is not NULL, the result codepoint(s) are also
+ *  appended to the hbuffer.
+ *
+ *  Context and locale specific rules must be checked before consulting
+ *  this function.
+ */
+
+static duk_codepoint_t duk__slow_case_conversion(duk_hthread *thr,
+                                                 duk_hbuffer_dynamic *buf,
+                                                 duk_codepoint_t cp,
+                                                 duk_bitdecoder_ctx *bd_ctx) {
+	duk_small_int_t skip = 0;
+	duk_small_int_t n;
+	duk_small_int_t t;
+	duk_small_int_t count;
+	duk_codepoint_t tmp_cp;
+	duk_codepoint_t start_i;
+	duk_codepoint_t start_o;
+
+	DUK_DDD(DUK_DDDPRINT("slow case conversion for codepoint: %ld", (long) cp));
+
+	/* range conversion with a "skip" */
+	DUK_DDD(DUK_DDDPRINT("checking ranges"));
+	for (;;) {
+		skip++;
+		n = (duk_small_int_t) duk_bd_decode(bd_ctx, 6);
+		if (n == 0x3f) {
+			/* end marker */
+			break;
+		}
+		DUK_DDD(DUK_DDDPRINT("skip=%ld, n=%ld", (long) skip, (long) n));
+
+		while (n--) {
+			start_i = (duk_codepoint_t) duk_bd_decode(bd_ctx, 16);
+			start_o = (duk_codepoint_t) duk_bd_decode(bd_ctx, 16);
+			count = (duk_small_int_t) duk_bd_decode(bd_ctx, 7);
+			DUK_DDD(DUK_DDDPRINT("range: start_i=%ld, start_o=%ld, count=%ld, skip=%ld",
+			                     (long) start_i, (long) start_o, (long) count, (long) skip));
+
+			if (cp >= start_i) {
+				tmp_cp = cp - start_i;  /* always >= 0 */
+				if (tmp_cp < (duk_codepoint_t) count * (duk_codepoint_t) skip &&
+				    (tmp_cp % (duk_codepoint_t) skip) == 0) {
+					DUK_DDD(DUK_DDDPRINT("range matches input codepoint"));
+					cp = start_o + tmp_cp;
+					goto single;
+				}
+			}
+		}
+	}
+
+	/* 1:1 conversion */
+	n = (duk_small_int_t) duk_bd_decode(bd_ctx, 6);
+	DUK_DDD(DUK_DDDPRINT("checking 1:1 conversions (count %ld)", (long) n));
+	while (n--) {
+		start_i = (duk_codepoint_t) duk_bd_decode(bd_ctx, 16);
+		start_o = (duk_codepoint_t) duk_bd_decode(bd_ctx, 16);
+		DUK_DDD(DUK_DDDPRINT("1:1 conversion %ld -> %ld", (long) start_i, (long) start_o));
+		if (cp == start_i) {
+			DUK_DDD(DUK_DDDPRINT("1:1 matches input codepoint"));
+			cp = start_o;
+			goto single;
+		}
+	}
+
+	/* complex, multicharacter conversion */
+	n = (duk_small_int_t) duk_bd_decode(bd_ctx, 7);
+	DUK_DDD(DUK_DDDPRINT("checking 1:n conversions (count %ld)", (long) n));
+	while (n--) {
+		start_i = (duk_codepoint_t) duk_bd_decode(bd_ctx, 16);
+		t = (duk_small_int_t) duk_bd_decode(bd_ctx, 2);
+		DUK_DDD(DUK_DDDPRINT("1:n conversion %ld -> %ld chars", (long) start_i, (long) t));
+		if (cp == start_i) {
+			DUK_DDD(DUK_DDDPRINT("1:n matches input codepoint"));
+			if (buf) {
+				while (t--) {
+					tmp_cp = (duk_codepoint_t) duk_bd_decode(bd_ctx, 16);
+					DUK_ASSERT(buf != NULL);
+					duk_hbuffer_append_xutf8(thr, buf, (duk_uint32_t) tmp_cp);  /* FIXME: duk_codepoint_t */
+				}
+			}
+			return -1;
+		} else {
+			while (t--) {
+				(void) duk_bd_decode(bd_ctx, 16);
+			}
+		}
+	}
+
+	/* default: no change */
+	DUK_DDD(DUK_DDDPRINT("no rule matches, output is same as input"));
+	/* fall through */
+
+ single:
+	if (buf) {
+		duk_hbuffer_append_xutf8(thr, buf, cp);
+	}
+	return cp;
+}
+
+/*
+ *  Case conversion helper, with context/local sensitivity.
+ *  For proper case conversion, one needs to know the character
+ *  and the preceding and following characters, as well as
+ *  locale/language.
+ */
+
+/* XXX: add 'language' argument when locale/language sensitive rule
+ * support added.
+ */
+static duk_codepoint_t duk__case_transform_helper(duk_hthread *thr,
+                                                  duk_hbuffer_dynamic *buf,
+                                                  duk_codepoint_t cp,
+                                                  duk_codepoint_t prev,
+                                                  duk_codepoint_t next,
+                                                  duk_bool_t uppercase) {
+	duk_bitdecoder_ctx bd_ctx;
+
+	/* fast path for ASCII */
+	if (cp < 0x80L) {
+		/* XXX: there are language sensitive rules for the ASCII range.
+		 * If/when language/locale support is implemented, they need to
+		 * be implemented here for the fast path.  There are no context
+		 * sensitive rules for ASCII range.
+		 */
+
+		if (uppercase) {
+			if (cp >= 'a' && cp <= 'z') {
+				cp = cp - 'a' + 'A';
+			}
+		} else {
+			if (cp >= 'A' && cp <= 'Z') {
+				cp = cp - 'A' + 'a';
+			}
+		}
+		goto singlechar;
+	}
+
+	/* context and locale specific rules which cannot currently be represented
+	 * in the caseconv bitstream: hardcoded rules in C
+	 */
+	if (uppercase) {
+		/* XXX: turkish / azeri not implemented */
+	} else {
+		/*
+		 *  Final sigma context specific rule.  This is a rather tricky rule
+		 *  and this handling is probably not 100% correct now.
+		 */
+
+		if (cp == 0x03a3L &&    /* U+03A3 = GREEK CAPITAL LETTER SIGMA */
+		    duk_unicode_is_letter(prev) &&        /* prev exists and is not a letter */
+		    !duk_unicode_is_letter(next)) {       /* next does not exist or next is not a letter */
+			/* Capital sigma occurred at "end of word", lowercase to
+			 * U+03C2 = GREEK SMALL LETTER FINAL SIGMA.  Otherwise
+			 * fall through and let the normal rules lowercase it to
+			 * U+03C3 = GREEK SMALL LETTER SIGMA.
+			 */
+			cp = 0x03c2L;
+			goto singlechar;
+		}
+
+		/* XXX: lithuanian not implemented */
+		/* XXX: lithuanian, explicit dot rules */
+		/* XXX: turkish / azeri, lowercase rules */
+#if 0
+		if (0 /* language == 'lt' */ &&
+		    cp == 0x0307L) {               /* U+0307 = COMBINING DOT ABOVE */
+			goto nochar;
+		}
+#endif
+	}
+
+	/* 1:1 or special conversions, but not locale/context specific: script generated rules */
+	DUK_MEMZERO(&bd_ctx, sizeof(bd_ctx));
+	if (uppercase) {
+		bd_ctx.data = (duk_uint8_t *) duk_unicode_caseconv_uc;
+		bd_ctx.length = (duk_size_t) sizeof(duk_unicode_caseconv_uc);
+	} else {
+		bd_ctx.data = (duk_uint8_t *) duk_unicode_caseconv_lc;
+		bd_ctx.length = (duk_size_t) sizeof(duk_unicode_caseconv_lc);
+	}
+	return duk__slow_case_conversion(thr, buf, cp, &bd_ctx);
+
+ singlechar:
+	if (buf) {
+		duk_hbuffer_append_xutf8(thr, buf, cp);
+	}
+	return cp;
+
+ /* unused now, not needed until Turkish/Azeri */
+#if 0
+ nochar:
+	return -1;
+#endif
+}
+
+/*
+ *  Replace valstack top with case converted version.
+ */
+
+void duk_unicode_case_convert_string(duk_hthread *thr, duk_small_int_t uppercase) {
+	duk_context *ctx = (duk_context *) thr;
+	duk_hstring *h_input;
+	duk_hbuffer_dynamic *h_buf;
+	duk_uint8_t *p, *p_start, *p_end;
+	duk_codepoint_t prev, curr, next;
+
+	h_input = duk_require_hstring(ctx, -1);
+	DUK_ASSERT(h_input != NULL);
+
+	/* XXX: should init the buffer with a spare of at least h_input->blen
+	 * to avoid unnecessary growth steps.
+	 */
+	duk_push_dynamic_buffer(ctx, 0);
+	h_buf = (duk_hbuffer_dynamic *) duk_get_hbuffer(ctx, -1);
+	DUK_ASSERT(h_buf != NULL);
+	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(h_buf));
+
+	/* [ ... input buffer ] */
+
+	p_start = (duk_uint8_t *) DUK_HSTRING_GET_DATA(h_input);
+	p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_input);
+	p = p_start;
+
+	prev = -1; DUK_UNREF(prev);
+	curr = -1;
+	next = -1;
+	for (;;) {
+		prev = curr;
+		curr = next;
+		next = -1;
+		if (p < p_end) {
+			next = (int) duk_unicode_decode_xutf8_checked(thr, &p, p_start, p_end);
+		} else {
+			/* end of input and last char has been processed */
+			if (curr < 0) {
+				break;
+			}
+		}
+
+		/* on first round, skip */
+		if (curr >= 0) {
+			/* may generate any number of output codepoints */
+			duk__case_transform_helper(thr,
+			                           h_buf,
+			                           (duk_codepoint_t) curr,
+			                           prev,
+			                           next,
+			                           uppercase);
+		}
+	}
+
+	duk_to_string(ctx, -1);  /* invalidates h_buf pointer */
+	duk_remove(ctx, -2);
+}
+
+#ifdef DUK_USE_REGEXP_SUPPORT
+
+/*
+ *  Canonicalize() abstract operation needed for canonicalization of individual
+ *  codepoints during regexp compilation and execution, see E5 Section 15.10.2.8.
+ *  Note that codepoints are canonicalized one character at a time, so no context
+ *  specific rules can apply.  Locale specific rules can apply, though.
+ */
+
+duk_codepoint_t duk_unicode_re_canonicalize_char(duk_hthread *thr, duk_codepoint_t cp) {
+	duk_codepoint_t y;
+
+	y = duk__case_transform_helper(thr,
+	                               NULL,    /* buf */
+	                               cp,      /* curr char */
+	                               -1,      /* prev char */
+	                               -1,      /* next char */
+	                               1);      /* uppercase */
+
+	if ((y < 0) || (cp >= 0x80 && y < 0x80)) {
+		/* multiple codepoint conversion or non-ASCII mapped to ASCII
+		 * --> leave as is.
+		 */
+		return cp;
+	}
+
+	return y;
+}
+
+/*
+ *  E5 Section 15.10.2.6 "IsWordChar" abstract operation.  Assume
+ *  x < 0 for characters read outside the string.
+ */
+
+duk_small_int_t duk_unicode_re_is_wordchar(duk_codepoint_t x) {
+	/*
+	 *  Note: the description in E5 Section 15.10.2.6 has a typo, it
+	 *  contains 'A' twice and lacks 'a'; the intent is [0-9a-zA-Z_].
+	 */
+	if ((x >= '0' && x <= '9') ||
+	    (x >= 'a' && x <= 'z') ||
+	    (x >= 'A' && x <= 'Z') ||
+	    (x == '_')) {
+		return 1;
+	}
+	return 0;
+}
+
+/*
+ *  Regexp range tables
+ */
+
+/* exposed because lexer needs these too */
+duk_uint16_t duk_unicode_re_ranges_digit[2] = {
+	(duk_uint16_t) 0x0030UL, (duk_uint16_t) 0x0039UL,
+};
+duk_uint16_t duk_unicode_re_ranges_white[22] = {
+	(duk_uint16_t) 0x0009UL, (duk_uint16_t) 0x000DUL,
+	(duk_uint16_t) 0x0020UL, (duk_uint16_t) 0x0020UL,
+	(duk_uint16_t) 0x00A0UL, (duk_uint16_t) 0x00A0UL,
+	(duk_uint16_t) 0x1680UL, (duk_uint16_t) 0x1680UL,
+	(duk_uint16_t) 0x180EUL, (duk_uint16_t) 0x180EUL,
+	(duk_uint16_t) 0x2000UL, (duk_uint16_t) 0x200AUL,
+	(duk_uint16_t) 0x2028UL, (duk_uint16_t) 0x2029UL,
+	(duk_uint16_t) 0x202FUL, (duk_uint16_t) 0x202FUL,
+	(duk_uint16_t) 0x205FUL, (duk_uint16_t) 0x205FUL,
+	(duk_uint16_t) 0x3000UL, (duk_uint16_t) 0x3000UL,
+	(duk_uint16_t) 0xFEFFUL, (duk_uint16_t) 0xFEFFUL,
+};
+duk_uint16_t duk_unicode_re_ranges_wordchar[8] = {
+	(duk_uint16_t) 0x0030UL, (duk_uint16_t) 0x0039UL,
+	(duk_uint16_t) 0x0041UL, (duk_uint16_t) 0x005AUL,
+	(duk_uint16_t) 0x005FUL, (duk_uint16_t) 0x005FUL,
+	(duk_uint16_t) 0x0061UL, (duk_uint16_t) 0x007AUL,
+};
+duk_uint16_t duk_unicode_re_ranges_not_digit[4] = {
+	(duk_uint16_t) 0x0000UL, (duk_uint16_t) 0x002FUL,
+	(duk_uint16_t) 0x003AUL, (duk_uint16_t) 0xFFFFUL,
+};
+duk_uint16_t duk_unicode_re_ranges_not_white[24] = {
+	(duk_uint16_t) 0x0000UL, (duk_uint16_t) 0x0008UL,
+	(duk_uint16_t) 0x000EUL, (duk_uint16_t) 0x001FUL,
+	(duk_uint16_t) 0x0021UL, (duk_uint16_t) 0x009FUL,
+	(duk_uint16_t) 0x00A1UL, (duk_uint16_t) 0x167FUL,
+	(duk_uint16_t) 0x1681UL, (duk_uint16_t) 0x180DUL,
+	(duk_uint16_t) 0x180FUL, (duk_uint16_t) 0x1FFFUL,
+	(duk_uint16_t) 0x200BUL, (duk_uint16_t) 0x2027UL,
+	(duk_uint16_t) 0x202AUL, (duk_uint16_t) 0x202EUL,
+	(duk_uint16_t) 0x2030UL, (duk_uint16_t) 0x205EUL,
+	(duk_uint16_t) 0x2060UL, (duk_uint16_t) 0x2FFFUL,
+	(duk_uint16_t) 0x3001UL, (duk_uint16_t) 0xFEFEUL,
+	(duk_uint16_t) 0xFF00UL, (duk_uint16_t) 0xFFFFUL,
+};
+duk_uint16_t duk_unicode_re_ranges_not_wordchar[10] = {
+	(duk_uint16_t) 0x0000UL, (duk_uint16_t) 0x002FUL,
+	(duk_uint16_t) 0x003AUL, (duk_uint16_t) 0x0040UL,
+	(duk_uint16_t) 0x005BUL, (duk_uint16_t) 0x005EUL,
+	(duk_uint16_t) 0x0060UL, (duk_uint16_t) 0x0060UL,
+	(duk_uint16_t) 0x007BUL, (duk_uint16_t) 0xFFFFUL,
+};
+
+#endif  /* DUK_USE_REGEXP_SUPPORT */
+
+#line 1 "duk_unicode_tables.c"
+/*
+ *  Unicode support tables automatically generated during build.
+ */
+
+/* include removed: duk_internal.h */
+
+/*
+ *  Unicode tables containing ranges of Unicode characters in a
+ *  packed format.  These tables are used to match non-ASCII
+ *  characters of complex productions by resorting to a linear
+ *  range-by-range comparison.  This is very slow, but is expected
+ *  to be very rare in practical Ecmascript source code, and thus
+ *  compactness is most important.
+ *
+ *  The tables are matched using uni_range_match() and the format
+ *  is described in src/extract_chars.py.
+ */
+
+#ifdef DUK_USE_SOURCE_NONBMP
+/* IdentifierStart production with ASCII excluded */
+/* duk_unicode_ids_noa[] */
+/*
+ *  Automatically generated by extract_chars.py, do not edit!
+ */
+
+const duk_uint8_t duk_unicode_ids_noa[797] = {
+249,176,176,80,111,7,47,15,47,254,11,197,191,0,72,2,15,115,66,19,57,2,34,2,
+240,66,244,50,247,185,248,234,241,99,8,241,127,58,240,182,47,31,241,191,21,
+18,245,50,15,1,24,27,35,15,2,2,240,239,15,244,156,15,10,241,26,21,6,240,
+101,10,4,15,9,240,159,157,242,100,15,4,8,159,1,98,102,115,19,240,98,98,4,
+52,15,2,14,18,47,0,31,5,85,19,240,98,98,18,18,31,17,50,15,5,47,2,130,34,
+240,98,98,18,68,15,4,15,1,31,21,115,19,240,98,98,18,68,15,16,18,47,1,15,3,
+2,84,34,52,18,2,20,20,36,191,8,15,38,114,34,240,114,146,68,15,12,23,31,21,
+114,34,240,114,146,68,15,18,2,31,1,31,4,114,34,241,147,15,2,15,3,31,10,86,
+240,36,240,130,130,3,111,44,242,2,29,111,44,18,3,18,3,7,50,98,34,2,3,18,50,
+26,3,66,15,7,31,20,15,49,114,241,79,13,79,101,241,191,6,15,2,85,52,4,24,37,
+205,15,3,241,107,241,178,4,255,224,59,35,54,32,35,63,25,35,63,17,35,54,32,
+35,62,47,41,35,63,51,241,127,0,240,47,69,223,254,21,227,240,18,240,166,243,
+180,47,1,194,63,0,240,47,0,240,47,0,194,47,1,242,79,21,5,15,53,244,137,241,
+146,6,243,107,240,223,37,240,227,76,241,207,7,111,42,240,122,242,95,68,15,
+79,241,255,3,111,41,240,238,31,2,241,111,12,241,79,27,43,241,79,93,50,63,0,
+251,15,50,255,224,8,53,63,22,53,55,32,32,32,47,15,63,37,38,32,66,38,67,53,
+92,98,38,246,96,224,240,44,245,112,80,57,32,68,112,32,32,35,42,51,100,80,
+240,63,25,255,233,107,241,242,241,242,247,87,63,3,241,107,242,106,15,2,240,
+122,98,98,98,98,98,98,98,111,66,15,254,12,146,240,184,132,52,95,70,114,47,
+74,35,111,25,79,78,240,63,11,242,127,0,255,224,244,15,255,0,8,168,15,60,15,
+255,0,64,190,15,38,255,227,127,243,95,30,63,253,79,0,177,240,111,31,240,47,
+9,159,64,241,152,63,87,51,33,240,9,244,39,34,35,47,7,240,255,36,240,15,34,
+243,5,64,240,15,12,191,7,240,191,13,143,31,240,224,242,47,25,240,146,39,
+240,111,7,64,111,32,32,65,52,48,32,240,162,241,85,53,53,166,38,248,63,19,
+240,240,255,240,1,169,96,223,7,95,33,255,240,0,255,143,254,2,3,242,227,245,
+175,24,109,70,2,146,194,66,2,18,18,245,207,19,255,224,93,240,79,48,63,38,
+241,171,246,100,47,119,241,111,10,127,10,207,73,69,53,53,50,241,91,47,10,
+47,3,33,46,61,241,79,107,243,127,37,255,223,13,79,33,242,31,15,240,63,11,
+242,127,14,63,20,87,36,241,207,142,255,226,86,83,2,241,194,20,3,240,127,
+156,240,107,240,175,184,15,1,50,34,240,191,30,240,223,117,242,107,240,107,
+240,63,127,243,159,254,42,239,37,243,223,29,255,238,68,255,226,97,248,63,
+83,255,234,145,255,227,33,255,240,2,44,95,254,18,191,255,0,52,187,31,255,0,
+18,242,244,82,243,114,19,3,19,50,178,2,98,243,18,51,114,98,240,194,50,66,4,
+98,255,224,70,63,9,47,9,47,15,47,9,47,15,47,9,47,15,47,9,47,15,47,9,39,255,
+240,1,114,128,255,240,9,92,144,241,176,255,239,39,12,15,206,15,255,0,46,
+214,255,225,16,0,
+};
+#else
+/* IdentifierStart production with ASCII and non-BMP excluded */
+/* duk_unicode_ids_noabmp[] */
+/*
+ *  Automatically generated by extract_chars.py, do not edit!
+ */
+
+const duk_uint8_t duk_unicode_ids_noabmp[614] = {
+249,176,176,80,111,7,47,15,47,254,11,197,191,0,72,2,15,115,66,19,57,2,34,2,
+240,66,244,50,247,185,248,234,241,99,8,241,127,58,240,182,47,31,241,191,21,
+18,245,50,15,1,24,27,35,15,2,2,240,239,15,244,156,15,10,241,26,21,6,240,
+101,10,4,15,9,240,159,157,242,100,15,4,8,159,1,98,102,115,19,240,98,98,4,
+52,15,2,14,18,47,0,31,5,85,19,240,98,98,18,18,31,17,50,15,5,47,2,130,34,
+240,98,98,18,68,15,4,15,1,31,21,115,19,240,98,98,18,68,15,16,18,47,1,15,3,
+2,84,34,52,18,2,20,20,36,191,8,15,38,114,34,240,114,146,68,15,12,23,31,21,
+114,34,240,114,146,68,15,18,2,31,1,31,4,114,34,241,147,15,2,15,3,31,10,86,
+240,36,240,130,130,3,111,44,242,2,29,111,44,18,3,18,3,7,50,98,34,2,3,18,50,
+26,3,66,15,7,31,20,15,49,114,241,79,13,79,101,241,191,6,15,2,85,52,4,24,37,
+205,15,3,241,107,241,178,4,255,224,59,35,54,32,35,63,25,35,63,17,35,54,32,
+35,62,47,41,35,63,51,241,127,0,240,47,69,223,254,21,227,240,18,240,166,243,
+180,47,1,194,63,0,240,47,0,240,47,0,194,47,1,242,79,21,5,15,53,244,137,241,
+146,6,243,107,240,223,37,240,227,76,241,207,7,111,42,240,122,242,95,68,15,
+79,241,255,3,111,41,240,238,31,2,241,111,12,241,79,27,43,241,79,93,50,63,0,
+251,15,50,255,224,8,53,63,22,53,55,32,32,32,47,15,63,37,38,32,66,38,67,53,
+92,98,38,246,96,224,240,44,245,112,80,57,32,68,112,32,32,35,42,51,100,80,
+240,63,25,255,233,107,241,242,241,242,247,87,63,3,241,107,242,106,15,2,240,
+122,98,98,98,98,98,98,98,111,66,15,254,12,146,240,184,132,52,95,70,114,47,
+74,35,111,25,79,78,240,63,11,242,127,0,255,224,244,15,255,0,8,168,15,60,15,
+255,0,64,190,15,38,255,227,127,243,95,30,63,253,79,0,177,240,111,31,240,47,
+9,159,64,241,152,63,87,51,33,240,9,244,39,34,35,47,7,240,255,36,240,15,34,
+243,5,64,240,15,12,191,7,240,191,13,143,31,240,224,242,47,25,240,146,39,
+240,111,7,64,111,32,32,65,52,48,32,240,162,241,85,53,53,166,38,248,63,19,
+240,240,255,240,1,169,96,223,7,95,33,255,240,0,255,143,254,2,3,242,227,245,
+175,24,109,70,2,146,194,66,2,18,18,245,207,19,255,224,93,240,79,48,63,38,
+241,171,246,100,47,119,241,111,10,127,10,207,73,69,53,53,50,0,
+};
+#endif
+
+#ifdef DUK_USE_SOURCE_NONBMP
+/* IdentifierStart production with Letter and ASCII excluded */
+/* duk_unicode_ids_m_let_noa[] */
+/*
+ *  Automatically generated by extract_chars.py, do not edit!
+ */
+
+const duk_uint8_t duk_unicode_ids_m_let_noa[42] = {
+255,240,0,94,18,255,233,99,241,51,63,254,215,32,240,184,240,2,255,240,6,89,
+249,255,240,4,148,79,37,255,224,192,9,15,120,79,255,0,15,30,245,48,
+};
+#else
+/* IdentifierStart production with Letter, ASCII, and non-BMP excluded */
+/* duk_unicode_ids_m_let_noabmp[] */
+/*
+ *  Automatically generated by extract_chars.py, do not edit!
+ */
+
+const duk_uint8_t duk_unicode_ids_m_let_noabmp[24] = {
+255,240,0,94,18,255,233,99,241,51,63,254,215,32,240,184,240,2,255,240,6,89,
+249,0,
+};
+#endif
+
+#ifdef DUK_USE_SOURCE_NONBMP
+/* IdentifierPart production with IdentifierStart and ASCII excluded */
+/* duk_unicode_idp_m_ids_noa[] */
+/*
+ *  Automatically generated by extract_chars.py, do not edit!
+ */
+
+const duk_uint8_t duk_unicode_idp_m_ids_noa[397] = {
+255,225,243,246,15,254,0,116,255,191,29,32,33,33,32,243,170,242,47,15,112,
+245,118,53,49,35,57,240,144,241,15,11,244,218,240,25,241,56,241,67,40,34,
+36,241,210,249,99,242,130,47,2,38,177,57,240,50,242,160,38,49,50,160,177,
+57,240,50,242,160,36,81,50,64,240,107,64,194,242,160,39,34,34,240,97,57,
+240,50,242,160,38,49,50,145,177,57,240,64,242,212,66,35,160,240,9,240,50,
+242,198,34,35,129,193,57,240,65,242,160,38,34,35,129,193,57,240,65,242,198,
+34,35,160,177,57,240,65,243,128,85,32,39,240,65,242,240,54,215,41,244,144,
+53,33,197,57,243,1,121,192,32,32,81,242,63,4,33,106,47,20,160,245,111,4,41,
+211,82,34,54,67,235,46,255,225,179,47,254,42,98,240,242,240,241,241,1,243,
+79,14,160,57,241,50,57,248,16,246,139,91,185,245,47,1,129,121,242,244,242,
+185,47,13,58,121,245,132,242,31,1,201,240,56,210,241,9,105,241,237,242,47,
+4,153,121,246,130,47,5,80,80,251,255,23,240,115,255,225,0,31,35,31,5,15,
+109,197,4,191,254,175,34,247,240,245,47,16,255,225,30,95,91,31,255,0,100,
+121,159,55,13,31,100,31,254,0,64,64,80,240,148,244,161,242,79,1,201,127,2,
+240,9,240,231,240,188,241,227,242,29,240,25,244,29,208,145,57,241,48,242,
+96,34,49,97,32,255,224,21,114,19,159,255,0,62,24,15,254,29,95,0,240,38,209,
+240,162,251,41,241,112,255,225,177,15,254,25,105,255,228,75,34,22,63,26,37,
+15,254,75,66,242,126,241,25,240,34,241,250,255,240,10,249,228,69,151,54,
+241,3,248,98,255,228,125,242,47,255,12,23,244,254,0,
+};
+#else
+/* IdentifierPart production with IdentifierStart, ASCII, and non-BMP excluded */
+/* duk_unicode_idp_m_ids_noabmp[] */
+/*
+ *  Automatically generated by extract_chars.py, do not edit!
+ */
+
+const duk_uint8_t duk_unicode_idp_m_ids_noabmp[348] = {
+255,225,243,246,15,254,0,116,255,191,29,32,33,33,32,243,170,242,47,15,112,
+245,118,53,49,35,57,240,144,241,15,11,244,218,240,25,241,56,241,67,40,34,
+36,241,210,249,99,242,130,47,2,38,177,57,240,50,242,160,38,49,50,160,177,
+57,240,50,242,160,36,81,50,64,240,107,64,194,242,160,39,34,34,240,97,57,
+240,50,242,160,38,49,50,145,177,57,240,64,242,212,66,35,160,240,9,240,50,
+242,198,34,35,129,193,57,240,65,242,160,38,34,35,129,193,57,240,65,242,198,
+34,35,160,177,57,240,65,243,128,85,32,39,240,65,242,240,54,215,41,244,144,
+53,33,197,57,243,1,121,192,32,32,81,242,63,4,33,106,47,20,160,245,111,4,41,
+211,82,34,54,67,235,46,255,225,179,47,254,42,98,240,242,240,241,241,1,243,
+79,14,160,57,241,50,57,248,16,246,139,91,185,245,47,1,129,121,242,244,242,
+185,47,13,58,121,245,132,242,31,1,201,240,56,210,241,9,105,241,237,242,47,
+4,153,121,246,130,47,5,80,80,251,255,23,240,115,255,225,0,31,35,31,5,15,
+109,197,4,191,254,175,34,247,240,245,47,16,255,225,30,95,91,31,255,0,100,
+121,159,55,13,31,100,31,254,0,64,64,80,240,148,244,161,242,79,1,201,127,2,
+240,9,240,231,240,188,241,227,242,29,240,25,244,29,208,145,57,241,48,242,
+96,34,49,97,32,255,224,21,114,19,159,255,0,62,24,15,254,29,95,0,240,38,209,
+240,162,251,41,241,112,0,
+};
+#endif
+
+/*
+ *  Case conversion tables generated using src/extract_caseconv.py.
+ */
+
+/* duk_unicode_caseconv_uc[] */
+/* duk_unicode_caseconv_lc[] */
+
+/*
+ *  Automatically generated by extract_caseconv.py, do not edit!
+ */
+
+const duk_uint8_t duk_unicode_caseconv_uc[1288] = {
+132,3,128,3,0,184,7,192,6,192,112,35,242,199,224,64,74,192,49,32,128,162,
+128,108,65,1,189,129,254,131,3,173,3,136,6,7,98,7,34,68,15,12,14,140,72,30,
+104,28,112,32,67,0,65,4,0,138,0,128,4,1,88,65,76,83,15,128,15,132,8,31,16,
+31,24,12,62,64,62,80,32,124,192,124,224,64,250,0,250,64,97,246,1,246,129,3,
+238,3,247,64,135,220,135,242,2,15,187,15,237,2,31,120,31,248,4,62,244,63,
+212,8,125,240,127,232,16,253,128,253,192,33,253,1,253,128,67,252,3,253,0,
+136,92,8,88,8,18,104,18,91,26,44,48,44,0,94,90,0,33,64,155,253,7,252,132,
+212,0,32,32,32,6,0,76,192,76,129,128,157,0,156,136,1,75,1,74,46,2,244,2,
+242,12,6,12,6,8,16,13,8,13,0,48,27,64,27,48,64,57,192,57,162,0,119,192,119,
+132,128,252,128,252,20,2,35,2,34,18,4,142,4,140,20,13,196,13,192,16,30,200,
+30,192,192,70,16,70,2,32,145,96,145,70,193,48,129,48,67,130,104,130,104,44,
+30,1,30,0,150,61,66,61,64,192,125,68,125,100,33,99,65,99,56,50,200,18,200,
+6,69,157,133,157,96,169,144,105,144,11,211,64,211,64,12,167,35,167,34,15,
+78,103,78,100,126,157,234,157,228,21,59,253,59,240,90,122,26,122,0,163,128,
+214,128,214,2,1,197,1,196,6,3,140,3,136,12,7,200,7,196,16,20,0,13,48,32,63,
+128,63,112,69,142,101,142,64,130,1,136,1,135,4,3,114,3,112,8,26,120,202,
+120,176,65,1,30,1,29,130,2,105,1,150,5,255,96,22,160,115,128,31,224,47,0,
+38,32,9,32,47,224,10,96,48,0,72,96,50,64,50,32,50,160,62,192,51,32,51,0,51,
+64,71,160,51,192,68,0,53,0,52,224,55,224,62,224,59,160,49,192,62,96,62,32,
+74,5,141,224,74,37,141,160,74,69,142,0,74,96,48,32,74,128,48,192,75,32,49,
+224,75,96,50,0,76,0,50,96,76,96,50,128,76,180,241,160,77,0,50,224,77,101,
+140,64,78,37,141,192,78,64,51,160,78,160,51,224,79,165,140,128,81,0,53,192,
+81,32,72,128,81,128,72,160,82,64,54,224,104,160,115,32,110,224,110,192,117,
+128,112,192,120,64,116,96,121,128,113,128,122,0,114,64,122,32,115,0,122,
+160,116,192,122,192,116,0,122,224,121,224,126,0,115,64,126,32,116,32,126,
+64,127,32,126,160,114,160,153,224,152,3,175,52,239,163,175,165,140,99,211,
+99,204,3,247,192,115,35,252,163,253,132,41,196,38,68,48,132,48,101,140,37,
+140,5,140,160,71,69,140,192,71,217,128,55,224,5,48,5,48,20,152,10,240,1,56,
+7,194,0,74,3,12,3,144,192,230,64,194,0,192,64,236,48,58,80,48,128,48,16,88,
+120,20,212,21,72,122,90,0,72,3,49,30,151,128,21,0,194,7,166,32,5,112,48,
+161,233,152,1,100,12,40,122,106,0,65,2,190,31,80,128,233,64,196,199,212,
+176,58,80,49,48,48,1,245,76,14,148,12,76,12,4,125,91,3,165,3,19,3,66,31,
+128,135,194,0,230,71,224,97,240,144,57,145,248,40,124,40,14,100,126,14,31,
+11,3,153,31,132,135,195,0,230,71,225,97,240,208,57,145,248,104,124,56,14,
+100,126,30,31,15,3,153,31,136,135,194,0,230,71,226,97,240,144,57,145,248,
+168,124,40,14,100,126,46,31,11,3,153,31,140,135,195,0,230,71,227,97,240,
+208,57,145,248,232,124,56,14,100,126,62,31,15,3,153,31,144,135,202,0,230,
+71,228,97,242,144,57,145,249,40,124,168,14,100,126,78,31,43,3,153,31,148,
+135,203,0,230,71,229,97,242,208,57,145,249,104,124,184,14,100,126,94,31,47,
+3,153,31,152,135,202,0,230,71,230,97,242,144,57,145,249,168,124,168,14,100,
+126,110,31,43,3,153,31,156,135,203,0,230,71,231,97,242,208,57,145,249,232,
+124,184,14,100,126,126,31,47,3,153,31,160,135,218,0,230,71,232,97,246,144,
+57,145,250,40,125,168,14,100,126,142,31,107,3,153,31,164,135,219,0,230,71,
+233,97,246,208,57,145,250,104,125,184,14,100,126,158,31,111,3,153,31,168,
+135,218,0,230,71,234,97,246,144,57,145,250,168,125,168,14,100,126,174,31,
+107,3,153,31,172,135,219,0,230,71,235,97,246,208,57,145,250,232,125,184,14,
+100,126,190,31,111,3,153,31,178,135,238,128,230,71,236,224,57,16,57,145,
+251,72,14,24,14,100,126,218,3,145,3,66,31,183,192,228,64,208,128,230,71,
+239,32,57,16,57,145,252,40,127,40,14,100,127,14,3,151,3,153,31,196,128,226,
+64,230,71,241,160,57,112,52,33,252,124,14,92,13,8,14,100,127,50,3,151,3,
+153,31,210,192,230,64,194,0,192,7,244,240,57,144,48,128,48,17,253,104,14,
+100,13,8,127,95,3,153,3,8,3,66,31,226,192,233,64,194,0,192,7,248,240,58,80,
+48,128,48,17,254,72,14,132,12,76,127,154,3,165,3,66,31,231,192,233,64,194,
+0,208,135,252,161,255,160,57,145,255,56,14,164,14,100,127,210,3,143,3,153,
+31,246,128,234,64,208,135,253,240,58,144,52,32,57,145,255,200,14,164,14,
+103,236,2,0,70,0,70,251,1,128,17,128,18,126,192,160,4,96,4,207,176,60,1,24,
+1,24,1,39,236,19,0,70,0,70,0,76,251,5,128,20,192,21,62,193,160,5,48,5,79,
+177,56,21,16,21,27,236,82,5,68,5,53,251,21,129,81,1,78,254,197,160,84,224,
+84,111,177,120,21,16,20,244,
+};
+const duk_uint8_t duk_unicode_caseconv_lc[616] = {
+144,3,0,3,128,184,6,192,7,192,112,24,144,37,96,64,54,32,81,64,128,226,0,
+235,65,129,199,1,230,130,3,145,3,177,34,7,70,7,134,36,15,244,13,236,24,32,
+0,34,129,0,65,0,67,4,0,166,32,172,41,132,40,11,64,19,15,132,15,128,8,31,24,
+31,16,12,62,80,62,64,32,124,224,124,192,64,250,64,250,0,97,246,129,246,1,3,
+241,3,240,2,7,230,7,228,4,15,212,15,208,8,31,184,31,176,4,63,116,62,224,8,
+127,32,125,200,32,254,192,254,128,33,253,161,247,96,67,253,3,252,0,135,250,
+135,222,129,15,252,15,188,2,31,250,31,124,4,66,192,66,224,64,146,216,147,
+64,209,96,1,97,130,242,199,224,35,240,95,228,63,232,38,161,1,0,1,1,48,2,
+100,2,102,12,4,228,4,232,64,10,80,10,89,112,23,144,23,160,96,48,64,48,96,
+128,104,0,104,65,128,217,128,218,2,1,203,1,204,18,3,188,3,190,36,7,200,7,
+204,16,15,192,15,201,64,34,32,34,49,32,72,192,72,225,64,220,0,220,65,1,236,
+1,236,140,4,96,4,97,34,9,20,9,22,108,19,4,19,8,56,38,128,38,138,193,224,1,
+224,25,99,212,3,212,44,7,214,71,212,66,22,51,150,52,3,44,128,44,129,100,89,
+214,89,216,10,153,2,153,4,189,52,5,52,8,202,114,42,114,48,244,230,84,230,
+103,233,222,105,222,129,83,191,83,191,133,167,160,167,161,10,48,13,48,20,0,
+32,26,192,26,208,64,56,128,56,192,192,113,64,113,129,1,251,129,252,2,44,
+114,44,115,4,16,12,56,12,64,32,27,128,27,144,64,211,197,211,198,2,8,6,88,9,
+164,16,17,216,17,224,47,245,1,120,0,255,1,129,2,83,1,134,2,84,1,142,1,221,
+1,143,2,89,1,144,2,91,1,145,1,146,1,147,2,96,1,148,2,99,1,151,2,104,1,152,
+1,153,1,157,2,114,1,159,2,117,1,167,1,168,1,174,2,136,1,183,2,146,1,241,1,
+243,1,246,1,149,1,247,1,191,2,32,1,158,2,58,44,101,2,61,1,154,2,62,44,102,
+2,67,1,128,2,68,2,137,2,69,2,140,3,118,3,119,3,134,3,172,3,140,3,204,3,207,
+3,215,3,244,3,184,3,249,3,242,4,192,4,207,30,158,0,223,31,188,31,179,31,
+204,31,195,31,236,31,229,31,252,31,243,33,38,3,201,33,42,0,107,33,43,0,229,
+33,50,33,78,33,131,33,132,44,96,44,97,44,98,2,107,44,99,29,125,44,100,2,
+125,44,109,2,81,44,110,2,113,44,111,2,80,44,112,2,82,167,125,29,121,167,
+141,2,101,2,2,97,0,52,129,131,128,
+};
+
+#line 1 "duk_util_bitdecoder.c"
+/*
+ *  Bitstream decoder.
+ */
+
+/* include removed: duk_internal.h */
+
+/* Decode 'bits' bits from the input stream (bits must be 1...24).
+ * When reading past bitstream end, zeroes are shifted in.  The result
+ * is signed to match duk_bd_decode_flagged.
+ */
+duk_int32_t duk_bd_decode(duk_bitdecoder_ctx *ctx, duk_small_int_t bits) {
+	duk_small_int_t shift;
+	duk_uint32_t mask;
+	duk_uint32_t tmp;
+
+	/* Note: cannot read more than 24 bits without possibly shifting top bits out.
+	 * Fixable, but adds complexity.
+	 */
+	DUK_ASSERT(bits >= 1 && bits <= 24);
+
+	while (ctx->currbits < bits) {
+#if 0
+		DUK_DDD(DUK_DDDPRINT("decode_bits: shift more data (bits=%ld, currbits=%ld)",
+		                     (long) bits, (long) ctx->currbits));
+#endif
+		ctx->currval <<= 8;
+		if (ctx->offset < ctx->length) {
+			/* If ctx->offset >= ctx->length, we "shift zeroes in"
+			 * instead of croaking.
+			 */
+			ctx->currval |= ctx->data[ctx->offset++];
+		}
+		ctx->currbits += 8;
+	}
+#if 0
+	DUK_DDD(DUK_DDDPRINT("decode_bits: bits=%ld, currbits=%ld, currval=0x%08lx",
+	                     (long) bits, (long) ctx->currbits, (unsigned long) ctx->currval));
+#endif
+
+	/* Extract 'top' bits of currval; note that the extracted bits do not need
+	 * to be cleared, we just ignore them on next round.
+	 */
+	shift = ctx->currbits - bits;
+	mask = (1 << bits) - 1;
+	tmp = (ctx->currval >> shift) & mask;
+	ctx->currbits = shift;  /* remaining */
+
+#if 0
+	DUK_DDD(DUK_DDDPRINT("decode_bits: %ld bits -> 0x%08lx (%ld), currbits=%ld, currval=0x%08lx",
+	                     (long) bits, (unsigned long) tmp, (long) tmp, (long) ctx->currbits, (unsigned long) ctx->currval));
+#endif
+
+	return tmp;
+}
+
+duk_small_int_t duk_bd_decode_flag(duk_bitdecoder_ctx *ctx) {
+	return (duk_small_int_t) duk_bd_decode(ctx, 1);
+}
+
+/* Decode a one-bit flag, and if set, decode a value of 'bits', otherwise return
+ * default value.  Return value is signed so that negative marker value can be
+ * used by caller as a "not present" value.
+ */
+duk_int32_t duk_bd_decode_flagged(duk_bitdecoder_ctx *ctx, duk_small_int_t bits, duk_int32_t def_value) {
+	if (duk_bd_decode_flag(ctx)) {
+		return (duk_int32_t) duk_bd_decode(ctx, bits);
+	} else {
+		return def_value;
+	}
+}
+
+#line 1 "duk_util_bitencoder.c"
+/*
+ *  Bitstream encoder.
+ */
+
+/* include removed: duk_internal.h */
+
+void duk_be_encode(duk_bitencoder_ctx *ctx, duk_uint32_t data, duk_small_int_t bits) {
+	duk_uint8_t tmp;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(ctx->currbits < 8);
+
+	/* This limitation would be fixable but adds unnecessary complexity. */
+	DUK_ASSERT(bits >= 1 && bits <= 24);
+
+	ctx->currval = (ctx->currval << bits) | data;
+	ctx->currbits += bits;
+
+	while (ctx->currbits >= 8) {
+		if (ctx->offset < ctx->length) {
+			tmp = (duk_uint8_t) ((ctx->currval >> (ctx->currbits - 8)) & 0xff);
+			ctx->data[ctx->offset++] = tmp;
+		} else {
+			/* If buffer has been exhausted, truncate bitstream */
+			ctx->truncated = 1;
+		}
+
+		ctx->currbits -= 8;
+	}
+}
+
+void duk_be_finish(duk_bitencoder_ctx *ctx) {
+	duk_small_int_t npad;
+
+	DUK_ASSERT(ctx != NULL);
+	DUK_ASSERT(ctx->currbits < 8);
+
+	npad = (duk_small_int_t) (8 - ctx->currbits);
+	if (npad > 0) {
+		duk_be_encode(ctx, 0, npad);
+	}
+	DUK_ASSERT(ctx->currbits == 0);
+}
+
+#line 1 "duk_util_hashbytes.c"
+/*
+ *  Hash function duk_util_hashbytes().
+ *
+ *  Currently, 32-bit MurmurHash2.
+ *
+ *  Don't rely on specific hash values; hash function may be endianness
+ *  dependent, for instance.
+ */
+
+/* include removed: duk_internal.h */
+
+/* 'magic' constants for Murmurhash2 */
+#define DUK__MAGIC_M  ((duk_uint32_t) 0x5bd1e995UL)
+#define DUK__MAGIC_R  24
+
+duk_uint32_t duk_util_hashbytes(duk_uint8_t *data, duk_size_t len, duk_uint32_t seed) {
+	duk_uint32_t h = seed ^ len;
+
+	while (len >= 4) {
+		/* Portability workaround is required for platforms without
+		 * unaligned access.  The replacement code emulates little
+		 * endian access even on big endian architectures, which is
+		 * OK as long as it is consistent for a build.
+		 */
+#ifdef DUK_USE_HASHBYTES_UNALIGNED_U32_ACCESS
+		duk_uint32_t k = *((duk_uint32_t *) data);
+#else
+		duk_uint32_t k = ((duk_uint32_t) data[0]) |
+		                 (((duk_uint32_t) data[1]) << 8) |
+		                 (((duk_uint32_t) data[2]) << 16) |
+		                 (((duk_uint32_t) data[3]) << 24);
+#endif
+
+		k *= DUK__MAGIC_M;
+		k ^= k >> DUK__MAGIC_R;
+		k *= DUK__MAGIC_M;
+		h *= DUK__MAGIC_M;
+		h ^= k;
+		data += 4;
+		len -= 4;
+	}
+
+	switch (len) {
+		case 3:	h ^= data[2] << 16;
+		case 2:	h ^= data[1] << 8;
+		case 1:	h ^= data[0];
+			h *= DUK__MAGIC_M;
+        }
+
+	h ^= h >> 13;
+	h *= DUK__MAGIC_M;
+	h ^= h >> 15;
+
+	return h;
+}
+
+#line 1 "duk_util_hashprime.c"
+/*
+ *  Round a number upwards to a prime (not usually the nearest one).
+ *
+ *  Uses a table of successive 32-bit primes whose ratio is roughly
+ *  constant.  This keeps the relative upwards 'rounding error' bounded
+ *  and the data size small.  A simple 'predict-correct' compression is
+ *  used to compress primes to one byte per prime.  See genhashsizes.py
+ *  for details.
+ *
+ *  The minimum prime returned here must be coordinated with the possible
+ *  probe sequence steps in duk_hobject and duk_heap stringtable.
+ */
+
+/* include removed: duk_internal.h */
+
+/* hash size ratio goal, must match genhashsizes.py */
+#define DUK__HASH_SIZE_RATIO   1177  /* floor(1.15 * (1 << 10)) */
+
+/* prediction corrections for prime list (see genhashsizes.py) */
+static const duk_int8_t duk__hash_size_corrections[] = {
+	17,  /* minimum prime */
+	4, 3, 4, 1, 4, 1, 1, 2, 2, 2, 2, 1, 6, 6, 9, 5, 1, 2, 2, 5, 1, 3, 3, 3,
+	5, 4, 4, 2, 4, 8, 3, 4, 23, 2, 4, 7, 8, 11, 2, 12, 15, 10, 1, 1, 5, 1, 5,
+	8, 9, 17, 14, 10, 7, 5, 2, 46, 21, 1, 9, 9, 4, 4, 10, 23, 36, 6, 20, 29,
+	18, 6, 19, 21, 16, 11, 5, 5, 48, 9, 1, 39, 14, 8, 4, 29, 9, 1, 15, 48, 12,
+	22, 6, 15, 27, 4, 2, 17, 28, 8, 9, 4, 5, 8, 3, 3, 8, 37, 11, 15, 8, 30,
+	43, 6, 33, 41, 5, 20, 32, 41, 38, 24, 77, 14, 19, 11, 4, 35, 18, 19, 41,
+	10, 23, 16, 9, 2,
+	-1
+};
+
+/* probe steps (see genhashsizes.py), currently assumed to be 32 entries long
+ * (DUK_UTIL_GET_HASH_PROBE_STEP macro).
+ */
+duk_uint8_t duk_util_probe_steps[32] = {
+	2, 3, 5, 7, 11, 13, 19, 31, 41, 47, 59, 67, 73, 79, 89, 101, 103, 107,
+	109, 127, 137, 139, 149, 157, 163, 167, 173, 181, 191, 193, 197, 199
+};
+
+duk_uint32_t duk_util_get_hash_prime(duk_uint32_t size) {
+	const duk_int8_t *p = duk__hash_size_corrections;
+	duk_uint32_t curr;
+
+	curr = (duk_uint32_t) *p++;
+	for (;;) {
+		duk_small_int_t t = (duk_small_int_t) *p++;
+		if (t < 0) {
+			/* may happen if size is very close to 2^32-1 */
+			break;
+		}
+
+		/* prediction: portable variant using doubles if 64-bit values not available */
+#ifdef DUK_USE_64BIT_OPS
+		curr = (duk_uint32_t) ((((duk_uint64_t) curr) * ((duk_uint64_t) DUK__HASH_SIZE_RATIO)) >> 10);
+#else
+		/* 32-bit x 11-bit = 43-bit, fits accurately into a double */
+		curr = (duk_uint32_t) DUK_FLOOR(((double) curr) * ((double) DUK__HASH_SIZE_RATIO) / 1024.0);
+#endif
+
+		/* correction */
+		curr += t;
+
+		DUK_DDD(DUK_DDDPRINT("size=%ld, curr=%ld", (long) size, (long) curr));
+
+		if (curr >= size) {
+			return curr;
+		}
+	}
+	return 0;
+}
+
+#line 1 "duk_util_misc.c"
+/*
+ *  Misc util stuff
+ */
+
+/* include removed: duk_internal.h */
+
+/*
+ *  Lowercase digits for radix values 2 to 36.  Also doubles as lowercase
+ *  hex nybble table.
+ */
+
+duk_uint8_t duk_lc_digits[36] = { '0', '1', '2', '3', '4', '5', '6', '7',
+                                  '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
+                                  'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
+                                  'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+                                  'w', 'x', 'y', 'z' };
+
+duk_uint8_t duk_uc_nybbles[16] = { '0', '1', '2', '3', '4', '5', '6', '7',
+                                   '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+
+/*
+ *  Table for decoding ASCII hex digits, -1 if invalid.
+ */
+
+duk_int8_t duk_hex_dectab[256] = {
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 0x00-0x0f */
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 0x10-0x1f */
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 0x20-0x2f */
+	 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, -1, -1, -1, -1, -1, -1,  /* 0x30-0x3f */
+	-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 0x40-0x4f */
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 0x50-0x5f */
+	-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 0x60-0x6f */
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 0x70-0x7f */
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 0x80-0x8f */
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 0x90-0x9f */
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 0xa0-0xaf */
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 0xb0-0xbf */
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 0xc0-0xcf */
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 0xd0-0xdf */
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 0xe0-0xef */
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1   /* 0xf0-0xff */
+};
+#line 1 "duk_util_tinyrandom.c"
+/*
+ *  A tiny random number generator.
+ *
+ *  Currently used for Math.random().
+ *
+ *  http://www.woodmann.com/forum/archive/index.php/t-3100.html
+ */
+
+/* include removed: duk_internal.h */
+
+#define DUK__UPDATE_RND(rnd) do { \
+		(rnd) += ((rnd) * (rnd)) | 0x05; \
+		(rnd) = ((rnd) & 0xffffffffU);       /* if duk_uint32_t is exactly 32 bits, this is a NOP */ \
+	} while (0)
+
+#define DUK__RND_BIT(rnd)  ((rnd) >> 31)  /* only use the highest bit */
+
+duk_uint32_t duk_util_tinyrandom_get_bits(duk_hthread *thr, duk_small_int_t n) {
+	duk_small_int_t i;
+	duk_uint32_t res = 0;
+	duk_uint32_t rnd;
+
+	rnd = thr->heap->rnd_state;
+
+	for (i = 0; i < n; i++) {
+		DUK__UPDATE_RND(rnd);
+		res <<= 1;
+		res += DUK__RND_BIT(rnd);
+	}
+
+	thr->heap->rnd_state = rnd;
+
+	return res;
+}
+
+duk_double_t duk_util_tinyrandom_get_double(duk_hthread *thr) {
+	duk_double_t t;
+	duk_small_int_t n;
+	duk_uint32_t rnd;
+
+	/*
+	 *  XXX: could make this a lot faster if we create the double memory
+	 *  representation directly.  Feasible easily (must be uniform random).
+	 */
+
+	rnd = thr->heap->rnd_state;
+
+	n = 53;  /* enough to cover the whole mantissa */
+	t = 0.0;
+
+	do {
+		DUK__UPDATE_RND(rnd);
+		t += DUK__RND_BIT(rnd);
+		t /= 2.0;
+	} while (--n);
+
+	thr->heap->rnd_state = rnd;
+
+	DUK_ASSERT(t >= (duk_double_t) 0.0);
+	DUK_ASSERT(t < (duk_double_t) 1.0);
+
+	return t;
+}
+
diff --git a/src/osgEarthDrivers/script_engine_duktape/duktape.h b/src/osgEarthDrivers/script_engine_duktape/duktape.h
new file mode 100644
index 0000000..717319e
--- /dev/null
+++ b/src/osgEarthDrivers/script_engine_duktape/duktape.h
@@ -0,0 +1,3668 @@
+/*
+ *  Duktape public API for Duktape 0.11.0.
+ *  See the API reference for documentation on call semantics.
+ *  The exposed API is inside the DUK_API_PUBLIC_H_INCLUDED
+ *  include guard.  Other parts of the header are Duktape
+ *  internal and related to platform/compiler/feature detection.
+ *
+ *  Git commit 621b545c474dd7a3def7aefed0557b1a825a4578 (v0.10.0-827-g621b545).
+ *
+ *  See Duktape AUTHORS.txt and LICENSE.txt for copyright and
+ *  licensing information.
+ */
+
+/* LICENSE.txt */
+/*
+ *  ===============
+ *  Duktape license
+ *  ===============
+ *  
+ *  (http://opensource.org/licenses/MIT)
+ *  
+ *  Copyright (c) 2013-2014 by Duktape authors (see AUTHORS.txt)
+ *  
+ *  Permission is hereby granted, free of charge, to any person obtaining a copy
+ *  of this software and associated documentation files (the "Software"), to deal
+ *  in the Software without restriction, including without limitation the rights
+ *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ *  copies of the Software, and to permit persons to whom the Software is
+ *  furnished to do so, subject to the following conditions:
+ *  
+ *  The above copyright notice and this permission notice shall be included in
+ *  all copies or substantial portions of the Software.
+ *  
+ *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ *  THE SOFTWARE.
+ *  
+ */
+
+/* AUTHORS.txt */
+/*
+ *  ===============
+ *  Duktape authors
+ *  ===============
+ *  
+ *  Copyright
+ *  =========
+ *  
+ *  Duktape copyrights are held by its authors.  Each author has a copyright
+ *  to their contribution, and agrees to irrevocably license the contribution
+ *  under the Duktape ``LICENSE.txt``.
+ *  
+ *  Authors
+ *  =======
+ *  
+ *  Please include an e-mail address, a link to your GitHub profile, or something
+ *  similar to allow your contribution to be identified accurately.
+ *  
+ *  The following people have contributed code and agreed to irrevocably license
+ *  their contributions under the Duktape ``LICENSE.txt`` (in order of appearance):
+ *  
+ *  * Sami Vaarala <sami.vaarala at iki.fi>
+ *  * Niki Dobrev
+ *  * Andreas \u00d6man <andreas at lonelycoder.com>
+ *  
+ *  Other contributions
+ *  ===================
+ *  
+ *  The following people have contributed something other than code (e.g. reported
+ *  bugs, provided ideas, etc; in order of appearance):
+ *  
+ *  * Greg Burns
+ *  * Anthony Rabine
+ *  * Carlos Costa
+ *  * Aur\u00e9lien Bouilland
+ *  * Preet Desai (Pris Matic)
+ *  * judofyr (http://www.reddit.com/user/judofyr)
+ *  * Jason Woofenden
+ *  * Micha\u0142 Przyby\u015b
+ *  * Anthony Howe
+ *  * Conrad Pankoff
+ *  * Jim Schimpf
+ *  * Rajaran Gaunker (https://github.com/zimbabao)
+ *  * Andreas \u00d6man
+ *  * Doug Sanden
+ *  * Remo Eichenberger (https://github.com/remoe)
+ */
+
+#ifndef DUKTAPE_H_INCLUDED
+#define DUKTAPE_H_INCLUDED
+
+/*
+ *  Determine platform features, select feature selection defines
+ *  (e.g. _XOPEN_SOURCE), include system headers, and define DUK_USE_XXX
+ *  defines which are (only) checked in Duktape internal code for
+ *  activated features.  Duktape feature selection is based on automatic
+ *  feature detection, user supplied DUK_OPT_xxx defines, and optionally
+ *  a "duk_custom.h" user header (if DUK_OPT_HAVE_CUSTOM_H is defined).
+ *
+ *  When compiling Duktape, DUK_COMPILING_DUKTAPE is set, and this file
+ *  is included before any system headers are included.  Feature selection
+ *  defines (e.g. _XOPEN_SOURCE) are defined here before any system headers
+ *  are included (which is a requirement for system headers to work correctly).
+ *  This file is responsible for including all system headers and contains
+ *  all platform dependent cruft in general.  When compiling user code,
+ *  DUK_COMPILING_DUKTAPE is not defined, and we must avoid e.g. defining
+ *  unnecessary feature selection defines.
+ *
+ *  The general order of handling:
+ *    - Compiler feature detection (require no includes)
+ *    - Intermediate platform detection (-> easier platform defines)
+ *    - Platform detection, system includes, byte order detection, etc
+ *    - ANSI C wrappers (e.g. DUK_MEMCMP), wrappers for constants, etc
+ *    - DUK_USE_xxx defines are resolved based on input defines
+ *    - Duktape Date provider settings
+ *    - Final sanity checks
+ *
+ *  DUK_F_XXX are internal feature detection macros which should not be
+ *  used outside this header.
+ *
+ *  Useful resources:
+ *
+ *    http://sourceforge.net/p/predef/wiki/Home/
+ *    http://sourceforge.net/p/predef/wiki/Architectures/
+ *    http://stackoverflow.com/questions/5919996/how-to-detect-reliably-mac-os-x-ios-linux-windows-in-c-preprocessor
+ *    http://en.wikipedia.org/wiki/C_data_types#Fixed-width_integer_types
+ *
+ *  Preprocessor defines available in a particular GCC:
+ *
+ *    gcc -dM -E - </dev/null   # http://www.brain-dump.org/blog/entry/107
+ */
+
+#ifndef DUK_FEATURES_H_INCLUDED
+#define DUK_FEATURES_H_INCLUDED
+
+/*
+ *  Compiler features
+ */
+
+#undef DUK_F_C99
+#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
+#define DUK_F_C99
+#endif
+
+#undef DUK_F_CPP
+#if defined(__cplusplus)
+#define DUK_F_CPP
+#endif
+
+#undef DUK_F_CPP11
+#if defined(__cplusplus) && (__cplusplus >= 201103L)
+#define DUK_F_CPP11
+#endif
+
+/*
+ *  Provides the duk_rdtsc() inline function (if available), limited to
+ *  GCC C99.
+ *
+ *  See: http://www.mcs.anl.gov/~kazutomo/rdtsc.html
+ */
+
+/* XXX: more accurate detection of what gcc versions work; more inline
+ * asm versions for other compilers.
+ */
+#if defined(__GNUC__) && defined(__i386__) && defined(DUK_F_C99) && \
+    !defined(__cplusplus) /* unsigned long long not standard */
+static __inline__ unsigned long long duk_rdtsc(void) {
+	unsigned long long int x;
+	__asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
+	return x;
+}
+#define DUK_RDTSC_AVAILABLE 1
+#elif defined(__GNUC__) && defined(__x86_64__) && defined(DUK_F_C99) && \
+    !defined(__cplusplus) /* unsigned long long not standard */
+static __inline__ unsigned long long duk_rdtsc(void) {
+	unsigned hi, lo;
+	__asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
+	return ((unsigned long long) lo) | (((unsigned long long) hi) << 32);
+}
+#define DUK_RDTSC_AVAILABLE 1
+#else
+/* not available */
+#undef DUK_RDTSC_AVAILABLE
+#endif
+
+/*
+ *  Intermediate platform, architecture, and compiler detection.  These are
+ *  hopelessly intertwined - e.g. architecture defines depend on compiler etc.
+ *
+ *  Provide easier defines for platforms and compilers which are often tricky
+ *  or verbose to detect.  The intent is not to provide intermediate defines for
+ *  all features; only if existing feature defines are inconvenient.
+ */
+
+/* Intel x86 (32-bit) */
+#if defined(i386) || defined(__i386) || defined(__i386__) || \
+    defined(__i486__) || defined(__i586__) || defined(__i686__) || \
+    defined(__IA32__) || defined(_M_IX86) || defined(__X86__) || \
+    defined(_X86_) || defined(__THW_INTEL__) || defined(__I86__)
+#define DUK_F_X86
+#endif
+
+/* AMD64 (64-bit) */
+#if defined(__amd64__) || defined(__amd64) || \
+    defined(__x86_64__) || defined(__x86_64) || \
+    defined(_M_X64) || defined(_M_AMD64)
+#define DUK_F_X64
+#endif
+
+/* X32: 64-bit with 32-bit pointers (allows packed tvals).  X32 support is
+ * not very mature yet.
+ *
+ * https://sites.google.com/site/x32abi/
+ */
+#if defined(DUK_F_X64) && \
+    (defined(_ILP32) || defined(__ILP32__))
+#define DUK_F_X32
+/* define only one of: DUK_F_X86, DUK_F_X32, or DUK_F_X64 */
+#undef DUK_F_X64
+#undef DUK_F_X86
+#endif
+
+/* ARM */
+#if defined(__arm__) || defined(__thumb__) || defined(_ARM) || defined(_M_ARM)
+#define DUK_F_ARM
+#endif
+
+/* MIPS */
+/* FIXME: 32-bit vs. 64-bit MIPS */
+#if defined(__mips__) || defined(mips) || defined(_MIPS_ISA) || \
+    defined(_R3000) || defined(_R4000) || defined(_R5900) || \
+    defined(_MIPS_ISA_MIPS1) || defined(_MIPS_ISA_MIPS2) || \
+    defined(_MIPS_ISA_MIPS3) || defined(_MIPS_ISA_MIPS4) || \
+    defined(__mips) || defined(__MIPS__)
+#define DUK_F_MIPS
+#endif
+
+/* Motorola 68K.  Not defined by VBCC, so user must define one of these
+ * manually when using VBCC.
+ */
+#if defined(__m68k__) || defined(M68000) || defined(__MC68K__)
+#define DUK_F_M68K
+#endif
+
+/* Linux */
+#if defined(__linux) || defined(__linux__) || defined(linux)
+#define DUK_F_LINUX
+#endif
+
+/* FreeBSD */
+#if defined(__FreeBSD__) || defined(__FreeBSD)
+#define DUK_F_FREEBSD
+#endif
+
+/* NetBSD */
+#if defined(__NetBSD__) || defined(__NetBSD)
+#define DUK_F_NETBSD
+#endif
+
+/* OpenBSD */
+#if defined(__OpenBSD__) || defined(__OpenBSD)
+#define DUK_F_OPENBSD
+#endif
+
+/* BSD variant */
+#if defined(DUK_F_FREEBSD) || defined(DUK_F_NETBSD) || defined(DUK_F_OPENBSD) || \
+    defined(__bsdi__) || defined(__DragonFly__)
+#define DUK_F_BSD
+#endif
+
+/* Generic Unix */
+#if defined(__unix) || defined(__unix__) || defined(unix) || \
+    defined(DUK_F_LINUX) || defined(DUK_F_BSD)
+#define DUK_F_UNIX
+#endif
+
+/* Windows (32-bit or above) */
+#if defined(_WIN32) || defined(WIN32) || defined(_WIN64) || defined(WIN64) || \
+    defined(__WIN32__) || defined(__TOS_WIN__) || defined(__WINDOWS__)
+#define DUK_F_WINDOWS
+#endif
+
+/* Atari ST TOS. __TOS__ defined by PureC (which doesn't work as a target now
+ * because int is 16-bit, to be fixed).  No platform define in VBCC apparently,
+ * so to use with VBCC, user must define '__TOS__' manually.
+  */
+#if defined(__TOS__)
+#define DUK_F_TOS
+#endif
+
+/* AmigaOS.  Neither AMIGA nor __amigaos__ is defined on VBCC, so user must
+ * define 'AMIGA' manually.
+ */
+#if defined(AMIGA) || defined(__amigaos__)
+#define DUK_F_AMIGAOS
+#endif
+
+/* Flash player (e.g. Crossbridge) */
+#if defined(__FLASHPLAYER__)
+#define DUK_F_FLASHPLAYER
+#endif
+
+/* Emscripten (provided explicitly by user), improve if possible */
+#if defined(EMSCRIPTEN)
+#define DUK_F_EMSCRIPTEN
+#endif
+
+/* QNX */
+#if defined(__QNX__)
+#define DUK_F_QNX
+#endif
+
+/* GCC and GCC version convenience define. */
+#if defined(__GNUC__)
+#define DUK_F_GCC
+#if defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__)
+/* Convenience, e.g. gcc 4.5.1 == 40501; http://stackoverflow.com/questions/6031819/emulating-gccs-builtin-unreachable */
+#define DUK_F_GCC_VERSION  (__GNUC__ * 10000L + __GNUC_MINOR__ * 100L + __GNUC_PATCHLEVEL__)
+#else
+#error cannot figure out gcc version
+#endif
+#endif
+
+/* Clang */
+#if defined(__clang__)
+#define DUK_F_CLANG
+#endif
+
+/* MSVC */
+#if defined(_MSC_VER)
+/* MSVC preprocessor defines: http://msdn.microsoft.com/en-us/library/b0084kay.aspx
+ * _MSC_FULL_VER includes the build number, but it has at least two formats, see e.g.
+ * BOOST_MSVC_FULL_VER in http://www.boost.org/doc/libs/1_52_0/boost/config/compiler/visualc.hpp
+ */
+#define DUK_F_MSVC
+#endif
+
+/* MinGW */
+#if defined(__MINGW32__) || defined(__MINGW64__)
+#define DUK_F_MINGW
+#endif
+
+/* BCC (Bruce's C compiler): this is a "torture target" for compilation */
+#if defined(__BCC__) || defined(__BCC_VERSION__)
+#define DUK_F_BCC
+#endif
+
+#if defined(__VBCC__)
+#define DUK_F_VBCC
+#endif
+
+#if (defined(DUK_F_C99) || defined(DUK_F_CPP11)) && \
+    !defined(DUK_F_BCC)
+/* ULL / LL preprocessor constants should be avoided because they're not
+ * always available.  With suitable options, some compilers will support
+ * 64-bit integer types but won't support ULL / LL preprocessor constants.
+ * Assume C99/C++11 environments have these.  However, BCC is nominally
+ * C99 but doesn't support these constants.
+ */
+#define DUK_F_ULL_CONSTS
+#endif
+
+/*
+ *  Platform detection, system includes, Date provider selection.
+ *
+ *  Feature selection (e.g. _XOPEN_SOURCE) must happen before any system
+ *  headers are included.  This header should avoid providing any feature
+ *  selection defines when compiling user code (only when compiling Duktape
+ *  itself).  If a feature selection option is required for user code to
+ *  compile correctly (e.g. it is needed for type detection), it should
+ *  probably be -checked- here, not defined here.
+ *
+ *  Date provider selection seems a bit out-of-place here, but since
+ *  the date headers and provider functions are heavily platform
+ *  specific, there's little point in duplicating the platform if-else
+ *  ladder.  All platform specific Date provider functions are in
+ *  duk_bi_date.c; here we provide appropriate #defines to enable them,
+ *  and include all the necessary system headers so that duk_bi_date.c
+ *  compiles.  Date "providers" are:
+ *
+ *    NOW = getting current time (required)
+ *    TZO = getting local time offset (required)
+ *    PRS = parse datetime (optional)
+ *    FMT = format datetime (optional)
+ *
+ *  There's a lot of duplication here, unfortunately, because many
+ *  platforms have similar (but not identical) headers, Date providers,
+ *  etc.  The duplication could be removed by more complicated nested
+ *  #ifdefs, but it would then be more difficult to make fixes which
+ *  affect only a specific platform.
+ *
+ *  FIXME: add a way to provide custom functions to provide the critical
+ *  primitives; this would be convenient when porting to unknown platforms
+ *  (rather than muck with Duktape internals).
+ */
+
+#if defined(DUK_COMPILING_DUKTAPE) && \
+ (defined(DUK_F_LINUX) || defined(DUK_F_EMSCRIPTEN))
+/* A more recent Emscripten (2014-05) seems to lack "linux" environment
+ * defines, so check for Emscripten explicitly.
+ */
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE  200809L
+#endif
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE      /* e.g. getdate_r */
+#endif
+#ifndef _XOPEN_SOURCE
+#define _XOPEN_SOURCE    /* e.g. strptime */
+#endif
+#endif
+
+#if defined(DUK_F_QNX) && defined(DUK_COMPILING_DUKTAPE)
+/* See: /opt/qnx650/target/qnx6/usr/include/sys/platform.h */
+#define _XOPEN_SOURCE    600
+#define _POSIX_C_SOURCE  200112L
+#endif
+
+#undef DUK_F_MSVC_CRT_SECURE
+#if defined(DUK_F_WINDOWS) && defined(_MSC_VER)
+/* http://msdn.microsoft.com/en-us/library/8ef0s5kh.aspx
+ * http://msdn.microsoft.com/en-us/library/wd3wzwts.aspx
+ * Seem to be available since VS2005.
+ */
+#if (_MSC_VER >= 1400)
+/* VS2005+, secure CRT functions are preferred.  Windows Store applications
+ * (and probably others) should use these.
+ */
+#define DUK_F_MSVC_CRT_SECURE
+#endif
+#if (_MSC_VER < 1700)
+/* VS2012+ has stdint.h, < VS2012 does not (but it's available for download). */
+#define DUK_F_NO_STDINT_H
+#endif
+/* Initial fix: disable secure CRT related warnings when compiling Duktape
+ * itself (must be defined before including Windows headers).  Don't define
+ * for user code including duktape.h.
+ */
+#if defined(DUK_COMPILING_DUKTAPE) && !defined(_CRT_SECURE_NO_WARNINGS)
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+#endif  /* DUK_F_WINDOWS && _MSC_VER */
+
+#if defined(DUK_F_TOS) || defined(DUK_F_BCC)
+#define DUK_F_NO_STDINT_H
+#endif
+
+#if defined(__APPLE__)
+/* Mac OSX, iPhone, Darwin */
+#define DUK_USE_DATE_NOW_GETTIMEOFDAY
+#define DUK_USE_DATE_TZO_GMTIME_R
+#define DUK_USE_DATE_PRS_STRPTIME
+#define DUK_USE_DATE_FMT_STRFTIME
+#include <architecture/byte_order.h>
+#include <limits.h>
+#include <sys/param.h>
+#include <sys/time.h>
+#include <time.h>
+#elif defined(DUK_F_OPENBSD)
+/* http://www.monkey.org/openbsd/archive/ports/0401/msg00089.html */
+#define DUK_USE_DATE_NOW_GETTIMEOFDAY
+#define DUK_USE_DATE_TZO_GMTIME_R
+#define DUK_USE_DATE_PRS_STRPTIME
+#define DUK_USE_DATE_FMT_STRFTIME
+#include <sys/types.h>
+#include <sys/endian.h>
+#include <limits.h>
+#include <sys/param.h>
+#include <sys/time.h>
+#include <time.h>
+#elif defined(DUK_F_BSD)
+/* other BSD */
+#define DUK_USE_DATE_NOW_GETTIMEOFDAY
+#define DUK_USE_DATE_TZO_GMTIME_R
+#define DUK_USE_DATE_PRS_STRPTIME
+#define DUK_USE_DATE_FMT_STRFTIME
+#include <sys/types.h>
+#include <sys/endian.h>
+#include <limits.h>
+#include <sys/param.h>
+#include <sys/time.h>
+#include <time.h>
+#elif defined(DUK_F_TOS)
+/* Atari ST TOS */
+#define DUK_USE_DATE_NOW_TIME
+#define DUK_USE_DATE_TZO_GMTIME
+/* no parsing (not an error) */
+#define DUK_USE_DATE_FMT_STRFTIME
+#include <limits.h>
+#include <time.h>
+#elif defined(DUK_F_AMIGAOS)
+#if defined(DUK_F_M68K)
+/* AmigaOS on M68k */
+#define DUK_USE_DATE_NOW_TIME
+#define DUK_USE_DATE_TZO_GMTIME
+/* no parsing (not an error) */
+#define DUK_USE_DATE_FMT_STRFTIME
+#include <limits.h>
+#include <time.h>
+#else
+#error AmigaOS but not M68K, not supported now
+#endif
+#elif defined(DUK_F_WINDOWS)
+/* Windows 32-bit and 64-bit are currently the same. */
+/* MSVC does not have sys/param.h */
+#define DUK_USE_DATE_NOW_WINDOWS
+#define DUK_USE_DATE_TZO_WINDOWS
+/* Note: PRS and FMT are intentionally left undefined for now.  This means
+ * there is no platform specific date parsing/formatting but there is still
+ * the ISO 8601 standard format.
+ */
+#include <windows.h>
+#include <limits.h>
+#elif defined(DUK_F_FLASHPLAYER)
+/* Crossbridge */
+#define DUK_USE_DATE_NOW_GETTIMEOFDAY
+#define DUK_USE_DATE_TZO_GMTIME_R
+#define DUK_USE_DATE_PRS_STRPTIME
+#define DUK_USE_DATE_FMT_STRFTIME
+#include <endian.h>
+#include <limits.h>
+#include <sys/param.h>
+#include <sys/time.h>
+#include <time.h>
+#elif defined(DUK_F_QNX)
+#define DUK_USE_DATE_NOW_GETTIMEOFDAY
+#define DUK_USE_DATE_TZO_GMTIME_R
+#define DUK_USE_DATE_PRS_STRPTIME
+#define DUK_USE_DATE_FMT_STRFTIME
+#include <sys/types.h>
+#include <limits.h>
+#include <sys/param.h>
+#include <sys/time.h>
+#include <time.h>
+#elif defined(DUK_F_LINUX)
+#define DUK_USE_DATE_NOW_GETTIMEOFDAY
+#define DUK_USE_DATE_TZO_GMTIME_R
+#define DUK_USE_DATE_PRS_STRPTIME
+#define DUK_USE_DATE_FMT_STRFTIME
+#include <sys/types.h>
+#if defined(DUK_F_BCC)
+/* no endian.h */
+#else
+#include <endian.h>
+#endif  /* DUK_F_BCC */
+#include <limits.h>
+#include <sys/param.h>
+#include <sys/time.h>
+#include <time.h>
+#elif defined(__posix)
+/* POSIX */
+#define DUK_USE_DATE_NOW_GETTIMEOFDAY
+#define DUK_USE_DATE_TZO_GMTIME_R
+#define DUK_USE_DATE_PRS_STRPTIME
+#define DUK_USE_DATE_FMT_STRFTIME
+#include <sys/types.h>
+#include <endian.h>
+#include <limits.h>
+#include <sys/param.h>
+#include <sys/time.h>
+#include <time.h>
+#else
+/* Other UNIX, hopefully others */
+#define DUK_USE_DATE_NOW_GETTIMEOFDAY
+#define DUK_USE_DATE_TZO_GMTIME_R
+#define DUK_USE_DATE_PRS_STRPTIME
+#define DUK_USE_DATE_FMT_STRFTIME
+#include <sys/types.h>
+#if defined(DUK_F_BCC)
+/* no endian.h */
+#else
+#include <endian.h>
+#endif  /* DUK_F_BCC */
+#include <limits.h>
+#include <sys/param.h>
+#include <sys/time.h>
+#include <time.h>
+#endif
+
+/* Shared includes */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>  /* varargs */
+#include <setjmp.h>
+#include <stddef.h>  /* e.g. ptrdiff_t */
+#if defined(DUK_F_NO_STDINT_H)
+/* stdint.h not available */
+#else
+/* technically C99 (C++11) but found in many systems */
+#include <stdint.h>
+#endif
+#include <math.h>
+
+/*
+ *  Detection for specific libc variants (like uclibc) and other libc specific
+ *  features.  Potentially depends on the #includes above.
+ */
+
+#if defined(__UCLIBC__)
+#define DUK_F_UCLIBC
+#endif
+
+/*
+ *  Wrapper typedefs and constants for integer types, also sanity check types.
+ *
+ *  C99 typedefs are quite good but not always available, and we want to avoid
+ *  forcibly redefining the C99 typedefs.  So, there are Duktape wrappers for
+ *  all C99 typedefs and Duktape code should only use these typedefs.  Type
+ *  detection when C99 is not supported is best effort and may end up detecting
+ *  some types incorrectly.
+ *
+ *  Pointer sizes are a portability problem: pointers to different types may
+ *  have a different size and function pointers are very difficult to manage
+ *  portably.
+ *
+ *  http://en.wikipedia.org/wiki/C_data_types#Fixed-width_integer_types
+ *
+ *  Note: there's an interesting corner case when trying to define minimum
+ *  signed integer value constants which leads to the current workaround of
+ *  defining e.g. -0x80000000 as (-0x7fffffffL - 1L).  See doc/code-issues.txt
+ *  for a longer discussion.
+ *
+ *  Note: avoid typecasts and computations in macro integer constants as they
+ *  can then no longer be used in macro relational expressions (such as
+ *  #if DUK_SIZE_MAX < 0xffffffffUL).  There is internal code which relies on
+ *  being able to compare DUK_SIZE_MAX against a limit.
+ */
+
+/* FIXME: add feature options to force basic types from outside? */
+
+#if !defined(INT_MAX)
+#error INT_MAX not defined
+#endif
+
+/* Check that architecture is two's complement, standard C allows e.g.
+ * INT_MIN to be -2**31+1 (instead of -2**31).
+ */
+#if defined(INT_MAX) && defined(INT_MIN)
+#if INT_MAX != -(INT_MIN + 1)
+#error platform does not seem complement of two
+#endif
+#else
+#error cannot check complement of two
+#endif
+
+/* Pointer size determination based on architecture.
+ * XXX: unsure about BCC correctness.
+ */
+#if defined(DUK_F_X86) || defined(DUK_F_X32) || \
+    defined(DUK_F_BCC) || \
+    (defined(__WORDSIZE) && (__WORDSIZE == 32))
+#define DUK_F_32BIT_PTRS
+#elif defined(DUK_F_X64) || \
+      (defined(__WORDSIZE) && (__WORDSIZE == 64))
+#define DUK_F_64BIT_PTRS
+#else
+/* not sure, not needed with C99 anyway */
+#endif
+
+/* Intermediate define for 'have inttypes.h' */
+#undef DUK_F_HAVE_INTTYPES
+#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \
+    !(defined(DUK_F_AMIGAOS) && defined(DUK_F_VBCC))
+/* vbcc + AmigaOS has C99 but no inttypes.h */
+#define DUK_F_HAVE_INTTYPES
+#elif defined(__cplusplus) && (__cplusplus >= 201103L)
+/* C++11 apparently ratified stdint.h */
+#define DUK_F_HAVE_INTTYPES
+#endif
+
+/* Basic integer typedefs and limits, preferably from inttypes.h, otherwise
+ * through automatic detection.
+ */
+#if defined(DUK_F_HAVE_INTTYPES)
+/* C99 */
+#define DUK_F_HAVE_64BIT
+#include <inttypes.h>
+
+typedef uint8_t duk_uint8_t;
+typedef int8_t duk_int8_t;
+typedef uint16_t duk_uint16_t;
+typedef int16_t duk_int16_t;
+typedef uint32_t duk_uint32_t;
+typedef int32_t duk_int32_t;
+typedef uint64_t duk_uint64_t;
+typedef int64_t duk_int64_t;
+typedef uint_least8_t duk_uint_least8_t;
+typedef int_least8_t duk_int_least8_t;
+typedef uint_least16_t duk_uint_least16_t;
+typedef int_least16_t duk_int_least16_t;
+typedef uint_least32_t duk_uint_least32_t;
+typedef int_least32_t duk_int_least32_t;
+typedef uint_least64_t duk_uint_least64_t;
+typedef int_least64_t duk_int_least64_t;
+typedef uint_fast8_t duk_uint_fast8_t;
+typedef int_fast8_t duk_int_fast8_t;
+typedef uint_fast16_t duk_uint_fast16_t;
+typedef int_fast16_t duk_int_fast16_t;
+typedef uint_fast32_t duk_uint_fast32_t;
+typedef int_fast32_t duk_int_fast32_t;
+typedef uint_fast64_t duk_uint_fast64_t;
+typedef int_fast64_t duk_int_fast64_t;
+typedef uintptr_t duk_uintptr_t;
+typedef intptr_t duk_intptr_t;
+typedef uintmax_t duk_uintmax_t;
+typedef intmax_t duk_intmax_t;
+
+#define DUK_UINT8_MIN         0
+#define DUK_UINT8_MAX         UINT8_MAX
+#define DUK_INT8_MIN          INT8_MIN
+#define DUK_INT8_MAX          INT8_MAX
+#define DUK_UINT_LEAST8_MIN   0
+#define DUK_UINT_LEAST8_MAX   UINT_LEAST8_MAX
+#define DUK_INT_LEAST8_MIN    INT_LEAST8_MIN
+#define DUK_INT_LEAST8_MAX    INT_LEAST8_MAX
+#define DUK_UINT_FAST8_MIN    0
+#define DUK_UINT_FAST8_MAX    UINT_FAST8_MAX
+#define DUK_INT_FAST8_MIN     INT_FAST8_MIN
+#define DUK_INT_FAST8_MAX     INT_FAST8_MAX
+#define DUK_UINT16_MIN        0
+#define DUK_UINT16_MAX        UINT16_MAX
+#define DUK_INT16_MIN         INT16_MIN
+#define DUK_INT16_MAX         INT16_MAX
+#define DUK_UINT_LEAST16_MIN  0
+#define DUK_UINT_LEAST16_MAX  UINT_LEAST16_MAX
+#define DUK_INT_LEAST16_MIN   INT_LEAST16_MIN
+#define DUK_INT_LEAST16_MAX   INT_LEAST16_MAX
+#define DUK_UINT_FAST16_MIN   0
+#define DUK_UINT_FAST16_MAX   UINT_FAST16_MAX
+#define DUK_INT_FAST16_MIN    INT_FAST16_MIN
+#define DUK_INT_FAST16_MAX    INT_FAST16_MAX
+#define DUK_UINT32_MIN        0
+#define DUK_UINT32_MAX        UINT32_MAX
+#define DUK_INT32_MIN         INT32_MIN
+#define DUK_INT32_MAX         INT32_MAX
+#define DUK_UINT_LEAST32_MIN  0
+#define DUK_UINT_LEAST32_MAX  UINT_LEAST32_MAX
+#define DUK_INT_LEAST32_MIN   INT_LEAST32_MIN
+#define DUK_INT_LEAST32_MAX   INT_LEAST32_MAX
+#define DUK_UINT_FAST32_MIN   0
+#define DUK_UINT_FAST32_MAX   UINT_FAST32_MAX
+#define DUK_INT_FAST32_MIN    INT_FAST32_MIN
+#define DUK_INT_FAST32_MAX    INT_FAST32_MAX
+#define DUK_UINT64_MIN        0
+#define DUK_UINT64_MAX        UINT64_MAX
+#define DUK_INT64_MIN         INT64_MIN
+#define DUK_INT64_MAX         INT64_MAX
+#define DUK_UINT_LEAST64_MIN  0
+#define DUK_UINT_LEAST64_MAX  UINT_LEAST64_MAX
+#define DUK_INT_LEAST64_MIN   INT_LEAST64_MIN
+#define DUK_INT_LEAST64_MAX   INT_LEAST64_MAX
+#define DUK_UINT_FAST64_MIN   0
+#define DUK_UINT_FAST64_MAX   UINT_FAST64_MAX
+#define DUK_INT_FAST64_MIN    INT_FAST64_MIN
+#define DUK_INT_FAST64_MAX    INT_FAST64_MAX
+
+#define DUK_UINTPTR_MIN       0
+#define DUK_UINTPTR_MAX       UINTPTR_MAX
+#define DUK_INTPTR_MIN        INTPTR_MIN
+#define DUK_INTPTR_MAX        INTPTR_MAX
+
+#define DUK_UINTMAX_MIN       0
+#define DUK_UINTMAX_MAX       UINTMAX_MAX
+#define DUK_INTMAX_MIN        INTMAX_MIN
+#define DUK_INTMAX_MAX        INTMAX_MAX
+
+#define DUK_SIZE_MIN          0
+#define DUK_SIZE_MAX          SIZE_MAX
+
+#else  /* C99 types */
+
+/* When C99 types are not available, we use heuristic detection to get
+ * the basic 8, 16, 32, and (possibly) 64 bit types.  The fast/least
+ * types are then assumed to be exactly the same for now: these could
+ * be improved per platform but C99 types are very often now available.
+ * 64-bit types are not available on all platforms; this is OK at least
+ * on 32-bit platforms.
+ *
+ * This detection code is necessarily a bit hacky and can provide typedefs
+ * and defines that won't work correctly on some exotic platform.
+ */
+
+#if (defined(CHAR_BIT) && (CHAR_BIT == 8)) || \
+    (defined(UCHAR_MAX) && (UCHAR_MAX == 255))
+typedef unsigned char duk_uint8_t;
+typedef signed char duk_int8_t;
+#else
+#error cannot detect 8-bit type
+#endif
+
+#if defined(USHRT_MAX) && (USHRT_MAX == 65535UL)
+typedef unsigned short duk_uint16_t;
+typedef signed short duk_int16_t;
+#elif defined(UINT_MAX) && (UINT_MAX == 65535UL)
+/* On some platforms int is 16-bit but long is 32-bit (e.g. PureC) */
+typedef unsigned int duk_uint16_t;
+typedef signed int duk_int16_t;
+#else
+#error cannot detect 16-bit type
+#endif
+
+#if defined(UINT_MAX) && (UINT_MAX == 4294967295UL)
+typedef unsigned int duk_uint32_t;
+typedef signed int duk_int32_t;
+#elif defined(ULONG_MAX) && (ULONG_MAX == 4294967295UL)
+/* On some platforms int is 16-bit but long is 32-bit (e.g. PureC) */
+typedef unsigned long duk_uint32_t;
+typedef signed long duk_int32_t;
+#else
+#error cannot detect 32-bit type
+#endif
+
+/* 64-bit type detection is a bit tricky.
+ *
+ * ULLONG_MAX is a standard define.  __LONG_LONG_MAX__ and __ULONG_LONG_MAX__
+ * are used by at least GCC (even if system headers don't provide ULLONG_MAX).
+ * Some GCC variants may provide __LONG_LONG_MAX__ but not __ULONG_LONG_MAX__.
+ *
+ * ULL / LL constants are rejected / warned about by some compilers, even if
+ * the compiler has a 64-bit type and the compiler/system headers provide an
+ * unsupported constant (ULL/LL)!  Try to avoid using ULL / LL constants.
+ * As a side effect we can only check that e.g. ULONG_MAX is larger than 32
+ * bits but can't be sure it is exactly 64 bits.  Self tests will catch such
+ * cases.
+ */
+#undef DUK_F_HAVE_64BIT
+#if !defined(DUK_F_HAVE_64BIT) && defined(ULONG_MAX)
+#if (ULONG_MAX > 4294967295UL)
+#define DUK_F_HAVE_64BIT
+typedef unsigned long duk_uint64_t;
+typedef signed long duk_int64_t;
+#endif
+#endif
+#if !defined(DUK_F_HAVE_64BIT) && defined(ULLONG_MAX)
+#if (ULLONG_MAX > 4294967295UL)
+#define DUK_F_HAVE_64BIT
+typedef unsigned long long duk_uint64_t;
+typedef signed long long duk_int64_t;
+#endif
+#endif
+#if !defined(DUK_F_HAVE_64BIT) && defined(__ULONG_LONG_MAX__)
+#if (__ULONG_LONG_MAX__ > 4294967295UL)
+#define DUK_F_HAVE_64BIT
+typedef unsigned long long duk_uint64_t;
+typedef signed long long duk_int64_t;
+#endif
+#endif
+#if !defined(DUK_F_HAVE_64BIT) && defined(__LONG_LONG_MAX__)
+#if (__LONG_LONG_MAX__ > 2147483647L)
+#define DUK_F_HAVE_64BIT
+typedef unsigned long long duk_uint64_t;
+typedef signed long long duk_int64_t;
+#endif
+#endif
+#if !defined(DUK_F_HAVE_64BIT) && \
+    (defined(DUK_F_MINGW) || defined(DUK_F_MSVC))
+/* Both MinGW and MSVC have a 64-bit type. */
+#define DUK_F_HAVE_64BIT
+typedef unsigned long duk_uint64_t;
+typedef signed long duk_int64_t;
+#endif
+#if !defined(DUK_F_HAVE64BIT)
+/* cannot detect 64-bit type, not always needed so don't error */
+#endif
+
+typedef duk_uint8_t duk_uint_least8_t;
+typedef duk_int8_t duk_int_least8_t;
+typedef duk_uint16_t duk_uint_least16_t;
+typedef duk_int16_t duk_int_least16_t;
+typedef duk_uint32_t duk_uint_least32_t;
+typedef duk_int32_t duk_int_least32_t;
+typedef duk_uint8_t duk_uint_fast8_t;
+typedef duk_int8_t duk_int_fast8_t;
+typedef duk_uint16_t duk_uint_fast16_t;
+typedef duk_int16_t duk_int_fast16_t;
+typedef duk_uint32_t duk_uint_fast32_t;
+typedef duk_int32_t duk_int_fast32_t;
+#if defined(DUK_F_HAVE_64BIT)
+typedef duk_uint64_t duk_uint_least64_t;
+typedef duk_int64_t duk_int_least64_t;
+typedef duk_uint64_t duk_uint_fast64_t;
+typedef duk_int64_t duk_int_fast64_t;
+#endif
+#if defined(DUK_F_HAVE_64BIT)
+typedef duk_uint64_t duk_uintmax_t;
+typedef duk_int64_t duk_intmax_t;
+#else
+typedef duk_uint32_t duk_uintmax_t;
+typedef duk_int32_t duk_intmax_t;
+#endif
+
+/* Note: the funny looking computations for signed minimum 16-bit, 32-bit, and
+ * 64-bit values are intentional as the obvious forms (e.g. -0x80000000L) are
+ * -not- portable.  See code-issues.txt for a detailed discussion.
+ */
+#define DUK_UINT8_MIN         0UL
+#define DUK_UINT8_MAX         0xffUL
+#define DUK_INT8_MIN          (-0x80L)
+#define DUK_INT8_MAX          0x7fL
+#define DUK_UINT_LEAST8_MIN   0UL
+#define DUK_UINT_LEAST8_MAX   0xffUL
+#define DUK_INT_LEAST8_MIN    (-0x80L)
+#define DUK_INT_LEAST8_MAX    0x7fL
+#define DUK_UINT_FAST8_MIN    0UL
+#define DUK_UINT_FAST8_MAX    0xffUL
+#define DUK_INT_FAST8_MIN     (-0x80L)
+#define DUK_INT_FAST8_MAX     0x7fL
+#define DUK_UINT16_MIN        0UL
+#define DUK_UINT16_MAX        0xffffUL
+#define DUK_INT16_MIN         (-0x7fffL - 1L)
+#define DUK_INT16_MAX         0x7fffL
+#define DUK_UINT_LEAST16_MIN  0UL
+#define DUK_UINT_LEAST16_MAX  0xffffUL
+#define DUK_INT_LEAST16_MIN   (-0x7fffL - 1L)
+#define DUK_INT_LEAST16_MAX   0x7fffL
+#define DUK_UINT_FAST16_MIN   0UL
+#define DUK_UINT_FAST16_MAX   0xffffUL
+#define DUK_INT_FAST16_MIN    (-0x7fffL - 1L)
+#define DUK_INT_FAST16_MAX    0x7fffL
+#define DUK_UINT32_MIN        0UL
+#define DUK_UINT32_MAX        0xffffffffUL
+#define DUK_INT32_MIN         (-0x7fffffffL - 1L)
+#define DUK_INT32_MAX         0x7fffffffL
+#define DUK_UINT_LEAST32_MIN  0UL
+#define DUK_UINT_LEAST32_MAX  0xffffffffUL
+#define DUK_INT_LEAST32_MIN   (-0x7fffffffL - 1L)
+#define DUK_INT_LEAST32_MAX   0x7fffffffL
+#define DUK_UINT_FAST32_MIN   0UL
+#define DUK_UINT_FAST32_MAX   0xffffffffUL
+#define DUK_INT_FAST32_MIN    (-0x7fffffffL - 1L)
+#define DUK_INT_FAST32_MAX    0x7fffffffL
+
+/* 64-bit constants.  Since LL / ULL constants are not always available,
+ * use computed values.  These values can't be used in preprocessor
+ * comparisons; flag them as such.
+ */
+#if defined(DUK_F_HAVE_64BIT)
+#define DUK_UINT64_MIN        ((duk_uint64_t) 0)
+#define DUK_UINT64_MAX        ((duk_uint64_t) -1)
+#define DUK_INT64_MIN         ((duk_int64_t) (~(DUK_UINT64_MAX >> 1)))
+#define DUK_INT64_MAX         ((duk_int64_t) (DUK_UINT64_MAX >> 1))
+#define DUK_UINT_LEAST64_MIN  DUK_UINT64_MIN
+#define DUK_UINT_LEAST64_MAX  DUK_UINT64_MAX
+#define DUK_INT_LEAST64_MIN   DUK_INT64_MIN
+#define DUK_INT_LEAST64_MAX   DUK_INT64_MAX
+#define DUK_UINT_FAST64_MIN   DUK_UINT64_MIN
+#define DUK_UINT_FAST64_MAX   DUK_UINT64_MAX
+#define DUK_INT_FAST64_MIN    DUK_INT64_MIN
+#define DUK_INT_FAST64_MAX    DUK_INT64_MAX
+#define DUK_UINT64_MIN_COMPUTED
+#define DUK_UINT64_MAX_COMPUTED
+#define DUK_INT64_MIN_COMPUTED
+#define DUK_INT64_MAX_COMPUTED
+#define DUK_UINT_LEAST64_MIN_COMPUTED
+#define DUK_UINT_LEAST64_MAX_COMPUTED
+#define DUK_INT_LEAST64_MIN_COMPUTED
+#define DUK_INT_LEAST64_MAX_COMPUTED
+#define DUK_UINT_FAST64_MIN_COMPUTED
+#define DUK_UINT_FAST64_MAX_COMPUTED
+#define DUK_INT_FAST64_MIN_COMPUTED
+#define DUK_INT_FAST64_MAX_COMPUTED
+#endif
+
+#if defined(DUK_F_HAVE_64BIT)
+#define DUK_UINTMAX_MIN       DUK_UINT64_MIN
+#define DUK_UINTMAX_MAX       DUK_UINT64_MAX
+#define DUK_INTMAX_MIN        DUK_INT64_MIN
+#define DUK_INTMAX_MAX        DUK_INT64_MAX
+#define DUK_UINTMAX_MIN_COMPUTED
+#define DUK_UINTMAX_MAX_COMPUTED
+#define DUK_INTMAX_MIN_COMPUTED
+#define DUK_INTMAX_MAX_COMPUTED
+#else
+#define DUK_UINTMAX_MIN       0UL
+#define DUK_UINTMAX_MAX       0xffffffffUL
+#define DUK_INTMAX_MIN        (-0x7fffffffL - 1L)
+#define DUK_INTMAX_MAX        0x7fffffffL
+#endif
+
+/* This detection is not very reliable. */
+#if defined(DUK_F_32BIT_PTRS)
+typedef duk_int32_t duk_intptr_t;
+typedef duk_uint32_t duk_uintptr_t;
+#define DUK_UINTPTR_MIN       DUK_UINT32_MIN
+#define DUK_UINTPTR_MAX       DUK_UINT32_MAX
+#define DUK_INTPTR_MIN        DUK_INT32_MIN
+#define DUK_INTPTR_MAX        DUK_INT32_MAX
+#elif defined(DUK_F_64BIT_PTRS) && defined(DUK_F_HAVE_64BIT)
+typedef duk_int64_t duk_intptr_t;
+typedef duk_uint64_t duk_uintptr_t;
+#define DUK_UINTPTR_MIN       DUK_UINT64_MIN
+#define DUK_UINTPTR_MAX       DUK_UINT64_MAX
+#define DUK_INTPTR_MIN        DUK_INT64_MIN
+#define DUK_INTPTR_MAX        DUK_INT64_MAX
+#define DUK_UINTPTR_MIN_COMPUTED
+#define DUK_UINTPTR_MAX_COMPUTED
+#define DUK_INTPTR_MIN_COMPUTED
+#define DUK_INTPTR_MAX_COMPUTED
+#else
+#error cannot determine intptr type
+#endif
+
+/* SIZE_MAX may be missing so use an approximate value for it. */
+#undef DUK_SIZE_MAX_COMPUTED
+#if !defined(SIZE_MAX)
+#define DUK_SIZE_MAX_COMPUTED
+#define SIZE_MAX              ((size_t) (-1))
+#endif
+#define DUK_SIZE_MIN          0
+#define DUK_SIZE_MAX          SIZE_MAX
+
+#endif  /* C99 types */
+
+/* A few types are assumed to always exist. */
+typedef size_t duk_size_t;
+typedef ptrdiff_t duk_ptrdiff_t;
+
+/* The best type for an "all around int" in Duktape internals is "at least
+ * 32 bit signed integer" which is most convenient.  Same for unsigned type.
+ * Prefer 'int' when large enough, as it is almost always a convenient type.
+ */
+#if defined(UINT_MAX) && (UINT_MAX >= 0xffffffffUL)
+typedef int duk_int_t;
+typedef unsigned int duk_uint_t;
+#define DUK_INT_MIN           INT_MIN
+#define DUK_INT_MAX           INT_MAX
+#define DUK_UINT_MIN          0
+#define DUK_UINT_MAX          UINT_MAX
+#else
+typedef duk_int_fast32_t duk_int_t;
+typedef duk_uint_fast32_t duk_uint_t;
+#define DUK_INT_MIN           DUK_INT_FAST32_MIN
+#define DUK_INT_MAX           DUK_INT_FAST32_MAX
+#define DUK_UINT_MIN          DUK_UINT_FAST32_MIN
+#define DUK_UINT_MAX          DUK_UINT_FAST32_MAX
+#endif
+
+/* Same as 'duk_int_t' but guaranteed to be a 'fast' variant if this
+ * distinction matters for the CPU.  These types are used mainly in the
+ * executor where it might really matter.
+ */
+typedef duk_int_fast32_t duk_int_fast_t;
+typedef duk_uint_fast32_t duk_uint_fast_t;
+#define DUK_INT_FAST_MIN      DUK_INT_FAST32_MIN
+#define DUK_INT_FAST_MAX      DUK_INT_FAST32_MAX
+#define DUK_UINT_FAST_MIN     DUK_UINT_FAST32_MIN
+#define DUK_UINT_FAST_MAX     DUK_UINT_FAST32_MAX
+
+/* Small integers (16 bits or more) can fall back to the 'int' type, but
+ * have a typedef so they are marked "small" explicitly.
+ */
+typedef int duk_small_int_t;
+typedef unsigned int duk_small_uint_t;
+#define DUK_SMALL_INT_MIN     INT_MIN
+#define DUK_SMALL_INT_MAX     INT_MAX
+#define DUK_SMALL_UINT_MIN    0
+#define DUK_SMALL_UINT_MAX    UINT_MAX
+
+/* Fast variants of small integers, again for really fast paths like the
+ * executor.
+ */
+typedef duk_int_fast16_t duk_small_int_fast_t;
+typedef duk_uint_fast16_t duk_small_uint_fast_t;
+#define DUK_SMALL_INT_FAST_MIN    DUK_INT_FAST16_MIN
+#define DUK_SMALL_INT_FAST_MAX    DUK_INT_FAST16_MAX
+#define DUK_SMALL_UINT_FAST_MIN   DUK_UINT_FAST16_MIN
+#define DUK_SMALL_UINT_FAST_MAX   DUK_UINT_FAST16_MAX
+
+/* Boolean values are represented with the platform 'int'. */
+typedef duk_small_int_t duk_bool_t;
+#define DUK_BOOL_MIN              DUK_SMALL_INT_MIN
+#define DUK_BOOL_MAX              DUK_SMALL_INT_MAX
+
+/* Index values must have at least 32-bit signed range. */
+typedef duk_int_t duk_idx_t;
+#define DUK_IDX_MIN               DUK_INT_MIN
+#define DUK_IDX_MAX               DUK_INT_MAX
+
+/* Array index values, could be exact 32 bits.
+ * Currently no need for signed duk_arridx_t.
+ */
+typedef duk_uint_t duk_uarridx_t;
+#define DUK_UARRIDX_MIN           DUK_UINT_MIN
+#define DUK_UARRIDX_MAX           DUK_UINT_MAX
+
+/* Duktape/C function return value, platform int is enough for now to
+ * represent 0, 1, or negative error code.  Must be compatible with
+ * assigning truth values (e.g. duk_ret_t rc = (foo == bar);).
+ */
+typedef duk_small_int_t duk_ret_t;
+#define DUK_RET_MIN               DUK_SMALL_INT_MIN
+#define DUK_RET_MAX               DUK_SMALL_INT_MAX
+
+/* Error codes are represented with platform int.  High bits are used
+ * for flags and such, so 32 bits are needed.
+ */
+typedef duk_int_t duk_errcode_t;
+#define DUK_ERRCODE_MIN           DUK_INT_MIN
+#define DUK_ERRCODE_MAX           DUK_INT_MAX
+
+/* Codepoint type.  Must be 32 bits or more because it is used also for
+ * internal codepoints.  The type is signed because negative codepoints
+ * are used as internal markers (e.g. to mark EOF or missing argument).
+ * (X)UTF-8/CESU-8 encode/decode take and return an unsigned variant to
+ * ensure duk_uint32_t casts back and forth nicely.  Almost everything
+ * else uses the signed one.
+ */
+typedef duk_int_t duk_codepoint_t;
+typedef duk_uint_t duk_ucodepoint_t;
+#define DUK_CODEPOINT_MIN         DUK_INT_MIN
+#define DUK_CODEPOINT_MAX         DUK_INT_MAX
+#define DUK_UCODEPOINT_MIN        DUK_UINT_MIN
+#define DUK_UCODEPOINT_MAX        DUK_UINT_MAX
+
+/* IEEE double typedef. */
+typedef double duk_double_t;
+
+/* We're generally assuming that we're working on a platform with a 32-bit
+ * address space.  If DUK_SIZE_MAX is a typecast value (which is necessary
+ * if SIZE_MAX is missing), the check must be avoided because the
+ * preprocessor can't do a comparison.
+ */
+#if !defined(DUK_SIZE_MAX)
+#error DUK_SIZE_MAX is undefined, probably missing SIZE_MAX
+#elif !defined(DUK_SIZE_MAX_COMPUTED)
+#if DUK_SIZE_MAX < 0xffffffffUL
+/* XXX: compare against a lower value; can SIZE_MAX realistically be
+ * e.g. 0x7fffffffUL on a 32-bit system?
+ */
+#error size_t is too small
+#endif
+#endif
+
+/* Convenience define: 32-bit pointers.  32-bit platforms are an important
+ * footprint optimization target, and this define allows e.g. struct sizes
+ * to be organized for compactness.
+ */
+
+#undef DUK_USE_32BIT_PTRS
+#if defined(DUK_UINTPTR_MAX) && !defined(DUK_UINTPTR_MAX_COMPUTED)
+#if DUK_UINTPTR_MAX <= 0xffffffffUL
+#define DUK_USE_32BIT_PTRS
+#endif
+#endif
+
+/*
+ *  Check whether we should use 64-bit integers
+ */
+
+/* Quite incomplete now.  Use 64-bit types if detected (C99 or other detection)
+ * unless they are known to be unreliable.  For instance, 64-bit types are
+ * available on VBCC but seem to misbehave.
+ */
+#if defined(DUK_F_HAVE_64BIT) && !defined(DUK_F_VBCC)
+#define DUK_USE_64BIT_OPS
+#else
+#undef DUK_USE_64BIT_OPS
+#endif
+
+/*
+ *  Alignment requirement and support for unaligned accesses
+ *
+ *  Assume unaligned accesses are not supported unless specifically allowed
+ *  in the target platform.  Some platforms may support unaligned accesses
+ *  but alignment to 4 or 8 may still be desirable.
+ */
+
+#undef DUK_USE_UNALIGNED_ACCESSES_POSSIBLE
+#undef DUK_USE_ALIGN_4
+#undef DUK_USE_ALIGN_8
+
+#if defined(DUK_F_EMSCRIPTEN)
+/* Required on at least some targets, so use whenever Emscripten used,
+ * regardless of compilation target.
+ */
+#define DUK_USE_ALIGN_8
+#elif defined(DUK_F_ARM)
+#define DUK_USE_ALIGN_4
+#elif defined(DUK_F_MIPS)
+#define DUK_USE_ALIGN_4
+#elif defined(DUK_F_X86) || defined(DUK_F_X32) || defined(DUK_F_X64) || \
+      defined(DUK_F_BCC)
+#define DUK_USE_UNALIGNED_ACCESSES_POSSIBLE
+#else
+/* unknown, use safe default */
+#define DUK_USE_ALIGN_8
+#endif
+
+/* User forced alignment to 4 or 8. */
+#if defined(DUK_OPT_FORCE_ALIGN)
+#undef DUK_USE_UNALIGNED_ACCESSES_POSSIBLE
+#undef DUK_USE_ALIGN_4
+#undef DUK_USE_ALIGN_8
+#if (DUK_OPT_FORCE_ALIGN == 4)
+#define DUK_USE_ALIGN_4
+#elif (DUK_OPT_FORCE_ALIGN == 8)
+#define DUK_USE_ALIGN_8
+#else
+#error invalid DUK_OPT_FORCE_ALIGN value
+#endif
+#endif
+
+/* Compiler specific hackery needed to force struct size to match aligment,
+ * see e.g. duk_hbuffer.h.
+ *
+ * http://stackoverflow.com/questions/11130109/c-struct-size-alignment
+ * http://stackoverflow.com/questions/10951039/specifying-64-bit-alignment
+ */
+#if defined(DUK_F_MSVC)
+#define DUK_USE_PACK_MSVC_PRAGMA
+#elif defined(DUK_F_GCC)
+#define DUK_USE_PACK_GCC_ATTR
+#elif defined(DUK_F_CLANG)
+#define DUK_USE_PACK_CLANG_ATTR
+#else
+#define DUK_USE_PACK_DUMMY_MEMBER
+#endif
+
+#ifdef DUK_USE_UNALIGNED_ACCESSES_POSSIBLE
+#define DUK_USE_HASHBYTES_UNALIGNED_U32_ACCESS
+#else
+#undef DUK_USE_HASHBYTES_UNALIGNED_U32_ACCESS
+#endif
+
+/* Object property allocation layout has implications for memory and code
+ * footprint and generated code size/speed.  The best layout also depends
+ * on whether the platform has alignment requirements or benefits from
+ * having mostly aligned accesses.
+ */
+#undef DUK_USE_HOBJECT_LAYOUT_1
+#undef DUK_USE_HOBJECT_LAYOUT_2
+#undef DUK_USE_HOBJECT_LAYOUT_3
+#if defined(DUK_USE_UNALIGNED_ACCESSES_POSSIBLE) && \
+    !defined(DUK_USE_ALIGN_4) && !defined(DUK_USE_ALIGN_8)
+/* On platforms without any alignment issues, layout 1 is preferable
+ * because it compiles to slightly less code and provides direct access
+ * to property keys.
+ */
+#define DUK_USE_HOBJECT_LAYOUT_1
+#else
+/* On other platforms use layout 2, which requires some padding but
+ * is a bit more natural than layout 3 in ordering the entries.  Layout
+ * 3 is currently not used.
+ */
+#define DUK_USE_HOBJECT_LAYOUT_2
+#endif
+
+/*
+ *  Byte order and double memory layout detection
+ *
+ *  Endianness detection is a major portability hassle because the macros
+ *  and headers are not standardized.  There's even variance across UNIX
+ *  platforms.  Even with "standard" headers, details like underscore count
+ *  varies between platforms, e.g. both __BYTE_ORDER and _BYTE_ORDER are used
+ *  (Crossbridge has a single underscore, for instance).
+ *
+ *  The checks below are structured with this in mind: several approaches are
+ *  used, and at the end we check if any of them worked.  This allows generic
+ *  approaches to be tried first, and platform/compiler specific hacks tried
+ *  last.  As a last resort, the user can force a specific endianness, as it's
+ *  not likely that automatic detection will work on the most exotic platforms.
+ *
+ *  Duktape supports little and big endian machines.  There's also support
+ *  for a hybrid used by some ARM machines where integers are little endian
+ *  but IEEE double values use a mixed order (12345678 -> 43218765).  This
+ *  byte order for doubles is referred to as "mixed endian".
+ */
+
+#undef DUK_F_BYTEORDER
+#undef DUK_USE_BYTEORDER_FORCED
+
+/* DUK_F_BYTEORDER is set as an intermediate value when detection
+ * succeeds, to one of:
+ *   1 = little endian
+ *   2 = mixed (arm hybrid) endian
+ *   3 = big endian
+ */
+
+/* For custom platforms allow user to define byteorder explicitly.
+ * Since endianness headers are not standardized, this is a useful
+ * workaround for custom platforms for which endianness detection
+ * is not directly supported.  Perhaps custom hardware is used and
+ * user cannot submit upstream patches.
+ */
+#if defined(DUK_OPT_FORCE_BYTEORDER)
+#if (DUK_OPT_FORCE_BYTEORDER == 1)
+#define DUK_F_BYTEORDER 1
+#elif (DUK_OPT_FORCE_BYTEORDER == 2)
+#define DUK_F_BYTEORDER 2
+#elif (DUK_OPT_FORCE_BYTEORDER == 3)
+#define DUK_F_BYTEORDER 3
+#else
+#error invalid DUK_OPT_FORCE_BYTEORDER value
+#endif
+#define DUK_USE_BYTEORDER_FORCED
+#endif  /* DUK_OPT_FORCE_BYTEORDER */
+
+/* More or less standard endianness predefines provided by header files.
+ * The ARM hybrid case is detected by assuming that __FLOAT_WORD_ORDER
+ * will be big endian, see: http://lists.mysql.com/internals/443.
+ */
+#if !defined(DUK_F_BYTEORDER)
+#if defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && (__BYTE_ORDER == __LITTLE_ENDIAN) || \
+    defined(_BYTE_ORDER) && defined(_LITTLE_ENDIAN) && (_BYTE_ORDER == _LITTLE_ENDIAN) || \
+    defined(__LITTLE_ENDIAN__)
+/* Integer little endian */
+#if defined(__FLOAT_WORD_ORDER) && defined(__LITTLE_ENDIAN) && (__FLOAT_WORD_ORDER == __LITTLE_ENDIAN) || \
+    defined(_FLOAT_WORD_ORDER) && defined(_LITTLE_ENDIAN) && (_FLOAT_WORD_ORDER == _LITTLE_ENDIAN)
+#define DUK_F_BYTEORDER 1
+#elif defined(__FLOAT_WORD_ORDER) && defined(__BIG_ENDIAN) && (__FLOAT_WORD_ORDER == __BIG_ENDIAN) || \
+      defined(_FLOAT_WORD_ORDER) && defined(_BIG_ENDIAN) && (_FLOAT_WORD_ORDER == _BIG_ENDIAN)
+#define DUK_F_BYTEORDER 2
+#elif !defined(__FLOAT_WORD_ORDER) && !defined(_FLOAT_WORD_ORDER)
+/* Float word order not known, assume not a hybrid. */
+#define DUK_F_BYTEORDER 1
+#else
+/* byte order is little endian but cannot determine IEEE double word order */
+#endif  /* float word order */
+#elif defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && (__BYTE_ORDER == __BIG_ENDIAN) || \
+      defined(_BYTE_ORDER) && defined(_BIG_ENDIAN) && (_BYTE_ORDER == _BIG_ENDIAN) || \
+      defined(__BIG_ENDIAN__)
+/* Integer big endian */
+#if defined(__FLOAT_WORD_ORDER) && defined(__BIG_ENDIAN) && (__FLOAT_WORD_ORDER == __BIG_ENDIAN) || \
+    defined(_FLOAT_WORD_ORDER) && defined(_BIG_ENDIAN) && (_FLOAT_WORD_ORDER == _BIG_ENDIAN)
+#define DUK_F_BYTEORDER 3
+#elif !defined(__FLOAT_WORD_ORDER) && !defined(_FLOAT_WORD_ORDER)
+/* Float word order not known, assume not a hybrid. */
+#define DUK_F_BYTEORDER 3
+#else
+/* byte order is big endian but cannot determine IEEE double word order */
+#endif  /* float word order */
+#else
+/* cannot determine byte order */
+#endif  /* integer byte order */
+#endif  /* !defined(DUK_F_BYTEORDER) */
+
+/* GCC and Clang provide endianness defines as built-in predefines, with
+ * leading and trailing double underscores (e.g. __BYTE_ORDER__).  See
+ * output of "make gccpredefs" and "make clangpredefs".  Clang doesn't
+ * seem to provide __FLOAT_WORD_ORDER__.
+ * http://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html
+ */
+#if !defined(DUK_F_BYTEORDER) && defined(__BYTE_ORDER__)
+#if defined(__ORDER_LITTLE_ENDIAN__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
+/* Integer little endian */
+#if defined(__FLOAT_WORD_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && \
+    (__FLOAT_WORD_ORDER__ == __ORDER_LITTLE_ENDIAN__)
+#define DUK_F_BYTEORDER 1
+#elif defined(__FLOAT_WORD_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && \
+      (__FLOAT_WORD_ORDER__ == __ORDER_BIG_ENDIAN__)
+#define DUK_F_BYTEORDER 2
+#elif !defined(__FLOAT_WORD_ORDER__)
+/* Float word order not known, assume not a hybrid. */
+#define DUK_F_BYTEORDER 1
+#else
+/* byte order is little endian but cannot determine IEEE double word order */
+#endif  /* float word order */
+#elif defined(__ORDER_BIG_ENDIAN__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
+/* Integer big endian */
+#if defined(__FLOAT_WORD_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && \
+    (__FLOAT_WORD_ORDER__ == __ORDER_BIG_ENDIAN__)
+#define DUK_F_BYTEORDER 3
+#elif !defined(__FLOAT_WORD_ORDER__)
+/* Float word order not known, assume not a hybrid. */
+#define DUK_F_BYTEORDER 3
+#else
+/* byte order is big endian but cannot determine IEEE double word order */
+#endif  /* float word order */
+#else
+/* cannot determine byte order; __ORDER_PDP_ENDIAN__ is related to 32-bit
+ * integer ordering and is not relevant
+ */
+#endif  /* integer byte order */
+#endif  /* !defined(DUK_F_BYTEORDER) && defined(__BYTE_ORDER__) */
+
+/* Atari ST TOS */
+#if !defined(DUK_F_BYTEORDER) && defined(DUK_F_TOS)
+#define DUK_F_BYTEORDER 3
+#endif
+
+/* AmigaOS on M68k */
+#if !defined(DUK_F_BYTEORDER) && defined(DUK_F_AMIGAOS)
+#if defined(DUK_F_M68K)
+#define DUK_F_BYTEORDER 3
+#endif
+#endif
+
+/* On Windows, assume we're little endian.  Even Itanium which has a
+ * configurable endianness runs little endian in Windows.
+ */
+#if !defined(DUK_F_BYTEORDER) && defined(DUK_F_WINDOWS)
+/* XXX: verify that Windows on ARM is little endian for floating point
+ * values too.
+ */
+#define DUK_F_BYTEORDER 1
+#endif  /* Windows */
+
+/* Crossbridge should work with the standard byteorder #ifdefs.  It doesn't
+ * provide _FLOAT_WORD_ORDER but the standard approach now covers that case
+ * too.  This has been left here just in case.
+ */
+#if !defined(DUK_F_BYTEORDER) && defined(DUK_F_FLASHPLAYER)
+#define DUK_F_BYTEORDER 1
+#endif
+
+/* QNX gcc cross compiler seems to define e.g. __LITTLEENDIAN__ or __BIGENDIAN__:
+ *  $ /opt/qnx650/host/linux/x86/usr/bin/i486-pc-nto-qnx6.5.0-gcc -dM -E - </dev/null | grep -ni endian
+ *  67:#define __LITTLEENDIAN__ 1
+ *  $ /opt/qnx650/host/linux/x86/usr/bin/mips-unknown-nto-qnx6.5.0-gcc -dM -E - </dev/null | grep -ni endian
+ *  81:#define __BIGENDIAN__ 1
+ *  $ /opt/qnx650/host/linux/x86/usr/bin/arm-unknown-nto-qnx6.5.0-gcc -dM -E - </dev/null | grep -ni endian
+ *  70:#define __LITTLEENDIAN__ 1
+ */
+#if !defined(DUK_F_BYTEORDER) && defined(DUK_F_QNX)
+/* XXX: ARM hybrid? */
+#if defined(__LITTLEENDIAN__)
+#define DUK_F_BYTEORDER 1
+#elif defined(__BIGENDIAN__)
+#define DUK_F_BYTEORDER 3
+#endif
+#endif
+
+/* Bruce's C Compiler (BCC), assume we're on x86. */
+#if !defined(DUK_F_BYTEORDER) && defined(DUK_F_BCC)
+#define DUK_F_BYTEORDER 1
+#endif
+
+/* Check whether or not byte order detection worked based on the intermediate
+ * define, and define final values.  If detection failed, #error out.
+ */
+#if defined(DUK_F_BYTEORDER)
+#if (DUK_F_BYTEORDER == 1)
+#define DUK_USE_INTEGER_LE
+#define DUK_USE_DOUBLE_LE
+#elif (DUK_F_BYTEORDER == 2)
+#define DUK_USE_INTEGER_LE  /* integer endianness is little on purpose */
+#define DUK_USE_DOUBLE_ME
+#elif (DUK_F_BYTEORDER == 3)
+#define DUK_USE_INTEGER_BE
+#define DUK_USE_DOUBLE_BE
+#else
+#error unsupported: byte order detection failed (internal error, should not happen)
+#endif  /* byte order */
+#else
+#error unsupported: byte order detection failed
+#endif  /* defined(DUK_F_BYTEORDER) */
+
+/*
+ *  Check whether or not a packed duk_tval representation is possible.
+ *  What's basically required is that pointers are 32-bit values
+ *  (sizeof(void *) == 4).  Best effort check, not always accurate.
+ *  If guess goes wrong, crashes may result; self tests also verify
+ *  the guess.
+ */
+
+#undef DUK_USE_PACKED_TVAL_POSSIBLE
+
+/* Strict C99 case: DUK_UINTPTR_MAX (= UINTPTR_MAX) should be very reliable */
+#if !defined(DUK_USE_PACKED_TVAL_POSSIBLE) && defined(DUK_F_HAVE_INTTYPES) && defined(DUK_UINTPTR_MAX)
+#if (DUK_UINTPTR_MAX <= 0xffffffffUL)
+#define DUK_USE_PACKED_TVAL_POSSIBLE
+#endif
+#endif
+
+/* Non-C99 case, still relying on DUK_UINTPTR_MAX, as long as it is not a computed value */
+#if !defined(DUK_USE_PACKED_TVAL_POSSIBLE) && defined(DUK_UINTPTR_MAX) && !defined(DUK_UINTPTR_MAX_COMPUTED)
+#if (DUK_UINTPTR_MAX <= 0xffffffffUL)
+#define DUK_USE_PACKED_TVAL_POSSIBLE
+#endif
+#endif
+
+/* DUK_SIZE_MAX (= SIZE_MAX) is often reliable */
+#if !defined(DUK_USE_PACKED_TVAL_POSSIBLE) && defined(DUK_SIZE_MAX) && !defined(DUK_SIZE_MAX_COMPUTED)
+#if DUK_SIZE_MAX <= 0xffffffffUL
+#define DUK_USE_PACKED_TVAL_POSSIBLE
+#endif
+#endif
+
+/* M68K: packed always possible */
+#if !defined(DUK_USE_PACKED_TVAL_POSSIBLE) && defined(DUK_F_M68K)
+#define DUK_USE_PACKED_TVAL_POSSIBLE
+#endif
+
+/* With Emscripten, force unpacked duk_tval just to be safe, as it seems to
+ * break at least on Firefox (probably IEEE double arithmetic is not 100%
+ * supported, especially for NaNs).
+ */
+#if defined(DUK_USE_PACKED_TVAL_POSSIBLE) && defined(DUK_F_EMSCRIPTEN)
+#undef DUK_USE_PACKED_TVAL_POSSIBLE
+#endif
+
+/* Microsoft Visual Studio 2010 on x64 fails the above rules and tries to
+ * use a packed type.  Force unpacked on x64 in general.
+ */
+#if defined(DUK_USE_PACKED_TVAL_POSSIBLE) && defined(DUK_F_X64)
+#undef DUK_USE_PACKED_TVAL_POSSIBLE
+#endif
+
+/* GCC/clang inaccurate math would break compliance and probably duk_tval,
+ * so refuse to compile.  Relax this if -ffast-math is tested to work.
+ */
+#if defined(__FAST_MATH__)
+#error __FAST_MATH__ defined, refusing to compile
+#endif
+
+/*
+ *  Detection of double constants and math related functions.  Availability
+ *  of constants and math functions is a significant porting concern.
+ *
+ *  INFINITY/HUGE_VAL is problematic on GCC-3.3: it causes an overflow warning
+ *  and there is no pragma in GCC-3.3 to disable it.  Using __builtin_inf()
+ *  avoids this problem for some reason.
+ */
+
+#define DUK_DOUBLE_2TO32     4294967296.0
+#define DUK_DOUBLE_2TO31     2147483648.0
+
+#undef DUK_USE_COMPUTED_INFINITY
+#if defined(DUK_F_GCC_VERSION) && (DUK_F_GCC_VERSION < 40600)
+/* GCC older than 4.6: avoid overflow warnings related to using INFINITY */
+#define DUK_DOUBLE_INFINITY  (__builtin_inf())
+#elif defined(INFINITY)
+#define DUK_DOUBLE_INFINITY  ((double) INFINITY)
+#elif !defined(DUK_F_VBCC) && !defined(DUK_F_MSVC) && !defined(DUK_F_BCC)
+#define DUK_DOUBLE_INFINITY  (1.0 / 0.0)
+#else
+/* In VBCC (1.0 / 0.0) results in a warning and 0.0 instead of infinity.
+ * Use a computed infinity (initialized when a heap is created at the
+ * latest).
+ */
+extern double duk_computed_infinity;
+#define DUK_USE_COMPUTED_INFINITY
+#define DUK_DOUBLE_INFINITY  duk_computed_infinity
+#endif
+
+#undef DUK_USE_COMPUTED_NAN
+#if defined(NAN)
+#define DUK_DOUBLE_NAN       NAN
+#elif !defined(DUK_F_VBCC) && !defined(DUK_F_MSVC) && !defined(DUK_F_BCC)
+#define DUK_DOUBLE_NAN       (0.0 / 0.0)
+#else
+/* In VBCC (0.0 / 0.0) results in a warning and 0.0 instead of NaN.
+ * In MSVC (VS2010 Express) (0.0 / 0.0) results in a compile error.
+ * Use a computed NaN (initialized when a heap is created at the
+ * latest).
+ */
+extern double duk_computed_nan;
+#define DUK_USE_COMPUTED_NAN
+#define DUK_DOUBLE_NAN       duk_computed_nan
+#endif
+
+/* Many platforms are missing fpclassify() and friends, so use replacements
+ * if necessary.  The replacement constants (FP_NAN etc) can be anything but
+ * match Linux constants now.
+ */
+#undef DUK_USE_REPL_FPCLASSIFY
+#undef DUK_USE_REPL_SIGNBIT
+#undef DUK_USE_REPL_ISFINITE
+#undef DUK_USE_REPL_ISNAN
+#undef DUK_USE_REPL_ISINF
+
+/* Complex condition broken into separate parts. */
+#undef DUK_F_USE_REPL_ALL
+#if !(defined(FP_NAN) && defined(FP_INFINITE) && defined(FP_ZERO) && \
+      defined(FP_SUBNORMAL) && defined(FP_NORMAL))
+/* Missing some obvious constants. */
+#define DUK_F_USE_REPL_ALL
+#elif defined(DUK_F_AMIGAOS) && defined(DUK_F_VBCC)
+/* VBCC is missing the built-ins even in C99 mode (perhaps a header issue) */
+#define DUK_F_USE_REPL_ALL
+#elif defined(DUK_F_FREEBSD) && defined(DUK_F_CLANG)
+/* Placeholder fix for (detection is wider than necessary):
+ * http://llvm.org/bugs/show_bug.cgi?id=17788
+ */
+#define DUK_F_USE_REPL_ALL
+#elif defined(DUK_F_UCLIBC)
+/* At least some uclibc versions have broken floating point math.  For
+ * example, fpclassify() can incorrectly classify certain NaN formats.
+ * To be safe, use replacements.
+ */
+#define DUK_F_USE_REPL_ALL
+#endif
+
+#if defined(DUK_F_USE_REPL_ALL)
+#define DUK_USE_REPL_FPCLASSIFY
+#define DUK_USE_REPL_SIGNBIT
+#define DUK_USE_REPL_ISFINITE
+#define DUK_USE_REPL_ISNAN
+#define DUK_USE_REPL_ISINF
+#define DUK_FPCLASSIFY       duk_repl_fpclassify
+#define DUK_SIGNBIT          duk_repl_signbit
+#define DUK_ISFINITE         duk_repl_isfinite
+#define DUK_ISNAN            duk_repl_isnan
+#define DUK_ISINF            duk_repl_isinf
+#define DUK_FP_NAN           0
+#define DUK_FP_INFINITE      1
+#define DUK_FP_ZERO          2
+#define DUK_FP_SUBNORMAL     3
+#define DUK_FP_NORMAL        4
+#else
+#define DUK_FPCLASSIFY       fpclassify
+#define DUK_SIGNBIT          signbit
+#define DUK_ISFINITE         isfinite
+#define DUK_ISNAN            isnan
+#define DUK_ISINF            isinf
+#define DUK_FP_NAN           FP_NAN
+#define DUK_FP_INFINITE      FP_INFINITE
+#define DUK_FP_ZERO          FP_ZERO
+#define DUK_FP_SUBNORMAL     FP_SUBNORMAL
+#define DUK_FP_NORMAL        FP_NORMAL
+#endif
+
+#if defined(DUK_F_USE_REPL_ALL)
+#undef DUK_F_USE_REPL_ALL
+#endif
+
+/* Some math functions are C99 only.  This is also an issue with some
+ * embedded environments using uclibc where uclibc has been configured
+ * not to provide some functions.  For now, use replacements whenever
+ * using uclibc.
+ */
+#undef DUK_USE_MATH_FMIN
+#undef DUK_USE_MATH_FMAX
+#undef DUK_USE_MATH_ROUND
+#if defined(DUK_F_UCLIBC)
+/* uclibc may be missing these */
+#elif defined(DUK_F_AMIGAOS) && defined(DUK_F_VBCC)
+/* vbcc + AmigaOS may be missing these */
+#elif !defined(DUK_F_C99) && !defined(DUK_F_CPP11)
+/* build is not C99 or C++11, play it safe */
+#else
+/* C99 or C++11, no known issues */
+#define DUK_USE_MATH_FMIN
+#define DUK_USE_MATH_FMAX
+#define DUK_USE_MATH_ROUND
+#endif
+
+/* These functions don't currently need replacement but are wrapped for
+ * completeness.  Because these are used as function pointers, they need
+ * to be defined as concrete C functions (not macros).
+ */
+#define DUK_FABS             fabs
+#define DUK_FMIN             fmin
+#define DUK_FMAX             fmax
+#define DUK_FLOOR            floor
+#define DUK_CEIL             ceil
+#define DUK_FMOD             fmod
+#define DUK_POW              pow
+#define DUK_ACOS             acos
+#define DUK_ASIN             asin
+#define DUK_ATAN             atan
+#define DUK_ATAN2            atan2
+#define DUK_SIN              sin
+#define DUK_COS              cos
+#define DUK_TAN              tan
+#define DUK_EXP              exp
+#define DUK_LOG              log
+#define DUK_SQRT             sqrt
+
+/* NetBSD 6.0 x86 (at least) has a few problems with pow() semantics,
+ * see test-bug-netbsd-math-pow.js.  Use NetBSD specific workaround.
+ * (This might be a wider problem; if so, generalize the define name.)
+ */
+#undef DUK_USE_POW_NETBSD_WORKAROUND
+#if defined(DUK_F_NETBSD)
+#define DUK_USE_POW_NETBSD_WORKAROUND
+#endif
+
+/* Rely as little as possible on compiler behavior for NaN comparison,
+ * signed zero handling, etc.  Currently never activated but may be needed
+ * for broken compilers.
+ */
+#undef DUK_USE_PARANOID_MATH
+
+/* There was a curious bug where test-bi-date-canceling.js would fail e.g.
+ * on 64-bit Ubuntu, gcc-4.8.1, -m32, and no -std=c99.  Some date computations
+ * using doubles would be optimized which then broke some corner case tests.
+ * The problem goes away by adding 'volatile' to the datetime computations.
+ * Not sure what the actual triggering conditions are, but using this on
+ * non-C99 systems solves the known issues and has relatively little cost
+ * on other platforms.  See bugs/issue-2e9d9c2d761dabaf8136c0897b91a270d1a47147.yaml.
+ */
+#undef DUK_USE_PARANOID_DATE_COMPUTATION
+#if !defined(DUK_F_C99)
+#define DUK_USE_PARANOID_DATE_COMPUTATION
+#endif
+
+/*
+ *  ANSI C string/memory function wrapper defines to allow easier workarounds.
+ *  Also convenience macros like DUK_MEMZERO which may be mapped to existing
+ *  platform function to zero memory (like the deprecated bzero).
+ *
+ *  For instance, some platforms don't support zero-size memcpy correctly,
+ *  some arcane uclibc versions have a buggy memcpy (but working memmove)
+ *  and so on.  Such broken platforms can be dealt with here.
+ *
+ *  NOTE: ANSI C (various versions) and some implementations require that the
+ *  pointer arguments to memset(), memcpy(), and memmove() be valid values
+ *  even when byte size is 0 (even a NULL pointer is considered invalid in
+ *  this context).  Zero-size operations as such are allowed, as long as their
+ *  pointer arguments point to a valid memory area.  The DUK_MEMSET(),
+ *  DUK_MEMCPY(), and DUK_MEMMOVE() macros require this same behavior, i.e.:
+ *  (1) pointers must be valid and non-NULL, (2) zero size must otherwise be
+ *  allowed.  If these are not fulfilled, a macro wrapper is needed.
+ *
+ *    http://stackoverflow.com/questions/5243012/is-it-guaranteed-to-be-safe-to-perform-memcpy0-0-0
+ *    http://lists.cs.uiuc.edu/pipermail/llvmdev/2007-October/011065.html
+ *
+ *  Not sure what's the required behavior when a pointer points just past the
+ *  end of a buffer, which often happens in practice (e.g. zero size memmoves).
+ *  For example, if allocation size is 3, the following pointer would not
+ *  technically point to a valid memory byte:
+ *
+ *    <-- alloc -->
+ *    | 0 | 1 | 2 | .....
+ *                  ^-- p=3, points after last valid byte (2)
+ *
+ *  If this is a practical issue, wrappers are again needed.
+ */
+
+typedef FILE duk_file;
+#define DUK_STDIN       stdin
+#define DUK_STDOUT      stdout
+#define DUK_STDERR      stderr
+
+/* Special naming to avoid conflict with e.g. DUK_FREE() in duk_heap.h
+ * (which is unfortunately named).
+ */
+#define DUK_ANSI_MALLOC      malloc
+#define DUK_ANSI_REALLOC     realloc
+#define DUK_ANSI_CALLOC      calloc
+#define DUK_ANSI_FREE        free
+
+/* Old uclibcs have a broken memcpy so use memmove instead (this is overly
+ * wide now on purpose):
+ * http://lists.uclibc.org/pipermail/uclibc-cvs/2008-October/025511.html
+ */
+#if defined(DUK_F_UCLIBC)
+#define DUK_MEMCPY       memmove
+#else
+#define DUK_MEMCPY       memcpy
+#endif
+
+#define DUK_MEMMOVE      memmove
+#define DUK_MEMCMP       memcmp
+#define DUK_MEMSET       memset
+#define DUK_STRLEN       strlen
+#define DUK_STRCMP       strcmp
+#define DUK_STRNCMP      strncmp
+#define DUK_PRINTF       printf
+#define DUK_FPRINTF      fprintf
+#define DUK_SPRINTF      sprintf
+
+#if defined(DUK_F_MSVC)
+/* _snprintf() does NOT NUL terminate on truncation, but Duktape code never
+ * assumes that.
+ * http://stackoverflow.com/questions/2915672/snprintf-and-visual-studio-2010
+ */
+#define DUK_SNPRINTF     _snprintf
+#else
+#define DUK_SNPRINTF     snprintf
+#endif
+
+#define DUK_VSPRINTF     vsprintf
+
+#if defined(DUK_F_MSVC)
+#if (_MSC_VER < 1600)
+/* Older MSVC version are missing vsnprintf() but have _vsnprintf(). */
+#define DUK_VSNPRINTF    _vsnprintf
+#else
+#define DUK_VSNPRINTF    vsnprintf
+#endif
+#else
+#define DUK_VSNPRINTF    vsnprintf
+#endif  /* DUK_F_MSVC */
+
+#define DUK_SSCANF       sscanf
+#define DUK_VSSCANF      vsscanf
+#define DUK_FOPEN        fopen
+#define DUK_FCLOSE       fclose
+#define DUK_FREAD        fread
+#define DUK_FWRITE       fwrite
+#define DUK_FSEEK        fseek
+#define DUK_FTELL        ftell
+#define DUK_FFLUSH       fflush
+#define DUK_FPUTC        fputc
+
+#define DUK_MEMZERO(p,n) \
+	DUK_MEMSET((p), 0, (n))
+
+/*
+ *  Avoiding platform function pointers.
+ *
+ *  On some platforms built-in functions may be implemented as macros or
+ *  inline functions, so they can't be necessarily addressed by function
+ *  pointers.  This is certainly the case with some platform "polyfills"
+ *  which provide missing C99/C++11 functions through macros, and may be
+ *  the case with VS2013 (see GH-17).
+ */
+
+/* This is now the default: the cost in footprint is negligible. */
+#define DUK_USE_AVOID_PLATFORM_FUNCPTRS
+
+/*
+ *  Vararg macro wrappers.  We need va_copy() which is defined in C99 / C++11,
+ *  so an awkward replacement is needed for pre-C99 / pre-C++11 environments.
+ *  This will quite likely need portability hacks for some non-C99 environments.
+ */
+
+#if defined(DUK_F_C99) || defined(DUK_F_CPP11)
+/* C99 / C++11 and above: rely on va_copy() which is required.
+ * Omit parenthesis on macro right side on purpose to minimize differences
+ * to direct use.
+ */
+#define DUK_VA_COPY(dest,src) va_copy(dest,src)
+#elif defined(DUK_F_GCC) || defined(DUK_F_CLANG)
+/* GCC: assume we have __va_copy() in non-C99 mode, which should be correct
+ * for even quite old GCC versions.  Clang matches GCC behavior.
+ */
+#define DUK_VA_COPY(dest,src) __va_copy(dest,src)
+#else
+/* Pre-C99: va_list type is implementation dependent.  This replacement
+ * assumes it is a plain value so that a simple assignment will work.
+ * This is not the case on all platforms (it may be a single-array element,
+ * for instance).
+ */
+#define DUK_VA_COPY(dest,src) do { (dest) = (src); } while (0)
+#endif
+
+/*
+ *  Miscellaneous ANSI C or other platform wrappers.
+ */
+
+#define DUK_ABORT        abort
+#define DUK_EXIT         exit
+#define DUK_SETJMP       setjmp
+#define DUK_LONGJMP      longjmp
+
+/*
+ *  Macro hackery to convert e.g. __LINE__ to a string without formatting,
+ *  see: http://stackoverflow.com/questions/240353/convert-a-preprocessor-token-to-a-string
+ */
+
+#define DUK_F_STRINGIFY_HELPER(x)  #x
+#define DUK_MACRO_STRINGIFY(x)  DUK_F_STRINGIFY_HELPER(x)
+
+/*
+ *  Cause segfault macro.
+ *
+ *  This is optionally used by panic handling to cause the program to segfault
+ *  (instead of e.g. abort()) on panic.  Valgrind will then indicate the C
+ *  call stack leading to the panic.
+ */
+
+#define DUK_CAUSE_SEGFAULT()  do { \
+		*((duk_uint32_t *) NULL) = (duk_uint32_t) 0xdeadbeefUL; \
+	} while (0)
+
+/*
+ *  Macro for suppressing warnings for potentially unreferenced variables.
+ *  The variables can be actually unreferenced or unreferenced in some
+ *  specific cases only; for instance, if a variable is only debug printed,
+ *  it is unreferenced when debug printing is disabled.
+ *
+ *  (Introduced here because it's potentially compiler specific.)
+ */
+
+#define DUK_UNREF(x)  do { \
+		(void) (x); \
+	} while (0)
+
+/*
+ *  DUK_NORETURN: macro for declaring a 'noreturn' function.
+ *  Unfortunately the noreturn declaration may appear in various
+ *  places of a function declaration, so the solution is to wrap
+ *  the entire declaration inside the macro.  Compiler support
+ *  for using a noreturn declaration on function pointers varies;
+ *  this macro must only be used for actual function declarations.
+ *
+ *  http://gcc.gnu.org/onlinedocs/gcc-4.3.2//gcc/Function-Attributes.html
+ *  http://clang.llvm.org/docs/LanguageExtensions.html
+ */
+
+#if defined(DUK_F_GCC_VERSION) && (DUK_F_GCC_VERSION >= 20500L)
+/* since gcc-2.5 */
+#define DUK_NORETURN(decl)  decl __attribute__((noreturn))
+#elif defined(__clang__)
+/* syntax same as gcc */
+#define DUK_NORETURN(decl)  decl __attribute__((noreturn))
+#elif defined(DUK_F_MSVC)
+/* http://msdn.microsoft.com/en-us/library/aa235362(VS.60).aspx */
+#define DUK_NORETURN(decl)  __declspec(noreturn) decl
+#else
+/* Don't know how to declare a noreturn function, so don't do it; this
+ * may cause some spurious compilation warnings (e.g. "variable used
+ * uninitialized").
+ */
+#define DUK_NORETURN(decl)  decl
+#endif
+
+/*
+ *  Macro for stating that a certain line cannot be reached.
+ *
+ *  http://gcc.gnu.org/onlinedocs/gcc-4.5.0/gcc/Other-Builtins.html#Other-Builtins
+ *  http://clang.llvm.org/docs/LanguageExtensions.html
+ */
+
+#if defined(DUK_F_GCC_VERSION) && (DUK_F_GCC_VERSION >= 40500L)
+/* since gcc-4.5 */
+#define DUK_UNREACHABLE()  do { __builtin_unreachable(); } while(0)
+#elif defined(__clang__) && defined(__has_builtin)
+#if __has_builtin(__builtin_unreachable)
+/* same as gcc */
+#define DUK_UNREACHABLE()  do { __builtin_unreachable(); } while(0)
+#endif
+#else
+/* unknown */
+#endif
+
+#if !defined(DUK_UNREACHABLE)
+/* Don't know how to declare unreachable point, so don't do it; this
+ * may cause some spurious compilation warnings (e.g. "variable used
+ * uninitialized").
+ */
+#define DUK_UNREACHABLE()  /* unreachable */
+#endif
+
+/*
+ *  Likely and unlikely branches.  Using these is not at all a clear cut case,
+ *  so the selection is a two-step process: (1) DUK_USE_BRANCH_HINTS is set
+ *  if the architecture, compiler etc make it useful to use the hints, and (2)
+ *  a separate check determines how to do them.
+ *
+ *  These macros expect the argument to be a relational expression with an
+ *  integer value.  If used with pointers, you should use an explicit check
+ *  like:
+ *
+ *    if (DUK_LIKELY(ptr != NULL)) { ... }
+ *
+ *  instead of:
+ *
+ *    if (DUK_LIKELY(ptr)) { ... }
+ *
+ *  http://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html  (__builtin_expect)
+ */
+
+/* pretty much a placeholder now */
+#if defined(DUK_F_GCC)
+#define DUK_USE_BRANCH_HINTS
+#elif defined(DUK_F_CLANG)
+#define DUK_USE_BRANCH_HINTS
+#else
+#undef DUK_USE_BRANCH_HINTS
+#endif
+
+#if defined(DUK_USE_BRANCH_HINTS)
+#if defined(DUK_F_GCC_VERSION) && (DUK_F_GCC_VERISON >= 40500L)
+/* GCC: test not very accurate; enable only in relatively recent builds
+ * because of bugs in gcc-4.4 (http://lists.debian.org/debian-gcc/2010/04/msg00000.html)
+ */
+#define DUK_LIKELY(x)    __builtin_expect((x), 1)
+#define DUK_UNLIKELY(x)  __builtin_expect((x), 0)
+#elif defined(DUK_F_CLANG)
+#define DUK_LIKELY(x)    __builtin_expect((x), 1)
+#define DUK_UNLIKELY(x)  __builtin_expect((x), 0)
+#endif
+#endif  /* DUK_USE_BRANCH_HINTS */
+
+#if !defined(DUK_LIKELY)
+#define DUK_LIKELY(x)    (x)
+#endif
+#if !defined(DUK_UNLIKELY)
+#define DUK_UNLIKELY(x)  (x)
+#endif
+
+/*
+ *  __FILE__, __LINE__, __func__ are wrapped.  Especially __func__ is a
+ *  problem because it is not available even in some compilers which try
+ *  to be C99 compatible (e.g. VBCC with -c99 option).
+ */
+
+#define DUK_FILE_MACRO  __FILE__
+
+#define DUK_LINE_MACRO  __LINE__
+
+#if !defined(DUK_F_VBCC) && !defined(DUK_F_MSVC)
+#define DUK_FUNC_MACRO  __func__
+#else
+#define DUK_FUNC_MACRO  "unknown"
+#endif
+
+/*
+ *  Architecture string, human readable value exposed in Duktape.env
+ */
+
+#if defined(DUK_F_X86)
+#define DUK_USE_ARCH_STRING "x86"
+#elif defined(DUK_F_X32)
+#define DUK_USE_ARCH_STRING "x32"
+#elif defined(DUK_F_X64)
+#define DUK_USE_ARCH_STRING "x64"
+#elif defined(DUK_F_ARM)
+#define DUK_USE_ARCH_STRING "arm"
+#elif defined(DUK_F_MIPS)
+#define DUK_USE_ARCH_STRING "mips"
+#elif defined(DUK_F_M68K)
+#define DUK_USE_ARCH_STRING "m68k"
+#elif defined(DUK_F_FLASHPLAYER)
+#define DUK_USE_ARCH_STRING "flashplayer"
+#elif defined(DUK_F_EMSCRIPTEN)
+#define DUK_USE_ARCH_STRING "emscripten"
+#else
+#define DUK_USE_ARCH_STRING "unknown"
+#endif
+
+/* 
+ *  Tagged type representation (duk_tval)
+ */
+
+#undef DUK_USE_PACKED_TVAL
+#undef DUK_USE_FULL_TVAL
+
+#if defined(DUK_USE_PACKED_TVAL_POSSIBLE) && !defined(DUK_OPT_NO_PACKED_TVAL)
+#define DUK_USE_PACKED_TVAL
+#undef DUK_USE_FULL_TVAL
+#endif
+
+/*
+ *  Memory management options
+ */
+
+#define DUK_USE_REFERENCE_COUNTING
+#define DUK_USE_DOUBLE_LINKED_HEAP
+#define DUK_USE_MARK_AND_SWEEP
+#define DUK_USE_MS_STRINGTABLE_RESIZE
+#undef DUK_USE_GC_TORTURE
+
+#if defined(DUK_OPT_NO_REFERENCE_COUNTING)
+#undef DUK_USE_REFERENCE_COUNTING
+#undef DUK_USE_DOUBLE_LINKED_HEAP
+/* XXX: undef DUK_USE_MS_STRINGTABLE_RESIZE as it is more expensive
+ * with more frequent mark-and-sweeps?
+ */
+#endif
+
+#if defined(DUK_OPT_NO_MARK_AND_SWEEP)
+#undef DUK_USE_MARK_AND_SWEEP
+#endif
+
+#if defined(DUK_USE_MARK_AND_SWEEP)
+#define DUK_USE_VOLUNTARY_GC
+#if defined(DUK_OPT_NO_VOLUNTARY_GC)
+#undef DUK_USE_VOLUNTARY_GC
+#endif
+#endif
+
+#if !defined(DUK_USE_MARK_AND_SWEEP) && !defined(DUK_USE_REFERENCE_COUNTING)
+#error must have either mark-and-sweep or reference counting enabled
+#endif
+
+#if defined(DUK_OPT_NO_MS_STRINGTABLE_RESIZE)
+#undef DUK_USE_MS_STRINGTABLE_RESIZE
+#endif
+
+#if defined(DUK_OPT_GC_TORTURE)
+#define DUK_USE_GC_TORTURE
+#endif
+
+/*
+ *  Error handling options
+ */
+
+#define DUK_USE_AUGMENT_ERROR_CREATE
+#define DUK_USE_AUGMENT_ERROR_THROW
+#define DUK_USE_TRACEBACKS
+#define DUK_USE_ERRCREATE
+#define DUK_USE_ERRTHROW
+
+#define DUK_USE_VERBOSE_ERRORS
+
+#if defined(DUK_OPT_NO_AUGMENT_ERRORS)
+#undef DUK_USE_AUGMENT_ERROR_CREATE
+#undef DUK_USE_AUGMENT_ERROR_THROW
+#undef DUK_USE_TRACEBACKS
+#undef DUK_USE_ERRCREATE
+#undef DUK_USE_ERRTHROW
+#elif defined(DUK_OPT_NO_TRACEBACKS)
+#undef DUK_USE_TRACEBACKS
+#endif
+
+#if defined(DUK_OPT_NO_VERBOSE_ERRORS)
+#undef DUK_USE_VERBOSE_ERRORS
+#endif
+
+#if defined(DUK_USE_TRACEBACKS)
+#if defined(DUK_OPT_TRACEBACK_DEPTH)
+#define DUK_USE_TRACEBACK_DEPTH  DUK_OPT_TRACEBACK_DEPTH
+#else
+#define DUK_USE_TRACEBACK_DEPTH  10
+#endif
+#endif
+
+/* Include messages in executor internal errors. */
+#define DUK_USE_VERBOSE_EXECUTOR_ERRORS
+
+/*
+ *  Execution and debugger options
+ */
+
+#define DUK_USE_INTERRUPT_COUNTER
+#if defined(DUK_OPT_NO_INTERRUPT_COUNTER)
+#undef DUK_USE_INTERRUPT_COUNTER
+#endif
+
+/* For opcodes with indirect indices, check final index against stack size.
+ * This should not be necessary because the compiler is trusted, and we don't
+ * bound check non-indirect indices either.
+ */
+#undef DUK_USE_EXEC_INDIRECT_BOUND_CHECK
+#if defined(DUK_OPT_DEBUG) || defined(DUK_OPT_ASSERTIONS)
+/* Enabled with debug/assertions just so that any issues can be caught. */
+#define DUK_USE_EXEC_INDIRECT_BOUND_CHECK
+#endif
+
+/*
+ *  Debug printing and assertion options
+ */
+
+#undef DUK_USE_DEBUG
+#undef DUK_USE_DPRINT
+#undef DUK_USE_DDPRINT
+#undef DUK_USE_DDDPRINT
+#undef DUK_USE_DPRINT_RDTSC
+#undef DUK_USE_ASSERTIONS
+
+/* Global debug enable.  Compile must be clean on C99 regardless of whether or
+ * not debugging is enabled.  On non-C99 platforms compile should be clean with
+ * debugging disabled but may produce warnings with debugging enabled (related
+ * to debug macro hackery and such).
+ */
+#if defined(DUK_OPT_DEBUG)
+#define DUK_USE_DEBUG
+#endif
+
+#if defined(DUK_OPT_DEBUG) && defined(DUK_OPT_DPRINT)
+#define DUK_USE_DPRINT
+#endif
+#if defined(DUK_OPT_DEBUG) && defined(DUK_OPT_DDPRINT)
+#define DUK_USE_DDPRINT
+#endif
+#if defined(DUK_OPT_DEBUG) && defined(DUK_OPT_DDDPRINT)
+#define DUK_USE_DDDPRINT
+#endif
+
+#undef DUK_USE_DPRINT_COLORS
+#if defined(DUK_OPT_DPRINT_COLORS)
+#define DUK_USE_DPRINT_COLORS
+#endif
+
+#if defined(DUK_RDTSC_AVAILABLE) && defined(DUK_OPT_DPRINT_RDTSC)
+#define DUK_USE_DPRINT_RDTSC
+#else
+#undef DUK_USE_DPRINT_RDTSC
+#endif
+
+#if defined(DUK_OPT_ASSERTIONS)
+#define DUK_USE_ASSERTIONS
+#endif
+
+/* The static buffer for debug printing is quite large by default, so there
+ * is an option to shrink it manually for constrained builds.
+ */
+#if defined(DUK_OPT_DEBUG_BUFSIZE)
+#define DUK_USE_DEBUG_BUFSIZE  DUK_OPT_DEBUG_BUFSIZE
+#else
+#define DUK_USE_DEBUG_BUFSIZE  65536L
+#endif
+
+/*
+ *  Ecmascript features / compliance options
+ */
+
+#if defined(DUK_F_BCC)
+/* Math built-in is stubbed out on BCC to allow compiler torture testing. */
+#else
+#define DUK_USE_MATH_BUILTIN
+#endif
+
+#define DUK_USE_REGEXP_SUPPORT
+#if defined(DUK_OPT_NO_REGEXP_SUPPORT)
+#undef DUK_USE_REGEXP_SUPPORT
+#endif
+
+#undef DUK_USE_STRICT_UTF8_SOURCE
+#if defined(DUK_OPT_STRICT_UTF8_SOURCE)
+#define DUK_USE_STRICT_UTF8_SOURCE
+#endif
+
+#define DUK_USE_OCTAL_SUPPORT
+#if defined(DUK_OPT_NO_OCTAL_SUPPORT)
+#undef DUK_USE_OCTAL_SUPPORT
+#endif
+
+#define DUK_USE_SOURCE_NONBMP
+#if defined(DUK_OPT_NO_SOURCE_NONBMP)
+#undef DUK_USE_SOURCE_NONBMP
+#endif
+
+#define DUK_USE_BROWSER_LIKE
+#if defined(DUK_OPT_NO_BROWSER_LIKE)
+#undef DUK_USE_BROWSER_LIKE
+#endif
+
+/* E5/E5.1 Section B features. */
+#define DUK_USE_SECTION_B
+#if defined(DUK_OPT_NO_SECTION_B)
+#undef DUK_USE_SECTION_B
+#endif
+
+/* Non-standard regexp parsing features. */
+#define DUK_USE_NONSTD_REGEXP_DOLLAR_ESCAPE
+
+/* Treat function statements (function declarations outside top level of
+ * Program or FunctionBody) same as normal function declarations.  This is
+ * also V8 behavior.  See test-dev-func-decl-outside-top.js.
+ */ 
+#define DUK_USE_NONSTD_FUNC_STMT
+#if defined(DUK_OPT_NO_NONSTD_FUNC_STMT)
+#undef DUK_USE_NONSTD_FUNC_STMT
+#endif
+
+/* Array.prototype.splice() non-standard but real world compatible behavior
+ * when deleteCount is omitted.
+ */
+#define DUK_USE_NONSTD_ARRAY_SPLICE_DELCOUNT
+#if defined(DUK_OPT_NO_NONSTD_ARRAY_SPLICE_DELCOUNT)
+#undef DUK_USE_NONSTD_ARRAY_SPLICE_DELCOUNT
+#endif
+
+/* Non-standard 'caller' property for function instances, see
+ * test-bi-function-nonstd-caller-prop.js.
+ */
+#undef DUK_USE_NONSTD_FUNC_CALLER_PROPERTY
+#if defined(DUK_OPT_NONSTD_FUNC_CALLER_PROPERTY)
+#define DUK_USE_NONSTD_FUNC_CALLER_PROPERTY
+#endif
+
+/* Non-standard Object.prototype.__proto__ (ES6 draft), see
+ * test-bi-object-proto-__proto__.js.
+ */
+#define DUK_USE_ES6_OBJECT_PROTO_PROPERTY
+#if defined(DUK_OPT_NO_ES6_OBJECT_PROTO_PROPERTY)
+#undef DUK_USE_ES6_OBJECT_PROTO_PROPERTY
+#endif
+
+/* Non-standard Object.setPrototypeOf (ES6 draft), see
+ * test-bi-object-setprototypeof.js.
+ */
+#define DUK_USE_ES6_OBJECT_SETPROTOTYPEOF
+#if defined(DUK_OPT_NO_ES6_OBJECT_SETPROTOTYPEOF)
+#undef DUK_USE_ES6_OBJECT_SETPROTOTYPEOF
+#endif
+
+/* ES6 Proxy object (subset for now). */
+#define DUK_USE_ES6_PROXY
+#if defined(DUK_OPT_NO_ES6_PROXY)
+#undef DUK_USE_ES6_PROXY
+#endif
+
+/* Record pc-to-line information. */
+#define DUK_USE_PC2LINE
+#if defined(DUK_OPT_NO_PC2LINE)
+#undef DUK_USE_PC2LINE
+#endif
+
+/* Non-standard function 'source' property. */
+#undef DUK_USE_NONSTD_FUNC_SOURCE_PROPERTY
+#if defined(DUK_OPT_NONSTD_FUNC_SOURCE_PROPERTY)
+#define DUK_USE_NONSTD_FUNC_SOURCE_PROPERTY
+#endif
+
+/* CommonJS modules */
+#define DUK_USE_COMMONJS_MODULES
+#if defined(DUK_OPT_NO_COMMONJS_MODULES)
+#undef DUK_USE_COMMONJS_MODULES
+#endif
+
+/* Additional key argument to setter/getter calls when triggered by property
+ * accesses.
+ */
+
+#define DUK_USE_NONSTD_GETTER_KEY_ARGUMENT
+#define DUK_USE_NONSTD_SETTER_KEY_ARGUMENT
+#if defined(DUK_OPT_NO_NONSTD_ACCESSOR_KEY_ARGUMENT)
+#undef DUK_USE_NONSTD_GETTER_KEY_ARGUMENT
+#undef DUK_USE_NONSTD_SETTER_KEY_ARGUMENT
+#endif
+
+/*
+ *  Tailcalls
+ */
+
+/* Tailcalls are enabled by default.  The non-standard function 'caller'
+ * property feature conflicts with tailcalls quite severely so tailcalls
+ * are disabled if the 'caller' property is enabled.
+ */
+#define DUK_USE_TAILCALL
+#if defined(DUK_USE_NONSTD_FUNC_CALLER_PROPERTY)
+#undef DUK_USE_TAILCALL
+#endif
+
+/*
+ *  Deep vs. shallow stack.
+ *
+ *  Some embedded platforms have very shallow stack (e.g. 64kB); default to
+ *  a shallow stack on unknown platforms or known embedded platforms.
+ */
+
+#if defined(DUK_F_LINUX) || defined(DUK_F_BSD) || defined(DUK_F_WINDOWS) || \
+    defined(DUK_OPT_DEEP_C_STACK)
+#define DUK_USE_DEEP_C_STACK
+#else
+#undef DUK_USE_DEEP_C_STACK
+#endif
+
+/*
+ *  Ecmascript compiler
+ */
+
+/* Ensure final bytecode never exceeds a certain byte size and never uses
+ * line numbers above a certain limit.  This ensures that there is no need
+ * to deal with unbounded ranges in e.g. pc2line data structures.  For now,
+ * limits are set so that signed 32-bit values can represent line number
+ * and byte offset with room to spare.
+ */
+#define DUK_USE_ESBC_LIMITS
+#define DUK_USE_ESBC_MAX_LINENUMBER  0x7fff0000L
+#define DUK_USE_ESBC_MAX_BYTES       0x7fff0000L
+
+/*
+ *  User panic handler, panic exit behavior for default panic handler
+ */
+
+#undef DUK_USE_PANIC_HANDLER
+#if defined(DUK_OPT_PANIC_HANDLER)
+#define DUK_USE_PANIC_HANDLER(code,msg) DUK_OPT_PANIC_HANDLER((code),(msg))
+#endif
+
+#undef DUK_USE_PANIC_ABORT
+#undef DUK_USE_PANIC_EXIT
+#undef DUK_USE_PANIC_SEGFAULT
+
+#if defined(DUK_OPT_SEGFAULT_ON_PANIC)
+#define DUK_USE_PANIC_SEGFAULT
+#else
+#define DUK_USE_PANIC_ABORT
+#endif
+
+/*
+ *  File I/O support.  This is now used in a few API calls to e.g. push
+ *  a string from file contents or eval a file.  For portability it must
+ *  be possible to disable I/O altogether.
+ */
+
+#undef DUK_USE_FILE_IO
+#if !defined(DUK_OPT_NO_FILE_IO)
+#define DUK_USE_FILE_IO
+#endif
+
+/*
+ *  Optional run-time self tests executed when a heap is created.  Some
+ *  platform/compiler issues cannot be determined at compile time.  One
+ *  particular example is the bug described in misc/clang_aliasing.c.
+ */
+
+#undef DUK_USE_SELF_TESTS
+#if defined(DUK_OPT_SELF_TESTS)
+#define DUK_USE_SELF_TESTS
+#endif
+
+/* Double aliasing testcase fails when Emscripten-generated code is run
+ * on Firefox.  This is not fatal because it only affects packed duk_tval
+ * which we avoid with Emscripten.
+ */
+#undef DUK_USE_NO_DOUBLE_ALIASING_SELFTEST
+#if defined(DUK_F_EMSCRIPTEN)
+#define DUK_USE_NO_DOUBLE_ALIASING_SELFTEST
+#endif
+
+/*
+ *  Codecs
+ */
+
+#define DUK_USE_JX
+#if defined(DUK_OPT_NO_JX)
+#undef DUK_USE_JX
+#endif
+
+#define DUK_USE_JC
+#if defined(DUK_OPT_NO_JC)
+#undef DUK_USE_JC
+#endif
+
+/*
+ *  InitJS code
+ */
+
+/* Always use the built-in InitJS code for now. */
+#define DUK_USE_BUILTIN_INITJS
+
+/* User provided InitJS. */
+#undef DUK_USE_USER_INITJS
+#if defined(DUK_OPT_USER_INITJS)
+#define DUK_USE_USER_INITJS (DUK_OPT_USER_INITJS)
+#endif
+
+/*
+ *  Miscellaneous
+ */
+
+#define DUK_USE_PROVIDE_DEFAULT_ALLOC_FUNCTIONS
+#undef DUK_USE_EXPLICIT_NULL_INIT
+
+#if !defined(DUK_USE_PACKED_TVAL)
+#define DUK_USE_EXPLICIT_NULL_INIT
+#endif
+
+#define DUK_USE_ZERO_BUFFER_DATA
+#if defined(DUK_OPT_NO_ZERO_BUFFER_DATA)
+#undef DUK_USE_ZERO_BUFFER_DATA
+#endif
+
+#if defined(DUK_F_C99) || (defined(DUK_F_CPP11) && defined(__GNUC__))
+#define DUK_USE_VARIADIC_MACROS
+#else
+#undef DUK_USE_VARIADIC_MACROS
+#endif
+
+/*
+ *  Variable size array initialization.
+ *
+ *  Variable size array at the end of a structure is nonportable. 
+ *  There are three alternatives:
+ *
+ *    1) C99 (flexible array member): char buf[]
+ *    2) Compiler specific (e.g. GCC): char buf[0]
+ *    3) Portable but wastes memory / complicates allocation: char buf[1]
+ */
+
+/* XXX: Currently unused, only hbuffer.h needed this at some point. */
+#undef DUK_USE_FLEX_C99
+#undef DUK_USE_FLEX_ZEROSIZE
+#undef DUK_USE_FLEX_ONESIZE
+#if defined(DUK_F_C99)
+#define DUK_USE_FLEX_C99
+#elif defined(__GNUC__)
+#define DUK_USE_FLEX_ZEROSIZE
+#else
+#define DUK_USE_FLEX_ONESIZE
+#endif
+
+/*
+ *  GCC pragmas
+ */
+
+/* XXX: GCC pragma inside a function fails in some earlier GCC versions (e.g. gcc 4.5).
+ * This is very approximate but allows clean builds for development right now.
+ */
+/* http://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html */
+#if defined(__GNUC__) && defined(__GNUC_MINOR__) && (__GNUC__ == 4) && (__GNUC_MINOR__ >= 6)
+#define DUK_USE_GCC_PRAGMAS
+#else
+#undef DUK_USE_GCC_PRAGMAS
+#endif
+
+/*
+ *  User declarations
+ */
+
+#if defined(DUK_OPT_DECLARE)
+#define DUK_USE_USER_DECLARE() DUK_OPT_DECLARE
+#else
+#define DUK_USE_USER_DECLARE() /* no user declarations */
+#endif
+
+/*
+ *  Alternative customization header
+ *
+ *  If you want to modify the final DUK_USE_xxx flags directly (without
+ *  using the available DUK_OPT_Xxx flags), define DUK_OPT_HAVE_CUSTOM_H
+ *  and tweak the final flags there.
+ */
+
+#if defined(DUK_OPT_HAVE_CUSTOM_H)
+#include "duk_custom.h"
+#endif
+
+#endif  /* DUK_FEATURES_H_INCLUDED */
+
+/*
+ *  BEGIN PUBLIC API
+ */
+
+#ifndef DUK_API_PUBLIC_H_INCLUDED
+#define DUK_API_PUBLIC_H_INCLUDED
+
+/*
+ *  Avoid C++ name mangling
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ *  Some defines forwarded from feature detection
+ */
+
+#undef DUK_API_VARIADIC_MACROS
+#ifdef DUK_USE_VARIADIC_MACROS
+#define DUK_API_VARIADIC_MACROS
+#endif
+
+#define DUK_API_NORETURN(decl) DUK_NORETURN(decl)
+
+/*
+ *  Public API specific typedefs
+ *
+ *  (duk_context *) maps directly to internal type (duk_hthread *).
+ *  Currently only primitive typedefs have a '_t' suffix.
+ *
+ *  Many types are wrapped by Duktape for portability to rare platforms
+ *  where e.g. 'int' is a 16-bit type.  See practical typing discussion
+ *  in Duktape web documentation.
+ */
+
+struct duk_memory_functions;
+struct duk_function_list_entry;
+struct duk_number_list_entry;
+
+typedef void duk_context;
+typedef struct duk_memory_functions duk_memory_functions;
+typedef struct duk_function_list_entry duk_function_list_entry;
+typedef struct duk_number_list_entry duk_number_list_entry;
+
+typedef duk_ret_t (*duk_c_function)(duk_context *ctx);
+typedef void *(*duk_alloc_function) (void *udata, duk_size_t size);
+typedef void *(*duk_realloc_function) (void *udata, void *ptr, duk_size_t size);
+typedef void (*duk_free_function) (void *udata, void *ptr);
+typedef void (*duk_fatal_function) (duk_context *ctx, duk_errcode_t code, const char *msg);
+typedef void (*duk_decode_char_function) (void *udata, duk_codepoint_t codepoint);
+typedef duk_codepoint_t (*duk_map_char_function) (void *udata, duk_codepoint_t codepoint);
+typedef duk_ret_t (*duk_safe_call_function) (duk_context *ctx);
+
+struct duk_memory_functions {
+	duk_alloc_function alloc;
+	duk_realloc_function realloc;
+	duk_free_function free;
+	void *udata;
+};
+
+struct duk_function_list_entry {
+	const char *key;
+	duk_c_function value;
+	duk_int_t nargs;
+};
+
+struct duk_number_list_entry {
+	const char *key;
+	duk_double_t value;
+};
+
+/*
+ *  Constants
+ */
+
+/* Duktape version, (major * 10000) + (minor * 100) + patch.  Allows C code
+ * to #ifdef against Duktape API version.  The same value is also available
+ * to Ecmascript code in Duktape.version.  Unofficial development snapshots
+ * have 99 for patch level (e.g. 0.10.99 would be a development version
+ * after 0.10.0 but before the next official release).
+ */
+#define DUK_VERSION                       1100L
+
+/* Used to represent invalid index; if caller uses this without checking,
+ * this index will map to a non-existent stack entry.  Also used in some
+ * API calls as a marker to denote "no value".
+ */
+#define DUK_INVALID_INDEX                 INT_MIN 
+
+/* Indicates that a native function does not have a fixed number of args,
+ * and the argument stack should not be capped/extended at all.
+ */
+#define DUK_VARARGS                       ((duk_int_t) (-1))
+
+/* Number of value stack entries (in addition to actual call arguments)
+ * guaranteed to be allocated on entry to a Duktape/C function.
+ */
+#define DUK_API_ENTRY_STACK               64
+
+/* Value types, used by e.g. duk_get_type() */
+#define DUK_TYPE_NONE                     0    /* no value, e.g. invalid index */
+#define DUK_TYPE_UNDEFINED                1    /* Ecmascript undefined */
+#define DUK_TYPE_NULL                     2    /* Ecmascript null */
+#define DUK_TYPE_BOOLEAN                  3    /* Ecmascript boolean: 0 or 1 */
+#define DUK_TYPE_NUMBER                   4    /* Ecmascript number: double */
+#define DUK_TYPE_STRING                   5    /* Ecmascript string: CESU-8 / extended UTF-8 encoded */
+#define DUK_TYPE_OBJECT                   6    /* Ecmascript object: includes objects, arrays, functions, threads */
+#define DUK_TYPE_BUFFER                   7    /* fixed or dynamic, garbage collected byte buffer */
+#define DUK_TYPE_POINTER                  8    /* raw void pointer */
+
+/* Value mask types, used by e.g. duk_get_type_mask() */
+#define DUK_TYPE_MASK_NONE                (1 << DUK_TYPE_NONE)
+#define DUK_TYPE_MASK_UNDEFINED           (1 << DUK_TYPE_UNDEFINED)
+#define DUK_TYPE_MASK_NULL                (1 << DUK_TYPE_NULL)
+#define DUK_TYPE_MASK_BOOLEAN             (1 << DUK_TYPE_BOOLEAN)
+#define DUK_TYPE_MASK_NUMBER              (1 << DUK_TYPE_NUMBER)
+#define DUK_TYPE_MASK_STRING              (1 << DUK_TYPE_STRING)
+#define DUK_TYPE_MASK_OBJECT              (1 << DUK_TYPE_OBJECT)
+#define DUK_TYPE_MASK_BUFFER              (1 << DUK_TYPE_BUFFER)
+#define DUK_TYPE_MASK_POINTER             (1 << DUK_TYPE_POINTER)
+#define DUK_TYPE_MASK_THROW               (1 << 10)  /* internal flag value: throw if mask doesn't match */
+
+/* Coercion hints */
+#define DUK_HINT_NONE                     0    /* prefer number, unless input is a Date, in which
+                                                * case prefer string (E5 Section 8.12.8)
+                                                */
+#define DUK_HINT_STRING                   1    /* prefer string */
+#define DUK_HINT_NUMBER                   2    /* prefer number */
+
+/* Enumeration flags for duk_enum() */
+#define DUK_ENUM_INCLUDE_NONENUMERABLE    (1 << 0)    /* enumerate non-numerable properties in addition to enumerable */
+#define DUK_ENUM_INCLUDE_INTERNAL         (1 << 1)    /* enumerate internal properties (regardless of enumerability) */
+#define DUK_ENUM_OWN_PROPERTIES_ONLY      (1 << 2)    /* don't walk prototype chain, only check own properties */
+#define DUK_ENUM_ARRAY_INDICES_ONLY       (1 << 3)    /* only enumerate array indices */
+#define DUK_ENUM_SORT_ARRAY_INDICES       (1 << 4)    /* sort array indices, use with DUK_ENUM_ARRAY_INDICES_ONLY */
+#define DUK_ENUM_NO_PROXY_BEHAVIOR        (1 << 5)    /* enumerate a proxy object itself without invoking proxy behavior */
+
+/* Compilation flags for duk_compile() and duk_eval() */
+#define DUK_COMPILE_EVAL                  (1 << 0)    /* compile eval code (instead of program) */
+#define DUK_COMPILE_FUNCTION              (1 << 1)    /* compile function code (instead of program) */
+#define DUK_COMPILE_STRICT                (1 << 2)    /* use strict (outer) context for program, eval, or function */
+#define DUK_COMPILE_SAFE                  (1 << 3)    /* (internal) catch compilation errors */
+#define DUK_COMPILE_NORESULT              (1 << 4)    /* (internal) omit eval result */
+#define DUK_COMPILE_NOSOURCE              (1 << 5)    /* (internal) no source string on stack */
+#define DUK_COMPILE_STRLEN                (1 << 6)    /* (internal) take strlen() of src_buffer (avoids double evaluation in macro) */
+
+/* Flags for duk_push_thread_raw() */
+#define DUK_THREAD_NEW_GLOBAL_ENV         (1 << 0)    /* create a new global environment */
+
+/* Duktape specific error codes */
+#define DUK_ERR_UNIMPLEMENTED_ERROR       50   /* UnimplementedError */
+#define DUK_ERR_UNSUPPORTED_ERROR         51   /* UnsupportedError */
+#define DUK_ERR_INTERNAL_ERROR            52   /* InternalError */
+#define DUK_ERR_ALLOC_ERROR               53   /* AllocError */
+#define DUK_ERR_ASSERTION_ERROR           54   /* AssertionError */
+#define DUK_ERR_API_ERROR                 55   /* APIError */
+#define DUK_ERR_UNCAUGHT_ERROR            56   /* UncaughtError */
+
+/* Ecmascript E5 specification error codes */
+#define DUK_ERR_ERROR                     100  /* Error */
+#define DUK_ERR_EVAL_ERROR                101  /* EvalError */
+#define DUK_ERR_RANGE_ERROR               102  /* RangeError */
+#define DUK_ERR_REFERENCE_ERROR           103  /* ReferenceError */
+#define DUK_ERR_SYNTAX_ERROR              104  /* SyntaxError */
+#define DUK_ERR_TYPE_ERROR                105  /* TypeError */
+#define DUK_ERR_URI_ERROR                 106  /* URIError */
+
+/* Return codes for C functions (shortcut for throwing an error) */
+#define DUK_RET_UNIMPLEMENTED_ERROR       (-DUK_ERR_UNIMPLEMENTED_ERROR)
+#define DUK_RET_UNSUPPORTED_ERROR         (-DUK_ERR_UNSUPPORTED_ERROR)
+#define DUK_RET_INTERNAL_ERROR            (-DUK_ERR_INTERNAL_ERROR)
+#define DUK_RET_ALLOC_ERROR               (-DUK_ERR_ALLOC_ERROR)
+#define DUK_RET_ASSERTION_ERROR           (-DUK_ERR_ASSERTION_ERROR)
+#define DUK_RET_API_ERROR                 (-DUK_ERR_API_ERROR)
+#define DUK_RET_UNCAUGHT_ERROR            (-DUK_ERR_UNCAUGHT_ERROR)
+#define DUK_RET_ERROR                     (-DUK_ERR_ERROR)
+#define DUK_RET_EVAL_ERROR                (-DUK_ERR_EVAL_ERROR)
+#define DUK_RET_RANGE_ERROR               (-DUK_ERR_RANGE_ERROR)
+#define DUK_RET_REFERENCE_ERROR           (-DUK_ERR_REFERENCE_ERROR)
+#define DUK_RET_SYNTAX_ERROR              (-DUK_ERR_SYNTAX_ERROR)
+#define DUK_RET_TYPE_ERROR                (-DUK_ERR_TYPE_ERROR)
+#define DUK_RET_URI_ERROR                 (-DUK_ERR_URI_ERROR)
+
+/* Return codes for protected calls (duk_safe_call(), duk_pcall()). */
+#define DUK_EXEC_SUCCESS                  0
+#define DUK_EXEC_ERROR                    1
+
+/* Log levels */
+#define  DUK_LOG_TRACE                    0
+#define  DUK_LOG_DEBUG                    1
+#define  DUK_LOG_INFO                     2
+#define  DUK_LOG_WARN                     3
+#define  DUK_LOG_ERROR                    4
+#define  DUK_LOG_FATAL                    5
+
+/*
+ *  If no variadic macros, __FILE__ and __LINE__ are passed through globals
+ *  which is ugly and not thread safe.
+ */
+
+#ifndef DUK_API_VARIADIC_MACROS
+extern const char *duk_api_global_filename;
+extern duk_int_t duk_api_global_line;
+#endif
+
+/*
+ *  Context management
+ */
+
+duk_context *duk_create_heap(duk_alloc_function alloc_func,
+                             duk_realloc_function realloc_func,
+                             duk_free_function free_func,
+                             void *alloc_udata,
+                             duk_fatal_function fatal_handler);
+void duk_destroy_heap(duk_context *ctx);
+
+#define duk_create_heap_default() \
+	duk_create_heap(NULL, NULL, NULL, NULL, NULL)
+
+/*
+ *  Memory management
+ *
+ *  Raw functions have no side effects (cannot trigger GC).
+ */
+
+void *duk_alloc_raw(duk_context *ctx, duk_size_t size);
+void duk_free_raw(duk_context *ctx, void *ptr);
+void *duk_realloc_raw(duk_context *ctx, void *ptr, duk_size_t size);
+void *duk_alloc(duk_context *ctx, duk_size_t size);
+void duk_free(duk_context *ctx, void *ptr);
+void *duk_realloc(duk_context *ctx, void *ptr, duk_size_t size);
+void duk_get_memory_functions(duk_context *ctx, duk_memory_functions *out_funcs);
+void duk_gc(duk_context *ctx, duk_uint_t flags);
+
+/*
+ *  Error handling
+ */
+
+DUK_API_NORETURN(void duk_throw(duk_context *ctx));
+
+DUK_API_NORETURN(void duk_error_raw(duk_context *ctx, duk_errcode_t err_code, const char *filename, duk_int_t line, const char *fmt, ...));
+#ifdef DUK_API_VARIADIC_MACROS
+#define duk_error(ctx,err_code,...)  \
+	duk_error_raw((ctx), (duk_errcode_t) (err_code), __FILE__, (duk_int_t) __LINE__, __VA_ARGS__)
+#else
+DUK_API_NORETURN(void duk_error_stash(duk_context *ctx, duk_errcode_t err_code, const char *fmt, ...));
+#define duk_error  \
+	duk_api_global_filename = __FILE__, \
+	duk_api_global_line = (duk_int_t) __LINE__, \
+	duk_error_stash  /* arguments follow */
+#endif
+
+DUK_API_NORETURN(void duk_fatal(duk_context *ctx, duk_errcode_t err_code, const char *err_msg));
+
+/*
+ *  Other state related functions
+ */
+
+duk_bool_t duk_is_strict_call(duk_context *ctx);
+duk_bool_t duk_is_constructor_call(duk_context *ctx);
+duk_int_t duk_get_magic(duk_context *ctx);
+
+/*
+ *  Stack management
+ */
+
+duk_idx_t duk_normalize_index(duk_context *ctx, duk_idx_t index);
+duk_idx_t duk_require_normalize_index(duk_context *ctx, duk_idx_t index);
+duk_bool_t duk_is_valid_index(duk_context *ctx, duk_idx_t index);
+void duk_require_valid_index(duk_context *ctx, duk_idx_t index);
+
+duk_idx_t duk_get_top(duk_context *ctx);
+void duk_set_top(duk_context *ctx, duk_idx_t index);
+duk_idx_t duk_get_top_index(duk_context *ctx);
+duk_idx_t duk_require_top_index(duk_context *ctx);
+
+/* Although extra/top could be an unsigned type here, using a signed type
+ * makes the API more robust to calling code calculation errors or corner
+ * cases (where caller might occasionally come up with negative values).
+ * Negative values are treated as zero, which is better than casting them
+ * to a large unsigned number.  (This principle is used elsewhere in the
+ * API too.)
+ */
+duk_bool_t duk_check_stack(duk_context *ctx, duk_idx_t extra);
+void duk_require_stack(duk_context *ctx, duk_idx_t extra);
+duk_bool_t duk_check_stack_top(duk_context *ctx, duk_idx_t top);
+void duk_require_stack_top(duk_context *ctx, duk_idx_t top);
+
+/*
+ *  Stack manipulation (other than push/pop)
+ */
+
+void duk_swap(duk_context *ctx, duk_idx_t index1, duk_idx_t index2);
+void duk_swap_top(duk_context *ctx, duk_idx_t index);
+void duk_dup(duk_context *ctx, duk_idx_t from_index);
+void duk_dup_top(duk_context *ctx);
+void duk_insert(duk_context *ctx, duk_idx_t to_index);
+void duk_replace(duk_context *ctx, duk_idx_t to_index);
+void duk_copy(duk_context *ctx, duk_idx_t from_index, duk_idx_t to_index);
+void duk_remove(duk_context *ctx, duk_idx_t index);
+/* FIXME: undocumented */
+void duk_xmove(duk_context *from_ctx, duk_context *to_ctx, duk_idx_t count);
+
+/*
+ *  Push operations
+ *
+ *  Push functions return the absolute (relative to bottom of frame)
+ *  position of the pushed value for convenience.
+ *
+ *  Note: duk_dup() is technically a push.
+ */
+
+void duk_push_undefined(duk_context *ctx);
+void duk_push_null(duk_context *ctx);
+void duk_push_boolean(duk_context *ctx, duk_bool_t val);
+void duk_push_true(duk_context *ctx);
+void duk_push_false(duk_context *ctx);
+void duk_push_number(duk_context *ctx, duk_double_t val);
+void duk_push_nan(duk_context *ctx);
+void duk_push_int(duk_context *ctx, duk_int_t val);
+void duk_push_uint(duk_context *ctx, duk_uint_t val);
+const char *duk_push_string(duk_context *ctx, const char *str);
+const char *duk_push_string_file(duk_context *ctx, const char *path);
+const char *duk_push_lstring(duk_context *ctx, const char *str, duk_size_t len);
+void duk_push_pointer(duk_context *ctx, void *p);
+const char *duk_push_sprintf(duk_context *ctx, const char *fmt, ...);
+const char *duk_push_vsprintf(duk_context *ctx, const char *fmt, va_list ap);
+
+void duk_push_this(duk_context *ctx);
+void duk_push_current_function(duk_context *ctx);
+void duk_push_current_thread(duk_context *ctx);
+void duk_push_global_object(duk_context *ctx);
+void duk_push_heap_stash(duk_context *ctx);
+void duk_push_global_stash(duk_context *ctx);
+void duk_push_thread_stash(duk_context *ctx, duk_context *target_ctx);
+
+duk_idx_t duk_push_object(duk_context *ctx);
+duk_idx_t duk_push_array(duk_context *ctx);
+duk_idx_t duk_push_c_function(duk_context *ctx, duk_c_function func, duk_idx_t nargs);
+duk_idx_t duk_push_thread_raw(duk_context *ctx, duk_uint_t flags);
+
+#define duk_push_thread(ctx) \
+	duk_push_thread_raw((ctx), 0 /*flags*/)
+
+#define duk_push_thread_new_globalenv(ctx) \
+	duk_push_thread_raw((ctx), DUK_THREAD_NEW_GLOBAL_ENV /*flags*/)
+
+duk_idx_t duk_push_error_object_raw(duk_context *ctx, duk_errcode_t err_code, const char *filename, duk_int_t line, const char *fmt, ...);
+#ifdef DUK_API_VARIADIC_MACROS
+#define duk_push_error_object(ctx,err_code,...)  \
+	duk_push_error_object_raw((ctx),(err_code),__FILE__,__LINE__,__VA_ARGS__)
+#else
+duk_idx_t duk_push_error_object_stash(duk_context *ctx, duk_errcode_t err_code, const char *fmt, ...);
+#define duk_push_error_object  \
+	duk_api_global_filename = __FILE__, \
+	duk_api_global_line = __LINE__, \
+	duk_push_error_object_stash  /* arguments follow */
+#endif
+
+void *duk_push_buffer(duk_context *ctx, duk_size_t size, duk_bool_t dynamic);
+void *duk_push_fixed_buffer(duk_context *ctx, duk_size_t size);
+void *duk_push_dynamic_buffer(duk_context *ctx, duk_size_t size);
+
+/*
+ *  Pop operations
+ */
+
+void duk_pop(duk_context *ctx);
+void duk_pop_n(duk_context *ctx, duk_idx_t count);
+void duk_pop_2(duk_context *ctx);
+void duk_pop_3(duk_context *ctx);
+
+/*
+ *  Type checks
+ *
+ *  duk_is_none(), which would indicate whether index it outside of stack,
+ *  is not needed; duk_is_valid_index() gives the same information.
+ */
+
+/* FIXME: a duk_small_int_t suffices to represent type and type mask (at least now). */
+duk_int_t duk_get_type(duk_context *ctx, duk_idx_t index);
+duk_bool_t duk_check_type(duk_context *ctx, duk_idx_t index, duk_int_t type);
+duk_uint_t duk_get_type_mask(duk_context *ctx, duk_idx_t index);
+duk_bool_t duk_check_type_mask(duk_context *ctx, duk_idx_t index, duk_uint_t mask);
+
+duk_bool_t duk_is_undefined(duk_context *ctx, duk_idx_t index);
+duk_bool_t duk_is_null(duk_context *ctx, duk_idx_t index);
+duk_bool_t duk_is_null_or_undefined(duk_context *ctx, duk_idx_t index);
+duk_bool_t duk_is_boolean(duk_context *ctx, duk_idx_t index);
+duk_bool_t duk_is_number(duk_context *ctx, duk_idx_t index);
+duk_bool_t duk_is_nan(duk_context *ctx, duk_idx_t index);
+duk_bool_t duk_is_string(duk_context *ctx, duk_idx_t index);
+duk_bool_t duk_is_object(duk_context *ctx, duk_idx_t index);
+duk_bool_t duk_is_buffer(duk_context *ctx, duk_idx_t index);
+duk_bool_t duk_is_pointer(duk_context *ctx, duk_idx_t index);
+
+duk_bool_t duk_is_array(duk_context *ctx, duk_idx_t index);
+duk_bool_t duk_is_function(duk_context *ctx, duk_idx_t index);
+duk_bool_t duk_is_c_function(duk_context *ctx, duk_idx_t index);
+duk_bool_t duk_is_ecmascript_function(duk_context *ctx, duk_idx_t index);
+duk_bool_t duk_is_bound_function(duk_context *ctx, duk_idx_t index);
+duk_bool_t duk_is_thread(duk_context *ctx, duk_idx_t index);
+
+duk_bool_t duk_is_callable(duk_context *ctx, duk_idx_t index);
+duk_bool_t duk_is_dynamic(duk_context *ctx, duk_idx_t index);
+duk_bool_t duk_is_fixed(duk_context *ctx, duk_idx_t index);
+
+duk_bool_t duk_is_primitive(duk_context *ctx, duk_idx_t index);
+#define duk_is_object_coercible(ctx,index) \
+	duk_check_type_mask((ctx), (index), DUK_TYPE_MASK_BOOLEAN | \
+	                                    DUK_TYPE_MASK_NUMBER | \
+	                                    DUK_TYPE_MASK_STRING | \
+	                                    DUK_TYPE_MASK_OBJECT | \
+	                                    DUK_TYPE_MASK_BUFFER | \
+	                                    DUK_TYPE_MASK_POINTER)
+
+/*
+ *  Get operations: no coercion, returns default value for invalid
+ *  indices and invalid value types.
+ *
+ *  duk_get_undefined() and duk_get_null() would be pointless and
+ *  are not included.
+ */
+
+duk_bool_t duk_get_boolean(duk_context *ctx, duk_idx_t index);
+duk_double_t duk_get_number(duk_context *ctx, duk_idx_t index);
+duk_int_t duk_get_int(duk_context *ctx, duk_idx_t index);
+duk_uint_t duk_get_uint(duk_context *ctx, duk_idx_t index);
+const char *duk_get_string(duk_context *ctx, duk_idx_t index);
+const char *duk_get_lstring(duk_context *ctx, duk_idx_t index, duk_size_t *out_len);
+void *duk_get_buffer(duk_context *ctx, duk_idx_t index, duk_size_t *out_size);
+void *duk_get_pointer(duk_context *ctx, duk_idx_t index);
+duk_c_function duk_get_c_function(duk_context *ctx, duk_idx_t index);
+duk_context *duk_get_context(duk_context *ctx, duk_idx_t index);
+duk_size_t duk_get_length(duk_context *ctx, duk_idx_t index);
+
+/*
+ *  Require operations: no coercion, throw error if index or type
+ *  is incorrect.  No defaulting.
+ */
+
+#define duk_require_type_mask(ctx,index,mask) \
+	((void) duk_check_type_mask((ctx), (index), (mask) | DUK_TYPE_MASK_THROW))
+
+void duk_require_undefined(duk_context *ctx, duk_idx_t index);
+void duk_require_null(duk_context *ctx, duk_idx_t index);
+duk_bool_t duk_require_boolean(duk_context *ctx, duk_idx_t index);
+duk_double_t duk_require_number(duk_context *ctx, duk_idx_t index);
+duk_int_t duk_require_int(duk_context *ctx, duk_idx_t index);
+duk_uint_t duk_require_uint(duk_context *ctx, duk_idx_t index);
+const char *duk_require_string(duk_context *ctx, duk_idx_t index);
+const char *duk_require_lstring(duk_context *ctx, duk_idx_t index, duk_size_t *out_len);
+void *duk_require_buffer(duk_context *ctx, duk_idx_t index, duk_size_t *out_size);
+void *duk_require_pointer(duk_context *ctx, duk_idx_t index);
+duk_c_function duk_require_c_function(duk_context *ctx, duk_idx_t index);
+duk_context *duk_require_context(duk_context *ctx, duk_idx_t index);
+
+#define duk_require_object_coercible(ctx,index) \
+	((void) duk_check_type_mask((ctx), (index), DUK_TYPE_MASK_BOOLEAN | \
+	                                            DUK_TYPE_MASK_NUMBER | \
+	                                            DUK_TYPE_MASK_STRING | \
+	                                            DUK_TYPE_MASK_OBJECT | \
+	                                            DUK_TYPE_MASK_BUFFER | \
+	                                            DUK_TYPE_MASK_POINTER | \
+	                                            DUK_TYPE_MASK_THROW))
+
+/*
+ *  Coercion operations: in-place coercion, return coerced value where
+ *  applicable.  If index is invalid, throw error.  Some coercions may
+ *  throw an expected error (e.g. from a toString() or valueOf() call)
+ *  or an internal error (e.g. from out of memory).
+ */
+
+void duk_to_undefined(duk_context *ctx, duk_idx_t index);
+void duk_to_null(duk_context *ctx, duk_idx_t index);
+duk_bool_t duk_to_boolean(duk_context *ctx, duk_idx_t index);
+duk_double_t duk_to_number(duk_context *ctx, duk_idx_t index);
+duk_int_t duk_to_int(duk_context *ctx, duk_idx_t index);
+duk_uint_t duk_to_uint(duk_context *ctx, duk_idx_t index);
+duk_int32_t duk_to_int32(duk_context *ctx, duk_idx_t index);
+duk_uint32_t duk_to_uint32(duk_context *ctx, duk_idx_t index);
+duk_uint16_t duk_to_uint16(duk_context *ctx, duk_idx_t index);
+const char *duk_to_string(duk_context *ctx, duk_idx_t index);
+const char *duk_to_lstring(duk_context *ctx, duk_idx_t index, duk_size_t *out_len);
+void *duk_to_buffer(duk_context *ctx, duk_idx_t index, duk_size_t *out_size);
+void *duk_to_fixed_buffer(duk_context *ctx, duk_idx_t index, duk_size_t *out_size);
+void *duk_to_dynamic_buffer(duk_context *ctx, duk_idx_t index, duk_size_t *out_size);
+void *duk_to_pointer(duk_context *ctx, duk_idx_t index);
+void duk_to_object(duk_context *ctx, duk_idx_t index);
+void duk_to_defaultvalue(duk_context *ctx, duk_idx_t index, duk_int_t hint);  /* FIXME: small int? */
+void duk_to_primitive(duk_context *ctx, duk_idx_t index, duk_int_t hint);  /* FIXME: small int? */
+
+/* safe variants of a few coercion operations */
+const char *duk_safe_to_lstring(duk_context *ctx, duk_idx_t index, duk_size_t *out_len);
+#define duk_safe_to_string(ctx,index) \
+	duk_safe_to_lstring((ctx), (index), NULL)
+
+/*
+ *  Misc conversion
+ */
+
+const char *duk_base64_encode(duk_context *ctx, duk_idx_t index);
+void duk_base64_decode(duk_context *ctx, duk_idx_t index);
+const char *duk_hex_encode(duk_context *ctx, duk_idx_t index);
+void duk_hex_decode(duk_context *ctx, duk_idx_t index);
+const char *duk_json_encode(duk_context *ctx, duk_idx_t index);
+void duk_json_decode(duk_context *ctx, duk_idx_t index);
+
+/*
+ *  Buffer
+ */
+
+void *duk_resize_buffer(duk_context *ctx, duk_idx_t index, duk_size_t new_size);
+
+/*
+ *  Property access
+ *
+ *  The basic function assumes key is on stack.  The _string variant takes
+ *  a C string as a property name, while the _index variant takes an array
+ *  index as a property name (e.g. 123 is equivalent to the key "123").
+ */
+
+duk_bool_t duk_get_prop(duk_context *ctx, duk_idx_t obj_index);
+duk_bool_t duk_get_prop_string(duk_context *ctx, duk_idx_t obj_index, const char *key);
+duk_bool_t duk_get_prop_index(duk_context *ctx, duk_idx_t obj_index, duk_uarridx_t arr_index);
+duk_bool_t duk_put_prop(duk_context *ctx, duk_idx_t obj_index);
+duk_bool_t duk_put_prop_string(duk_context *ctx, duk_idx_t obj_index, const char *key);
+duk_bool_t duk_put_prop_index(duk_context *ctx, duk_idx_t obj_index, duk_uarridx_t arr_index);
+duk_bool_t duk_del_prop(duk_context *ctx, duk_idx_t obj_index);
+duk_bool_t duk_del_prop_string(duk_context *ctx, duk_idx_t obj_index, const char *key);
+duk_bool_t duk_del_prop_index(duk_context *ctx, duk_idx_t obj_index, duk_uarridx_t arr_index);
+duk_bool_t duk_has_prop(duk_context *ctx, duk_idx_t obj_index);
+duk_bool_t duk_has_prop_string(duk_context *ctx, duk_idx_t obj_index, const char *key);
+duk_bool_t duk_has_prop_index(duk_context *ctx, duk_idx_t obj_index, duk_uarridx_t arr_index);
+
+duk_bool_t duk_get_global_string(duk_context *ctx, const char *key);
+
+/*
+ *  Module helpers: put multiple function or constant properties
+ */
+
+void duk_put_function_list(duk_context *ctx, duk_idx_t obj_index, const duk_function_list_entry *funcs);
+void duk_put_number_list(duk_context *ctx, duk_idx_t obj_index, const duk_number_list_entry *numbers);
+
+/*
+ *  Variable access
+ */
+
+/* FIXME: incomplete, not usable now */
+void duk_get_var(duk_context *ctx);
+void duk_put_var(duk_context *ctx);
+duk_bool_t duk_del_var(duk_context *ctx);
+duk_bool_t duk_has_var(duk_context *ctx);
+
+/*
+ *  Object operations
+ */
+
+void duk_compact(duk_context *ctx, duk_idx_t obj_index);
+void duk_enum(duk_context *ctx, duk_idx_t obj_index, duk_uint_t enum_flags);
+duk_bool_t duk_next(duk_context *ctx, duk_idx_t enum_index, duk_bool_t get_value);
+
+/*
+ *  String manipulation
+ */
+
+void duk_concat(duk_context *ctx, duk_idx_t count);
+void duk_join(duk_context *ctx, duk_idx_t count);
+void duk_decode_string(duk_context *ctx, duk_idx_t index, duk_decode_char_function callback, void *udata);
+void duk_map_string(duk_context *ctx, duk_idx_t index, duk_map_char_function callback, void *udata);
+void duk_substring(duk_context *ctx, duk_idx_t index, duk_size_t start_char_offset, duk_size_t end_char_offset);
+void duk_trim(duk_context *ctx, duk_idx_t index);
+duk_codepoint_t duk_char_code_at(duk_context *ctx, duk_idx_t index, duk_size_t char_offset);
+
+/*
+ *  Ecmascript operators
+ */
+
+duk_bool_t duk_equals(duk_context *ctx, duk_idx_t index1, duk_idx_t index2);
+duk_bool_t duk_strict_equals(duk_context *ctx, duk_idx_t index1, duk_idx_t index2);
+
+/*
+ *  Function (method) calls
+ */
+
+void duk_call(duk_context *ctx, duk_idx_t nargs);
+void duk_call_method(duk_context *ctx, duk_idx_t nargs);
+void duk_call_prop(duk_context *ctx, duk_idx_t obj_index, duk_idx_t nargs);
+duk_int_t duk_pcall(duk_context *ctx, duk_idx_t nargs);
+duk_int_t duk_pcall_method(duk_context *ctx, duk_idx_t nargs);
+duk_int_t duk_pcall_prop(duk_context *ctx, duk_idx_t obj_index, duk_idx_t nargs);
+void duk_new(duk_context *ctx, duk_idx_t nargs);
+duk_int_t duk_safe_call(duk_context *ctx, duk_safe_call_function func, duk_idx_t nargs, duk_idx_t nrets);
+
+/*
+ *  Thread management
+ */
+
+/* There are currently no native functions to yield/resume, due to the internal
+ * limitations on coroutine handling.  These will be added later.
+ */
+
+/*
+ *  Compilation and evaluation
+ */
+
+duk_int_t duk_eval_raw(duk_context *ctx, const char *src_buffer, duk_size_t src_length, duk_uint_t flags);
+duk_int_t duk_compile_raw(duk_context *ctx, const char *src_buffer, duk_size_t src_length, duk_uint_t flags);
+
+/* plain */
+#define duk_eval(ctx)  \
+	((void) duk_push_string((ctx), __FILE__), \
+	 (void) duk_eval_raw((ctx), NULL, 0, DUK_COMPILE_EVAL))
+
+#define duk_eval_noresult(ctx)  \
+	((void) duk_push_string((ctx), __FILE__), \
+	 (void) duk_eval_raw((ctx), NULL, 0, DUK_COMPILE_EVAL | DUK_COMPILE_NORESULT))
+
+#define duk_peval(ctx)  \
+	((void) duk_push_string((ctx), __FILE__), \
+	 duk_eval_raw((ctx), NULL, 0, DUK_COMPILE_EVAL | DUK_COMPILE_SAFE))
+
+#define duk_peval_noresult(ctx)  \
+	((void) duk_push_string((ctx), __FILE__), \
+	 duk_eval_raw((ctx), NULL, 0, DUK_COMPILE_EVAL | DUK_COMPILE_SAFE | DUK_COMPILE_NORESULT))
+
+#define duk_compile(ctx,flags)  \
+	((void) duk_compile_raw((ctx), NULL, 0, (flags)))
+
+#define duk_pcompile(ctx,flags)  \
+	(duk_compile_raw((ctx), NULL, 0, (flags) | DUK_COMPILE_SAFE))
+
+/* string */
+#define duk_eval_string(ctx,src)  \
+	((void) duk_push_string((ctx), __FILE__), \
+	 (void) duk_eval_raw((ctx), (src), 0, DUK_COMPILE_EVAL | DUK_COMPILE_NOSOURCE | DUK_COMPILE_STRLEN))
+
+#define duk_eval_string_noresult(ctx,src)  \
+	((void) duk_push_string((ctx), __FILE__), \
+	 (void) duk_eval_raw((ctx), (src), 0, DUK_COMPILE_EVAL | DUK_COMPILE_NOSOURCE | DUK_COMPILE_STRLEN | DUK_COMPILE_NORESULT))
+
+#define duk_peval_string(ctx,src)  \
+	((void) duk_push_string((ctx), __FILE__), \
+	 duk_eval_raw((ctx), (src), 0, DUK_COMPILE_EVAL | DUK_COMPILE_SAFE | DUK_COMPILE_NOSOURCE | DUK_COMPILE_STRLEN))
+
+#define duk_peval_string_noresult(ctx,src)  \
+	((void) duk_push_string((ctx), __FILE__), \
+	 duk_eval_raw((ctx), (src), 0, DUK_COMPILE_EVAL | DUK_COMPILE_SAFE | DUK_COMPILE_NOSOURCE | DUK_COMPILE_STRLEN | DUK_COMPILE_NORESULT))
+
+#define duk_compile_string(ctx,flags,src)  \
+	((void) duk_push_string((ctx), __FILE__), \
+	 (void) duk_compile_raw((ctx), (src), 0, (flags) | DUK_COMPILE_NOSOURCE | DUK_COMPILE_STRLEN))
+
+#define duk_compile_string_filename(ctx,flags,src)  \
+	((void) duk_compile_raw((ctx), (src), 0, (flags) | DUK_COMPILE_NOSOURCE | DUK_COMPILE_STRLEN))
+
+#define duk_pcompile_string(ctx,flags,src)  \
+	((void) duk_push_string((ctx), __FILE__), \
+	 duk_compile_raw((ctx), (src), 0, (flags) | DUK_COMPILE_SAFE | DUK_COMPILE_NOSOURCE | DUK_COMPILE_STRLEN))
+
+#define duk_pcompile_string_filename(ctx,flags,src)  \
+	(duk_compile_raw((ctx), (src), 0, (flags) | DUK_COMPILE_SAFE | DUK_COMPILE_NOSOURCE | DUK_COMPILE_STRLEN))
+
+/* lstring */
+#define duk_eval_lstring(ctx,buf,len)  \
+	((void) duk_push_string((ctx), __FILE__), \
+	 (void) duk_eval_raw((ctx), buf, len, DUK_COMPILE_EVAL | DUK_COMPILE_NOSOURCE))
+
+#define duk_eval_lstring_noresult(ctx,buf,len)  \
+	((void) duk_push_string((ctx), __FILE__), \
+	 (void) duk_eval_raw((ctx), buf, len, DUK_COMPILE_EVAL | DUK_COMPILE_NOSOURCE | DUK_COMPILE_NORESULT))
+
+#define duk_peval_lstring(ctx,buf,len)  \
+	((void) duk_push_string((ctx), __FILE__), \
+	 duk_eval_raw((ctx), buf, len, DUK_COMPILE_EVAL | DUK_COMPILE_NOSOURCE | DUK_COMPILE_SAFE))
+
+#define duk_peval_lstring_noresult(ctx,buf,len)  \
+	((void) duk_push_string((ctx), __FILE__), \
+	 duk_eval_raw((ctx), buf, len, DUK_COMPILE_EVAL | DUK_COMPILE_SAFE | DUK_COMPILE_NOSOURCE | DUK_COMPILE_NORESULT))
+
+#define duk_compile_lstring(ctx,flags,buf,len)  \
+	((void) duk_push_string((ctx), __FILE__), \
+	 (void) duk_compile_raw((ctx), buf, len, (flags) | DUK_COMPILE_NOSOURCE))
+
+#define duk_compile_lstring_filename(ctx,flags,buf,len)  \
+	((void) duk_compile_raw((ctx), buf, len, (flags) | DUK_COMPILE_NOSOURCE))
+
+#define duk_pcompile_lstring(ctx,flags,buf,len)  \
+	((void) duk_push_string((ctx), __FILE__), \
+	 duk_compile_raw((ctx), buf, len, (flags) | DUK_COMPILE_SAFE | DUK_COMPILE_NOSOURCE))
+
+#define duk_pcompile_lstring_filename(ctx,flags,buf,len)  \
+	(duk_compile_raw((ctx), buf, len, (flags) | DUK_COMPILE_SAFE | DUK_COMPILE_NOSOURCE))
+
+/* file */
+#define duk_eval_file(ctx,path)  \
+	((void) duk_push_string_file((ctx), (path)), \
+	 (void) duk_push_string((ctx), (path)), \
+	 (void) duk_eval_raw((ctx), NULL, 0, DUK_COMPILE_EVAL))
+
+#define duk_eval_file_noresult(ctx,path)  \
+	((void) duk_push_string_file((ctx), (path)), \
+	 (void) duk_push_string((ctx), (path)), \
+	 (void) duk_eval_raw((ctx), NULL, 0, DUK_COMPILE_EVAL | DUK_COMPILE_NORESULT))
+
+#define duk_peval_file(ctx,path)  \
+	((void) duk_push_string_file((ctx), (path)), \
+	 (void) duk_push_string((ctx), (path)), \
+	 duk_eval_raw((ctx), NULL, 0, DUK_COMPILE_EVAL | DUK_COMPILE_SAFE))
+
+#define duk_peval_file_noresult(ctx,path)  \
+	((void) duk_push_string_file((ctx), (path)), \
+	 (void) duk_push_string((ctx), (path)), \
+	 duk_eval_raw((ctx), NULL, 0, DUK_COMPILE_EVAL | DUK_COMPILE_SAFE | DUK_COMPILE_NORESULT))
+
+#define duk_compile_file(ctx,flags,path)  \
+	((void) duk_push_string_file((ctx), (path)), \
+	 (void) duk_push_string((ctx), (path)), \
+	 (void) duk_compile_raw((ctx), NULL, 0, (flags)))
+
+#define duk_pcompile_file(ctx,flags,path)  \
+	((void) duk_push_string_file((ctx), (path)), \
+	 (void) duk_push_string((ctx), (path)), \
+	 duk_compile_raw((ctx), NULL, 0, (flags) | DUK_COMPILE_SAFE))
+
+/*
+ *  Logging
+ */
+
+/* FIXME: here a small integer type would be proper */
+void duk_log(duk_context *ctx, duk_int_t level, const char *fmt, ...);
+
+/*
+ *  Debugging
+ */
+
+void duk_push_context_dump(duk_context *ctx);
+
+#if defined(DUK_USE_FILE_IO)
+/* internal use */
+#define duk_dump_context_filehandle(ctx,fh) \
+	do { \
+		duk_push_context_dump((ctx)); \
+		fprintf(stdout, "%s\n", duk_safe_to_string(ctx, -1)); \
+		duk_pop(ctx); \
+	} while (0)
+
+/* external use */
+#define duk_dump_context_stdout(ctx) \
+	duk_dump_context_filehandle(ctx,stdout)
+#define duk_dump_context_stderr(ctx) \
+	duk_dump_context_filehandle(ctx,stderr)
+#else  /* DUK_USE_FILE_IO */
+#define duk_dump_context_stdout(ctx)  do {} while (0)
+#define duk_dump_context_stderr(ctx)  do {} while (0)
+#endif  /* DUK_USE_FILE_IO */
+
+/*
+ *  C++ name mangling
+ */
+
+#ifdef __cplusplus
+/* end 'extern "C"' wrapper */
+}
+#endif
+
+#endif  /* DUK_API_PUBLIC_H_INCLUDED */
+
+/*
+ *  END PUBLIC API
+ */
+
+/*
+ *  Sanity check for the final effective internal defines.  This file also
+ *  double checks user tweaks made by an optional duk_custom.h header.
+ */
+
+#ifndef DUK_FEATURES_SANITY_H_INCLUDED
+#define DUK_FEATURES_SANITY_H_INCLUDED
+
+/*
+ *  Deprecated feature options.
+ *
+ *  Catch so that user more easily notices and updates build.
+ */
+
+#if defined(DUK_OPT_NO_FUNC_STMT)
+#error DUK_OPT_NO_FUNC_STMT is deprecated, use DUK_OPT_NO_NONSTD_FUNC_STMT
+#endif
+
+#if defined(DUK_OPT_FUNC_NONSTD_CALLER_PROPERTY)
+#error DUK_OPT_FUNC_NONSTD_CALLER_PROPERTY is deprecated, use DUK_OPT_NONSTD_FUNC_CALLER_PROPERTY
+#endif
+
+#if defined(DUK_OPT_FUNC_NONSTD_SOURCE_PROPERTY)
+#error DUK_OPT_FUNC_NONSTD_SOURCE_PROPERTY is deprecated, use DUK_OPT_NONSTD_FUNC_SOURCE_PROPERTY
+#endif
+
+#if defined(DUK_OPT_NO_ARRAY_SPLICE_NONSTD_DELCOUNT)
+#error DUK_OPT_NO_ARRAY_SPLICE_NONSTD_DELCOUNT is deprecated, use DUK_OPT_NO_NONSTD_ARRAY_SPLICE_DELCOUNT
+#endif
+
+#if defined(DUK_OPT_NO_OBJECT_ES6_PROTO_PROPERTY)
+#error DUK_OPT_NO_OBJECT_ES6_PROTO_PROPERTY is deprecated, use DUK_OPT_NO_ES6_OBJECT_PROTO_PROPERTY
+#endif
+
+#if defined(DUK_OPT_NO_OBJECT_ES6_SETPROTOTYPEOF)
+#error DUK_OPT_NO_OBJECT_ES6_SETPROTOTYPEOF is deprecated, use DUK_OPT_NO_ES6_OBJECT_SETPROTOTYPEOF
+#endif
+
+#if defined(DUK_OPT_NO_JSONX)
+#error DUK_OPT_NO_JSONX is deprecated, use DUK_OPT_NO_JX
+#endif
+
+#if defined(DUK_OPT_NO_JSONC)
+#error DUK_OPT_NO_JSONC is deprecated, use DUK_OPT_NO_JC
+#endif
+
+/*
+ *  Debug print consistency
+ */
+
+#if defined(DUK_USE_DPRINT) && !defined(DUK_USE_DEBUG)
+#error DUK_USE_DPRINT without DUK_USE_DEBUG
+#endif
+
+#if defined(DUK_USE_DDPRINT) && !defined(DUK_USE_DEBUG)
+#error DUK_USE_DDPRINT without DUK_USE_DEBUG
+#endif
+
+#if defined(DUK_USE_DDDPRINT) && !defined(DUK_USE_DEBUG)
+#error DUK_USE_DDDPRINT without DUK_USE_DEBUG
+#endif
+
+/*
+ *  Garbage collection consistency
+ */
+
+#if defined(DUK_USE_REFERENCE_COUNTING) && !defined(DUK_USE_DOUBLE_LINKED_HEAP)
+#error DUK_USE_REFERENCE_COUNTING defined without DUK_USE_DOUBLE_LINKED_HEAP
+#endif
+
+#if defined(DUK_USE_GC_TORTURE) && !defined(DUK_USE_MARK_AND_SWEEP)
+#error DUK_USE_GC_TORTURE defined without DUK_USE_MARK_AND_SWEEP
+#endif
+
+#endif  /* DUK_FEATURES_SANITY_H_INCLUDED */
+
+/*
+ *  Union to access IEEE double memory representation, indexes for double
+ *  memory representation, and some macros for double manipulation.
+ *
+ *  Also used by packed duk_tval.  Use a union for bit manipulation to
+ *  minimize aliasing issues in practice.  The C99 standard does not
+ *  guarantee that this should work, but it's a very widely supported
+ *  practice for low level manipulation.
+ *
+ *  IEEE double format summary:
+ *
+ *    seeeeeee eeeeffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff
+ *       A        B        C        D        E        F        G        H
+ *
+ *    s       sign bit
+ *    eee...  exponent field
+ *    fff...  fraction
+ *
+ *  See http://en.wikipedia.org/wiki/Double_precision_floating-point_format.
+ *
+ *  NaNs are represented as exponent 0x7ff and mantissa != 0.  The NaN is a
+ *  signaling NaN when the highest bit of the mantissa is zero, and a quiet
+ *  NaN when the highest bit is set.
+ *
+ *  At least three memory layouts are relevant here:
+ *
+ *    A B C D E F G H    Big endian (e.g. 68k)           DUK_USE_DOUBLE_BE
+ *    H G F E D C B A    Little endian (e.g. x86)        DUK_USE_DOUBLE_LE
+ *    D C B A H G F E    Mixed/cross endian (e.g. ARM)   DUK_USE_DOUBLE_ME
+ *
+ *  ARM is a special case: ARM double values are in mixed/cross endian
+ *  format while ARM duk_uint64_t values are in standard little endian
+ *  format (H G F E D C B A).  When a double is read as a duk_uint64_t
+ *  from memory, the register will contain the (logical) value
+ *  E F G H A B C D.  This requires some special handling below.
+ *
+ *  Indexes of various types (8-bit, 16-bit, 32-bit) in memory relative to
+ *  the logical (big endian) order:
+ *
+ *  byte order      duk_uint8_t    duk_uint16_t     duk_uint32_t    
+ *    BE             01234567         0123               01
+ *    LE             76543210         3210               10
+ *    ME (ARM)       32107654         1032               01
+ *
+ *  Some processors may alter NaN values in a floating point load+store.
+ *  For instance, on X86 a FLD + FSTP may convert a signaling NaN to a
+ *  quiet one.  This is catastrophic when NaN space is used in packed
+ *  duk_tval values.  See: misc/clang_aliasing.c.
+ */
+
+#ifndef DUK_DBLUNION_H_INCLUDED
+#define DUK_DBLUNION_H_INCLUDED
+
+/*
+ *  Union for accessing double parts, also serves as packed duk_tval
+ */
+
+union duk_double_union {
+	double d;
+#ifdef DUK_USE_64BIT_OPS
+	duk_uint64_t ull[1];
+#endif
+	duk_uint32_t ui[2];
+	duk_uint16_t us[4];
+	duk_uint8_t uc[8];
+#ifdef DUK_USE_PACKED_TVAL_POSSIBLE
+	void *vp[2];  /* used by packed duk_tval, assumes sizeof(void *) == 4 */
+#endif
+};
+
+typedef union duk_double_union duk_double_union;
+
+/*
+ *  Indexes of various types with respect to big endian (logical) layout
+ */
+
+#if defined(DUK_USE_DOUBLE_LE)
+#ifdef DUK_USE_64BIT_OPS
+#define DUK_DBL_IDX_ULL0   0
+#endif
+#define DUK_DBL_IDX_UI0    1
+#define DUK_DBL_IDX_UI1    0
+#define DUK_DBL_IDX_US0    3
+#define DUK_DBL_IDX_US1    2
+#define DUK_DBL_IDX_US2    1
+#define DUK_DBL_IDX_US3    0
+#define DUK_DBL_IDX_UC0    7
+#define DUK_DBL_IDX_UC1    6
+#define DUK_DBL_IDX_UC2    5
+#define DUK_DBL_IDX_UC3    4
+#define DUK_DBL_IDX_UC4    3
+#define DUK_DBL_IDX_UC5    2
+#define DUK_DBL_IDX_UC6    1
+#define DUK_DBL_IDX_UC7    0
+#define DUK_DBL_IDX_VP0    DUK_DBL_IDX_UI0  /* packed tval */
+#define DUK_DBL_IDX_VP1    DUK_DBL_IDX_UI1  /* packed tval */
+#elif defined(DUK_USE_DOUBLE_BE)
+#ifdef DUK_USE_64BIT_OPS
+#define DUK_DBL_IDX_ULL0   0
+#endif
+#define DUK_DBL_IDX_UI0    0
+#define DUK_DBL_IDX_UI1    1
+#define DUK_DBL_IDX_US0    0
+#define DUK_DBL_IDX_US1    1
+#define DUK_DBL_IDX_US2    2
+#define DUK_DBL_IDX_US3    3
+#define DUK_DBL_IDX_UC0    0
+#define DUK_DBL_IDX_UC1    1
+#define DUK_DBL_IDX_UC2    2
+#define DUK_DBL_IDX_UC3    3
+#define DUK_DBL_IDX_UC4    4
+#define DUK_DBL_IDX_UC5    5
+#define DUK_DBL_IDX_UC6    6
+#define DUK_DBL_IDX_UC7    7
+#define DUK_DBL_IDX_VP0    DUK_DBL_IDX_UI0  /* packed tval */
+#define DUK_DBL_IDX_VP1    DUK_DBL_IDX_UI1  /* packed tval */
+#elif defined(DUK_USE_DOUBLE_ME)
+#ifdef DUK_USE_64BIT_OPS
+#define DUK_DBL_IDX_ULL0   0  /* not directly applicable, byte order differs from a double */
+#endif
+#define DUK_DBL_IDX_UI0    0
+#define DUK_DBL_IDX_UI1    1
+#define DUK_DBL_IDX_US0    1
+#define DUK_DBL_IDX_US1    0
+#define DUK_DBL_IDX_US2    3
+#define DUK_DBL_IDX_US3    2
+#define DUK_DBL_IDX_UC0    3
+#define DUK_DBL_IDX_UC1    2
+#define DUK_DBL_IDX_UC2    1
+#define DUK_DBL_IDX_UC3    0
+#define DUK_DBL_IDX_UC4    7
+#define DUK_DBL_IDX_UC5    6
+#define DUK_DBL_IDX_UC6    5
+#define DUK_DBL_IDX_UC7    4
+#define DUK_DBL_IDX_VP0    DUK_DBL_IDX_UI0  /* packed tval */
+#define DUK_DBL_IDX_VP1    DUK_DBL_IDX_UI1  /* packed tval */
+#else
+#error internal error
+#endif
+
+/*
+ *  Helper macros for reading/writing memory representation parts, used
+ *  by duk_numconv.c and duk_tval.h.
+ */
+
+#define DUK_DBLUNION_SET_DOUBLE(u,v)  do {  \
+		(u)->d = (v); \
+	} while (0)
+
+#define DUK_DBLUNION_SET_HIGH32(u,v)  do {  \
+		(u)->ui[DUK_DBL_IDX_UI0] = (duk_uint32_t) (v); \
+	} while (0)
+
+#ifdef DUK_USE_64BIT_OPS
+#ifdef DUK_USE_DOUBLE_ME
+#define DUK_DBLUNION_SET_HIGH32_ZERO_LOW32(u,v)  do { \
+		(u)->ull[DUK_DBL_IDX_ULL0] = (duk_uint64_t) (v); \
+	} while (0)
+#else
+#define DUK_DBLUNION_SET_HIGH32_ZERO_LOW32(u,v)  do { \
+		(u)->ull[DUK_DBL_IDX_ULL0] = ((duk_uint64_t) (v)) << 32; \
+	} while (0)
+#endif
+#else  /* DUK_USE_64BIT_OPS */
+#define DUK_DBLUNION_SET_HIGH32_ZERO_LOW32(u,v)  do { \
+		(u)->ui[DUK_DBL_IDX_UI0] = (duk_uint32_t) (v); \
+		(u)->ui[DUK_DBL_IDX_UI1] = (duk_uint32_t) 0; \
+	} while (0)
+#endif  /* DUK_USE_64BIT_OPS */
+
+#define DUK_DBLUNION_SET_LOW32(u,v)  do {  \
+		(u)->ui[DUK_DBL_IDX_UI1] = (duk_uint32_t) (v); \
+	} while (0)
+
+#define DUK_DBLUNION_GET_DOUBLE(u)  ((u)->d)
+#define DUK_DBLUNION_GET_HIGH32(u)  ((u)->ui[DUK_DBL_IDX_UI0])
+#define DUK_DBLUNION_GET_LOW32(u)   ((u)->ui[DUK_DBL_IDX_UI1])
+
+/*
+ *  Double NaN manipulation macros related to NaN normalization needed when
+ *  using the packed duk_tval representation.  NaN normalization is necessary
+ *  to keep double values compatible with the duk_tval format.
+ *
+ *  When packed duk_tval is used, the NaN space is used to store pointers
+ *  and other tagged values in addition to NaNs.  Actual NaNs are normalized
+ *  to a specific format.  The macros below are used by the implementation
+ *  to check and normalize NaN values when they might be created.  The macros
+ *  are essentially NOPs when the non-packed duk_tval representation is used.
+ *
+ *  A FULL check is exact and checks all bits.  A NOTFULL check is used by
+ *  the packed duk_tval and works correctly for all NaNs except those that
+ *  begin with 0x7ff0.  Since the 'normalized NaN' values used with packed
+ *  duk_tval begin with 0x7ff8, the partial check is reliable when packed
+ *  duk_tval is used.
+ *
+ *  The ME variant below is specifically for ARM byte order, which has the
+ *  feature that while doubles have a mixed byte order (32107654), unsigned
+ *  long long values has a little endian byte order (76543210).  When writing
+ *  a logical double value through a ULL pointer, the 32-bit words need to be
+ *  swapped; hence the #ifdefs below for ULL writes with DUK_USE_DOUBLE_ME.
+ *  This is not full ARM support but suffices for some environments.
+ */
+
+#ifdef DUK_USE_64BIT_OPS
+#ifdef DUK_USE_DOUBLE_ME
+#define DUK__DBLUNION_SET_NAN_FULL(u)  do { \
+		(u)->ull[DUK_DBL_IDX_ULL0] = 0x000000007ff80000ULL; \
+	} while (0)
+#else
+#define DUK__DBLUNION_SET_NAN_FULL(u)  do { \
+		(u)->ull[DUK_DBL_IDX_ULL0] = 0x7ff8000000000000ULL; \
+	} while (0)
+#endif
+#else  /* DUK_USE_64BIT_OPS */
+#define DUK__DBLUNION_SET_NAN_FULL(u)  do { \
+		(u)->ui[DUK_DBL_IDX_UI0] = (duk_uint32_t) 0x7ff80000UL; \
+		(u)->ui[DUK_DBL_IDX_UI1] = (duk_uint32_t) 0x00000000UL; \
+	} while (0)
+#endif  /* DUK_USE_64BIT_OPS */
+
+#define DUK__DBLUNION_SET_NAN_NOTFULL(u)  do { \
+		(u)->us[DUK_DBL_IDX_US0] = 0x7ff8UL; \
+	} while (0)
+
+#ifdef DUK_USE_64BIT_OPS
+#ifdef DUK_USE_DOUBLE_ME
+#define DUK__DBLUNION_IS_NAN_FULL(u) \
+	/* E == 0x7ff, F != 0 => NaN */ \
+	((((u)->us[DUK_DBL_IDX_US0] & 0x7ff0UL) == 0x7ff0UL) && \
+	 ((((u)->ull[DUK_DBL_IDX_ULL0]) & 0xffffffff000fffffULL) != 0))
+#else
+#define DUK__DBLUNION_IS_NAN_FULL(u) \
+	/* E == 0x7ff, F != 0 => NaN */ \
+	((((u)->us[DUK_DBL_IDX_US0] & 0x7ff0UL) == 0x7ff0UL) && \
+	 ((((u)->ull[DUK_DBL_IDX_ULL0]) & 0x000fffffffffffffULL) != 0))
+#endif
+#else  /* DUK_USE_64BIT_OPS */
+#define DUK__DBLUNION_IS_NAN_FULL(u) \
+	/* E == 0x7ff, F != 0 => NaN */ \
+	((((u)->ui[DUK_DBL_IDX_UI0] & 0x7ff00000UL) == 0x7ff00000UL) && \
+	 (((u)->ui[DUK_DBL_IDX_UI0] & 0x000fffffUL) != 0 || \
+          (u)->ui[DUK_DBL_IDX_UI1] != 0))
+#endif  /* DUK_USE_64BIT_OPS */
+
+#define DUK__DBLUNION_IS_NAN_NOTFULL(u) \
+	/* E == 0x7ff, topmost four bits of F != 0 => assume NaN */ \
+	((((u)->us[DUK_DBL_IDX_US0] & 0x7ff0UL) == 0x7ff0UL) && \
+	 (((u)->us[DUK_DBL_IDX_US0] & 0x000fUL) != 0x0000UL))
+
+#ifdef DUK_USE_64BIT_OPS
+#ifdef DUK_USE_DOUBLE_ME
+#define DUK__DBLUNION_IS_NORMALIZED_NAN_FULL(u) \
+	((u)->ull[DUK_DBL_IDX_ULL0] == 0x000000007ff80000ULL)
+#else
+#define DUK__DBLUNION_IS_NORMALIZED_NAN_FULL(u) \
+	((u)->ull[DUK_DBL_IDX_ULL0] == 0x7ff8000000000000ULL)
+#endif
+#else  /* DUK_USE_64BIT_OPS */
+#define DUK__DBLUNION_IS_NORMALIZED_NAN_FULL(u) \
+	(((u)->ui[DUK_DBL_IDX_UI0] == 0x7ff80000UL) && \
+	 ((u)->ui[DUK_DBL_IDX_UI1] == 0x00000000UL))
+#endif  /* DUK_USE_64BIT_OPS */
+
+#define DUK__DBLUNION_IS_NORMALIZED_NAN_NOTFULL(u) \
+	/* E == 0x7ff, F == 8 => normalized NaN */ \
+	((u)->us[DUK_DBL_IDX_US0] == 0x7ff8UL)
+
+#define DUK__DBLUNION_NORMALIZE_NAN_CHECK_FULL(u)  do { \
+		if (DUK__DBLUNION_IS_NAN_FULL((u))) { \
+			DUK__DBLUNION_SET_NAN_FULL((u)); \
+		} \
+	} while (0)
+
+#define DUK__DBLUNION_NORMALIZE_NAN_CHECK_NOTFULL(u)  do { \
+		if (DUK__DBLUNION_IS_NAN_NOTFULL((u))) { \
+			DUK__DBLUNION_SET_NAN_NOTFULL((u)); \
+		} \
+	} while (0)
+
+/* Concrete macros for NaN handling used by the implementation internals.
+ * Chosen so that they match the duk_tval representation: with a packed
+ * duk_tval, ensure NaNs are properly normalized; with a non-packed duk_tval
+ * these are essentially NOPs.
+ */
+
+#if defined(DUK_USE_PACKED_TVAL)
+#if defined(DUK_USE_FULL_TVAL)
+#define DUK_DBLUNION_NORMALIZE_NAN_CHECK(u)  DUK__DBLUNION_NORMALIZE_NAN_CHECK_FULL((u))
+#define DUK_DBLUNION_IS_NAN(u)               DUK__DBLUNION_IS_NAN_FULL((u))
+#define DUK_DBLUNION_IS_NORMALIZED_NAN(u)    DUK__DBLUNION_IS_NORMALIZED_NAN_FULL((u))
+#define DUK_DBLUNION_SET_NAN(d)              DUK__DBLUNION_SET_NAN_FULL((d))
+#else
+#define DUK_DBLUNION_NORMALIZE_NAN_CHECK(u)  DUK__DBLUNION_NORMALIZE_NAN_CHECK_NOTFULL((u))
+#define DUK_DBLUNION_IS_NAN(u)               DUK__DBLUNION_IS_NAN_NOTFULL((u))
+#define DUK_DBLUNION_IS_NORMALIZED_NAN(u)    DUK__DBLUNION_IS_NORMALIZED_NAN_NOTFULL((u))
+#define DUK_DBLUNION_SET_NAN(d)              DUK__DBLUNION_SET_NAN_NOTFULL((d))
+#endif
+#define DUK_DBLUNION_IS_NORMALIZED(u) \
+	(!DUK_DBLUNION_IS_NAN((u)) ||  /* either not a NaN */ \
+	 DUK_DBLUNION_IS_NORMALIZED_NAN((u)))  /* or is a normalized NaN */
+#else  /* DUK_USE_PACKED_TVAL */
+#define DUK_DBLUNION_NORMALIZE_NAN_CHECK(u)  /* nop: no need to normalize */
+#define DUK_DBLUNION_IS_NAN(u)               (DUK_ISNAN((u)->d))
+#define DUK_DBLUNION_IS_NORMALIZED_NAN(u)    (DUK_ISNAN((u)->d))
+#define DUK_DBLUNION_IS_NORMALIZED(u)        1  /* all doubles are considered normalized */
+#define DUK_DBLUNION_SET_NAN(u)  do { \
+		/* in non-packed representation we don't care about which NaN is used */ \
+		(u)->d = DUK_DOUBLE_NAN; \
+	} while (0)
+#endif  /* DUK_USE_PACKED_TVAL */
+
+#endif  /* DUK_DBLUNION_H_INCLUDED */
+
+#endif  /* DUKTAPE_H_INCLUDED */
diff --git a/src/osgEarthDrivers/script_engine_v8/CMakeLists.txt b/src/osgEarthDrivers/script_engine_v8/CMakeLists.txt
index ac095af..1fa22a0 100644
--- a/src/osgEarthDrivers/script_engine_v8/CMakeLists.txt
+++ b/src/osgEarthDrivers/script_engine_v8/CMakeLists.txt
@@ -15,7 +15,7 @@ SET(TARGET_H
 
 SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES} osgEarthFeatures)
 
-SET(TARGET_LIBRARIES_VARS ${TARGET_LIBRARIES_VARS} V8_BASE_LIBRARY V8_SNAPSHOT_LIBRARY V8_ICUUC_LIBRARY V8_ICUI18N_LIBRARY)
+SET(TARGET_LIBRARIES_VARS ${TARGET_LIBRARIES_VARS} V8_LIBRARY V8_BASE_LIBRARY V8_SNAPSHOT_LIBRARY V8_ICUUC_LIBRARY V8_ICUI18N_LIBRARY)
 
 SETUP_PLUGIN(osgearth_scriptengine_javascript)
 
diff --git a/src/osgEarthDrivers/script_engine_v8/JSWrappers.cpp b/src/osgEarthDrivers/script_engine_v8/JSWrappers.cpp
index fb6201e..2297056 100644
--- a/src/osgEarthDrivers/script_engine_v8/JSWrappers.cpp
+++ b/src/osgEarthDrivers/script_engine_v8/JSWrappers.cpp
@@ -558,7 +558,7 @@ JSFilterContext::PropertyCallback(v8::Local<v8::String> name, const v8::Property
   if (prop == "session")
     value = JSSession::WrapSession(v8::Isolate::GetCurrent(), const_cast<Session*>(context->getSession()));
   if (prop == "profile")
-    value = JSFeatureProfile::WrapFeatureProfile(v8::Isolate::GetCurrent(), const_cast<FeatureProfile*>(context->profile().get()));
+    value = JSFeatureProfile::WrapFeatureProfile(v8::Isolate::GetCurrent(), const_cast<FeatureProfile*>(context->profile()));
   if (prop == "extent" && context->extent().isSet())
     value = JSGeoExtent::WrapGeoExtent(v8::Isolate::GetCurrent(), const_cast<osgEarth::GeoExtent*>(&context->extent().get()));
   //if (prop == "geocentric")
@@ -1291,4 +1291,4 @@ JSSpatialReference::FreeSpatialReferenceCallback(v8::Isolate* isolate, v8::Persi
 {
   osg::ref_ptr<osgEarth::SpatialReference> srs = parameter;
   handle->Dispose();
-}
\ No newline at end of file
+}
diff --git a/src/osgEarthDrivers/script_engine_v8/JavascriptEngineV8 b/src/osgEarthDrivers/script_engine_v8/JavascriptEngineV8
index 05dfa0e..e7adb08 100644
--- a/src/osgEarthDrivers/script_engine_v8/JavascriptEngineV8
+++ b/src/osgEarthDrivers/script_engine_v8/JavascriptEngineV8
@@ -59,11 +59,14 @@
     v8::Handle<v8::Context> createGlobalContext();
 
     /** Compiles and runs javascript in the current context. */
-    ScriptResult executeScript(v8::Handle<v8::String> script);
+    ScriptResult executeScript(v8::Handle<v8::String> script, bool ignoreUndefinedResult=false);
 
   protected:
     v8::Persistent<v8::Context> _globalContext;
     v8::Isolate* _isolate;
+  private:
+    ScriptResult createErrorResult( std::string prefix, const v8::TryCatch& try_catch );
+
   };
 
 //} } } // namespace osgEarth::Drivers::JavascriptV8
diff --git a/src/osgEarthDrivers/script_engine_v8/JavascriptEngineV8.cpp b/src/osgEarthDrivers/script_engine_v8/JavascriptEngineV8.cpp
index 5842567..a37ffd8 100644
--- a/src/osgEarthDrivers/script_engine_v8/JavascriptEngineV8.cpp
+++ b/src/osgEarthDrivers/script_engine_v8/JavascriptEngineV8.cpp
@@ -57,7 +57,7 @@ JavascriptEngineV8::JavascriptEngineV8(const ScriptEngineOptions& options)
     v8::Context::Scope context_scope(globalContext);
 
     // Compile and run the script
-    ScriptResult result = executeScript(v8::String::New(options.script()->getCode().c_str(), options.script()->getCode().length()));
+    ScriptResult result = executeScript(v8::String::New(options.script()->getCode().c_str(), options.script()->getCode().length()), true);
     if (!result.success())
       OE_WARN << LC << "Error reading javascript: " << result.message() << std::endl;
   }
@@ -105,8 +105,37 @@ JavascriptEngineV8::logCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
   OE_WARN << LC << "javascript message: " << (*value) << std::endl;
 }
 
+ScriptResult 
+JavascriptEngineV8::createErrorResult( std::string prefix, const v8::TryCatch& try_catch )
+{
+    std::ostringstream str;
+
+    v8::String::AsciiValue error( try_catch.Exception() );
+    v8::Handle<v8::Message> message = try_catch.Message();
+
+    str << prefix << ": ";
+
+    if( !message.IsEmpty() ) {
+      //v8::String::AsciiValue filename(message->GetScriptResourceName());
+      int linenum = message->GetLineNumber();
+      str << "line " << linenum << " cols [" << message->GetStartColumn() << "-" << message->GetEndColumn() << "] " << std::string( *error ) << std::endl;
+      v8::String::AsciiValue sourceline( message->GetSourceLine() );
+      str << std::string( *sourceline ) << std::endl;
+      /*
+      v8::String::Utf8Value stack_trace(try_catch.StackTrace());
+      if (stack_trace.length() > 0) {
+      str <<  std::string(*stack_trace) << std::endl;
+      }
+      */
+    }
+    else {
+      str << std::string( *error );
+    }
+    return ScriptResult( EMPTY_STRING, false, str.str() );
+
+}
 ScriptResult
-JavascriptEngineV8::executeScript(v8::Handle<v8::String> script)
+JavascriptEngineV8::executeScript(v8::Handle<v8::String> script, bool ignoreUndefinedResult)
 {
   v8::String::Utf8Value utf8_value(script);
   std::string scriptStr(*utf8_value);
@@ -121,37 +150,17 @@ JavascriptEngineV8::executeScript(v8::Handle<v8::String> script)
   v8::Handle<v8::Script> compiled_script = v8::Script::Compile(script);
   if (compiled_script.IsEmpty())
   {
-    v8::String::AsciiValue error(try_catch.Exception());
-	v8::Handle<v8::Message> message = try_catch.Message();
-	if(!message.IsEmpty()) {
-		//v8::String::AsciiValue filename(message->GetScriptResourceName());
-		int linenum = message->GetLineNumber();
-		std::ostringstream str;
-		str << linenum << ":[" << message->GetStartColumn() << "-" << message->GetEndColumn() << "]:" << std::string(*error) << std::endl;
-		v8::String::AsciiValue sourceline(message->GetSourceLine());
-		str << std::string(*sourceline) << std::endl;
-		/*
-		v8::String::Utf8Value stack_trace(try_catch.StackTrace());
-		if (stack_trace.length() > 0) {
-			str <<  std::string(*stack_trace) << std::endl;
-		}
-		*/
-	    return ScriptResult(EMPTY_STRING, false, std::string("Script compile error: ") + str.str());
-	}
-	else {
-	    return ScriptResult(EMPTY_STRING, false, std::string("Script compile error: ") + std::string(*error));
-	}
+    return createErrorResult( "Script compile error", try_catch );
   }
 
   // Run the script
   v8::Handle<v8::Value> result = compiled_script->Run();
   if (result.IsEmpty())
   {
-    v8::String::AsciiValue error(try_catch.Exception());
-    return ScriptResult(EMPTY_STRING, false, std::string("Script result was empty: ") + std::string(*error));
+    return createErrorResult( "Script result was empty", try_catch );
   }
-
-  if (result->IsUndefined())
+  
+  if (result->IsUndefined() && !ignoreUndefinedResult)
     return ScriptResult(EMPTY_STRING, false, "Script result was undefined");
 
   v8::String::AsciiValue ascii(result);
@@ -199,7 +208,6 @@ JavascriptEngineV8::run(const std::string& code, osgEarth::Features::Feature con
   // Compile and run the script
   ScriptResult result = executeScript(v8::String::New(code.c_str(), code.length()));
 
-  //context.Dispose();
 
   return result;
 }
diff --git a/src/osgEarthDrivers/sky_gl/CMakeLists.txt b/src/osgEarthDrivers/sky_gl/CMakeLists.txt
new file mode 100644
index 0000000..6c56850
--- /dev/null
+++ b/src/osgEarthDrivers/sky_gl/CMakeLists.txt
@@ -0,0 +1,22 @@
+SET(TARGET_SRC 
+    GLSkyDriver.cpp
+    GLSkyNode.cpp
+)
+SET(TARGET_H
+    GLSkyOptions
+	GLSkyNode
+    GLSkyShaders
+)
+
+SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES}
+    osgEarthUtil
+)
+
+SETUP_PLUGIN(osgearth_sky_gl)
+
+
+# to install public driver includes:
+SET(LIB_NAME sky_gl)
+SET(LIB_PUBLIC_HEADERS GLSkyOptions)
+
+INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
diff --git a/src/osgEarthDrivers/sky_gl/GLSkyDriver.cpp b/src/osgEarthDrivers/sky_gl/GLSkyDriver.cpp
new file mode 100644
index 0000000..6915bbc
--- /dev/null
+++ b/src/osgEarthDrivers/sky_gl/GLSkyDriver.cpp
@@ -0,0 +1,62 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include "GLSkyOptions"
+#include "GLSkyNode"
+#include <osgDB/FileNameUtils>
+#include <osgEarth/Map>
+#include <osgEarth/MapNode>
+
+#define LC "[GLSkyDriver] "
+
+using namespace osgEarth::Util;
+
+namespace osgEarth { namespace Drivers { namespace GLSky
+{
+    class GLSkyDriver : public SkyDriver
+    {
+    public:
+        GLSkyDriver()
+        {
+            supportsExtension(
+                "osgearth_sky_gl",
+                "osgEarth GL Sky Plugin" );
+        }
+
+        const char* className()
+        {
+            return "osgEarth GL Sky Plugin";
+        }
+
+        ReadResult readNode(const std::string& file_name, const osgDB::Options* options) const
+        {
+            if ( !acceptsExtension(osgDB::getLowerCaseFileExtension( file_name )))
+                return ReadResult::FILE_NOT_HANDLED;
+
+            MapNode* mapNode = getMapNode(options);
+            const Profile* profile = mapNode ? mapNode->getMap()->getProfile() : 0L;
+            return new GLSkyNode(profile, getSkyOptions(options));
+        }
+
+    protected:
+        virtual ~GLSkyDriver() { }
+    };
+
+    REGISTER_OSGPLUGIN(osgearth_sky_gl, GLSkyDriver)
+
+} } } // namespace osgEarth::Drivers::GLSky
diff --git a/src/osgEarthDrivers/sky_gl/GLSkyNode b/src/osgEarthDrivers/sky_gl/GLSkyNode
new file mode 100644
index 0000000..da00fba
--- /dev/null
+++ b/src/osgEarthDrivers/sky_gl/GLSkyNode
@@ -0,0 +1,72 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+#include "GLSkyOptions"
+#include <osgEarthUtil/Sky>
+#include <osgEarth/MapNode>
+#include <osgEarth/PhongLightingEffect>
+#include <osg/MatrixTransform>
+
+namespace osg {
+    class EllipsoidModel;
+    class Light;
+    class LightSource;
+}
+
+namespace osgEarth { namespace Drivers { namespace GLSky
+{
+    using namespace osgEarth;
+    using namespace osgEarth::Util;
+
+    /**
+     * Node that roots the silverlining adapter.
+     */
+    class GLSkyNode : public SkyNode
+    {
+    public:
+        GLSkyNode(
+            const Profile* profile);
+
+        GLSkyNode(
+            const Profile*      profile,
+            const GLSkyOptions& options);
+
+    public: // SkyNode
+
+        osg::Light* getSunLight() { return _light.get(); }
+
+        void attach(osg::View* view, int lightNum);
+
+        void onSetEphemeris();
+        void onSetDateTime();
+        void onSetReferencePoint();
+
+    protected:
+        virtual ~GLSkyNode();
+
+    private:
+        void initialize(const Profile* profile);
+
+        osg::ref_ptr<osg::Light>          _light;
+        osg::ref_ptr<const Profile>       _profile;
+        osg::ref_ptr<PhongLightingEffect> _lighting;
+        GLSkyOptions                      _options;
+    };
+
+} } } // namespace osgEarth::Drivers::GLSky
diff --git a/src/osgEarthDrivers/sky_gl/GLSkyNode.cpp b/src/osgEarthDrivers/sky_gl/GLSkyNode.cpp
new file mode 100644
index 0000000..10618a1
--- /dev/null
+++ b/src/osgEarthDrivers/sky_gl/GLSkyNode.cpp
@@ -0,0 +1,146 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+#include "GLSkyNode"
+#include "GLSkyShaders"
+#include <osgEarthUtil/Ephemeris>
+
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/SpatialReference>
+#include <osgEarth/GeoData>
+#include <osgEarth/PhongLightingEffect>
+
+#define LC "[GLSkyNode] "
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+using namespace osgEarth::Drivers::GLSky;
+
+//---------------------------------------------------------------------------
+
+GLSkyNode::GLSkyNode(const Profile* profile) :
+SkyNode()
+{
+    initialize(profile);
+}
+
+GLSkyNode::GLSkyNode(const Profile*      profile,
+                     const GLSkyOptions& options) :
+SkyNode ( options ),
+_options( options )
+{
+    initialize(profile);
+}
+
+void
+GLSkyNode::initialize(const Profile* profile)
+{
+    _profile = profile;
+    _light = new osg::Light(0);
+    _light->setAmbient(osg::Vec4(0.1f, 0.1f, 0.1f, 1.0f));
+    _light->setDiffuse(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
+    _light->setSpecular(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
+    
+    if ( _options.ambient().isSet() )
+    {
+        float a = osg::clampBetween(_options.ambient().get(), 0.0f, 1.0f);
+        _light->setAmbient(osg::Vec4(a, a, a, 1.0f));
+    }
+
+    // installs the main uniforms and the shaders that will light the subgraph (terrain).
+    osg::StateSet* stateset = this->getOrCreateStateSet();
+
+    _lighting = new PhongLightingEffect();
+    _lighting->setCreateLightingUniform( false );
+    _lighting->attach( stateset );
+
+    onSetDateTime();
+}
+
+GLSkyNode::~GLSkyNode()
+{
+    if ( _lighting.valid() )
+        _lighting->detach();
+}
+
+void
+GLSkyNode::onSetEphemeris()
+{
+    // trigger the date/time update.
+    onSetDateTime();
+}
+
+void
+GLSkyNode::onSetReferencePoint()
+{
+    onSetDateTime();
+}
+
+void
+GLSkyNode::onSetDateTime()
+{
+    if ( !getSunLight() || !_profile.valid() )
+        return;
+
+    const DateTime& dt = getDateTime();
+    osg::Vec3d sunPosECEF = getEphemeris()->getSunPositionECEF( dt );
+
+    if ( _profile->getSRS()->isGeographic() )
+    {
+        sunPosECEF.normalize();
+        getSunLight()->setPosition( osg::Vec4(sunPosECEF, 0.0) );
+    }
+    else
+    {
+        // pull the ref point:
+        GeoPoint refpoint = getReferencePoint();
+        if ( !refpoint.isValid() )
+        {
+            // not found; use the center of the profile:
+            _profile->getExtent().getCentroid(refpoint);
+        }
+
+        // convert to lat/long:
+        GeoPoint refLatLong;
+        refpoint.transform(_profile->getSRS()->getGeographicSRS(), refLatLong);
+
+        // Matrix to convert the ECEF sun position to the local tangent plane
+        // centered on our reference point:
+        osg::Matrixd world2local;
+        refLatLong.createWorldToLocal(world2local);
+
+        // convert the sun position:
+        osg::Vec3d sunPosLocal = sunPosECEF * world2local;
+        sunPosLocal.normalize();
+
+        getSunLight()->setPosition( osg::Vec4(sunPosLocal, 0.0) );
+    }
+}
+
+void
+GLSkyNode::attach( osg::View* view, int lightNum )
+{
+    if ( !view ) return;
+
+    _light->setLightNum( lightNum );
+    view->setLight( _light.get() );
+    view->setLightingMode( osg::View::SKY_LIGHT );
+
+    onSetDateTime();
+}
diff --git a/src/osgEarthDrivers/yahoo/YahooOptions b/src/osgEarthDrivers/sky_gl/GLSkyOptions
similarity index 52%
copy from src/osgEarthDrivers/yahoo/YahooOptions
copy to src/osgEarthDrivers/sky_gl/GLSkyOptions
index 37d31fc..cab3f35 100644
--- a/src/osgEarthDrivers/yahoo/YahooOptions
+++ b/src/osgEarthDrivers/sky_gl/GLSkyOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -16,54 +16,51 @@
  * You should have received a copy of the GNU Lesser General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
-#ifndef OSGEARTH_DRIVER_YAHOO_DRIVEROPTIONS
-#define OSGEARTH_DRIVER_YAHOO_DRIVEROPTIONS 1
+#ifndef OSGEARTH_DRIVER_GL_SKY_OPTIONS
+#define OSGEARTH_DRIVER_GL_SKY_OPTIONS 1
 
-#include <osgEarth/Common>
-#include <osgEarth/TileSource>
+#include <osgEarthUtil/Sky>
 
-namespace osgEarth { namespace Drivers
+namespace osgEarth { namespace Drivers { namespace GLSky
 {
     using namespace osgEarth;
+    using namespace osgEarth::Util;
 
-    class YahooOptions : public TileSourceOptions // NO EXPORT; header only
+    /**
+     * Options for creating a sky with GL-like phong shading.
+     */
+    class GLSkyOptions : public SkyOptions
     {
     public:
-        optional<std::string>& dataset() { return _dataset; }
-        const optional<std::string>& dataset() const { return _dataset; }
-
-    public:
-        YahooOptions( const TileSourceOptions& opt =TileSourceOptions() ) : TileSourceOptions( opt )
+        GLSkyOptions(const ConfigOptions& options =ConfigOptions()) :
+          SkyOptions(options)
         {
-            setDriver( "yahoo" );
+            setDriver( "gl" );
             fromConfig( _conf );
         }
+        virtual ~GLSkyOptions() { }
 
-        /** dtor */
-        virtual ~YahooOptions() { }
+    public: // properties
 
     public:
         Config getConfig() const {
-            Config conf = TileSourceOptions::getConfig();
-            conf.updateIfSet("dataset", _dataset);
+            Config conf = SkyOptions::getConfig();
+            // adds
             return conf;
         }
 
     protected:
         void mergeConfig( const Config& conf ) {
-            TileSourceOptions::mergeConfig( conf );            
-            fromConfig( conf );
+            SkyOptions::mergeConfig( conf );
+            fromConfig(conf);
         }
 
     private:
         void fromConfig( const Config& conf ) {
-            conf.getIfSet( "dataset", _dataset );
+            // gets
         }
-
-        optional<std::string> _dataset;
     };
 
-} } // namespace osgEarth::Drivers
-
-#endif // OSGEARTH_DRIVER_YAHOO_DRIVEROPTIONS
+} } } // namespace osgEarth::Drivers::GLSky
 
+#endif // OSGEARTH_DRIVER_GL_SKY_OPTIONS
diff --git a/src/osgEarthDrivers/sky_gl/GLSkyShaders b/src/osgEarthDrivers/sky_gl/GLSkyShaders
new file mode 100644
index 0000000..ca2316b
--- /dev/null
+++ b/src/osgEarthDrivers/sky_gl/GLSkyShaders
@@ -0,0 +1,128 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_DRIVER_GL_SKY_SHADERS
+#define OSGEARTH_DRIVER_GL_SKY_SHADERS 1
+
+#include <osgEarth/VirtualProgram>
+
+namespace osgEarth { namespace Drivers { namespace GLSky
+{
+#ifdef OSG_GLES2_AVAILABLE
+    static const char* Phong_Vertex =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+        "uniform bool oe_mode_GL_LIGHTING; \n"
+        "varying vec4 oe_lighting_adjustment; \n"
+        "varying vec4 oe_lighting_zero_vec; \n"
+        "varying vec3 oe_Normal; \n"
+
+        "void oe_sky_vertex_main(inout vec4 VertexVIEW) \n"
+        "{ \n"
+        "    if ( oe_mode_GL_LIGHTING == false ) return; \n"
+        "    oe_lighting_adjustment = vec4(1.0); \n"
+        "    vec3 N = oe_Normal; \n"
+        "    float NdotL = dot( N, normalize(gl_LightSource[0].position.xyz) ); \n"
+        "    NdotL = max( 0.0, NdotL ); \n"
+
+        // NOTE: See comment in the fragment shader below for an explanation of
+        //       this oe_zero_vec value.
+        "    oe_lighting_zero_vec = vec4(0.0); \n"
+
+        "    vec4 adj = \n"
+        "        gl_FrontLightProduct[0].ambient + \n"
+        "        gl_FrontLightProduct[0].diffuse * NdotL; \n"
+        "    oe_lighting_adjustment = clamp( adj, 0.0, 1.0 ); \n"
+        "} \n";
+
+    static const char* Phong_Fragment =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+        
+        "uniform bool oe_mode_GL_LIGHTING; \n"
+        "varying vec4 oe_lighting_adjustment; \n"
+        "varying vec4 oe_lighting_zero_vec; \n"
+
+        "void oe_sky_fragment_main(inout vec4 color) \n"
+        "{ \n"
+        "    if ( oe_mode_GL_LIGHTING == false ) return; \n"
+        //NOTE: The follow was changed from the single line
+        //      "color *= oe_lighting_adjustment" to the current code to fix
+        //      an issue on iOS devices.  Adding a varying vec4 value set to
+        //      (0.0,0.0,0.0,0.0) to the color should not make a difference,
+        //      but it is part of the solution to the issue we were seeing.
+        //      Without it and the additional lines of code, the globe was
+        //      rendering textureless (just a white surface with lighting).
+        "    float alpha = color.a; \n"
+        "    color = color * oe_lighting_adjustment + oe_lighting_zero_vec; \n"
+        "    color.a = alpha; \n"
+        "} \n";
+
+#else
+
+    static const char* Phong_Vertex =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+        
+        "uniform bool oe_mode_GL_LIGHTING; \n"
+        "varying vec3 oe_glsky_vertexView3; \n"
+
+        "void oe_sky_vertex_main(inout vec4 VertexVIEW) \n"
+        "{ \n"
+        "    if ( oe_mode_GL_LIGHTING == false ) return; \n"
+        "    oe_glsky_vertexView3 = VertexVIEW.xyz / VertexVIEW.w; \n"
+        "} \n";
+
+    static const char* Phong_Fragment =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+        "uniform bool oe_mode_GL_LIGHTING; \n"
+        "varying vec3 oe_glsky_vertexView3; \n"
+        "varying vec3 oe_Normal; \n"
+
+        "void oe_sky_fragment_main(inout vec4 color) \n"
+        "{ \n"        
+        "    if ( oe_mode_GL_LIGHTING == false ) return; \n"
+
+        "    vec3 L = normalize(gl_LightSource[0].position.xyz); \n"
+        "    vec3 V = normalize(oe_glsky_vertexView3); \n"
+        "    vec3 N = normalize(oe_Normal); \n"
+        "    vec3 R = normalize(-reflect(L,N)); \n"
+
+        "    float NdotL = max(dot(N,L), 0.0); \n"
+
+        "    vec4 ambient = gl_FrontLightProduct[0].ambient; \n"
+        "    vec4 diffuse = clamp(gl_FrontLightProduct[0].diffuse * NdotL, 0.0, 1.0); \n"
+        "    vec4 specular= vec4(0); \n"
+#if 0
+        "    if (NdotL > 0.0) { \n"
+        "        vec3 HV = normalize(L+V); \n"
+        "        float HVdotN = max(dot(HV,N), 0.0); \n"
+        "        specular = gl_FrontLightProduct[0].specular * pow(HVdotN, 16.0); \n"
+        "    } \n"
+#endif
+
+        "    color.rgb = ambient.rgb + diffuse.rgb*color.rgb + specular.rgb; \n"
+        "} \n";
+#endif
+
+} } } // namespace osgEarth::Drivers::GLSky
+
+#endif //OSGEARTH_DRIVER_GL_SKY_SHADERS
diff --git a/src/osgEarthDrivers/sky_silverlining/CMakeLists.txt b/src/osgEarthDrivers/sky_silverlining/CMakeLists.txt
new file mode 100644
index 0000000..0e25d0d
--- /dev/null
+++ b/src/osgEarthDrivers/sky_silverlining/CMakeLists.txt
@@ -0,0 +1,32 @@
+SET(TARGET_SRC 
+    SilverLiningDriver.cpp
+    SilverLiningNode.cpp
+    SilverLiningContext.cpp
+    SilverLiningSkyDrawable.cpp
+    SilverLiningCloudsDrawable.cpp
+)
+SET(TARGET_H
+    SilverLiningOptions
+    SilverLiningNode
+    SilverLiningContext
+    SilverLiningSkyDrawable
+    SilverLiningCloudsDrawable
+)
+
+INCLUDE_DIRECTORIES( 
+    ${SILVERLINING_INCLUDE_DIR}
+)
+
+SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES}
+	${SILVERLINING_LIBRARY}
+    osgEarthUtil
+)
+
+SETUP_PLUGIN(osgearth_sky_silverlining)
+
+
+# to install public driver includes:
+SET(LIB_NAME sky_silverlining)
+SET(LIB_PUBLIC_HEADERS SilverLiningOptions)
+
+INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
diff --git a/src/osgEarthDrivers/sky_silverlining/SilverLiningCloudsDrawable b/src/osgEarthDrivers/sky_silverlining/SilverLiningCloudsDrawable
new file mode 100644
index 0000000..bf1fd9e
--- /dev/null
+++ b/src/osgEarthDrivers/sky_silverlining/SilverLiningCloudsDrawable
@@ -0,0 +1,62 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osg/Drawable>
+#include <osg/RenderInfo>
+#include <osg/Version>
+
+namespace osgEarth { namespace Drivers { namespace SilverLining
+{
+    class SilverLiningContext;
+
+    using namespace osgEarth;
+
+    /**
+     * Custom drawable for rendering the SilverLining clouds
+     */
+    class CloudsDrawable : public osg::Drawable
+    {
+    public:
+        CloudsDrawable(SilverLiningContext* SL =0L);
+        META_Object(SilverLining, CloudsDrawable);
+
+		/* Sets whether to draw this item */
+		void setDraw(bool draw);
+     
+    public: // osg::Drawable
+
+        // custom draw (called with an active GC)
+        void drawImplementation(osg::RenderInfo& ri) const;
+        
+        // custom bounds computation
+#if OSG_VERSION_GREATER_THAN(3,3,1)
+        osg::BoundingBox computeBoundingBox() const;
+#else
+        osg::BoundingBox computeBound() const;
+#endif
+
+    protected:
+        virtual ~CloudsDrawable() { }
+
+        osg::observer_ptr<SilverLiningContext> _SL;
+		bool _draw;
+        
+        CloudsDrawable(const CloudsDrawable& copy, const osg::CopyOp& op=osg::CopyOp::SHALLOW_COPY) { }
+    };
+
+} } } // namespace osgEarth::Drivers::SilverLining
diff --git a/src/osgEarthDrivers/sky_silverlining/SilverLiningCloudsDrawable.cpp b/src/osgEarthDrivers/sky_silverlining/SilverLiningCloudsDrawable.cpp
new file mode 100644
index 0000000..128342c
--- /dev/null
+++ b/src/osgEarthDrivers/sky_silverlining/SilverLiningCloudsDrawable.cpp
@@ -0,0 +1,65 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include "SilverLiningCloudsDrawable"
+#include "SilverLiningContext"
+#include <osgEarth/SpatialReference>
+#include <SilverLining.h>
+
+#define LC "[SilverLining:SkyDrawable] "
+
+using namespace osgEarth;
+using namespace osgEarth::Drivers::SilverLining;
+
+CloudsDrawable::CloudsDrawable(SilverLiningContext* SL) :
+_SL( SL )
+{
+    // call this to ensure draw() gets called every frame.
+    setSupportsDisplayList( false );
+    
+    // not MT-safe (camera updates, etc)
+    this->setDataVariance(osg::Object::DYNAMIC);    
+}
+
+void
+CloudsDrawable::drawImplementation(osg::RenderInfo& renderInfo) const
+{
+    if(_SL->ready())
+    {
+        renderInfo.getState()->disableAllVertexArrays();
+        _SL->getAtmosphere()->DrawObjects( true, true, true );
+        renderInfo.getState()->dirtyAllVertexArrays();
+    }
+}
+
+osg::BoundingBox
+#if OSG_VERSION_GREATER_THAN(3,3,1)
+CloudsDrawable::computeBoundingBox() const
+#else
+CloudsDrawable::computeBound() const
+#endif
+{
+    osg::BoundingBox cloudBoundBox;
+    if ( !_SL->ready() )
+        return cloudBoundBox;
+    
+    double minX, minY, minZ, maxX, maxY, maxZ;
+    _SL->getAtmosphere()->GetCloudBounds( minX, minY, minZ, maxX, maxY, maxZ );
+    cloudBoundBox.set( osg::Vec3d(minX, minY, minZ), osg::Vec3d(maxX, maxY, maxZ) );
+    return cloudBoundBox;
+}
diff --git a/src/osgEarthDrivers/sky_silverlining/SilverLiningContext b/src/osgEarthDrivers/sky_silverlining/SilverLiningContext
new file mode 100644
index 0000000..fcc9603
--- /dev/null
+++ b/src/osgEarthDrivers/sky_silverlining/SilverLiningContext
@@ -0,0 +1,106 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include "SilverLiningOptions"
+#include <osg/Referenced>
+#include <osg/Light>
+#include <osg/Camera>
+#include <osgEarth/ThreadingUtils>
+
+namespace SilverLining {
+    class Atmosphere;
+    class CloudLayer;
+}
+namespace osgEarth {
+    class SpatialReference;
+}
+
+namespace osgEarth { namespace Drivers { namespace SilverLining
+{
+    using namespace osgEarth;
+
+    /**
+     * Contains all the SilverLining SDK pointers.
+     */
+    class SilverLiningContext : public osg::Referenced
+    {
+    public:
+        SilverLiningContext(const SilverLiningOptions& options);
+        
+        /** Sets the light source that will represent the sun */
+        void setLight(osg::Light* light);
+
+        /** Sets the spatial reference system of the map */
+        void setSRS(const SpatialReference* srs);
+
+    public: // accessors
+
+        bool ready() const { return _initAttempted && !_initFailed; }
+
+        ::SilverLining::Atmosphere* getAtmosphere() { return _atmosphere; }
+
+        /** Spatial reference of the map */
+        const SpatialReference* getSRS() const { return _srs.get(); }
+
+        void setSkyBoxSize(double size) { _skyBoxSize = size; }
+        double getSkyBoxSize() const { return _skyBoxSize; }
+
+        void initialize(osg::RenderInfo& renderInfo);
+
+        void updateLocation();
+
+        void updateLight();
+
+        /** Set/get the cached camers. NOT THREAD/MULTI-CAM SAFE. */
+        /** TODO */
+        void setCamera(osg::Camera* camera) { _camera = camera; }
+        osg::Camera* getCamera() { return _camera.get(); }
+
+        void setCameraPosition(const osg::Vec3d& pos) { _cameraPos = pos; }
+        const osg::Vec3d& getCameraPosition() const { return _cameraPos; }
+
+    protected:
+
+        virtual ~SilverLiningContext();
+
+    private:
+
+        void setupClouds();
+
+    private:
+        ::SilverLining::Atmosphere* _atmosphere;
+        ::SilverLining::CloudLayer* _clouds;
+
+        double _skyBoxSize;
+
+        osg::observer_ptr<osg::Light>        _light;
+        osg::ref_ptr<const SpatialReference> _srs;
+
+        bool             _initAttempted;
+        bool             _initFailed;
+        Threading::Mutex _initMutex;
+
+        double _maxAmbientLightingAlt;
+
+        osg::observer_ptr<osg::Camera> _camera;
+        osg::Vec3d                     _cameraPos; // eye point
+
+        SilverLiningOptions _options;
+    };
+
+} } } // namespace osgEarth::Drivers::SilverLining
diff --git a/src/osgEarthDrivers/sky_silverlining/SilverLiningContext.cpp b/src/osgEarthDrivers/sky_silverlining/SilverLiningContext.cpp
new file mode 100644
index 0000000..8fc0afd
--- /dev/null
+++ b/src/osgEarthDrivers/sky_silverlining/SilverLiningContext.cpp
@@ -0,0 +1,225 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include "SilverLiningContext"
+#include <SilverLining.h> // SilverLinking SDK
+#include <osg/Light>
+#include <osgEarth/SpatialReference>
+
+#define LC "[SilverLiningContext] "
+
+using namespace osgEarth;
+using namespace osgEarth::Drivers::SilverLining;
+
+
+SilverLiningContext::SilverLiningContext(const SilverLiningOptions& options) :
+_options              ( options ),
+_initAttempted        ( false ),
+_initFailed           ( false ),
+_maxAmbientLightingAlt( -1.0 ),
+_atmosphere           ( 0L ),
+_clouds               ( 0L )
+{
+    // Create a SL atmosphere (the main SL object).
+    // TODO: plug in the username + license key.
+    _atmosphere = new ::SilverLining::Atmosphere(
+        options.user()->c_str(),
+        options.licenseCode()->c_str() );
+}
+
+void
+SilverLiningContext::setLight(osg::Light* light)
+{
+    _light = light;
+}
+
+void
+SilverLiningContext::setSRS(const SpatialReference* srs)
+{
+    _srs = srs;
+}
+
+void
+SilverLiningContext::initialize(osg::RenderInfo& renderInfo)
+{
+    if ( !_initAttempted && !_initFailed )
+    {
+        // lock/double-check:
+        Threading::ScopedMutexLock excl(_initMutex);
+        if ( !_initAttempted && !_initFailed )
+        {
+            _initAttempted = true;
+
+            // constant random seed ensures consistent clouds across windows
+            // TODO: replace this with something else since this is global! -gw
+            ::srand(1234);
+
+            int result = _atmosphere->Initialize(
+                ::SilverLining::Atmosphere::OPENGL,
+                _options.resourcePath()->c_str(),
+                true,
+                0 );
+
+            if ( result != ::SilverLining::Atmosphere::E_NOERROR )
+            {
+                _initFailed = true;
+                OE_WARN << LC << "SilverLining failed to initialize: " << result << std::endl;
+            }
+            else
+            {
+                OE_INFO << LC << "SilverLining initialized OK!" << std::endl;
+
+                // Defaults for a projected terrain. ECEF terrain vectors are set
+                // in updateLocation().
+                _atmosphere->SetUpVector( 0.0, 0.0, 1.0 );
+                _atmosphere->SetRightVector( 1.0, 0.0, 0.0 );
+
+#if 0 // todo: review this
+                _maxAmbientLightingAlt = 
+                    _atmosphere->GetConfigOptionDouble("atmosphere-height");
+#endif
+
+                if ( _options.drawClouds() == true )
+                {
+                    setupClouds();
+                }
+            }
+        }
+    }
+}
+
+void
+SilverLiningContext::setupClouds()
+{
+    _clouds = ::SilverLining::CloudLayerFactory::Create( CUMULUS_CONGESTUS );
+    _clouds->SetIsInfinite( true );
+    _clouds->SetFadeTowardEdges(true);
+    _clouds->SetBaseAltitude( 2000 );
+    _clouds->SetThickness( 200 );
+    _clouds->SetBaseLength( 100000 );
+    _clouds->SetBaseWidth( 100000 );
+    _clouds->SetDensity( 0.6 );
+    _clouds->SetAlpha( 0.8 );
+
+    _clouds->SeedClouds( *_atmosphere );
+    _clouds->GenerateShadowMaps( false );
+    
+    _clouds->SetLayerPosition(0, 0);
+
+    _atmosphere->GetConditions()->AddCloudLayer( _clouds );
+}
+
+void
+SilverLiningContext::updateLight()
+{
+    if ( !ready() || !_light.valid() || !_srs.valid() )
+        return;
+
+    float ra, ga, ba, rd, gd, bd, x, y, z;
+
+    // Clamp the camera's altitude while fetching the colors so the
+    // lighting's ambient component doesn't fade to black at high altitude.
+    ::SilverLining::Location savedLoc = _atmosphere->GetConditions()->GetLocation();
+    ::SilverLining::Location clampedLoc = savedLoc;
+    if ( _maxAmbientLightingAlt > 0.0 )
+    {
+        clampedLoc.SetAltitude( std::min(clampedLoc.GetAltitude(), _maxAmbientLightingAlt) );
+        _atmosphere->GetConditions()->SetLocation( clampedLoc );
+    }
+
+    _atmosphere->GetAmbientColor( &ra, &ga, &ba );
+    _atmosphere->GetSunColor( &rd, &gd, &bd );
+
+    // Restore the actual altitude.
+    if ( _maxAmbientLightingAlt > 0.0 )
+    {
+        _atmosphere->GetConditions()->SetLocation( savedLoc );
+    }
+
+    if ( _srs->isGeographic() )
+    {
+        _atmosphere->GetSunPositionGeographic( &x, &y, &z );
+    }
+    else
+    {
+        _atmosphere->GetSunPosition(&x, &y, &z);
+    }
+
+    osg::Vec3 direction(x, y, z);
+    direction.normalize();
+
+    _light->setAmbient( osg::Vec4(ra, ga, ba, 1.0f) );
+    _light->setDiffuse( osg::Vec4(rd, gd, bd, 1.0f) );
+    _light->setPosition( osg::Vec4(direction, 0.0f) ); //w=0 means "at infinity"
+}
+
+void
+SilverLiningContext::updateLocation()
+{
+    if ( !ready() || !_srs.valid() )
+        return;
+
+    if ( _srs->isGeographic() )
+    {
+        // Get new local orientation
+        osg::Vec3d up = _cameraPos;
+        up.normalize();
+        osg::Vec3d north = osg::Vec3d(0, 1, 0);
+        osg::Vec3d east = north ^ up;
+
+        // Check for edge case of north or south pole
+        if (east.length2() == 0)
+        {
+            east = osg::Vec3d(1, 0, 0);
+        }
+
+        east.normalize();
+
+        _atmosphere->SetUpVector(up.x(), up.y(), up.z());
+        _atmosphere->SetRightVector(east.x(), east.y(), east.z());
+
+        // Get new lat / lon / altitude
+        osg::Vec3d latLonAlt;
+        _srs->transformFromWorld(_cameraPos, latLonAlt);
+
+        ::SilverLining::Location loc;
+        loc.SetAltitude ( latLonAlt.z() );
+        loc.SetLongitude( osg::DegreesToRadians(latLonAlt.x()) );
+        loc.SetLatitude ( osg::DegreesToRadians(latLonAlt.y()) );
+
+        _atmosphere->GetConditions()->SetLocation( loc );
+
+        if ( _clouds )
+        {
+#if 1 //TODO: figure out why we need to call this a couple times before
+      //      it takes effect. -gw
+            static int c = 2;
+            if ( c > 0 ) {
+                --c;
+                _clouds->SetLayerPosition(0, 0);
+            }
+        }
+#endif
+    }
+}
+
+SilverLiningContext::~SilverLiningContext()
+{
+    // clean up all the SL handles.
+    delete _atmosphere;
+}
diff --git a/src/osgEarthDrivers/sky_silverlining/SilverLiningDriver.cpp b/src/osgEarthDrivers/sky_silverlining/SilverLiningDriver.cpp
new file mode 100644
index 0000000..c97730b
--- /dev/null
+++ b/src/osgEarthDrivers/sky_silverlining/SilverLiningDriver.cpp
@@ -0,0 +1,84 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include "SilverLiningOptions"
+#include "SilverLiningNode"
+#include <osgEarth/MapNode>
+#include <osgEarthUtil/Sky>
+#include <osgDB/FileNameUtils>
+
+#define LC "[SilverLiningDriver] "
+
+using namespace osgEarth::Util;
+
+namespace osgEarth { namespace Drivers { namespace SilverLining
+{
+    class SilverLiningDriver : public SkyDriver
+    {
+    public:
+        SilverLiningDriver()
+        {
+            supportsExtension(
+                "osgearth_sky_silverlining",
+                "osgEarth SilverLining Plugin" );
+        }
+
+        const char* className()
+        {
+            return "osgEarth SilverLining Plugin";
+        }
+
+        ReadResult readNode(const std::string& file_name, const osgDB::Options* options) const
+        {
+            if ( !acceptsExtension(osgDB::getLowerCaseFileExtension( file_name )))
+                return ReadResult::FILE_NOT_HANDLED;
+
+            SilverLiningOptions slOptions = getSkyOptions(options);
+
+            // if the Resource Path isn't set, attempt to set it from 
+            // the SL environment variable.
+            if ( !slOptions.resourcePath().isSet() )
+            {
+                const char* ev = ::getenv("SILVERLINING_PATH");
+                if ( ev )
+                {
+                    slOptions.resourcePath() = osgDB::concatPaths(
+                        std::string(ev),
+                        "Resources" );
+                }
+                else
+                {
+                    OE_WARN << LC
+                        << "No resource path! SilverLining might not initialize properly. "
+                        << "Consider setting the SILVERLINING_PATH environment variable."
+                        << std::endl;
+                }
+            }
+
+            MapNode* mapnode = getMapNode(options);
+            const Map* map = mapnode ? mapnode->getMap() : 0L;
+            return new SilverLiningNode( map, slOptions );
+        }
+
+    protected:
+        virtual ~SilverLiningDriver() { }
+    };
+
+    REGISTER_OSGPLUGIN(osgearth_sky_silverlining, SilverLiningDriver)
+
+} } } // namespace osgEarth::Drivers::SilverLining
diff --git a/src/osgEarthDrivers/sky_silverlining/SilverLiningNode b/src/osgEarthDrivers/sky_silverlining/SilverLiningNode
new file mode 100644
index 0000000..e149a69
--- /dev/null
+++ b/src/osgEarthDrivers/sky_silverlining/SilverLiningNode
@@ -0,0 +1,68 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+#include "SilverLiningOptions"
+#include <osgEarthUtil/Sky>
+#include <osgEarth/Map>
+#include <osgEarth/PhongLightingEffect>
+#include <osg/Light>
+
+namespace osgEarth { namespace Drivers { namespace SilverLining
+{
+    class SilverLiningContext;
+
+    using namespace osgEarth;
+    using namespace osgEarth::Util;
+
+    /**
+     * Node that roots the silverlining adapter.
+     */
+    class SilverLiningNode : public SkyNode
+    {
+    public:
+        SilverLiningNode(
+            const Map*                 map,
+            const SilverLiningOptions& options );
+
+    public: // SkyNode
+
+        osg::Light* getSunLight() { return _light.get(); }
+
+        void attach(osg::View* view, int lightNum);
+
+        void onSetDateTime();
+
+    public: // osg::Node
+
+        void traverse(osg::NodeVisitor&);
+
+    protected:
+        virtual ~SilverLiningNode();
+
+        osg::ref_ptr<SilverLiningContext> _SL;
+		osg::Geode* _geode;
+        osg::Drawable* _skyDrawable;
+		osg::ref_ptr<osg::Drawable> _cloudsDrawable;
+        osg::ref_ptr<osg::Light> _light;
+		double _lastAltitude;
+		const SilverLiningOptions _options;
+        osg::ref_ptr<PhongLightingEffect> _lighting;
+    };
+
+} } } // namespace osgEarth::Drivers::SilverLining
diff --git a/src/osgEarthDrivers/sky_silverlining/SilverLiningNode.cpp b/src/osgEarthDrivers/sky_silverlining/SilverLiningNode.cpp
new file mode 100644
index 0000000..82dad8f
--- /dev/null
+++ b/src/osgEarthDrivers/sky_silverlining/SilverLiningNode.cpp
@@ -0,0 +1,164 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+#include "SilverLiningNode"
+#include "SilverLiningContext"
+#include "SilverLiningSkyDrawable"
+#include "SilverLiningCloudsDrawable"
+
+#include <osg/Light>
+#include <osg/LightSource>
+#include <osgEarth/CullingUtils>
+#include <SilverLining.h>
+
+#define LC "[SilverLiningNode] "
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+using namespace osgEarth::Drivers::SilverLining;
+
+SilverLiningNode::SilverLiningNode(const Map*                 map,
+                                   const SilverLiningOptions& options) :
+_options     (options),
+_lastAltitude(DBL_MAX)
+{
+    // Create a new Light for the Sun.
+    _light = new osg::Light();
+    _light->setLightNum( 0 );
+    _light->setDiffuse( osg::Vec4(1,1,1,1) );
+    _light->setAmbient( osg::Vec4(0.2f, 0.2f, 0.2f, 1) );
+    _light->setPosition( osg::Vec4(1, 0, 0, 0) ); // w=0 means infinity
+    _light->setDirection( osg::Vec3(-1,0,0) );
+
+    osg::LightSource* source = new osg::LightSource();
+    source->setLight( _light.get() );
+    source->setReferenceFrame(osg::LightSource::RELATIVE_RF);
+    this->addChild( source );
+
+    // The main silver lining data:
+    _SL = new SilverLiningContext( options );
+    _SL->setLight( _light.get() );
+    _SL->setSRS  ( map->getSRS() );
+
+    // Geode to hold each of the SL drawables:
+    _geode = new osg::Geode();
+    _geode->setCullingActive( false );
+    this->addChild( _geode );
+
+    // Draws the sky:
+    _skyDrawable = new SkyDrawable( _SL.get() );
+    //_skyDrawable->getOrCreateStateSet()->setRenderBinDetails( 98, "RenderBin" );
+    _geode->addDrawable( _skyDrawable );
+
+    // Clouds
+    _cloudsDrawable = new CloudsDrawable( _SL.get() );
+    //_cloudsDrawable->getOrCreateStateSet()->setRenderBinDetails( 99, "RenderBin" );
+    _geode->addDrawable( _cloudsDrawable.get() );
+
+    // scene lighting
+    osg::StateSet* stateset = this->getOrCreateStateSet();
+    _lighting = new PhongLightingEffect();
+    _lighting->setCreateLightingUniform( false );
+    _lighting->attach( stateset );
+
+    // ensure it's depth sorted and draws after the terrain
+    //stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
+    //getOrCreateStateSet()->setRenderBinDetails( 100, "RenderBin" );
+
+    // SL requires an update pass.
+    ADJUST_UPDATE_TRAV_COUNT(this, +1);
+
+    // initialize date/time
+    onSetDateTime();
+}
+
+
+SilverLiningNode::~SilverLiningNode()
+{
+    if ( _lighting.valid() )
+        _lighting->detach();
+}
+
+void
+SilverLiningNode::attach(osg::View* view, int lightNum)
+{
+    _light->setLightNum( lightNum );
+    view->setLight( _light.get() );
+    view->setLightingMode( osg::View::SKY_LIGHT );
+}
+
+void
+SilverLiningNode::onSetDateTime()
+{
+    // set the SL local time to UTC/epoch.
+    ::SilverLining::LocalTime utcTime;
+    utcTime.SetFromEpochSeconds( getDateTime().asTimeStamp() );
+    _SL->getAtmosphere()->GetConditions()->SetTime( utcTime );
+}
+
+
+void
+SilverLiningNode::traverse(osg::NodeVisitor& nv)
+{
+    if ( _SL && _SL->ready() )
+    {
+        if ( nv.getVisitorType() == nv.UPDATE_VISITOR )
+        {
+			int frameNumber = nv.getFrameStamp()->getFrameNumber();
+            _SL->updateLocation();
+            _SL->updateLight();
+            _SL->getAtmosphere()->UpdateSkyAndClouds();
+            _skyDrawable->dirtyBound();
+
+            if( _cloudsDrawable )
+            {
+                if (_lastAltitude <= *_options.cloudsMaxAltitude() )
+                {
+                    if ( _cloudsDrawable->getNumParents() == 0 )
+                        _geode->addDrawable( _cloudsDrawable.get() );
+                    
+                    _cloudsDrawable->dirtyBound();
+                }
+                else
+                {
+                    if ( _cloudsDrawable->getNumParents() > 0 )
+                        _geode->removeDrawable( _cloudsDrawable.get() );
+                }
+            }
+        }
+        else if ( nv.getVisitorType() == nv.CULL_VISITOR )
+        {
+            // TODO: make this multi-camera safe
+            _SL->setCameraPosition( nv.getEyePoint() );
+            osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
+            _SL->getAtmosphere()->SetCameraMatrix( cv->getModelViewMatrix()->ptr() );
+            _SL->getAtmosphere()->SetProjectionMatrix( cv->getProjectionMatrix()->ptr() );
+
+			_lastAltitude = _SL->getSRS()->isGeographic() ?
+				cv->getEyePoint().length() - _SL->getSRS()->getEllipsoid()->getRadiusEquator() :
+				cv->getEyePoint().z();
+
+			if (_lastAltitude <= *_options.cloudsMaxAltitude() )
+			{
+				_SL->getAtmosphere()->CullObjects();
+			}
+        }
+    }
+    osgEarth::Util::SkyNode::traverse( nv );
+}
diff --git a/src/osgEarthDrivers/sky_silverlining/SilverLiningOptions b/src/osgEarthDrivers/sky_silverlining/SilverLiningOptions
new file mode 100644
index 0000000..5d47cab
--- /dev/null
+++ b/src/osgEarthDrivers/sky_silverlining/SilverLiningOptions
@@ -0,0 +1,104 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_DRIVER_SILVERLINING_OPTIONS
+#define OSGEARTH_DRIVER_SILVERLINING_OPTIONS 1
+
+#include <osgEarthUtil/Sky>
+
+namespace osgEarth { namespace Drivers { namespace SilverLining
+{
+    using namespace osgEarth;
+    using namespace osgEarth::Util;
+
+    /**
+     * Options for creating a SilverLining environment node
+     */
+    class SilverLiningOptions : public SkyOptions
+    {
+    public:
+        SilverLiningOptions(const SkyOptions& options =SkyOptions()) :
+          SkyOptions(options),
+          _drawClouds(false),
+          _cloudsMaxAltitude(20000)
+        {
+            setDriver( "silverlining" );
+            fromConfig( _conf );
+        }
+        virtual ~SilverLiningOptions() { }
+
+    public: // properties
+
+        /* User name for license activation */
+        optional<std::string>& user() { return _user; }
+        const optional<std::string>& user() const { return _user; }
+
+        /* License code string */
+        optional<std::string>& licenseCode() { return _licenseCode; }
+        const optional<std::string>& licenseCode() const { return _licenseCode; }
+
+        /* SilverLining resource path */
+        optional<std::string>& resourcePath() { return _resourcePath; }
+        const optional<std::string>& resourcePath() const { return _resourcePath; }
+
+        /* Whether to draw clouds */
+        optional<bool>& drawClouds() { return _drawClouds; }
+        const optional<bool>& drawClouds() const { return _drawClouds; }
+
+		/* Max altitude at which to draw/update clouds */
+		optional<double>& cloudsMaxAltitude() { return _cloudsMaxAltitude; }
+		const optional<double>& cloudsMaxAltitude() const { return _cloudsMaxAltitude; }
+
+    public:
+        Config getConfig() const {
+            Config conf = SkyOptions::getConfig();
+            conf.addIfSet("user", _user);
+            conf.addIfSet("license_code", _licenseCode);
+            conf.addIfSet("resource_path", _resourcePath);
+            conf.addIfSet("clouds", _drawClouds);
+			conf.addIfSet("clouds_max_altitude", _cloudsMaxAltitude);
+            return conf;
+        }
+
+    protected:
+        void mergeConfig( const Config& conf ) {
+            SkyOptions::mergeConfig( conf );
+            fromConfig(conf);
+        }
+
+    private:
+        void fromConfig( const Config& conf ) {
+            conf.getIfSet("user", _user);
+            conf.getIfSet("license_code", _licenseCode);
+            conf.getIfSet("resource_path", _resourcePath);
+            conf.getIfSet("clouds", _drawClouds);
+			conf.getIfSet("clouds_max_altitude", _cloudsMaxAltitude);
+        }
+
+        optional<std::string> _user;
+        optional<std::string> _licenseCode;
+        optional<std::string> _resourcePath;
+        optional<bool>        _drawClouds;
+		optional<double>      _cloudsMaxAltitude;
+		int                   _lastCullFrameNumber;
+    };
+
+} } } // namespace osgEarth::Drivers::SilverLiningPlugin
+
+#endif // OSGEARTH_DRIVER_SILVERLINING_OPTIONS
+
diff --git a/src/osgEarthDrivers/sky_silverlining/SilverLiningSkyDrawable b/src/osgEarthDrivers/sky_silverlining/SilverLiningSkyDrawable
new file mode 100644
index 0000000..1af61fc
--- /dev/null
+++ b/src/osgEarthDrivers/sky_silverlining/SilverLiningSkyDrawable
@@ -0,0 +1,58 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osg/Drawable>
+#include <osg/RenderInfo>
+#include <osg/Version>
+
+namespace osgEarth { namespace Drivers { namespace SilverLining
+{
+    class SilverLiningContext;
+
+    using namespace osgEarth;
+
+    /**
+     * Custom drawable for rendering the SilverLining effects
+     */
+    class SkyDrawable : public osg::Drawable
+    {
+    public:
+        SkyDrawable(SilverLiningContext* SL =0L);
+        META_Object(SilverLining, SkyDrawable);
+     
+    public: // osg::Drawable
+
+        // custom draw (called with an active GC)
+        void drawImplementation(osg::RenderInfo& ri) const;
+
+        // custom bounds computation
+#if OSG_VERSION_GREATER_THAN(3,3,1)
+        osg::BoundingBox computeBoundingBox() const;
+#else
+        osg::BoundingBox computeBound() const;
+#endif
+
+    protected:
+        virtual ~SkyDrawable() { }
+
+        osg::observer_ptr<SilverLiningContext> _SL;
+        
+        SkyDrawable(const SkyDrawable& copy, const osg::CopyOp& op=osg::CopyOp::SHALLOW_COPY) { }
+    };
+
+} } } // namespace osgEarth::Drivers::SilverLining
diff --git a/src/osgEarthDrivers/sky_silverlining/SilverLiningSkyDrawable.cpp b/src/osgEarthDrivers/sky_silverlining/SilverLiningSkyDrawable.cpp
new file mode 100644
index 0000000..64e3081
--- /dev/null
+++ b/src/osgEarthDrivers/sky_silverlining/SilverLiningSkyDrawable.cpp
@@ -0,0 +1,110 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include "SilverLiningSkyDrawable"
+#include "SilverLiningContext"
+#include <osgEarth/SpatialReference>
+#include <SilverLining.h>
+
+#define LC "[SilverLining:SkyDrawable] "
+
+using namespace osgEarth;
+using namespace osgEarth::Drivers::SilverLining;
+
+SkyDrawable::SkyDrawable(SilverLiningContext* SL) :
+_SL( SL )
+{
+    // call this to ensure draw() gets called every frame.
+    setSupportsDisplayList( false );
+
+    // not MT-safe (camera updates, etc)
+    this->setDataVariance( osg::Object::DYNAMIC );
+}
+
+void
+SkyDrawable::drawImplementation(osg::RenderInfo& renderInfo) const
+{
+    osg::Camera* camera = renderInfo.getCurrentCamera();
+    if ( camera )
+    {
+        renderInfo.getState()->disableAllVertexArrays();
+        _SL->initialize( renderInfo );
+
+        double fovy, ar, znear, zfar;
+        _SL->setCamera(camera);
+
+        //renderInfo.getCurrentCamera()->setNearFarRatio(.00000001);
+
+        camera->getProjectionMatrixAsPerspective(fovy, ar, znear, zfar);
+        _SL->setSkyBoxSize( zfar < 100000.0 ? zfar : 100000.0 );
+
+        _SL->getAtmosphere()->DrawSky(
+            true, 
+            _SL->getSRS()->isGeographic(),
+            _SL->getSkyBoxSize(),
+            true,
+            false );
+
+        renderInfo.getState()->dirtyAllVertexArrays();
+    }
+}
+
+osg::BoundingBox
+#if OSG_VERSION_GREATER_THAN(3,3,1)
+SkyDrawable::computeBoundingBox() const
+#else
+SkyDrawable::computeBound() const
+#endif
+{
+    osg::BoundingBox skyBoundBox;
+    if ( !_SL->ready() )
+        return skyBoundBox;
+    
+    ::SilverLining::Atmosphere* atmosphere = _SL->getAtmosphere();
+    double skyboxSize = _SL->getSkyBoxSize();
+    if ( skyboxSize == 0.0 )
+        skyboxSize = 1000.0;
+    
+    osg::Vec3d radiusVec = osg::Vec3d(skyboxSize, skyboxSize, skyboxSize) * 0.5;
+    osg::Vec3d camPos = _SL->getCameraPosition();
+    if (_SL->getCamera())
+    {
+        osg::Vec3f eye, center, up;
+        _SL->getCamera()->getViewMatrixAsLookAt(eye, center, up);
+        camPos = osg::Vec3d(eye.x(), eye.y(), eye.z());
+    }
+
+    skyBoundBox.set( camPos-radiusVec, camPos+radiusVec );
+    
+    // this enables the "blue ring" around the earth when viewing from space.
+    bool hasLimb = atmosphere->GetConfigOptionBoolean("enable-atmosphere-from-space");
+    if ( hasLimb )
+    {
+        // Compute bounds of atmospheric limb centered at (0,0,0)
+        double earthRadius = atmosphere->GetConfigOptionDouble("earth-radius-meters");
+        double atmosphereHeight = earthRadius + atmosphere->GetConfigOptionDouble("atmosphere-height");
+        double atmosphereThickness = atmosphere->GetConfigOptionDouble("atmosphere-scale-height-meters") + earthRadius;
+        
+        osg::BoundingBox atmosphereBox;
+        osg::Vec3d atmMin(-atmosphereThickness, -atmosphereThickness, -atmosphereThickness);
+        osg::Vec3d atmMax(atmosphereThickness, atmosphereThickness, atmosphereThickness);
+        atmosphereBox.set( atmMin, atmMax );
+        skyBoundBox.expandBy( atmosphereBox );
+    }
+    return skyBoundBox;
+}
diff --git a/src/osgEarthDrivers/sky_simple/CMakeLists.txt b/src/osgEarthDrivers/sky_simple/CMakeLists.txt
new file mode 100644
index 0000000..7bae2db
--- /dev/null
+++ b/src/osgEarthDrivers/sky_simple/CMakeLists.txt
@@ -0,0 +1,22 @@
+SET(TARGET_SRC 
+    SimpleSkyDriver.cpp
+    SimpleSkyNode.cpp
+)
+SET(TARGET_H
+    SimpleSkyOptions
+	SimpleSkyNode
+    SimpleSkyShaders
+)
+
+SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES}
+    osgEarthUtil
+)
+
+SETUP_PLUGIN(osgearth_sky_simple)
+
+
+# to install public driver includes:
+SET(LIB_NAME sky_simple)
+SET(LIB_PUBLIC_HEADERS SimpleSkyOptions)
+
+INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSkyDriver.cpp b/src/osgEarthDrivers/sky_simple/SimpleSkyDriver.cpp
new file mode 100644
index 0000000..5da5a97
--- /dev/null
+++ b/src/osgEarthDrivers/sky_simple/SimpleSkyDriver.cpp
@@ -0,0 +1,63 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include "SimpleSkyOptions"
+#include "SimpleSkyNode"
+#include <osgDB/FileNameUtils>
+#include <osgEarth/Map>
+#include <osgEarth/MapNode>
+
+#define LC "[SimpleSkyDriver] "
+
+using namespace osgEarth::Util;
+
+namespace osgEarth { namespace Drivers { namespace SimpleSky
+{
+    class SimpleSkyDriver : public SkyDriver
+    {
+    public:
+        SimpleSkyDriver()
+        {
+            supportsExtension(
+                "osgearth_sky_simple",
+                "osgEarth Simple Sky Plugin" );
+        }
+
+        const char* className()
+        {
+            return "osgEarth Simple Sky Plugin";
+        }
+
+        ReadResult readNode(const std::string& file_name, const osgDB::Options* options) const
+        {
+            if ( !acceptsExtension(osgDB::getLowerCaseFileExtension( file_name )))
+                return ReadResult::FILE_NOT_HANDLED;
+
+            MapNode* mapNode = getMapNode(options);
+            const SpatialReference* srs = mapNode ? mapNode->getMapSRS() : 0L;
+
+            return new SimpleSkyNode(srs, getSkyOptions(options));
+        }
+
+    protected:
+        virtual ~SimpleSkyDriver() { }
+    };
+
+    REGISTER_OSGPLUGIN(osgearth_sky_simple, SimpleSkyDriver)
+
+} } } // namespace osgEarth::Drivers::SimpleSky
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSkyNode b/src/osgEarthDrivers/sky_simple/SimpleSkyNode
new file mode 100644
index 0000000..909fe02
--- /dev/null
+++ b/src/osgEarthDrivers/sky_simple/SimpleSkyNode
@@ -0,0 +1,122 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+#include "SimpleSkyOptions"
+#include <osgEarthUtil/Sky>
+#include <osgEarth/MapNode>
+#include <osgEarth/PhongLightingEffect>
+#include <osg/MatrixTransform>
+
+namespace osg {
+    class EllipsoidModel;
+    class Light;
+}
+
+namespace osgEarth { namespace Drivers { namespace SimpleSky
+{
+    using namespace osgEarth;
+    using namespace osgEarth::Util;
+
+    /**
+     * Node that roots the silverlining adapter.
+     */
+    class SimpleSkyNode : public SkyNode
+    {
+    public:
+        SimpleSkyNode(
+            const SpatialReference* srs);
+
+        SimpleSkyNode(
+            const SpatialReference* srs,
+            const SimpleSkyOptions& options);
+
+    public: // SkyNode
+
+        osg::Light* getSunLight() { return _light.get(); }
+
+        void attach(osg::View* view, int lightNum);
+
+        void onSetEphemeris();
+        void onSetDateTime();
+        void onSetSunVisible();
+        void onSetMoonVisible();
+        void onSetStarsVisible();
+
+    public: // osg::Node
+
+        void traverse(osg::NodeVisitor&);
+
+        osg::BoundingSphere computeBound() const;
+
+    protected:
+        virtual ~SimpleSkyNode() { }
+
+    private:
+        /** Sets the sun's position as a latitude and longitude. */
+        //void setSunPosition(double lat_degrees, double lon_degrees, osg::View* view);
+
+        struct StarData
+        {
+            std::string name;
+            double right_ascension;
+            double declination;
+            double magnitude;
+            
+            StarData() { }
+            StarData( std::stringstream &ss );
+        };
+
+        osg::ref_ptr<osg::Light>   _light;
+        osg::ref_ptr<osg::Uniform> _lightPosUniform;
+        
+        osg::ref_ptr<osg::MatrixTransform> _sunXform;
+        osg::ref_ptr<osg::MatrixTransform> _moonXform;
+        osg::ref_ptr<osg::MatrixTransform> _starsXform;
+
+        osg::ref_ptr<osg::Group> _cullContainer;
+
+        float _innerRadius, _outerRadius, _sunDistance, _starRadius, _minStarMagnitude;
+        osg::ref_ptr<osg::Node> _sun, _stars, _atmosphere, _moon;
+        osg::ref_ptr<osg::Uniform> _starAlpha;
+        osg::ref_ptr<osg::Uniform> _starPointSize;
+
+        osg::ref_ptr<PhongLightingEffect> _phong;
+
+        osg::ref_ptr<const osg::EllipsoidModel> _ellipsoidModel;
+
+		const SimpleSkyOptions _options;
+
+        void initialize(const SpatialReference* srs);
+
+        void makeSceneLighting();
+        void makeAtmosphere( const osg::EllipsoidModel* );
+        void makeSun();
+        void makeMoon();
+
+        void makeStars();
+        osg::Node* buildStarGeometry(const std::vector<StarData>& stars);
+        void getDefaultStars(std::vector<StarData>& out_stars);
+        bool parseStarFile(const std::string& starFile, std::vector<StarData>& out_stars);
+
+        void setAmbientBrightness(float value);
+        void setSunPosition(const osg::Vec3& pos);
+        void setMoonPosition(const osg::Vec3d& pos);
+    };
+
+} } } // namespace osgEarth::Drivers::SimpleSky
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSkyNode.cpp b/src/osgEarthDrivers/sky_simple/SimpleSkyNode.cpp
new file mode 100644
index 0000000..4ba5b93
--- /dev/null
+++ b/src/osgEarthDrivers/sky_simple/SimpleSkyNode.cpp
@@ -0,0 +1,808 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+#include "SimpleSkyNode"
+#include "SimpleSkyShaders"
+
+#include <osgEarthUtil/StarData>
+#include <osgEarthUtil/Ephemeris>
+
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/NodeUtils>
+#include <osgEarth/Map>
+#include <osgEarth/Utils>
+#include <osgEarth/Registry>
+#include <osgEarth/Capabilities>
+#include <osgEarth/CullingUtils>
+#include <osgEarth/ShaderFactory>
+#include <osgEarth/ShaderGenerator>
+
+#include <osg/MatrixTransform>
+#include <osg/ShapeDrawable>
+#include <osg/PointSprite>
+#include <osg/BlendFunc>
+#include <osg/FrontFace>
+#include <osg/CullFace>
+#include <osg/Program>
+#include <osg/Camera>
+#include <osg/Point>
+#include <osg/Shape>
+#include <osg/Depth>
+#include <osg/Quat>
+
+#include <sstream>
+#include <time.h>
+
+#define LC "[SimpleSkyNode] "
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+using namespace osgEarth::Drivers::SimpleSky;
+
+//---------------------------------------------------------------------------
+
+#define BIN_STARS       -100003
+#define BIN_SUN         -100002
+#define BIN_MOON        -100001
+#define BIN_ATMOSPHERE  -100000
+
+#define TWO_PI 6.283185307179586476925286766559
+
+//---------------------------------------------------------------------------
+
+namespace
+{
+    // constucts an ellipsoidal mesh that we will use to draw the atmosphere
+    osg::Geometry* s_makeEllipsoidGeometry(const osg::EllipsoidModel* ellipsoid, 
+                                           double                     outerRadius, 
+                                           bool                       genTexCoords)
+    {
+        double hae = outerRadius - ellipsoid->getRadiusEquator();
+
+        osg::Geometry* geom = new osg::Geometry();
+        geom->setUseVertexBufferObjects(true);
+
+        int latSegments = 100;
+        int lonSegments = 2 * latSegments;
+
+        double segmentSize = 180.0/(double)latSegments; // degrees
+
+        osg::Vec3Array* verts = new osg::Vec3Array();
+        verts->reserve( latSegments * lonSegments );
+
+        osg::Vec2Array* texCoords = 0;
+        osg::Vec3Array* normals = 0;
+        if (genTexCoords)
+        {
+            texCoords = new osg::Vec2Array();
+            texCoords->reserve( latSegments * lonSegments );
+            geom->setTexCoordArray( 0, texCoords );
+
+            normals = new osg::Vec3Array();
+            normals->reserve( latSegments * lonSegments );
+            geom->setNormalArray( normals );
+            geom->setNormalBinding(osg::Geometry::BIND_PER_VERTEX );
+        }
+
+        osg::DrawElementsUShort* el = new osg::DrawElementsUShort( GL_TRIANGLES );
+        el->reserve( latSegments * lonSegments * 6 );
+
+        for( int y = 0; y <= latSegments; ++y )
+        {
+            double lat = -90.0 + segmentSize * (double)y;
+            for( int x = 0; x < lonSegments; ++x )
+            {
+                double lon = -180.0 + segmentSize * (double)x;
+                double gx, gy, gz;
+                ellipsoid->convertLatLongHeightToXYZ( osg::DegreesToRadians(lat), osg::DegreesToRadians(lon), hae, gx, gy, gz );
+                verts->push_back( osg::Vec3(gx, gy, gz) );
+
+                if (genTexCoords)
+                {
+                    double s = (lon + 180) / 360.0;
+                    double t = (lat + 90.0) / 180.0;
+                    texCoords->push_back( osg::Vec2(s, t ) );
+                }
+
+                if (normals)
+                {
+                    osg::Vec3 normal( gx, gy, gz);
+                    normal.normalize();
+                    normals->push_back( normal );
+                }
+
+
+                if ( y < latSegments )
+                {
+                    int x_plus_1 = x < lonSegments-1 ? x+1 : 0;
+                    int y_plus_1 = y+1;
+                    el->push_back( y*lonSegments + x );
+                    el->push_back( y_plus_1*lonSegments + x );
+                    el->push_back( y*lonSegments + x_plus_1 );
+                    el->push_back( y*lonSegments + x_plus_1 );
+                    el->push_back( y_plus_1*lonSegments + x );
+                    el->push_back( y_plus_1*lonSegments + x_plus_1 );
+                }
+            }
+        }
+
+        geom->setVertexArray( verts );
+        geom->addPrimitiveSet( el );
+
+        return geom;
+    }
+
+    // makes a disc geometry that we'll use to render the sun/moon
+    osg::Geometry* s_makeDiscGeometry(double radius)
+    {
+        int segments = 48;
+        float deltaAngle = 360.0/(float)segments;
+
+        osg::Geometry* geom = new osg::Geometry();
+        geom->setUseVertexBufferObjects(true);
+
+        osg::Vec3Array* verts = new osg::Vec3Array();
+        verts->reserve( 1 + segments );
+        geom->setVertexArray( verts );
+
+        osg::DrawElementsUShort* el = new osg::DrawElementsUShort( GL_TRIANGLES );
+        el->reserve( 1 + 2*segments );
+        geom->addPrimitiveSet( el );
+
+        verts->push_back( osg::Vec3(0,0,0) ); // center point
+
+        for( int i=0; i<segments; ++i )
+        {
+            double angle = osg::DegreesToRadians( deltaAngle * (float)i );
+            double x = radius * cos( angle );
+            double y = radius * sin( angle );
+            verts->push_back( osg::Vec3(x, y, 0.0) );
+
+            int i_plus_1 = i < segments-1? i+1 : 0;
+            el->push_back( 0 );
+            el->push_back( 1 + i_plus_1 );
+            el->push_back( 1 + i );
+        }
+
+        return geom;
+    }
+}
+
+//---------------------------------------------------------------------------
+
+SimpleSkyNode::SimpleSkyNode(const SpatialReference* srs) :
+SkyNode()
+{
+    initialize(srs);
+}
+
+SimpleSkyNode::SimpleSkyNode(const SpatialReference* srs,
+                             const SimpleSkyOptions& options) :
+SkyNode ( options ),
+_options( options )
+{
+    initialize(srs);
+}
+
+void
+SimpleSkyNode::initialize(const SpatialReference* srs)
+{
+    // protect us from the ShaderGenerator.
+    ShaderGenerator::setIgnoreHint(this, true);
+
+    osg::Vec3f lightPos(0.0f, 1.0f, 0.0f);
+
+    _light = new osg::Light( 0 );
+    _light->setPosition( osg::Vec4f(0.0f, 0.0f, 1.0, 0.0f) );
+    _light->setAmbient ( osg::Vec4f(0.03f, 0.03f, 0.03f, 1.0f) );
+    _light->setDiffuse ( osg::Vec4f(1.0f, 1.0f, 1.0f, 1.0f) );
+    _light->setSpecular( osg::Vec4f(1.0f, 1.0f, 1.0f, 1.0f) );
+
+    if ( _options.ambient().isSet() )
+    {
+        float a = osg::clampBetween(_options.ambient().get(), 0.0f, 1.0f);
+        _light->setAmbient(osg::Vec4(a, a, a, 1.0f));
+    }
+
+    // only supports geocentric for now.
+    if ( srs && !srs->isGeographic() )
+    {
+        OE_WARN << LC << "Sorry, SimpleSky only supports geocentric maps." << std::endl;
+        return;
+    }
+
+    // containers for sky elements.
+    _cullContainer = new osg::Group();
+    
+    // set up the astronomical parameters:
+    _ellipsoidModel = srs->getEllipsoid();
+    _innerRadius = osg::minimum(
+        _ellipsoidModel->getRadiusPolar(),
+        _ellipsoidModel->getRadiusEquator() );
+    _outerRadius = _innerRadius * 1.025f;
+    _sunDistance = _innerRadius * 12000.0f;
+    
+    if ( Registry::capabilities().supportsGLSL() )
+    {
+        _lightPosUniform = new osg::Uniform(osg::Uniform::FLOAT_VEC3, "atmos_v3LightDir");
+        _lightPosUniform->set( lightPos / lightPos.length() );
+        this->getOrCreateStateSet()->addUniform( _lightPosUniform.get() );
+
+        // default GL_LIGHTING uniform setting
+        this->getOrCreateStateSet()->addUniform(
+            Registry::shaderFactory()->createUniformForGLMode(GL_LIGHTING, 1) );
+
+        // make the uniforms and the terrain lighting shaders.
+        makeSceneLighting();
+
+        // make the sky elements (don't change the order here)
+        makeAtmosphere( _ellipsoidModel.get() );
+
+        makeSun();
+
+        makeMoon();
+
+        makeStars();
+    }
+
+    // Update everything based on the date/time.
+    onSetDateTime();
+}
+
+osg::BoundingSphere
+SimpleSkyNode::computeBound() const
+{
+    return osg::BoundingSphere();
+}
+
+void 
+    SimpleSkyNode::traverse( osg::NodeVisitor& nv ) 
+{ 
+    if ( nv.getVisitorType() == nv.CULL_VISITOR && _cullContainer.valid() ) 
+    { 
+        osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv); 
+
+        bool needToRestoreInheritanceMask =
+            (cv->getInheritanceMask() & osg::CullSettings::CLAMP_PROJECTION_MATRIX_CALLBACK) > 0; 
+
+        // If there's a custom projection matrix clamper installed, remove it temporarily. 
+        // We dont' want it mucking with our sky elements. 
+        osg::ref_ptr<osg::CullSettings::ClampProjectionMatrixCallback> cb = 
+            cv->getClampProjectionMatrixCallback(); 
+
+        cv->setClampProjectionMatrixCallback( 0L ); 
+
+        _cullContainer->accept( nv ); 
+
+        // restore a custom clamper. 
+        if ( cb.valid() ) 
+        { 
+            cv->setClampProjectionMatrixCallback( cb.get() ); 
+        } 
+
+        if (needToRestoreInheritanceMask) 
+        { 
+            cv->setInheritanceMask(
+                cv->getInheritanceMask() | osg::CullSettings::CLAMP_PROJECTION_MATRIX_CALLBACK); 
+        } 
+    } 
+
+    SkyNode::traverse( nv ); 
+} 
+
+void
+SimpleSkyNode::onSetEphemeris()
+{
+    // trigger the date/time update.
+    onSetDateTime();
+}
+
+void
+SimpleSkyNode::onSetDateTime()
+{
+    if ( _ellipsoidModel.valid() )
+    {
+        osg::View* view = 0L;
+        const DateTime& dt = getDateTime();
+
+        osg::Vec3d sunPos = getEphemeris()->getSunPositionECEF( dt );
+        osg::Vec3d moonPos = getEphemeris()->getMoonPositionECEF( dt );
+
+        sunPos.normalize();
+        setSunPosition( sunPos );
+        setMoonPosition( moonPos );
+
+        // position the stars:
+        double time_r = dt.hours()/24.0; // 0..1
+        double rot_z = -osg::PI + TWO_PI*time_r;
+
+        if ( _starsXform.valid() )
+            _starsXform->setMatrix( osg::Matrixd::rotate(-rot_z, 0, 0, 1) );
+    }
+}
+
+void
+SimpleSkyNode::attach( osg::View* view, int lightNum )
+{
+    if ( !view || !_light.valid() )
+        return;
+
+    _light->setLightNum( lightNum );
+    view->setLight( _light.get() );
+    view->setLightingMode( osg::View::SKY_LIGHT );
+    view->getCamera()->setClearColor( osg::Vec4(0,0,0,1) );
+
+    onSetDateTime();
+}
+
+void
+SimpleSkyNode::setSunPosition(const osg::Vec3& pos)
+{
+    _light->setPosition( osg::Vec4(pos, 0.0f) );
+    
+    if ( _lightPosUniform.valid() )
+    {
+        _lightPosUniform->set( pos/pos.length() );
+    }
+
+    if ( _sunXform.valid() )
+    {
+        _sunXform->setMatrix( osg::Matrix::translate( 
+            _sunDistance * pos.x(), 
+            _sunDistance * pos.y(),
+            _sunDistance * pos.z() ) );
+    }
+}
+
+void
+SimpleSkyNode::setMoonPosition(const osg::Vec3d& pos)
+{
+    if ( _moonXform.valid() )
+        _moonXform->setMatrix( osg::Matrixd::translate(pos.x(), pos.y(), pos.z()) );
+}
+
+void
+SimpleSkyNode::onSetStarsVisible()
+{
+    if ( _starsXform.valid() )
+        _starsXform->setNodeMask( getStarsVisible() ? ~0 : 0 );
+}
+
+void
+SimpleSkyNode::onSetMoonVisible()
+{
+    if ( _moonXform.valid() )
+        _moonXform->setNodeMask( getMoonVisible() ? ~0 : 0 );
+}
+
+void
+SimpleSkyNode::onSetSunVisible()
+{
+    if ( _sunXform.valid() )
+        _sunXform->setNodeMask( getSunVisible() ? ~0 : 0 );
+}
+
+void
+SimpleSkyNode::makeSceneLighting()
+{
+    // installs the main uniforms and the shaders that will light the subgraph (terrain).
+    osg::StateSet* stateset = this->getOrCreateStateSet();
+
+    VirtualProgram* vp = VirtualProgram::getOrCreate( stateset );
+    vp->setName( "SimpleSky Scene Lighting" );
+
+    if ( _options.atmosphericLighting() == true )
+    {
+        vp->setFunction(
+            "atmos_vertex_main",
+            Ground_Scattering_Vertex,
+            ShaderComp::LOCATION_VERTEX_VIEW);
+
+        vp->setFunction(
+            "atmos_fragment_main", 
+            Ground_Scattering_Fragment,
+            ShaderComp::LOCATION_FRAGMENT_LIGHTING);
+    }
+
+    else
+    {
+        _phong = new PhongLightingEffect();
+        _phong->setCreateLightingUniform( false );
+        _phong->attach( stateset );
+    }
+
+    // calculate and apply the uniforms:
+    // TODO: perhaps we can just hard-code most of these as GLSL consts.
+    float r_wl = ::powf( .65f, 4.0f );
+    float g_wl = ::powf( .57f, 4.0f );
+    float b_wl = ::powf( .475f, 4.0f );
+    osg::Vec3 RGB_wl( 1.0f/r_wl, 1.0f/g_wl, 1.0f/b_wl );
+    float Kr = 0.0025f;
+    float Kr4PI = Kr * 4.0f * osg::PI;
+    float Km = 0.0015f;
+    float Km4PI = Km * 4.0f * osg::PI;
+    float ESun = 15.0f;
+    float MPhase = -.095f;
+    float RayleighScaleDepth = 0.25f;
+    int   Samples = 2;
+    float Weather = 1.0f;
+
+    float Scale = 1.0f / (_outerRadius - _innerRadius);
+
+    stateset->getOrCreateUniform( "atmos_v3InvWavelength", osg::Uniform::FLOAT_VEC3 )->set( RGB_wl );
+    stateset->getOrCreateUniform( "atmos_fInnerRadius",    osg::Uniform::FLOAT )->set( _innerRadius );
+    stateset->getOrCreateUniform( "atmos_fInnerRadius2",   osg::Uniform::FLOAT )->set( _innerRadius * _innerRadius );
+    stateset->getOrCreateUniform( "atmos_fOuterRadius",    osg::Uniform::FLOAT )->set( _outerRadius );
+    stateset->getOrCreateUniform( "atmos_fOuterRadius2",   osg::Uniform::FLOAT )->set( _outerRadius * _outerRadius );
+    stateset->getOrCreateUniform( "atmos_fKrESun",         osg::Uniform::FLOAT )->set( Kr * ESun );
+    stateset->getOrCreateUniform( "atmos_fKmESun",         osg::Uniform::FLOAT )->set( Km * ESun );
+    stateset->getOrCreateUniform( "atmos_fKr4PI",          osg::Uniform::FLOAT )->set( Kr4PI );
+    stateset->getOrCreateUniform( "atmos_fKm4PI",          osg::Uniform::FLOAT )->set( Km4PI );
+    stateset->getOrCreateUniform( "atmos_fScale",          osg::Uniform::FLOAT )->set( Scale );
+    stateset->getOrCreateUniform( "atmos_fScaleDepth",     osg::Uniform::FLOAT )->set( RayleighScaleDepth );
+    stateset->getOrCreateUniform( "atmos_fScaleOverScaleDepth", osg::Uniform::FLOAT )->set( Scale / RayleighScaleDepth );
+    stateset->getOrCreateUniform( "atmos_g",               osg::Uniform::FLOAT )->set( MPhase );
+    stateset->getOrCreateUniform( "atmos_g2",              osg::Uniform::FLOAT )->set( MPhase * MPhase );
+    stateset->getOrCreateUniform( "atmos_nSamples",        osg::Uniform::INT )->set( Samples );
+    stateset->getOrCreateUniform( "atmos_fSamples",        osg::Uniform::FLOAT )->set( (float)Samples );
+    stateset->getOrCreateUniform( "atmos_fWeather",        osg::Uniform::FLOAT )->set( Weather );
+    stateset->getOrCreateUniform( "atmos_exposure",        osg::Uniform::FLOAT )->set( _options.exposure().value() );
+}
+
+void
+SimpleSkyNode::makeAtmosphere(const osg::EllipsoidModel* em)
+{
+    // create some skeleton geometry to shade:
+    osg::Geometry* drawable = s_makeEllipsoidGeometry( em, _outerRadius, false );
+
+    osg::Geode* geode = new osg::Geode();
+    geode->addDrawable( drawable );
+    
+    // configure the state set:
+    osg::StateSet* atmosSet = drawable->getOrCreateStateSet();
+    atmosSet->setMode( GL_LIGHTING, osg::StateAttribute::OFF );
+    atmosSet->setAttributeAndModes( new osg::CullFace(osg::CullFace::BACK), osg::StateAttribute::ON );
+    atmosSet->setAttributeAndModes( new osg::Depth( osg::Depth::LESS, 0, 1, false ) ); // no depth write
+    atmosSet->setAttributeAndModes( new osg::Depth(osg::Depth::ALWAYS, 0, 1, false) ); // no zbuffer
+    atmosSet->setAttributeAndModes( new osg::BlendFunc( GL_ONE, GL_ONE ), osg::StateAttribute::ON );
+
+    // first install the atmosphere rendering shaders.
+    if ( Registry::capabilities().supportsGLSL() )
+    {
+        VirtualProgram* vp = VirtualProgram::getOrCreate( atmosSet );
+        vp->setName( "SimpleSky Atmosphere" );
+        vp->setInheritShaders( false );
+
+        vp->setFunction(
+            "atmos_vertex_main",
+            Atmosphere_Vertex,
+            ShaderComp::LOCATION_VERTEX_VIEW);
+
+        vp->setFunction(
+            "atmos_fragment_main",
+            Atmosphere_Fragment,
+            ShaderComp::LOCATION_FRAGMENT_LIGHTING);
+    }
+
+    // A nested camera isolates the projection matrix calculations so the node won't 
+    // affect the clip planes in the rest of the scene.
+    osg::Camera* cam = new osg::Camera();
+    cam->getOrCreateStateSet()->setRenderBinDetails( BIN_ATMOSPHERE, "RenderBin" );
+    cam->setRenderOrder( osg::Camera::NESTED_RENDER );
+    cam->setComputeNearFarMode( osg::CullSettings::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES );
+    cam->addChild( geode );
+
+    _atmosphere = cam;
+
+    _cullContainer->addChild( _atmosphere.get() );
+}
+
+void
+SimpleSkyNode::makeSun()
+{
+    osg::Billboard* sun = new osg::Billboard();
+    sun->setMode( osg::Billboard::POINT_ROT_EYE );
+    sun->setNormal( osg::Vec3(0, 0, 1) );
+
+    float sunRadius = _innerRadius * 100.0f;
+
+    sun->addDrawable( s_makeDiscGeometry( sunRadius*80.0f ) ); 
+
+    osg::StateSet* set = sun->getOrCreateStateSet();
+    set->setMode( GL_BLEND, 1 );
+
+    set->getOrCreateUniform( "atmos_sunAlpha", osg::Uniform::FLOAT )->set( 1.0f );
+
+    // configure the stateset
+    set->setMode( GL_LIGHTING, osg::StateAttribute::OFF );
+    set->setMode( GL_CULL_FACE, osg::StateAttribute::OFF );
+    set->setAttributeAndModes( new osg::Depth(osg::Depth::ALWAYS, 0, 1, false), osg::StateAttribute::ON );
+
+    // create shaders
+    osg::Program* program = new osg::Program();
+    osg::Shader* vs = new osg::Shader( osg::Shader::VERTEX, Sun_Vertex );
+    program->addShader( vs );
+    osg::Shader* fs = new osg::Shader( osg::Shader::FRAGMENT, Sun_Fragment );
+    program->addShader( fs );
+    set->setAttributeAndModes( program, osg::StateAttribute::ON );
+
+    // A nested camera isolates the projection matrix calculations so the node won't 
+    // affect the clip planes in the rest of the scene.
+    osg::Camera* cam = new osg::Camera();
+    cam->getOrCreateStateSet()->setRenderBinDetails( BIN_SUN, "RenderBin" );
+    cam->setRenderOrder( osg::Camera::NESTED_RENDER );
+    cam->setComputeNearFarMode( osg::CullSettings::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES );
+    cam->addChild( sun );
+
+    _sun = cam;
+
+    // make the sun's transform:
+    // todo: move this?
+    _sunXform = new osg::MatrixTransform();
+    _sunXform->setMatrix( osg::Matrix::translate( 
+        _sunDistance * _light->getPosition().x(),
+        _sunDistance * _light->getPosition().y(),
+        _sunDistance * _light->getPosition().z() ) );
+    _sunXform->addChild( _sun.get() );
+
+    _cullContainer->addChild( _sunXform.get() );
+}
+
+void
+SimpleSkyNode::makeMoon()
+{
+    osg::ref_ptr< osg::EllipsoidModel > em = new osg::EllipsoidModel( 1738140.0, 1735970.0 );   
+    osg::Geode* moon = new osg::Geode;
+    moon->getOrCreateStateSet()->setAttributeAndModes( new osg::Program(), osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED );
+    osg::Geometry* geom = s_makeEllipsoidGeometry( em.get(), em->getRadiusEquator(), true );    
+    //TODO:  Embed this texture in code or provide a way to have a default resource directory for osgEarth.
+    //       Right now just need to have this file somewhere in your OSG_FILE_PATH
+    osg::Image* image = osgDB::readImageFile( "moon_1024x512.jpg" );
+    osg::Texture2D * texture = new osg::Texture2D( image );
+    texture->setFilter(osg::Texture::MIN_FILTER,osg::Texture::LINEAR);
+    texture->setFilter(osg::Texture::MAG_FILTER,osg::Texture::LINEAR);
+    texture->setResizeNonPowerOfTwoHint(false);
+    geom->getOrCreateStateSet()->setTextureAttributeAndModes( 0, texture, osg::StateAttribute::ON | osg::StateAttribute::PROTECTED);
+
+    osg::Vec4Array* colors = new osg::Vec4Array(1);    
+    geom->setColorArray( colors );
+    geom->setColorBinding(osg::Geometry::BIND_OVERALL);
+    (*colors)[0] = osg::Vec4(1, 1, 1, 1 );
+    moon->addDrawable( geom  ); 
+
+    osg::StateSet* set = moon->getOrCreateStateSet();
+    // configure the stateset
+    set->setMode( GL_LIGHTING, osg::StateAttribute::ON );
+    set->setAttributeAndModes( new osg::CullFace( osg::CullFace::BACK ), osg::StateAttribute::ON);
+    set->setRenderBinDetails( BIN_MOON, "RenderBin" );
+    set->setAttributeAndModes( new osg::Depth(osg::Depth::ALWAYS, 0, 1, false), osg::StateAttribute::ON );
+    set->setAttributeAndModes( new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA), osg::StateAttribute::ON );
+
+#ifdef OSG_GLES2_AVAILABLE
+
+    set->addUniform(new osg::Uniform("moonTex", 0));
+
+    // create shaders
+    osg::Program* program = new osg::Program();
+    osg::Shader* vs = new osg::Shader( osg::Shader::VERTEX, Moon_Vertex );
+    program->addShader( vs );
+    osg::Shader* fs = new osg::Shader( osg::Shader::FRAGMENT, Moon_Fragment );
+    program->addShader( fs );
+    set->setAttributeAndModes( program, osg::StateAttribute::ON | osg::StateAttribute::PROTECTED );
+#endif
+
+    // A nested camera isolates the projection matrix calculations so the node won't 
+    // affect the clip planes in the rest of the scene.
+    osg::Camera* cam = new osg::Camera();
+    cam->getOrCreateStateSet()->setRenderBinDetails( BIN_MOON, "RenderBin" );
+    cam->setRenderOrder( osg::Camera::NESTED_RENDER );
+    cam->setComputeNearFarMode( osg::CullSettings::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES );
+    cam->addChild( moon );
+
+    _moon = cam;
+
+    // make the moon's transform:
+    _moonXform = new osg::MatrixTransform();    
+    osg::Vec3d moonPosECEF = getEphemeris()->getMoonPositionECEF( getDateTime() );
+    _moonXform->setMatrix( osg::Matrix::translate( moonPosECEF ) ); 
+    _moonXform->addChild( _moon.get() );
+
+    _cullContainer->addChild( _moonXform.get() );
+
+    //If we couldn't load the moon texture, turn the moon off
+    if (!image)
+    {
+        OE_INFO << LC << "Couldn't load moon texture, add osgEarth's data directory your OSG_FILE_PATH" << std::endl;
+        //_moonXform->setNodeMask( 0 );
+        setMoonVisible(false);
+    }
+}
+
+SimpleSkyNode::StarData::StarData(std::stringstream &ss)
+{
+    std::getline( ss, name, ',' );
+    std::string buff;
+    std::getline( ss, buff, ',' );
+    std::stringstream(buff) >> right_ascension;
+    std::getline( ss, buff, ',' );
+    std::stringstream(buff) >> declination;
+    std::getline( ss, buff, '\n' );
+    std::stringstream(buff) >> magnitude;
+}
+
+void
+SimpleSkyNode::makeStars()
+{
+    const char* magEnv = ::getenv("OSGEARTH_MIN_STAR_MAGNITUDE");
+    if (magEnv)
+        _minStarMagnitude = as<float>(std::string(magEnv), -1.0f);
+    else
+        _minStarMagnitude = -1.0f;
+
+    _starRadius = 20000.0 * (_sunDistance > 0.0 ? _sunDistance : _outerRadius);
+
+    std::vector<StarData> stars;
+
+    if( _options.starFile().isSet() )
+    {
+        if ( parseStarFile(*_options.starFile(), stars) == false )
+        {
+            OE_WARN << LC 
+                << "Unable to use star field defined in \"" << *_options.starFile()
+                << "\", using default star data instead." << std::endl;
+        }
+    }
+
+    if ( stars.empty() )
+    {
+        getDefaultStars( stars );
+    }
+
+    _stars = buildStarGeometry(stars);
+
+    // make the moon's transform:
+    _starsXform = new osg::MatrixTransform();
+    _starsXform->addChild( _stars.get() );
+
+    _cullContainer->addChild( _starsXform.get() );
+}
+
+osg::Node*
+SimpleSkyNode::buildStarGeometry(const std::vector<StarData>& stars)
+{
+    double minMag = DBL_MAX, maxMag = DBL_MIN;
+
+    osg::Vec3Array* coords = new osg::Vec3Array();
+    std::vector<StarData>::const_iterator p;
+    for( p = stars.begin(); p != stars.end(); p++ )
+    {
+        osg::Vec3d v = getEphemeris()->getECEFfromRADecl(
+            p->right_ascension, 
+            p->declination, 
+            _starRadius );
+
+        coords->push_back( v );
+
+        if ( p->magnitude < minMag ) minMag = p->magnitude;
+        if ( p->magnitude > maxMag ) maxMag = p->magnitude;
+    }
+
+    osg::Vec4Array* colors = new osg::Vec4Array();
+    for( p = stars.begin(); p != stars.end(); p++ )
+    {
+        float c = ( (p->magnitude-minMag) / (maxMag-minMag) );
+        colors->push_back( osg::Vec4(c,c,c,1.0f) );
+    }
+
+    osg::Geometry* geometry = new osg::Geometry;
+    geometry->setUseVertexBufferObjects(true);
+
+    geometry->setVertexArray( coords );
+    geometry->setColorArray( colors );
+    geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
+    geometry->addPrimitiveSet( new osg::DrawArrays(osg::PrimitiveSet::POINTS, 0, coords->size()));
+
+    osg::StateSet* sset = geometry->getOrCreateStateSet();
+
+    sset->setTextureAttributeAndModes( 0, new osg::PointSprite(), osg::StateAttribute::ON );
+    sset->setMode( GL_VERTEX_PROGRAM_POINT_SIZE, osg::StateAttribute::ON );
+
+    std::string starVertSource, starFragSource;
+    if ( Registry::capabilities().getGLSLVersion() < 1.2f )
+    {
+        starVertSource = Stars_Vertex_110;
+        starFragSource = Stars_Fragment_110;
+    }
+    else
+    {
+        starVertSource = Stars_Vertex_120;
+        starFragSource = Stars_Fragment_120;
+    }
+
+    osg::Program* program = new osg::Program;
+    program->addShader( new osg::Shader(osg::Shader::VERTEX, starVertSource) );
+    program->addShader( new osg::Shader(osg::Shader::FRAGMENT, starFragSource) );
+    sset->setAttributeAndModes( program, osg::StateAttribute::ON );
+
+    sset->setRenderBinDetails( BIN_STARS, "RenderBin");
+    sset->setAttributeAndModes( new osg::Depth(osg::Depth::ALWAYS, 0, 1, false), osg::StateAttribute::ON );
+    sset->setMode(GL_BLEND, 1);
+
+    osg::Geode* starGeode = new osg::Geode;
+    starGeode->addDrawable( geometry );
+
+    // A separate camera isolates the projection matrix calculations.
+    osg::Camera* cam = new osg::Camera();
+    cam->getOrCreateStateSet()->setRenderBinDetails( BIN_STARS, "RenderBin" );
+    cam->setRenderOrder( osg::Camera::NESTED_RENDER );
+    cam->setComputeNearFarMode( osg::CullSettings::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES );
+    cam->addChild( starGeode );
+
+    return cam;
+}
+
+void
+SimpleSkyNode::getDefaultStars(std::vector<StarData>& out_stars)
+{
+    out_stars.clear();
+
+    for(const char **sptr = s_defaultStarData; *sptr; sptr++)
+    {
+        std::stringstream ss(*sptr);
+        out_stars.push_back(StarData(ss));
+
+        if (out_stars[out_stars.size() - 1].magnitude < _minStarMagnitude)
+            out_stars.pop_back();
+    }
+}
+
+bool
+SimpleSkyNode::parseStarFile(const std::string& starFile, std::vector<StarData>& out_stars)
+{
+    out_stars.clear();
+
+    std::fstream in(starFile.c_str());
+    if (!in)
+    {
+        OE_WARN <<  "Warning: Unable to open file star file \"" << starFile << "\"" << std::endl;
+        return false ;
+    }
+
+    while (!in.eof())
+    {
+        std::string line;
+
+        std::getline(in, line);
+        if (in.eof())
+            break;
+
+        if (line.empty() || line[0] == '#') 
+            continue;
+
+        std::stringstream ss(line);
+        out_stars.push_back(StarData(ss));
+
+        if (out_stars[out_stars.size() - 1].magnitude < _minStarMagnitude)
+            out_stars.pop_back();
+    }
+
+    in.close();
+
+    return true;
+}
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSkyOptions b/src/osgEarthDrivers/sky_simple/SimpleSkyOptions
new file mode 100644
index 0000000..5308e48
--- /dev/null
+++ b/src/osgEarthDrivers/sky_simple/SimpleSkyOptions
@@ -0,0 +1,90 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_DRIVER_SIMPLE_SKY_OPTIONS
+#define OSGEARTH_DRIVER_SIMPLE_SKY_OPTIONS 1
+
+#include <osgEarthUtil/Sky>
+
+namespace osgEarth { namespace Drivers { namespace SimpleSky
+{
+    using namespace osgEarth;
+    using namespace osgEarth::Util;
+
+    /**
+     * Options for creating a simple sky.
+     */
+    class SimpleSkyOptions : public SkyOptions
+    {
+    public:
+        SimpleSkyOptions(const ConfigOptions& options =ConfigOptions()) :
+          SkyOptions(options),
+          _atmosphericLighting(true),
+          _exposure           (3.0f)
+        {
+            setDriver( "simple" );
+            fromConfig( _conf );
+        }
+        virtual ~SimpleSkyOptions() { }
+
+    public: // properties
+
+        /** Use advanced atmospheric lighting (instead of simple shading) */
+        optional<bool>& atmosphericLighting() { return _atmosphericLighting; }
+        const optional<bool>& atmosphericLighting() const { return _atmosphericLighting; }
+
+        /** Exposure factor for ground lighting when using advanced lighting.
+          * Default is 3.0; [1..4] is a good range. */
+        optional<float>& exposure() { return _exposure; }
+        const optional<float>& exposure() const { return _exposure; }
+
+        /** replacement star definition file */
+        optional<std::string>& starFile() { return _starFile; }
+        const optional<std::string>& starFile() const { return _starFile; }
+
+    public:
+        Config getConfig() const {
+            Config conf = SkyOptions::getConfig();
+            conf.addIfSet("atmospheric_lighting", _atmosphericLighting);
+            conf.addIfSet("exposure", _exposure);
+            conf.addIfSet("star_file", _starFile);
+            return conf;
+        }
+
+    protected:
+        void mergeConfig( const Config& conf ) {
+            SkyOptions::mergeConfig( conf );
+            fromConfig(conf);
+        }
+
+    private:
+        void fromConfig( const Config& conf ) {
+            conf.getIfSet("atmospheric_lighting", _atmosphericLighting);
+            conf.getIfSet("exposure", _exposure);
+            conf.getIfSet("star_file", _starFile);
+        }
+
+        optional<bool>        _atmosphericLighting;
+        optional<float>       _exposure;
+        optional<std::string> _starFile;
+    };
+
+} } } // namespace osgEarth::Drivers::SimpleSky
+
+#endif // OSGEARTH_DRIVER_SIMPLE_SKY_OPTIONS
+
diff --git a/src/osgEarthDrivers/sky_simple/SimpleSkyShaders b/src/osgEarthDrivers/sky_simple/SimpleSkyShaders
new file mode 100644
index 0000000..474b238
--- /dev/null
+++ b/src/osgEarthDrivers/sky_simple/SimpleSkyShaders
@@ -0,0 +1,684 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_DRIVER_SIMPLE_SKY_SHADERS
+#define OSGEARTH_DRIVER_SIMPLE_SKY_SHADERS 1
+
+#include <osgEarth/VirtualProgram>
+
+namespace osgEarth { namespace Drivers { namespace SimpleSky
+{
+    // Atmospheric Scattering and Sun Shaders
+    // Adapted from code that is Copyright (c) 2004 Sean O'Neil
+
+    static char* Atmosphere_Vertex =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+        "uniform mat4 osg_ViewMatrixInverse;   // camera position in [3].xyz\n"
+        "uniform vec3 atmos_v3LightDir;        // The direction vector to the light source \n"
+        "uniform vec3 atmos_v3InvWavelength;   // 1 / pow(wavelength,4) for the rgb channels \n"
+        "uniform float atmos_fOuterRadius;     // Outer atmosphere radius \n"
+        "uniform float atmos_fOuterRadius2;    // fOuterRadius^2 \n"		
+        "uniform float atmos_fInnerRadius;     // Inner planetary radius \n"
+        "uniform float atmos_fInnerRadius2;    // fInnerRadius^2 \n"
+        "uniform float atmos_fKrESun;          // Kr * ESun \n"	
+        "uniform float atmos_fKmESun;          // Km * ESun \n"		
+        "uniform float atmos_fKr4PI;           // Kr * 4 * PI \n"	
+        "uniform float atmos_fKm4PI;           // Km * 4 * PI \n"		
+        "uniform float atmos_fScale;           // 1 / (fOuterRadius - fInnerRadius) \n"	
+        "uniform float atmos_fScaleDepth;      // The scale depth \n"
+        "uniform float atmos_fScaleOverScaleDepth;     // fScale / fScaleDepth \n"	
+        "uniform int atmos_nSamples; \n"	
+        "uniform float atmos_fSamples; \n"				
+
+        "varying vec3 atmos_v3Direction; \n"
+        "varying vec3 atmos_mieColor; \n"
+        "varying vec3 atmos_rayleighColor; \n"
+
+        "vec3 vVec; \n"
+        "float atmos_fCameraHeight;    // The camera's current height \n"		
+        "float atmos_fCameraHeight2;   // fCameraHeight^2 \n"
+
+        "float atmos_fastpow(in float x, in float y) \n"
+        "{ \n"
+        "    return x/(x+y-y*x); \n"
+        "} \n"
+
+        "float atmos_scale(float fCos) \n"	
+        "{ \n"
+        "    float x = 1.0 - fCos; \n"
+        "    return atmos_fScaleDepth * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25)))); \n"
+        "} \n"
+
+        "void atmos_SkyFromSpace(void) \n"
+        "{ \n"
+        "    // Get the ray from the camera to the vertex and its length (which is the far point of the ray passing through the atmosphere) \n"
+        "    vec3 v3Pos = gl_Vertex.xyz; \n"
+        "    vec3 v3Ray = v3Pos - vVec; \n"
+        "    float fFar = length(v3Ray); \n"
+        "    v3Ray /= fFar; \n"
+
+        "    // Calculate the closest intersection of the ray with the outer atmosphere \n"
+        "    // (which is the near point of the ray passing through the atmosphere) \n"
+        "    float B = 2.0 * dot(vVec, v3Ray); \n"
+        "    float C = atmos_fCameraHeight2 - atmos_fOuterRadius2; \n"
+        "    float fDet = max(0.0, B*B - 4.0 * C); \n"	
+        "    float fNear = 0.5 * (-B - sqrt(fDet)); \n"		
+
+        "    // Calculate the ray's starting position, then calculate its scattering offset \n"
+        "    vec3 v3Start = vVec + v3Ray * fNear; \n"			
+        "    fFar -= fNear; \n"	
+        "    float fStartAngle = dot(v3Ray, v3Start) / atmos_fOuterRadius; \n"			
+        "    float fStartDepth = exp(-1.0 / atmos_fScaleDepth); \n"
+        "    float fStartOffset = fStartDepth*atmos_scale(fStartAngle); \n"		
+
+        "    // Initialize the atmos_ing loop variables \n"	
+        "    float fSampleLength = fFar / atmos_fSamples; \n"		
+        "    float fScaledLength = fSampleLength * atmos_fScale; \n"					
+        "    vec3 v3SampleRay = v3Ray * fSampleLength; \n"	
+        "    vec3 v3SamplePoint = v3Start + v3SampleRay * 0.5; \n"	
+
+        "    // Now loop through the sample rays \n"
+        "    vec3 v3FrontColor = vec3(0.0, 0.0, 0.0); \n"
+        "    vec3 v3Attenuate; \n"  
+        "    for(int i=0; i<atmos_nSamples; i++) \n"		
+        "    { \n"
+        "        float fHeight = length(v3SamplePoint); \n"			
+        "        float fDepth = exp(atmos_fScaleOverScaleDepth * (atmos_fInnerRadius - fHeight)); \n"
+        "        float fLightAngle = dot(atmos_v3LightDir, v3SamplePoint) / fHeight; \n"		
+        "        float fCameraAngle = dot(v3Ray, v3SamplePoint) / fHeight; \n"			
+        "        float fscatter = (fStartOffset + fDepth*(atmos_scale(fLightAngle) - atmos_scale(fCameraAngle))); \n"	
+        "        v3Attenuate = exp(-fscatter * (atmos_v3InvWavelength * atmos_fKr4PI + atmos_fKm4PI)); \n"	
+        "        v3FrontColor += v3Attenuate * (fDepth * fScaledLength); \n"					
+        "        v3SamplePoint += v3SampleRay; \n"		
+        "    } \n"		
+
+        "    // Finally, scale the Mie and Rayleigh colors and set up the varying \n"			
+        "    // variables for the pixel shader \n"	
+        "    atmos_mieColor      = v3FrontColor * atmos_fKmESun; \n"				
+        "    atmos_rayleighColor = v3FrontColor * (atmos_v3InvWavelength * atmos_fKrESun); \n"						
+        "    atmos_v3Direction = vVec  - v3Pos; \n"			
+        "} \n"		
+
+        "void atmos_SkyFromAtmosphere(void) \n"		
+        "{ \n"
+        "  // Get the ray from the camera to the vertex, and its length (which is the far \n"
+        "  // point of the ray passing through the atmosphere) \n"		
+        "  vec3 v3Pos = gl_Vertex.xyz; \n"	
+        "  vec3 v3Ray = v3Pos - vVec; \n"			
+        "  float fFar = length(v3Ray); \n"					
+        "  v3Ray /= fFar; \n"				
+
+        "  // Calculate the ray's starting position, then calculate its atmos_ing offset \n"
+        "  vec3 v3Start = vVec; \n"
+        "  float fHeight = length(v3Start); \n"		
+        "  float fDepth = exp(atmos_fScaleOverScaleDepth * (atmos_fInnerRadius - atmos_fCameraHeight)); \n"
+        "  float fStartAngle = dot(v3Ray, v3Start) / fHeight; \n"	
+        "  float fStartOffset = fDepth*atmos_scale(fStartAngle); \n"
+
+        "  // Initialize the atmos_ing loop variables \n"		
+        "  float fSampleLength = fFar / atmos_fSamples; \n"			
+        "  float fScaledLength = fSampleLength * atmos_fScale; \n"				
+        "  vec3 v3SampleRay = v3Ray * fSampleLength; \n"		
+        "  vec3 v3SamplePoint = v3Start + v3SampleRay * 0.5; \n"
+
+        "  // Now loop through the sample rays \n"		
+        "  vec3 v3FrontColor = vec3(0.0, 0.0, 0.0); \n"		
+        "  vec3 v3Attenuate; \n"  
+        "  for(int i=0; i<atmos_nSamples; i++) \n"			
+        "  { \n"	
+        "    float fHeight = length(v3SamplePoint); \n"	
+        "    float fDepth = exp(atmos_fScaleOverScaleDepth * (atmos_fInnerRadius - fHeight)); \n"
+        "    float fLightAngle = dot(atmos_v3LightDir, v3SamplePoint) / fHeight; \n"
+        "    float fCameraAngle = dot(v3Ray, v3SamplePoint) / fHeight; \n"	
+        "    float fscatter = (fStartOffset + fDepth*(atmos_scale(fLightAngle) - atmos_scale(fCameraAngle))); \n"	
+        "    v3Attenuate = exp(-fscatter * (atmos_v3InvWavelength * atmos_fKr4PI + atmos_fKm4PI)); \n"	
+        "    v3FrontColor += v3Attenuate * (fDepth * fScaledLength); \n"		
+        "    v3SamplePoint += v3SampleRay; \n"		
+        "  } \n"
+
+        "  // Finally, scale the Mie and Rayleigh colors and set up the varying \n"
+        "  // variables for the pixel shader \n"					
+        "  atmos_mieColor      = v3FrontColor * atmos_fKmESun; \n"			
+        "  atmos_rayleighColor = v3FrontColor * (atmos_v3InvWavelength * atmos_fKrESun); \n"				
+        "  atmos_v3Direction = vVec - v3Pos; \n"				
+        "} \n"
+
+        "void atmos_vertex_main(inout vec4 VertexVIEW) \n"
+        "{ \n"
+        "    // Get camera position and height \n"
+        "    vVec = osg_ViewMatrixInverse[3].xyz; \n"
+        "    atmos_fCameraHeight = length(vVec); \n"
+        "    atmos_fCameraHeight2 = atmos_fCameraHeight*atmos_fCameraHeight; \n"
+        "    if(atmos_fCameraHeight >= atmos_fOuterRadius) \n"
+        "    { \n"
+        "        atmos_SkyFromSpace(); \n"
+        "    } \n"
+        "    else { \n"
+        "        atmos_SkyFromAtmosphere(); \n"
+        "    } \n"
+        "} \n";
+
+    static char* Atmosphere_Fragment = 
+        "uniform vec3 atmos_v3LightDir; \n"
+
+        "uniform float atmos_g; \n"				
+        "uniform float atmos_g2; \n"
+        "uniform float atmos_fWeather; \n"
+
+        "varying vec3 atmos_v3Direction; \n"	
+        "varying vec3 atmos_mieColor; \n"
+        "varying vec3 atmos_rayleighColor; \n"
+
+        "const float fExposure = 4.0; \n"
+
+        "float atmos_fastpow(in float x, in float y) \n"
+        "{ \n"
+        "    return x/(x+y-y*x); \n"
+        "} \n"
+
+        "void atmos_fragment_main(inout vec4 color) \n"
+        "{ \n"				
+        "    float fCos = dot(atmos_v3LightDir, atmos_v3Direction) / length(atmos_v3Direction); \n"
+        "    float fRayleighPhase = 1.0; \n" // 0.75 * (1.0 + fCos*fCos); \n"
+        "    float fMiePhase = 1.5 * ((1.0 - atmos_g2) / (2.0 + atmos_g2)) * (1.0 + fCos*fCos) / atmos_fastpow(1.0 + atmos_g2 - 2.0*atmos_g*fCos, 1.5); \n"
+        "    vec3 f4Color = fRayleighPhase * atmos_rayleighColor + fMiePhase * atmos_mieColor; \n"
+        "    vec3 skyColor = 1.0 - exp(f4Color * -fExposure); \n"
+        "    color.rgb = skyColor.rgb*atmos_fWeather; \n"
+        "    color.a = (skyColor.r+skyColor.g+skyColor.b) * 2.0; \n"
+        "} \n";
+
+
+    //-- GROUND LIGHTING WITH ATMOSPHERIC SCATTERING --------------------------
+    
+    static const char* Ground_Scattering_Vertex =
+        "uniform bool oe_mode_GL_LIGHTING; \n"
+
+        "uniform mat4 osg_ViewMatrixInverse;   // world camera position in [3].xyz \n"
+        "uniform mat4 osg_ViewMatrix;          // GL view matrix \n"
+        "uniform vec3 atmos_v3LightDir;        // The direction vector to the light source \n"
+        "uniform vec3 atmos_v3InvWavelength;   // 1 / pow(wavelength,4) for the rgb channels \n"
+        "uniform float atmos_fOuterRadius;     // Outer atmosphere radius \n"
+        "uniform float atmos_fOuterRadius2;    // fOuterRadius^2 \n"		
+        "uniform float atmos_fInnerRadius;     // Inner planetary radius \n"
+        "uniform float atmos_fInnerRadius2;    // fInnerRadius^2 \n"
+        "uniform float atmos_fKrESun;          // Kr * ESun \n"	
+        "uniform float atmos_fKmESun;          // Km * ESun \n"		
+        "uniform float atmos_fKr4PI;           // Kr * 4 * PI \n"	
+        "uniform float atmos_fKm4PI;           // Km * 4 * PI \n"		
+        "uniform float atmos_fScale;           // 1 / (fOuterRadius - fInnerRadius) \n"	
+        "uniform float atmos_fScaleDepth;      // The scale depth \n"
+        "uniform float atmos_fScaleOverScaleDepth;     // fScale / fScaleDepth \n"	
+        "uniform int atmos_nSamples; \n"	
+        "uniform float atmos_fSamples; \n"
+
+        "varying vec3 atmos_color; \n"         // primary sky light color
+        "varying vec3 atmos_atten; \n"         // sky light attenuation factor
+        "varying vec3 oe_Normal; \n"           // surface normal (from osgEarth)
+        "varying vec3 atmos_lightDir; \n"      // light direction in view space
+        
+        "float atmos_fCameraHeight;            // The camera's current height \n"		
+        "float atmos_fCameraHeight2;           // fCameraHeight^2 \n"
+
+        "varying vec3 atmos_up; \n"            // earth up vector at vertex location (not the normal)
+        "varying float atmos_space; \n"        // [0..1]: camera: 0=inner radius (ground); 1.0=outer radius
+        "varying vec3 atmos_vert; \n"
+
+        "float atmos_scale(float fCos) \n"	
+        "{ \n"
+        "    float x = 1.0 - fCos; \n"
+        "    return atmos_fScaleDepth * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25)))); \n"
+        "} \n"
+
+        "void atmos_GroundFromSpace(in vec4 vertexVIEW) \n"
+        "{ \n"
+        "    // Get the ray from the camera to the vertex and its length (which is the far point of the ray passing through the atmosphere) \n"
+        "    vec3 v3Pos = vertexVIEW.xyz; \n"
+        "    vec3 v3Ray = v3Pos; \n"
+        "    float fFar = length(v3Ray); \n"
+        "    v3Ray /= fFar; \n"
+                
+        "    vec4 ec4 = osg_ViewMatrix * vec4(0,0,0,1); \n"
+        "    vec3 earthCenter = ec4.xyz/ec4.w; \n"
+        "    vec3 normal = normalize(v3Pos-earthCenter); \n"
+        "    atmos_up = normal; \n"
+
+        "    // Calculate the closest intersection of the ray with the outer atmosphere \n"
+        "    // (which is the near point of the ray passing through the atmosphere) \n"
+        "    float B = 2.0 * dot(-earthCenter, v3Ray); \n"
+        "    float C = atmos_fCameraHeight2 - atmos_fOuterRadius2; \n"
+        "    float fDet = max(0.0, B*B - 4.0*C); \n"	
+        "    float fNear = 0.5 * (-B - sqrt(fDet)); \n"		
+
+        "    // Calculate the ray's starting position, then calculate its scattering offset \n"
+        "    vec3 v3Start = v3Ray * fNear; \n"			
+        "    fFar -= fNear; \n"
+        "    float fDepth = exp((atmos_fInnerRadius - atmos_fOuterRadius) / atmos_fScaleDepth);\n"
+        "    float fCameraAngle = dot(-v3Ray, normal); \n"
+        "    float fLightAngle = dot(atmos_lightDir, normal); \n"
+        "    float fCameraScale = atmos_scale(fCameraAngle); \n"
+        "    float fLightScale = atmos_scale(fLightAngle); \n"
+        "    float fCameraOffset = fDepth*fCameraScale; \n"
+        "    float fTemp = fLightScale * fCameraScale; \n"		
+
+        "    // Initialize the scattering loop variables \n"
+        "    float fSampleLength = fFar / atmos_fSamples; \n"		
+        "    float fScaledLength = fSampleLength * atmos_fScale; \n"					
+        "    vec3 v3SampleRay = v3Ray * fSampleLength; \n"	
+        "    vec3 v3SamplePoint = v3Start + v3SampleRay * 0.5; \n"	
+
+        "    // Now loop through the sample rays \n"
+        "    vec3 v3FrontColor = vec3(0.0, 0.0, 0.0); \n"
+        "    vec3 v3Attenuate = vec3(1,0,0); \n"
+
+        "    for(int i=0; i<atmos_nSamples; ++i) \n"
+        "    { \n"        
+        "        float fHeight = length(v3SamplePoint-earthCenter); \n"			
+        "        float fDepth = exp(atmos_fScaleOverScaleDepth * (atmos_fInnerRadius - fHeight)); \n"
+        "        float fScatter = fDepth*fTemp - fCameraOffset; \n"
+        "        v3Attenuate = exp(-fScatter * (atmos_v3InvWavelength * atmos_fKr4PI + atmos_fKm4PI)); \n"	
+        "        v3FrontColor += v3Attenuate * (fDepth * fScaledLength); \n"					
+        "        v3SamplePoint += v3SampleRay; \n"		
+        "    } \n"	
+
+        "    atmos_color = v3FrontColor * (atmos_v3InvWavelength * atmos_fKrESun + atmos_fKmESun); \n"
+        "    atmos_atten = v3Attenuate; \n"
+        "} \n"		
+
+        "void atmos_GroundFromAtmosphere(in vec4 vertexVIEW) \n"		
+        "{ \n"
+        "    // Get the ray from the camera to the vertex and its length (which is the far point of the ray passing through the atmosphere) \n"
+        "    vec3 v3Pos = vertexVIEW.xyz / vertexVIEW.w; \n"
+        "    vec3 v3Ray = v3Pos; \n"
+        "    float fFar = length(v3Ray); \n"
+        "    v3Ray /= fFar; \n"
+        
+        "    vec4 ec4 = osg_ViewMatrix * vec4(0,0,0,1); \n"
+        "    vec3 earthCenter = ec4.xyz/ec4.w; \n"
+        "    vec3 normal = normalize(v3Pos-earthCenter); \n"
+        "    atmos_up = normal; \n"
+
+        "    // Calculate the ray's starting position, then calculate its scattering offset \n"
+        "    float fDepth = exp((atmos_fInnerRadius - atmos_fCameraHeight) / atmos_fScaleDepth);\n"
+        "    float fCameraAngle = max(0.0, dot(-v3Ray, normal)); \n"
+        "    float fLightAngle = dot(atmos_lightDir, normal); \n"
+        "    float fCameraScale = atmos_scale(fCameraAngle); \n"
+        "    float fLightScale = atmos_scale(fLightAngle); \n"
+        "    float fCameraOffset = fDepth*fCameraScale; \n"
+        "    float fTemp = fLightScale * fCameraScale; \n"
+
+        "    // Initialize the scattering loop variables \n"	
+        "    float fSampleLength = fFar / atmos_fSamples; \n"		
+        "    float fScaledLength = fSampleLength * atmos_fScale; \n"					
+        "    vec3 v3SampleRay = v3Ray * fSampleLength; \n"	
+        "    vec3 v3SamplePoint = v3SampleRay * 0.5; \n"	
+
+        "    // Now loop through the sample rays \n"
+        "    vec3 v3FrontColor = vec3(0.0, 0.0, 0.0); \n"
+        "    vec3 v3Attenuate; \n"  
+        "    for(int i=0; i<atmos_nSamples; i++) \n"		
+        "    { \n"
+        "        float fHeight = length(v3SamplePoint-earthCenter); \n"			
+        "        float fDepth = exp(atmos_fScaleOverScaleDepth * (atmos_fInnerRadius - fHeight)); \n"
+        "        float fScatter = fDepth*fTemp - fCameraOffset; \n"
+        "        v3Attenuate = exp(-fScatter * (atmos_v3InvWavelength * atmos_fKr4PI + atmos_fKm4PI)); \n"	
+        "        v3FrontColor += v3Attenuate * (fDepth * fScaledLength); \n"					
+        "        v3SamplePoint += v3SampleRay; \n"		
+        "    } \n"		
+
+        "    atmos_color = v3FrontColor * (atmos_v3InvWavelength * atmos_fKrESun + atmos_fKmESun); \n"			
+        "    atmos_atten = v3Attenuate; \n"
+        "} \n"
+
+        "void atmos_vertex_main(inout vec4 vertexVIEW) \n"
+        "{ \n"
+        "    if ( oe_mode_GL_LIGHTING == false ) return; \n"
+
+        "    atmos_fCameraHeight = length(osg_ViewMatrixInverse[3].xyz); \n"
+        "    atmos_fCameraHeight2 = atmos_fCameraHeight*atmos_fCameraHeight; \n"
+        "    atmos_lightDir = normalize(gl_LightSource[0].position.xyz); \n" // view space
+        "    atmos_vert = vertexVIEW.xyz; \n"
+
+        "    atmos_space = (atmos_fCameraHeight-atmos_fInnerRadius)/(atmos_fOuterRadius-atmos_fInnerRadius); \n"
+        "    atmos_space = clamp(atmos_space, 0.0, 1.0); \n"
+
+        "    if(atmos_fCameraHeight >= atmos_fOuterRadius) \n"
+        "    { \n"
+        "        atmos_GroundFromSpace(vertexVIEW); \n"
+        "    } \n"
+        "    else \n"
+        "    { \n"
+        "        atmos_GroundFromAtmosphere(vertexVIEW); \n"
+        "    } \n"
+        "} \n";
+
+    static const char* Ground_Scattering_Fragment =
+        "uniform bool oe_mode_GL_LIGHTING; \n"
+        "uniform float atmos_exposure; \n"  // scene exposure (ground level)
+        "varying vec3 atmos_lightDir; \n"   // light direction (view coords)
+        "varying vec3 oe_Normal; \n"        // vertex normal (view coords)
+        "varying vec3 atmos_color; \n"      // atmospheric lighting color
+        "varying vec3 atmos_atten; \n"      // atmospheric lighting attentuation factor
+        "varying vec3 atmos_up; \n"         // earth up vector at fragment (in view coords)
+        "varying float atmos_space; \n"     // camera altitude (0=ground, 1=atmos outer radius)
+        "varying vec3 atmos_vert; \n"
+
+        "void atmos_fragment_main(inout vec4 color) \n"
+        "{ \n"
+        "    if ( oe_mode_GL_LIGHTING == false ) return; \n"
+
+        "    vec3 ambient = gl_LightSource[0].ambient.rgb;\n"
+
+        "    vec3 N = normalize(oe_Normal); \n"
+        "    vec3 L = normalize(gl_LightSource[0].position.xyz); \n"
+        "    vec3 U = normalize(atmos_up); \n"
+        "    float NdotL = max(dot(N,L), 0.0); \n"
+        "    float NdotLnormalized = (dot(N,L)+1.0)*0.5;\n"
+        "    NdotL = mix(NdotL, NdotLnormalized, ambient.r); \n"
+
+        // Calculate how much normal-based shading should be applied.
+        // Between "low" and "high" (which are NdotL's) we phase out the
+        // normal-based shading (since the atmospheric scattering will
+        // take over at that point). This provides a nice dawn or dusk
+        // "shining" effect on the sides of buildings or terrain.
+        // Adjust the "low": Higher value will increase the effect of
+        // normal-based shading at shallower sun angles (i.e. it will
+        // become more prominent. Perhaps it makes sense in the future to
+        // make this a "scattering effect" uniform or something -gw)
+        "    const float low = 0.5; \n" //0.6; \n"
+        "    const float high = 0.9; \n"
+        "    float UdotL = clamp(dot(U,L), low, high); \n"
+        "    float shadeFactor = 1.0 - (UdotL-low)/(high-low); \n"
+
+        // start applying normal-based shading when we're at twice the 
+        // altitude of the atmosphere's outer radius:
+        "    float normFactor = max(0.0, 1.0-(2.0*atmos_space)); \n"
+
+        // try to brighten up surfaces the sun is shining on
+        "    float overExposure = 1.0; \n" //1.0+(max(NdotL-0.5, 0.0)*0.25);\n"
+
+        // calculate the base scene color. Skip ambience since we'll be
+        // factoring that in later.
+        "    vec4 sceneColor = mix(color*overExposure, color*NdotL, normFactor*shadeFactor); \n"
+
+        "    if (NdotL > 0.0 ) { \n"
+        "        vec3 V = normalize(atmos_vert); \n"
+        "        vec3 H = normalize(L-V); \n"
+        "        float HdotN = max(dot(H,N), 0.0); \n"
+        "        float shine = clamp(gl_FrontMaterial.shininess, 1.0, 128.0); \n"
+        "        sceneColor += gl_FrontLightProduct[0].specular * pow(HdotN, shine); \n"
+        "    } \n"
+
+        // clamp the attentuation to the minimum ambient lighting:
+        "    vec3 attenuation = max(atmos_atten, ambient); \n"
+
+        // ramp exposure from ground (full) to space (50%).
+        "    float exposure = atmos_exposure*clamp(1.0-atmos_space, 0.5, 1.0); \n"
+
+        "    vec3 atmosColor = 1.0 - exp(-exposure * (atmos_color + sceneColor.rgb * attenuation)); \n"
+        "    color.rgb = gl_FrontMaterial.emission.rgb + atmosColor; \n"
+        "} \n";
+
+    //-- BASIC PHONG GROUND LIGHTING ------------------------------------------
+
+#ifdef OSG_GLES2_AVAILABLE
+    static const char* Ground_Phong_Vertex =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+        "uniform bool oe_mode_GL_LIGHTING; \n"
+        "varying vec4 oe_lighting_adjustment; \n"
+        "varying vec4 oe_lighting_zero_vec; \n"
+        "varying vec3 oe_Normal; \n"
+
+        "void atmos_vertex_main(inout vec4 VertexVIEW) \n"
+        "{ \n"
+        "    oe_lighting_adjustment = vec4(1.0); \n"
+        "    if (oe_mode_GL_LIGHTING) \n"
+        "    { \n"
+        "        vec3 N = oe_Normal; \n"
+        "        float NdotL = dot( N, normalize(gl_LightSource[0].position.xyz) ); \n"
+        "        NdotL = max( 0.0, NdotL ); \n"
+
+        // NOTE: See comment in the fragment shader below for an explanation of
+        //       this oe_zero_vec value.
+        "        oe_lighting_zero_vec = vec4(0.0); \n"
+
+        "        vec4 adj = \n"
+        "            gl_FrontLightProduct[0].ambient + \n"
+        "            gl_FrontLightProduct[0].diffuse * NdotL; \n"
+        "        oe_lighting_adjustment = clamp( adj, 0.0, 1.0 ); \n"
+        "    } \n"
+        "} \n";
+
+    static const char* Ground_Phong_Fragment =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+        
+        "uniform bool oe_mode_GL_LIGHTING; \n"
+        "varying vec4 oe_lighting_adjustment; \n"
+        "varying vec4 oe_lighting_zero_vec; \n"
+
+        "void atmos_fragment_main(inout vec4 color) \n"
+        "{ \n"        
+        //NOTE: The follow was changed from the single line
+        //      "color *= oe_lighting_adjustment" to the current code to fix
+        //      an issue on iOS devices.  Adding a varying vec4 value set to
+        //      (0.0,0.0,0.0,0.0) to the color should not make a difference,
+        //      but it is part of the solution to the issue we were seeing.
+        //      Without it and the additional lines of code, the globe was
+        //      rendering textureless (just a white surface with lighting).
+        "    if ( oe_mode_GL_LIGHTING ) \n"
+        "    { \n"
+        "        float alpha = color.a; \n"
+        "        color = color * oe_lighting_adjustment + oe_lighting_zero_vec; \n"
+        "        color.a = alpha; \n"
+        "    } \n"
+        "} \n";
+
+#else
+    static const char* Ground_Phong_Vertex =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+        
+        "uniform bool oe_mode_GL_LIGHTING; \n"
+        "varying vec3 atmos_vertexView3; \n"
+
+        "void atmos_vertex_main(inout vec4 VertexVIEW) \n"
+        "{ \n"
+        "    if ( oe_mode_GL_LIGHTING == false ) return; \n"
+
+        "    atmos_vertexView3 = VertexVIEW.xyz / VertexVIEW.w; \n"
+        "} \n";
+
+    static const char* Ground_Phong_Fragment =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+        "uniform bool oe_mode_GL_LIGHTING; \n"
+        "varying vec3 atmos_vertexView3; \n"
+        "varying vec3 oe_Normal; \n"
+
+        "void atmos_fragment_main(inout vec4 color) \n"
+        "{ \n"        
+        "    if ( oe_mode_GL_LIGHTING == false ) return; \n"
+
+        "    vec3 L = normalize(gl_LightSource[0].position.xyz); \n"
+        "    vec3 V = normalize(atmos_vertexView3); \n"
+        "    vec3 N = normalize(oe_Normal); \n"
+        "    vec3 R = normalize(-reflect(L,N)); \n"
+
+        "    float NdotL = max(dot(N,L), 0.0); \n"
+
+        "    vec4 ambient = gl_FrontLightProduct[0].ambient; \n"
+        "    vec4 diffuse = clamp(gl_FrontLightProduct[0].diffuse * NdotL, 0.0, 1.0); \n"
+        "    vec4 specular= vec4(0); \n"
+
+#if 0
+        "    if (NdotL > 0.0) { \n"
+        "        vec3 HV = normalize(L+V); \n"
+        "        float HVdotN = max(dot(HV,N), 0.0); \n"
+        "        specular = gl_FrontLightProduct[0].specular * pow(HVdotN, 16.0); \n"
+        "    } \n"
+#endif
+
+        "    color.rgb = ambient.rgb + diffuse.rgb*color.rgb + specular.rgb; \n"
+        "} \n";
+#endif
+
+
+    //-- SUN ----------------------------------------------------
+
+    static const char* Sun_Vertex =
+        "varying vec3 atmos_v3Direction; \n"
+        "void main() \n"
+        "{ \n"
+        "    vec3 v3Pos = gl_Vertex.xyz; \n"
+        "    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; \n"
+        "    atmos_v3Direction = vec3(0.0,0.0,1.0) - v3Pos; \n"
+        "    atmos_v3Direction = atmos_v3Direction/length(atmos_v3Direction); \n"
+        "} \n";
+
+    static const char* Sun_Fragment =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+        "uniform float atmos_sunAlpha; \n"
+        "varying vec3 atmos_v3Direction; \n"
+
+        "float atmos_fastpow(in float x, in float y) \n"
+        "{ \n"
+        "    return x/(x+y-y*x); \n"
+        "} \n"
+
+        "void main( void ) \n"
+        "{ \n"
+        "   float fCos = -atmos_v3Direction[2]; \n"         
+        "   float fMiePhase = 0.050387596899224826 * (1.0 + fCos*fCos) / atmos_fastpow(1.9024999999999999 - -1.8999999999999999*fCos, 1.5); \n"
+        "   gl_FragColor.rgb = fMiePhase*vec3(.3,.3,.2); \n"
+        "   gl_FragColor.a = atmos_sunAlpha*gl_FragColor.r; \n"
+        "} \n";
+
+    // -- MOON -------------------------------------------------
+
+    static const char* Moon_Vertex = 
+        "uniform mat4 osg_ModelViewProjectionMatrix;"
+        "varying vec4 moon_TexCoord;\n"
+        "void main() \n"
+        "{ \n"
+        "    moon_TexCoord = gl_MultiTexCoord0; \n"
+        "    gl_Position = osg_ModelViewProjectionMatrix * gl_Vertex; \n"
+        "} \n";
+
+    static const char* Moon_Fragment =
+        "varying vec4 moon_TexCoord;\n"
+        "uniform sampler2D moonTex;\n"
+        "void main( void ) \n"
+        "{ \n"
+        "   gl_FragColor = texture2D(moonTex, moon_TexCoord.st);\n"
+        "} \n";
+
+    // -- STARS ------------------------------------------------
+
+    static const char* Stars_Vertex_110 =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+        "uniform vec3 atmos_v3LightDir; \n"
+        "uniform mat4 osg_ViewMatrixInverse; \n"
+        "varying float visibility; \n"
+        "varying vec4 osg_FrontColor; \n"
+
+        "float remap( float val, float vmin, float vmax, float r0, float r1 ) \n"
+        "{ \n"
+        "    float vr = (clamp(val, vmin, vmax)-vmin)/(vmax-vmin); \n"
+        "    return r0 + vr * (r1-r0); \n"
+        "} \n"
+
+        "void main() \n"
+        "{ \n"
+        "    osg_FrontColor = gl_Color; \n"
+        "    gl_PointSize = gl_Color.r * 2.0; \n"
+        "    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; \n"
+        "    vec3 eye = osg_ViewMatrixInverse[3].xyz; \n"
+        "    float hae = length(eye) - 6378137.0; \n"
+        // "highness": visibility increases with altitude
+        "    float highness = remap( hae, 25000.0, 150000.0, 0.0, 1.0 ); \n"
+        "    eye = normalize(eye); \n"
+        // "darkness": visibility increase as the sun goes around the other side of the earth
+        "    float darkness = 1.0-remap(dot(eye,atmos_v3LightDir), -0.25, 0.0, 0.0, 1.0); \n"
+        "    visibility = clamp(highness + darkness, 0.0, 1.0); \n"
+        "} \n";
+
+    static const char* Stars_Fragment_110 =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+        "varying float visibility; \n"
+        "varying vec4 osg_FrontColor; \n"
+        "void main( void ) \n"
+        "{ \n"
+        "    gl_FragColor = osg_FrontColor * visibility; \n"
+        "} \n";
+
+
+    static const char* Stars_Vertex_120 =
+        "#version 120\n"
+
+        "uniform vec3 atmos_v3LightDir; \n"
+        "uniform mat4 osg_ViewMatrixInverse; \n"
+        "varying float visibility; \n"
+        "varying vec4 osg_FrontColor; \n"
+
+        "float remap( float val, float vmin, float vmax, float r0, float r1 ) \n"
+        "{ \n"
+        "    float vr = (clamp(val, vmin, vmax)-vmin)/(vmax-vmin); \n"
+        "    return r0 + vr * (r1-r0); \n"
+        "} \n"
+
+        "void main() \n"
+        "{ \n"
+        "    osg_FrontColor = gl_Color; \n"
+        "    gl_PointSize = gl_Color.r * 14.0; \n"
+        "    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; \n"
+        "    vec3 eye = osg_ViewMatrixInverse[3].xyz; \n"
+        "    float hae = length(eye) - 6378137.0; \n"
+        // "highness": visibility increases with altitude
+        "    float highness = remap( hae, 25000.0, 150000.0, 0.0, 1.0 ); \n"
+        "    eye = normalize(eye); \n"
+        // "darkness": visibility increase as the sun goes around the other side of the earth
+        "    float darkness = 1.0-remap(dot(eye,atmos_v3LightDir), -0.25, 0.0, 0.0, 1.0); \n"
+        "    visibility = clamp(highness + darkness, 0.0, 1.0); \n"
+        "} \n";
+
+    static const char* Stars_Fragment_120 =
+        "#version 120\n"
+        "varying float visibility; \n"
+        "varying vec4 osg_FrontColor; \n"
+        "void main( void ) \n"
+        "{ \n"
+        "    float b1 = 1.0-(2.0*abs(gl_PointCoord.s-0.5)); \n"
+        "    float b2 = 1.0-(2.0*abs(gl_PointCoord.t-0.5)); \n"
+        "    float i = b1*b1 * b2*b2; \n"
+        "    gl_FragColor = osg_FrontColor * i * visibility; \n"
+        "} \n";
+
+} } } // namespace osgEarth::Drivers::SimpleSky
+
+#endif //OSGEARTH_DRIVER_SIMPLE_SKY_SHADERS
\ No newline at end of file
diff --git a/src/osgEarthDrivers/splat_mask/CMakeLists.txt b/src/osgEarthDrivers/splat_mask/CMakeLists.txt
new file mode 100644
index 0000000..b30ef7f
--- /dev/null
+++ b/src/osgEarthDrivers/splat_mask/CMakeLists.txt
@@ -0,0 +1,14 @@
+SET(TARGET_SRC SplatMaskDriver.cpp)
+SET(TARGET_H SplatMaskOptions)
+
+SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES}
+	osgEarthUtil
+)
+
+SETUP_PLUGIN(osgearth_splat_mask)
+
+# to install public driver includes:
+SET(LIB_NAME splat_mask)
+SET(LIB_PUBLIC_HEADERS SplatMaskOptions)
+INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
+
diff --git a/src/osgEarthDrivers/splat_mask/SplatMaskDriver.cpp b/src/osgEarthDrivers/splat_mask/SplatMaskDriver.cpp
new file mode 100644
index 0000000..04497e6
--- /dev/null
+++ b/src/osgEarthDrivers/splat_mask/SplatMaskDriver.cpp
@@ -0,0 +1,205 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+#include <osgEarth/TileSource>
+#include <osgEarth/Registry>
+#include <osgEarth/URI>
+#include <osgEarth/ImageUtils>
+#include <osgEarth/ElevationLayer>
+
+#include <osgEarthUtil/SimplexNoise>
+
+#include <osgEarthDrivers/gdal/GDALOptions>
+using namespace osgEarth::Drivers;
+
+#include <osg/Notify>
+#include <osgDB/FileNameUtils>
+#include <osgDB/FileUtils>
+#include <osgDB/Registry>
+
+
+#include "SplatMaskOptions"
+
+using namespace osgEarth;
+using namespace osgEarth::Drivers;
+
+namespace osgEarth { namespace Drivers { namespace SplatMask
+{
+    class SplatMaskTileSource : public TileSource
+    {
+    public:
+        SplatMaskTileSource( const TileSourceOptions& options ) : 
+          TileSource( options ), 
+          _options(options)
+        {
+            _noise.setOctaves( 24 );
+        }
+
+    public: // TileSource interface
+
+        Status initialize(const osgDB::Options* dbOptions)
+        {            
+            _dbOptions = Registry::instance()->cloneOrCreateOptions( dbOptions );         
+            setProfile( osgEarth::Registry::instance()->getGlobalGeodeticProfile() );
+
+            GDALOptions gdal;
+            gdal.url() = *_options.classificationPath();
+            gdal.interpolation() = osgEarth::INTERP_NEAREST;
+            gdal.bilinearReprojection() = false;
+            gdal.tileSize() = 5;
+            ElevationLayerOptions classOptions("splat", gdal);
+            classOptions.cachePolicy() = CachePolicy::NO_CACHE;
+            _classLayer = new ElevationLayer(classOptions);
+
+            return STATUS_OK;
+        }
+    
+        /** Tell the terrain engine not to cache tiles form this source by default. */
+        CachePolicy getCachePolicyHint(const Profile*) const
+        {
+            return CachePolicy::NO_CACHE;
+        }
+
+        osg::Image* createImage(const TileKey&        key,
+                                ProgressCallback*     progress)
+        {
+            TileKey hfkey( key );
+            GeoHeightField classTable;
+            while( !classTable.valid() && hfkey.valid() )
+            {
+                classTable = _classLayer->createHeightField(hfkey, progress);
+                if ( !classTable.valid() )
+                    hfkey = hfkey.createParentKey();
+            }
+
+            if ( !classTable.valid() )
+            {
+                OE_WARN << "no classification. sorry." << std::endl;
+                return 0L;
+            }
+
+            const SpatialReference* srs = key.getProfile()->getSRS();
+
+            osg::Image* image = new osg::Image();
+            image->allocateImage( getPixelsPerTile(), getPixelsPerTile(), 1, GL_RGBA, GL_UNSIGNED_BYTE );
+
+            double dx = key.getExtent().width()  / (double)(image->s()-1);
+            double dy = key.getExtent().height() / (double)(image->t()-1);
+
+            ImageUtils::PixelWriter write(image);
+            for(int s=0; s<image->s(); ++s)
+            {
+                for(int t=0; t<image->t(); ++t)
+                {
+                    double lon = key.getExtent().xMin() + (double)s * dx;
+                    double lat = key.getExtent().yMin() + (double)t * dy;
+
+                    osg::Vec3d world(lon, lat, 0.0);
+                    if ( srs->isGeographic() )
+                    {
+                        srs->transform(world, srs->getECEF(), world);
+                        world.normalize();
+                    }
+
+                    double n = _noise.getValue(world.x(), world.y(), world.z());
+
+                    float r = n <  -0.5            ? 1.0f : 0.0f;
+                    float g = n >= -0.5 && n < 0.0 ? 1.0f : 0.0f;
+                    float b = n >=  0.0 && n < 0.5 ? 1.0f : 0.0f;
+                    float a = n >=  0.5            ? 1.0f : 0.0f;
+
+                    float rs = (float)s / (float)image->s();
+                    float rt = (float)t / (float)image->t();
+
+                    float elevation = 0.0f;
+                
+                    classTable.getElevation(
+                        key.getExtent().getSRS(),
+                        lon, lat, osgEarth::INTERP_NEAREST,
+                        key.getExtent().getSRS(),
+                        elevation);
+
+                    int cv = (int)elevation;
+                    float contrast = _options.contrast().get();
+
+                    if ( cv > 220 ) {
+                        r = g = b = a = 0.0f; // nodata
+                    }
+                    else if ( cv == 210 ) {
+                        a += contrast;
+                        r *= 0.1f, b *= 0.1f, g *= 0.1f;
+                    }
+                    else if ( cv < 130 ) {
+                        b += contrast;
+                        a = 0.0f;
+                    }
+                    else if ( cv >= 130 && cv <= 200 ) {
+                        g += contrast;
+                        a = 0.0f;
+                    }
+                    else {
+                        r += contrast;
+                        a = 0.0f;
+                    }
+
+                    osg::Vec4f value(r,g,b,a);
+                    value /= r+g+b+a;
+
+                    write(value, s, t);
+                }
+            }
+
+            return image;
+        }
+
+
+
+    private:
+        osg::ref_ptr<ElevationLayer> _classLayer;
+        SplatMaskOptions             _options;
+        osg::ref_ptr<osgDB::Options> _dbOptions;
+        osgEarth::Util::SimplexNoise _noise;
+    };
+
+
+    class SplatMaskDriver : public TileSourceDriver
+    {
+        public:
+            SplatMaskDriver()
+            {
+                supportsExtension( "osgearth_splat_mask", "Detail texture splat mask generator" );
+            }
+
+            virtual const char* className()
+            {
+                return "Detail Splat Mask Driver";
+            }
+
+            virtual ReadResult readObject(const std::string& file_name, const Options* options) const
+            {
+                if ( !acceptsExtension(osgDB::getLowerCaseFileExtension( file_name )))
+                    return ReadResult::FILE_NOT_HANDLED;
+
+                return new SplatMaskTileSource( getTileSourceOptions(options) );
+            }
+    };
+
+    REGISTER_OSGPLUGIN(osgearth_splat_mask, SplatMaskDriver)
+
+} } } // namespace osgEarth::Drivers::SplatMask
diff --git a/src/osgEarthDrivers/tilecache/TileCacheOptions b/src/osgEarthDrivers/splat_mask/SplatMaskOptions
similarity index 52%
copy from src/osgEarthDrivers/tilecache/TileCacheOptions
copy to src/osgEarthDrivers/splat_mask/SplatMaskOptions
index 27c89e9..4cca8ea 100644
--- a/src/osgEarthDrivers/tilecache/TileCacheOptions
+++ b/src/osgEarthDrivers/splat_mask/SplatMaskOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -16,64 +16,64 @@
  * You should have received a copy of the GNU Lesser General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
-#ifndef OSGEARTH_DRIVER_TILECACHE_DRIVEROPTIONS
-#define OSGEARTH_DRIVER_TILECACHE_DRIVEROPTIONS 1
+#ifndef OSGEARTH_DRIVER_SPLAT_MASK_OPTIONS
+#define OSGEARTH_DRIVER_SPLAT_MASK_OPTIONS 1
 
 #include <osgEarth/Common>
 #include <osgEarth/TileSource>
 
-namespace osgEarth { namespace Drivers
+namespace osgEarth { namespace Drivers { namespace SplatMask
 {
     using namespace osgEarth;
 
-    class TileCacheOptions : public TileSourceOptions // NO EXPORT; header only
+    /**
+     * Options for the SplatMask driver
+     */
+    class SplatMaskOptions : public TileSourceOptions // NO EXPORT; header only
     {
     public:
-        optional<URI>& url() { return _url; }
-        const optional<URI>& url() const { return _url; }
-
-        optional<std::string>& layer() { return _layer; }
-        const optional<std::string>& layer() const { return _layer; }
-
-        optional<std::string>& format() { return _format; }
-        const optional<std::string>& format() const { return _format; }
-
-    public:
-        TileCacheOptions( const TileSourceOptions& opt =TileSourceOptions() ) :
-            TileSourceOptions( opt )
+        SplatMaskOptions( const TileSourceOptions& opt =TileSourceOptions() ) :
+            TileSourceOptions( opt ),
+            _contrast( 0.0f )
         {
-            setDriver( "tilecache" );
+            setDriver( "splat_mask" );
             fromConfig( _conf );
         }
 
         /** dtor */
-        virtual ~TileCacheOptions() { }
+        virtual ~SplatMaskOptions() { }
 
-    protected:
+        optional<float>& contrast() { return _contrast; }
+        const optional<float>& contrast() const { return _contrast; }
+        
+        optional<std::string>& classificationPath() { return _classPath; }
+        const optional<std::string>& classificationPath() const { return _classPath; }
+
+    public:
         Config getConfig() const {
             Config conf = TileSourceOptions::getConfig();
-            conf.updateIfSet("url", _url );
-            conf.updateIfSet("layer", _layer);
-            conf.updateIfSet("format", _format);
+            conf.addIfSet("contrast", _contrast);
+            conf.addIfSet("classification_path", _classPath);
             return conf;
         }
+
+    protected:
         void mergeConfig( const Config& conf ) {
             TileSourceOptions::mergeConfig( conf );
             fromConfig( conf );
         }
+
     private:
         void fromConfig( const Config& conf ) {
-            conf.getIfSet( "url", _url );
-            conf.getIfSet( "layer", _layer );
-            conf.getIfSet( "format", _format );
+            conf.getIfSet("contrast", _contrast);
+            conf.getIfSet("classification_path", _classPath);
         }
 
-        optional<URI>         _url;
-        optional<std::string> _layer;
-        optional<std::string> _format;
+        optional<float> _contrast;
+        optional<std::string> _classPath;
     };
 
-} } // namespace osgEarth::Drivers
+} } } // namespace osgEarth::Drivers::SplatMask
 
-#endif // OSGEARTH_DRIVER_TILECACHE_DRIVEROPTIONS
+#endif // OSGEARTH_DRIVER_SPLAT_MASK_OPTIONS
 
diff --git a/src/osgEarthDrivers/template_matclass/CMakeLists.txt b/src/osgEarthDrivers/template_matclass/CMakeLists.txt
new file mode 100644
index 0000000..dde6e8c
--- /dev/null
+++ b/src/osgEarthDrivers/template_matclass/CMakeLists.txt
@@ -0,0 +1,16 @@
+
+SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES} osgEarthFeatures osgEarthSymbology)
+
+SET(TARGET_SRC
+	TemplateMatClassDriver.cpp)
+	
+SET(TARGET_H
+	TemplateMatClassOptions
+)
+
+SETUP_PLUGIN(osgearth_template_matclass)
+
+# to install public driver includes:
+SET(LIB_NAME template_matclass)
+SET(LIB_PUBLIC_HEADERS TemplateMatClassOptions)
+INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
diff --git a/src/osgEarthDrivers/template_matclass/TemplateMatClassDriver.cpp b/src/osgEarthDrivers/template_matclass/TemplateMatClassDriver.cpp
new file mode 100644
index 0000000..120fb20
--- /dev/null
+++ b/src/osgEarthDrivers/template_matclass/TemplateMatClassDriver.cpp
@@ -0,0 +1,163 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+#include <osgEarthFeatures/FeatureTileSource>
+#include <osgEarthFeatures/TransformFilter>
+#include <osgEarthSymbology/Style>
+#include <osgEarth/Registry>
+#include <osgEarth/FileUtils>
+#include <osgEarth/ImageLayer>
+
+#include <osg/Notify>
+#include <osgDB/FileNameUtils>
+#include <osgDB/FileUtils>
+#include <osgDB/Registry>
+#include <osgDB/ReadFile>
+#include <osgDB/WriteFile>
+
+#include "TemplateMatClassOptions"
+
+#include <sstream>
+
+#define LC "[MatClass] "
+
+using namespace osgEarth;
+using namespace osgEarth::Features;
+using namespace osgEarth::Drivers;
+
+/********************************************************************/
+
+class TemplateMatClassTileSource : public TileSource
+{
+private:
+    osg::ref_ptr<ImageLayer>     _imageLayer;
+    osg::ref_ptr<FeatureSource>  _featureSource;
+    osg::ref_ptr<osgDB::Options> _dbOptions;
+    TemplateMatClassOptions      _options;
+
+public:
+    TemplateMatClassTileSource( const TileSourceOptions& options ) :
+        TileSource( options ),
+        _options( options )
+    {
+        //nop
+    }
+
+public: // TileSource interface
+
+    // override
+    // One-time initialization. This is called by osgEarth.
+    Status initialize(const osgDB::Options* dbOptions)
+    {
+        _dbOptions = Registry::instance()->cloneOrCreateOptions(dbOptions);
+
+        const Profile* profile = getProfile();
+        if ( !profile )
+        {
+            profile = osgEarth::Registry::instance()->getGlobalGeodeticProfile();
+            setProfile( profile );
+        }
+
+        // load the image layer:
+        if ( _options.imageLayerOptions().isSet() )
+        {
+            _imageLayer = new ImageLayer( _options.imageLayerOptions().get() );
+            _imageLayer->setTargetProfileHint( profile );
+            // TODO: what about the cache?
+        }
+
+        // load the feature source:
+        if ( _options.featureSourceOptions().isSet() )
+        {
+            _featureSource = FeatureSourceFactory::create( _options.featureSourceOptions().get() );
+            _featureSource->initialize( _dbOptions.get() );
+        }
+
+        // set up the IO options so that we do not cache input data:
+        CachePolicy::NO_CACHE.apply( _dbOptions.get() );
+        return STATUS_OK;
+    }
+
+    // override
+    // Creates an image.
+    osg::Image* createImage(const TileKey&    key,
+                            ProgressCallback* progress )
+    {
+        if ( !_imageLayer.valid() || !_featureSource.valid() )
+            return 0L;
+
+        // fetch the image for this key:
+        GeoImage image = _imageLayer->createImage(key, progress);
+        if ( !image.valid() )
+            return 0L;
+
+        // fetch a set of features for this key. The features are in their
+        // own SRS, so we need to transform:
+        const SpatialReference* featureSRS = _featureSource->getFeatureProfile()->getSRS();
+        GeoExtent extentInFeatureSRS = key.getExtent().transform( featureSRS );
+
+        // assemble a spatial query. It helps if your features have a spatial index.
+        Query query;
+        query.bounds() = extentInFeatureSRS.bounds();
+        //query.expression() = ... // SQL expression compatible with data source
+        osg::ref_ptr<FeatureCursor> cursor = _featureSource->createFeatureCursor(query);
+
+        // create a new image to return.
+        osg::Image* output = new osg::Image();
+        //output->allocateImage(128, 128, 1, GL_RGB, GL_UNSIGNED_BYTE);
+
+        // do your magic here.
+
+        return output;
+    }
+};
+
+
+/**
+ * Plugin entry point for the AGGLite feature rasterizer
+ */
+class TemplateMatClassDriver : public TileSourceDriver
+{
+    public:
+        TemplateMatClassDriver() {}
+
+        virtual const char* className()
+        {
+            return "Template mat class driver";
+        }
+        
+        virtual bool acceptsExtension(const std::string& extension) const
+        {
+            return
+                osgDB::equalCaseInsensitive( extension, "osgearth_template_matclass" );
+        }
+
+        virtual ReadResult readObject(const std::string& file_name, const Options* options) const
+        {
+            std::string ext = osgDB::getFileExtension( file_name );
+            if ( !acceptsExtension( ext ) )
+            {
+                return ReadResult::FILE_NOT_HANDLED;
+            }
+
+            return new TemplateMatClassTileSource( getTileSourceOptions(options) );
+        }
+};
+
+REGISTER_OSGPLUGIN(osgearth_template_matclass, TemplateMatClassDriver)
diff --git a/src/osgEarthDrivers/template_matclass/TemplateMatClassOptions b/src/osgEarthDrivers/template_matclass/TemplateMatClassOptions
new file mode 100644
index 0000000..5001ac8
--- /dev/null
+++ b/src/osgEarthDrivers/template_matclass/TemplateMatClassOptions
@@ -0,0 +1,83 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_DRIVER_TEMPLATE_MAT_CLASS_DRIVEROPTIONS
+#define OSGEARTH_DRIVER_TEMPLATE_MAT_CLASS_DRIVEROPTIONS 1
+
+#include <osgEarth/Common>
+#include <osgEarth/TileSource>
+#include <osgEarthFeatures/FeatureSource>
+
+namespace osgEarth { namespace Drivers
+{
+    using namespace osgEarth;
+    using namespace osgEarth::Features;
+
+    /**
+     * Options structure for configuring the driver.
+     * getConfig() and fromConfig() let you serialize to/from the earth file.
+     */
+    class TemplateMatClassOptions : public TileSourceOptions // NO EXPORT; header only
+    {
+    public:
+        // Constructor - do not modify
+        TemplateMatClassOptions( const TileSourceOptions& options =TileSourceOptions() )
+            : TileSourceOptions( options )
+        {
+            setDriver( "template_matclass" );
+            fromConfig( _conf );
+        }
+
+        // Source image layer specification
+        optional<ImageLayerOptions>& imageLayerOptions() { return _imageLayerOptions; }
+        const optional<ImageLayerOptions>& imageLayerOptions() const { return _imageLayerOptions; }
+
+        // Vector feature source specification
+        optional<FeatureSourceOptions>& featureSourceOptions() { return _featureSourceOptions; }
+        const optional<FeatureSourceOptions>& featureSourceOptions() const { return _featureSourceOptions; }
+
+        // dtor
+        virtual ~TemplateMatClassOptions() { }
+
+    public:
+        Config getConfig() const {
+            Config conf = TileSourceOptions::getConfig();
+            conf.addObjIfSet( "image", _imageLayerOptions );
+            conf.addObjIfSet( "features", _featureSourceOptions );
+            return conf;
+        }
+
+    protected:
+        void mergeConfig( const Config& conf ) {
+            TileSourceOptions::mergeConfig( conf );
+            fromConfig(conf);
+        }
+
+    private:
+        void fromConfig( const Config& conf ) {
+            conf.getObjIfSet( "image", _imageLayerOptions );
+            conf.getObjIfSet( "features", _featureSourceOptions );
+        }
+
+        optional<ImageLayerOptions>    _imageLayerOptions;
+        optional<FeatureSourceOptions> _featureSourceOptions;
+    };
+
+} } // namespace osgEarth::Drivers
+
+#endif // OSGEARTH_DRIVER_TEMPLATE_MAT_CLASS_DRIVEROPTIONS
diff --git a/src/osgEarthDrivers/tilecache/ReaderWriterTileCache.cpp b/src/osgEarthDrivers/tilecache/ReaderWriterTileCache.cpp
index 541066f..3fc7c42 100644
--- a/src/osgEarthDrivers/tilecache/ReaderWriterTileCache.cpp
+++ b/src/osgEarthDrivers/tilecache/ReaderWriterTileCache.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -48,8 +48,7 @@ public:
 
     Status initialize( const osgDB::Options* dbOptions )
     {
-        _dbOptions = Registry::instance()->cloneOrCreateOptions(dbOptions);
-        CachePolicy::NO_CACHE.apply( _dbOptions.get() );
+        _dbOptions = Registry::instance()->cloneOrCreateOptions(dbOptions);        
 
         if ( !getProfile() )
         {
diff --git a/src/osgEarthDrivers/tilecache/TileCacheOptions b/src/osgEarthDrivers/tilecache/TileCacheOptions
index 27c89e9..fe220e7 100644
--- a/src/osgEarthDrivers/tilecache/TileCacheOptions
+++ b/src/osgEarthDrivers/tilecache/TileCacheOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/tileindex/ReaderWriterTileIndex.cpp b/src/osgEarthDrivers/tileindex/ReaderWriterTileIndex.cpp
index 5976494..085e51b 100644
--- a/src/osgEarthDrivers/tileindex/ReaderWriterTileIndex.cpp
+++ b/src/osgEarthDrivers/tileindex/ReaderWriterTileIndex.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -105,7 +105,7 @@ public:
 
                     start = osg::Timer::instance()->tick();
                     source = osgEarth::TileSourceFactory::create( opt );                               
-                    TileSource::Status compStatus = source->startup( 0 );
+                    TileSource::Status compStatus = source->open();
                     if (compStatus.isOK())
                     {
                         _tileSourceCache.insert( files[i], source.get() );                                                
diff --git a/src/osgEarthDrivers/tileindex/TileIndexOptions b/src/osgEarthDrivers/tileindex/TileIndexOptions
index 3cd0d57..df5d7b0 100644
--- a/src/osgEarthDrivers/tileindex/TileIndexOptions
+++ b/src/osgEarthDrivers/tileindex/TileIndexOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/tileservice/ReaderWriterTileService.cpp b/src/osgEarthDrivers/tileservice/ReaderWriterTileService.cpp
index 033be09..c050b1d 100644
--- a/src/osgEarthDrivers/tileservice/ReaderWriterTileService.cpp
+++ b/src/osgEarthDrivers/tileservice/ReaderWriterTileService.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -51,8 +51,7 @@ public:
 public:
     Status initialize( const osgDB::Options* dbOptions )
     {
-        _dbOptions = Registry::instance()->cloneOrCreateOptions(dbOptions);
-        CachePolicy::NO_CACHE.apply( _dbOptions.get() );
+        _dbOptions = Registry::instance()->cloneOrCreateOptions(dbOptions);        
 
         if ( !getProfile() )
         {
diff --git a/src/osgEarthDrivers/tileservice/TileServiceOptions b/src/osgEarthDrivers/tileservice/TileServiceOptions
index dee354d..0809615 100644
--- a/src/osgEarthDrivers/tileservice/TileServiceOptions
+++ b/src/osgEarthDrivers/tileservice/TileServiceOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/tms/CMakeLists.txt b/src/osgEarthDrivers/tms/CMakeLists.txt
index 3dec141..9ba6c79 100644
--- a/src/osgEarthDrivers/tms/CMakeLists.txt
+++ b/src/osgEarthDrivers/tms/CMakeLists.txt
@@ -1,8 +1,10 @@
 SET(TARGET_SRC
-  ReaderWriterTMS.cpp
+    TMSPlugin.cpp
+    TMSTileSource.cpp
 )
 SET(TARGET_H
-  TMSOptions
+    TMSOptions
+	TMSTileSource
 )
     
 SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES} osgEarthUtil)
diff --git a/src/osgEarthDrivers/tms/ReaderWriterTMS.cpp b/src/osgEarthDrivers/tms/ReaderWriterTMS.cpp
deleted file mode 100644
index b8d44b1..0000000
--- a/src/osgEarthDrivers/tms/ReaderWriterTMS.cpp
+++ /dev/null
@@ -1,241 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-* GNU Lesser General Public License for more details.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-
-#include <osgEarth/TileSource>
-#include <osgEarth/FileUtils>
-#include <osgEarth/ImageUtils>
-#include <osgEarth/Registry>
-#include <osgEarthUtil/TMS>
-
-#include <osg/Notify>
-#include <osgDB/FileNameUtils>
-#include <osgDB/FileUtils>
-#include <osgDB/Registry>
-#include <osgDB/ReadFile>
-#include <osgDB/WriteFile>
-
-#include <sstream>
-#include <iomanip>
-#include <string.h>
-
-#include "TMSOptions"
-
-using namespace osgEarth;
-using namespace osgEarth::Util;
-using namespace osgEarth::Drivers;
-
-#define LC "[TMS driver] "
-
-
-class TMSSource : public TileSource
-{
-public:
-    TMSSource(const TileSourceOptions& options) : TileSource(options), _options(options)
-    {
-        _invertY = _options.tmsType() == "google";
-    }
-
-    // one-time initialization (per source)
-    Status initialize(const osgDB::Options* dbOptions)
-    {
-        _dbOptions = Registry::instance()->cloneOrCreateOptions(dbOptions);
-
-        const Profile* profile = getProfile();
-
-        URI tmsURI = _options.url().value();
-        if ( tmsURI.empty() )
-        {
-            return Status::Error( "Fail: TMS driver requires a valid \"url\" property" );
-        }
-
-        // Take the override profile if one is given
-        if ( profile )
-        {
-            OE_INFO << LC 
-                << "Using override profile \"" << getProfile()->toString() 
-                << "\" for URI \"" << tmsURI.base() << "\"" 
-                << std::endl;
-
-            _tileMap = TMS::TileMap::create( 
-                _options.url()->full(),
-                profile,
-                _options.format().value(),
-                _options.tileSize().value(), 
-                _options.tileSize().value() );
-        }
-
-        else
-        {
-            // Attempt to read the tile map parameters from a TMS TileMap XML tile on the server:
-            _tileMap = TMS::TileMapReaderWriter::read( tmsURI.full(), _dbOptions.get() );
-            if (!_tileMap.valid())
-            {
-                return Status::Error( Stringify() << "Failed to read tilemap from " << tmsURI.full() );
-            }
-
-            OE_INFO << LC
-                << "TMS tile map datestamp = "
-                << DateTime(_tileMap->getTimeStamp()).asRFC1123()
-                << std::endl;
-            
-            profile = _tileMap->createProfile();
-            if ( profile )
-                setProfile( profile );
-        }
-
-        // Make sure we've established a profile by this point:
-        if ( !profile )
-        {
-            return Status::Error( Stringify() << "Failed to establish a profile for " << tmsURI.full() );
-        }
-
-        // TileMap and profile are valid at this point. Build the tile sets.
-        // Automatically set the min and max level of the TileMap
-        if ( _tileMap->getTileSets().size() > 0 )
-        {
-            OE_DEBUG << LC << "TileMap min/max " << _tileMap->getMinLevel() << ", " << _tileMap->getMaxLevel() << std::endl;
-            if (_tileMap->getDataExtents().size() > 0)
-            {
-                for (DataExtentList::iterator itr = _tileMap->getDataExtents().begin(); itr != _tileMap->getDataExtents().end(); ++itr)
-                {
-                    this->getDataExtents().push_back(*itr);
-                }
-            }
-            else
-            {
-                //Push back a single area that encompasses the whole profile going up to the max level
-                this->getDataExtents().push_back(DataExtent(profile->getExtent(), 0, _tileMap->getMaxLevel()));
-            }
-        }
-
-        // set up the IO options so that we do not cache TMS tiles:
-        CachePolicy::NO_CACHE.apply( _dbOptions.get() );
-
-        return STATUS_OK;
-    }
-
-    // TileSource timestamp is the time of the time map metadata.
-    TimeStamp getLastModifiedTime() const
-    {
-        if ( _tileMap.valid() )
-            return _tileMap->getTimeStamp();
-        else
-            return TileSource::getLastModifiedTime();
-    }
-
-    // reflect a default cache policy based on whether this TMS repo is
-    // local or remote.
-    CachePolicy getCachePolicyHint(const Profile* targetProfile) const
-    {
-        // if the source is local and the profiles line up, don't cache (by default).
-        if (!_options.url()->isRemote() &&
-            targetProfile && 
-            targetProfile->isEquivalentTo(getProfile()) )
-        {
-            return CachePolicy::NO_CACHE;
-        }
-        else
-        {
-            return CachePolicy::DEFAULT;
-        }
-    }
-
-    // creates an image from the TMS repo.
-    osg::Image* createImage(const TileKey&        key,
-                            ProgressCallback*     progress )
-    {
-        if (_tileMap.valid() && key.getLevelOfDetail() <= _tileMap->getMaxLevel() )
-        {
-            std::string image_url = _tileMap->getURL( key, _invertY );
-                
-            //OE_NOTICE << "TMSSource: Key=" << key.str() << ", URL=" << image_url << std::endl;
-
-            osg::ref_ptr<osg::Image> image;
-            if (!image_url.empty())
-            {
-                image = URI(image_url).readImage( _dbOptions.get(), progress ).getImage();
-            }
-
-            if (!image.valid())
-            {
-                if (image_url.empty() || !_tileMap->intersectsKey(key))
-                {
-                    //We couldn't read the image from the URL or the cache, so check to see if the given key is less than the max level
-                    //of the tilemap and create a transparent image.
-                    if (key.getLevelOfDetail() <= _tileMap->getMaxLevel())
-                    {
-                        OE_DEBUG << LC << "Returning empty image " << std::endl;
-                        return ImageUtils::createEmptyImage();
-                    }
-                }
-            }
-            return image.release();
-        }
-        return 0;
-    }
-
-    virtual int getPixelsPerTile() const
-    {
-        return _tileMap->getFormat().getWidth();
-    }
-
-    virtual std::string getExtension()  const 
-    {
-        return _tileMap->getFormat().getExtension();
-    }
-
-private:
-
-    osg::ref_ptr<TMS::TileMap>   _tileMap;
-    bool                         _invertY;
-    const TMSOptions             _options;
-    osg::ref_ptr<osgDB::Options> _dbOptions;
-};
-
-
-
-
-class ReaderWriterTMS : public TileSourceDriver
-{
-private:
-    typedef std::map< std::string,osg::ref_ptr<TMS::TileMap> > TileMapCache;
-    TileMapCache _tileMapCache;
-
-public:
-    ReaderWriterTMS()
-    {
-        supportsExtension( "osgearth_tms", "Tile Map Service" );
-    }
-
-    virtual const char* className()
-    {
-        return "Tile Map Service ReaderWriter";
-    }
-
-    virtual ReadResult readObject(const std::string& file_name, const Options* options) const
-    {
-        if ( !acceptsExtension(osgDB::getLowerCaseFileExtension( file_name )))
-            return ReadResult::FILE_NOT_HANDLED;
-
-        return new TMSSource( getTileSourceOptions(options) );
-    }
-};
-
-REGISTER_OSGPLUGIN(osgearth_tms, ReaderWriterTMS)
-
diff --git a/src/osgEarthDrivers/tms/TMSOptions b/src/osgEarthDrivers/tms/TMSOptions
index 630a9f9..f4e2a4f 100644
--- a/src/osgEarthDrivers/tms/TMSOptions
+++ b/src/osgEarthDrivers/tms/TMSOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/tms/TMSPlugin.cpp b/src/osgEarthDrivers/tms/TMSPlugin.cpp
new file mode 100644
index 0000000..a3d0b1e
--- /dev/null
+++ b/src/osgEarthDrivers/tms/TMSPlugin.cpp
@@ -0,0 +1,66 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include <osgEarth/TileSource>
+#include <osgEarth/Registry>
+#include <osgEarthUtil/TMS>
+
+#include <osg/Notify>
+#include <osgDB/FileNameUtils>
+#include <osgDB/Registry>
+
+#include "TMSOptions"
+#include "TMSTileSource"
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+#define LC "[TMS driver] "
+
+namespace osgEarth { namespace Drivers { namespace TileMapService
+{
+    class TMSDriver : public TileSourceDriver
+    {
+    private:
+        typedef std::map< std::string,osg::ref_ptr<TMS::TileMap> > TileMapCache;
+        TileMapCache _tileMapCache;
+
+    public:
+        TMSDriver()
+        {
+            supportsExtension( "osgearth_tms", "Tile Map Service Driver" );
+        }
+
+        virtual const char* className()
+        {
+            return "Tile Map Service Driver";
+        }
+
+        virtual ReadResult readObject(const std::string& file_name, const Options* options) const
+        {
+            if ( !acceptsExtension(osgDB::getLowerCaseFileExtension( file_name )))
+                return ReadResult::FILE_NOT_HANDLED;
+
+            return new TMSTileSource( getTileSourceOptions(options) );
+        }
+    };
+
+    REGISTER_OSGPLUGIN(osgearth_tms, TMSDriver)
+
+} } } // namespace osgEarth::Drivers::TMS
+
diff --git a/src/osgEarthDrivers/tms/TMSTileSource b/src/osgEarthDrivers/tms/TMSTileSource
new file mode 100644
index 0000000..d5b3333
--- /dev/null
+++ b/src/osgEarthDrivers/tms/TMSTileSource
@@ -0,0 +1,82 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTH_DRIVERS_TMS_TMSTILESOURCE
+#define OSGEARTH_DRIVERS_TMS_TMSTILESOURCE 1
+
+#include "TMSOptions"
+
+#include <osgEarth/TileSource>
+#include <osgEarth/Registry>
+#include <osgEarthUtil/TMS>
+
+#include <osg/Notify>
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+
+namespace osgEarth { namespace Drivers { namespace TileMapService
+{
+    class TMSTileSource : public TileSource
+    {
+    public:
+        TMSTileSource(const TileSourceOptions& options);
+
+    public: // TileSource
+
+        // one-time initialization (per source)
+        Status initialize(const osgDB::Options* dbOptions);
+
+        // TileSource timestamp is the time of the time map metadata.
+        TimeStamp getLastModifiedTime() const;
+
+        // reflect a default cache policy based on whether this TMS repo is
+        // local or remote.
+        CachePolicy getCachePolicyHint(const Profile* targetProfile) const;
+
+        // creates an image from the TMS repo.
+        osg::Image* createImage(
+            const             TileKey& key, 
+            ProgressCallback* progress);
+
+        // writes an image to the TMS repo
+        bool storeImage(
+            const TileKey& key, 
+            osg::Image*       image,
+            ProgressCallback* progress);
+
+        int getPixelsPerTile() const;
+
+        virtual std::string getExtension() const;
+
+    private:
+
+        osg::ref_ptr<TMS::TileMap>        _tileMap;
+        bool                              _invertY;
+        const TMSOptions                  _options;
+        osg::ref_ptr<osgDB::Options>      _dbOptions;
+        osg::ref_ptr<osgDB::ReaderWriter> _writer;
+        bool                              _forceRGB;
+
+        bool resolveWriter();
+    };
+
+} } } // namespace osgEarth::Drivers::TileMapService
+
+#endif // OSGEARTH_DRIVERS_TMS_TMSTILESOURCE
\ No newline at end of file
diff --git a/src/osgEarthDrivers/tms/TMSTileSource.cpp b/src/osgEarthDrivers/tms/TMSTileSource.cpp
new file mode 100644
index 0000000..167dc43
--- /dev/null
+++ b/src/osgEarthDrivers/tms/TMSTileSource.cpp
@@ -0,0 +1,310 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include "TMSTileSource"
+#include <osgEarth/ImageUtils>
+#include <osgEarth/FileUtils>
+#include <osgDB/FileUtils>
+#include <osgDB/FileNameUtils>
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+using namespace osgEarth::Drivers::TileMapService;
+
+#define LC "[TMSTileSource] "
+
+
+TMSTileSource::TMSTileSource(const TileSourceOptions& options) :
+TileSource(options),
+_options  (options),
+_forceRGB (false)
+{
+    _invertY = _options.tmsType() == "google";
+}
+
+
+TileSource::Status
+TMSTileSource::initialize(const osgDB::Options* dbOptions)
+{
+    // local copy of options we can modify if necessary.
+    _dbOptions = Registry::instance()->cloneOrCreateOptions(dbOptions);
+
+    // see if the use passed in a profile
+    const Profile* profile = getProfile();
+
+    // URI is mandatory.
+    URI tmsURI = _options.url().value();
+    if ( tmsURI.empty() )
+    {
+        return Status::Error( "Fail: TMS driver requires a valid \"url\" property" );
+    }
+
+    // A repo is writable only if it's local.
+    if ( tmsURI.isRemote() )
+    {
+        OE_INFO << LC << "Repo is remote; opening in read-only mode" << std::endl;
+    }
+
+    // Is this a new repo? (You can only create a new repo at a local non-archive URI.)
+    bool isNewRepo = false;
+
+    if (!tmsURI.isRemote() && 
+        !osgEarth::isPathToArchivedFile(tmsURI.full()) &&
+        !osgDB::fileExists(tmsURI.full()) )
+    {
+        isNewRepo = true;
+
+        // new repo REQUIRES a profile:
+        if ( !profile )
+        {
+            return Status::Error("Fail: profile required to create new TMS repo");
+        }
+    }
+
+    // Take the override profile if one is given
+    if ( profile )
+    {
+        OE_INFO << LC 
+            << "Using express profile \"" << getProfile()->toString() 
+            << "\" for URI \"" << tmsURI.base() << "\"" 
+            << std::endl;
+
+        _tileMap = TMS::TileMap::create( 
+            _options.url()->full(),
+            profile,
+            _options.format().value(),
+            _options.tileSize().value(), 
+            _options.tileSize().value() );
+
+        // If this is a new repo, write the tilemap file to disk now.
+        if ( isNewRepo )
+        {
+            if ( !_options.format().isSet() )
+            {
+                return Status::Error("Cannot create new repo with required [format] property");
+            }
+
+            TMS::TileMapReaderWriter::write( _tileMap.get(), tmsURI.full() );
+            OE_INFO << LC << "Created new TMS repo at " << tmsURI.full() << std::endl;
+        }
+    }
+
+    else
+    {
+        // Attempt to read the tile map parameters from a TMS TileMap XML tile on the server:
+        _tileMap = TMS::TileMapReaderWriter::read( tmsURI.full(), _dbOptions.get() );
+
+        if (!_tileMap.valid())
+        {
+            return Status::Error( Stringify() << "Failed to read tilemap from " << tmsURI.full() );
+        }
+
+        OE_INFO << LC
+            << "TMS tile map datestamp = "
+            << DateTime(_tileMap->getTimeStamp()).asRFC1123()
+            << std::endl;
+
+        profile = _tileMap->createProfile();
+        if ( profile )
+            setProfile( profile );
+    }
+
+    // Make sure we've established a profile by this point:
+    if ( !profile )
+    {
+        return Status::Error( Stringify() << "Failed to establish a profile for " << tmsURI.full() );
+    }
+
+    // resolve the writer
+    if ( !tmsURI.isRemote() && !resolveWriter() )
+    {
+        OE_WARN << LC << "Cannot create writer; writing disabled" << std::endl;
+    }
+
+    // TileMap and profile are valid at this point. Build the tile sets.
+    // Automatically set the min and max level of the TileMap
+    if ( _tileMap->getTileSets().size() > 0 )
+    {
+        OE_DEBUG << LC << "TileMap min/max " << _tileMap->getMinLevel() << ", " << _tileMap->getMaxLevel() << std::endl;
+        if (_tileMap->getDataExtents().size() > 0)
+        {
+            for (DataExtentList::iterator itr = _tileMap->getDataExtents().begin(); itr != _tileMap->getDataExtents().end(); ++itr)
+            {
+                this->getDataExtents().push_back(*itr);
+            }
+        }
+        else
+        {
+            //Push back a single area that encompasses the whole profile going up to the max level
+            this->getDataExtents().push_back(DataExtent(profile->getExtent(), 0, _tileMap->getMaxLevel()));
+        }
+    }
+    return STATUS_OK;
+}
+
+
+TimeStamp
+TMSTileSource::getLastModifiedTime() const
+{
+    if ( _tileMap.valid() )
+        return _tileMap->getTimeStamp();
+    else
+        return TileSource::getLastModifiedTime();
+}
+
+
+CachePolicy
+TMSTileSource::getCachePolicyHint(const Profile* targetProfile) const
+{
+    // if the source is local and the profiles line up, don't cache (by default).
+    if (!_options.url()->isRemote() &&
+        targetProfile && 
+        targetProfile->isEquivalentTo(getProfile()) )
+    {
+        return CachePolicy::NO_CACHE;
+    }
+    else
+    {
+        return CachePolicy::DEFAULT;
+    }
+}
+
+
+osg::Image*
+TMSTileSource::createImage(const TileKey&    key,
+                           ProgressCallback* progress)
+{
+    if (_tileMap.valid() && key.getLevelOfDetail() <= _tileMap->getMaxLevel() )
+    {
+        std::string image_url = _tileMap->getURL( key, _invertY );
+
+        osg::ref_ptr<osg::Image> image;
+        if (!image_url.empty())
+        {
+            image = URI(image_url).readImage( _dbOptions.get(), progress ).getImage();
+        }
+
+        if (!image.valid())
+        {
+            if (image_url.empty() || !_tileMap->intersectsKey(key))
+            {
+                //We couldn't read the image from the URL or the cache, so check to see if the given key is less than the max level
+                //of the tilemap and create a transparent image.
+                if (key.getLevelOfDetail() <= _tileMap->getMaxLevel())
+                {
+                    OE_DEBUG << LC << "Returning empty image " << std::endl;
+                    return ImageUtils::createEmptyImage();
+                }
+            }
+        }
+        return image.release();
+    }
+    return 0;
+}
+
+bool
+TMSTileSource::storeImage(const TileKey& key,
+                          osg::Image*    image,
+                          ProgressCallback* progress)
+{
+    if ( !_writer.valid() )
+    {
+        OE_WARN << LC << "Repo is read-only; store failed" << std::endl;
+        return false;
+    }
+
+    if (_tileMap.valid() && image)
+    {
+        // compute the URL from the tile map:
+        std::string image_url = _tileMap->getURL(key, _invertY);
+
+        // assert the folder exists:
+        if ( osgEarth::makeDirectoryForFile(image_url) )
+        {
+            osgDB::ReaderWriter::WriteResult result;
+
+            if ( _forceRGB && ImageUtils::hasAlphaChannel(image) )
+            {
+                osg::ref_ptr<osg::Image> rgbImage = ImageUtils::convertToRGB8(image);
+                result = _writer->writeImage( *(rgbImage.get()), image_url, _dbOptions.get());
+            }
+            else
+            {
+                result = _writer->writeImage(*image, image_url, _dbOptions.get());
+            }
+
+            if ( result.error() )
+            {
+                OE_WARN << LC << "store failed; url=[" << image_url << "] message=[" << result.message() << "]" << std::endl;
+                return false;
+            }
+        }
+        else
+        {
+            OE_WARN << LC << "Failed to make directory for " << image_url << std::endl;
+            return false;
+        }
+
+        return true;
+    }
+
+    return false;
+}
+
+int
+TMSTileSource::getPixelsPerTile() const
+{
+    return _tileMap->getFormat().getWidth();
+}
+
+std::string
+TMSTileSource::getExtension() const 
+{
+    return _tileMap->getFormat().getExtension();
+}
+
+bool
+TMSTileSource::resolveWriter()
+{
+    _writer = osgDB::Registry::instance()->getReaderWriterForMimeType(
+        _tileMap->getFormat().getMimeType());
+
+    if ( !_writer.valid() )
+    {
+        _writer = osgDB::Registry::instance()->getReaderWriterForExtension(
+            _tileMap->getFormat().getExtension());
+
+        if ( !_writer.valid() )
+        {
+            _writer = osgDB::Registry::instance()->getReaderWriterForExtension(
+                _options.format().value() );
+        }
+    }
+
+    // The OSG JPEG writer does not accept RGBA images, so force conversion.
+    _forceRGB =
+        _writer.valid() &&
+        (_writer->acceptsExtension("jpeg") || _writer->acceptsExtension("jpg") );
+
+    if ( _forceRGB )
+    {
+        OE_INFO << LC << "Note: images will be stored as RGB" << std::endl;
+    }
+
+    return _writer.valid();
+}
diff --git a/src/osgEarthDrivers/vdatum_egm2008/EGM2008.cpp b/src/osgEarthDrivers/vdatum_egm2008/EGM2008.cpp
index cd7c4bb..e17b917 100644
--- a/src/osgEarthDrivers/vdatum_egm2008/EGM2008.cpp
+++ b/src/osgEarthDrivers/vdatum_egm2008/EGM2008.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -53,7 +53,7 @@ namespace
             for( unsigned c=0; c<cols-1; ++c )
             {
                 float inputLon = 0.0f + float(c) * colStep;
-                if ( inputLon > 180.0 ) inputLon -= 360.0;
+                if ( inputLon >= 180.0 ) inputLon -= 360.0;
 
                 for( unsigned r=0; r<rows; ++r )
                 {
@@ -67,6 +67,10 @@ namespace
                 }
             }
 
+            // copy the first column to the last column
+            for(unsigned r=0; r<rows; ++r)
+                hf->setHeight(cols-1, r, hf->getHeight(0, r));
+
             _geoid = new Geoid();
             _geoid->setHeightField( hf );
             _geoid->setUnits( Units::METERS );
diff --git a/src/osgEarthDrivers/vdatum_egm2008/EGM2008Grid.h b/src/osgEarthDrivers/vdatum_egm2008/EGM2008Grid.h
index e17f076..8847ed3 100644
--- a/src/osgEarthDrivers/vdatum_egm2008/EGM2008Grid.h
+++ b/src/osgEarthDrivers/vdatum_egm2008/EGM2008Grid.h
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/vdatum_egm84/EGM84.cpp b/src/osgEarthDrivers/vdatum_egm84/EGM84.cpp
index d48ef1d..d4fc584 100644
--- a/src/osgEarthDrivers/vdatum_egm84/EGM84.cpp
+++ b/src/osgEarthDrivers/vdatum_egm84/EGM84.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -53,7 +53,7 @@ namespace
             for( unsigned c=0; c<cols-1; ++c )
             {
                 float inputLon = 0.0f + float(c) * colStep;
-                if ( inputLon > 180.0 ) inputLon -= 360.0;
+                if ( inputLon >= 180.0 ) inputLon -= 360.0;
 
                 for( unsigned r=0; r<rows; ++r )
                 {
@@ -67,6 +67,10 @@ namespace
                 }
             }
 
+            // copy the first column to the last column
+            for(unsigned r=0; r<rows; ++r)
+                hf->setHeight(cols-1, r, hf->getHeight(0, r));
+
             _geoid = new Geoid();
             _geoid->setHeightField( hf );
             _geoid->setUnits( Units::METERS );
diff --git a/src/osgEarthDrivers/vdatum_egm84/EGM84Grid.h b/src/osgEarthDrivers/vdatum_egm84/EGM84Grid.h
index f1da3f8..0cb18a2 100644
--- a/src/osgEarthDrivers/vdatum_egm84/EGM84Grid.h
+++ b/src/osgEarthDrivers/vdatum_egm84/EGM84Grid.h
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/vdatum_egm96/EGM96.cpp b/src/osgEarthDrivers/vdatum_egm96/EGM96.cpp
index eda6b8b..3859152 100644
--- a/src/osgEarthDrivers/vdatum_egm96/EGM96.cpp
+++ b/src/osgEarthDrivers/vdatum_egm96/EGM96.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -53,13 +53,15 @@ namespace
             for( unsigned c=0; c<cols-1; ++c )
             {
                 float inputLon = 0.0f + float(c) * colStep;
-                if ( inputLon > 180.0 ) inputLon -= 360.0;
+
+                if ( inputLon >= 180.0 ) inputLon -= 360.0;
+                
+                unsigned outc = unsigned( (inputLon-origin.x())/colStep );
 
                 for( unsigned r=0; r<rows; ++r )
                 {
                     float inputLat = 90.0f - float(r) * rowStep;
 
-                    unsigned outc = unsigned( (inputLon-origin.x())/colStep );
                     unsigned outr = unsigned( (inputLat-origin.y())/rowStep );
 
                     Linear h( (double)s_egm96grid[r*cols+c], Units::CENTIMETERS );
@@ -67,6 +69,10 @@ namespace
                 }
             }
 
+            // copy the first column to the last column
+            for(unsigned r=0; r<rows; ++r)
+                hf->setHeight(cols-1, r, hf->getHeight(0, r));
+
             _geoid = new Geoid();
             _geoid->setHeightField( hf );
             _geoid->setUnits( Units::METERS );
diff --git a/src/osgEarthDrivers/vdatum_egm96/EGM96Grid.h b/src/osgEarthDrivers/vdatum_egm96/EGM96Grid.h
index 4794ff9..ffffac9 100644
--- a/src/osgEarthDrivers/vdatum_egm96/EGM96Grid.h
+++ b/src/osgEarthDrivers/vdatum_egm96/EGM96Grid.h
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/vpb/ReaderWriterVPB.cpp b/src/osgEarthDrivers/vpb/ReaderWriterVPB.cpp
index 3b44800..d1df239 100644
--- a/src/osgEarthDrivers/vpb/ReaderWriterVPB.cpp
+++ b/src/osgEarthDrivers/vpb/ReaderWriterVPB.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -73,10 +73,10 @@ public:
         osgTerrain::TerrainTile* terrainTile = dynamic_cast<osgTerrain::TerrainTile*>(&group);
         if (terrainTile)
         {
-            OE_DEBUG<<"VPB: Found terrain tile TileID("<<
-                TileKey::getLOD(terrainTile->getTileID())<<", "<<
-                terrainTile->getTileID().x<<", "<<
-                terrainTile->getTileID().y<<")"<<std::endl;
+            //OE_DEBUG<<"VPB: Found terrain tile TileID("<<
+            //    TileKey::getLOD(terrainTile->getTileID())<<", "<<
+            //    terrainTile->getTileID().x<<", "<<
+            //    terrainTile->getTileID().y<<")"<<std::endl;
             
             _terrainTiles.push_back(terrainTile);
         }
@@ -197,61 +197,67 @@ public:
                     srs = csn->getCoordinateSystem();
                 }
 
-                CollectTiles ct;
-                _rootNode->accept(ct);
+                // We default to a global-geodetic profile, so only try to come up with another profile if the 
+                // database is not geocentric.  We do this to avoid small arounding errors where the VPB database
+                // isn't exactly -180,-90 180,90 and will result in unnecessary reprojection.
+                if (!csn->getEllipsoidModel())
+                {                                      
+                    CollectTiles ct;
+                    _rootNode->accept(ct);
 
-                    
-                osgTerrain::Locator* locator = ct.getLocator();
-                if (locator)
-                {
-                    double min_x, max_x, min_y, max_y;
-                    ct.getRange(min_x, min_y, max_x, max_y);
 
-                    OE_DEBUG << LC << "range("<<min_x<<", "<<min_y<<", "<<max_x<<", "<<max_y<< ")" <<std::endl;
-                    OE_DEBUG << LC << "range("<<osg::RadiansToDegrees(min_x)<<", "<<osg::RadiansToDegrees(min_y)<<", "
-                        <<osg::RadiansToDegrees(max_x)<<", "<<osg::RadiansToDegrees(max_y)<< ")" <<std::endl;
+                    osgTerrain::Locator* locator = ct.getLocator();
+                    if (locator)
+                    {
+                        double min_x, max_x, min_y, max_y;
+                        ct.getRange(min_x, min_y, max_x, max_y);
 
-                    srs = locator->getCoordinateSystem();
+                        OE_DEBUG << LC << "range("<<min_x<<", "<<min_y<<", "<<max_x<<", "<<max_y<< ")" <<std::endl;
+                        OE_DEBUG << LC << "range("<<osg::RadiansToDegrees(min_x)<<", "<<osg::RadiansToDegrees(min_y)<<", "
+                            <<osg::RadiansToDegrees(max_x)<<", "<<osg::RadiansToDegrees(max_y)<< ")" <<std::endl;
 
-                    double aspectRatio = (max_x-min_x)/(max_y-min_y);
-                    
-                    OE_DEBUG << LC << "aspectRatio = "<<aspectRatio<<std::endl;
+                        srs = locator->getCoordinateSystem();
 
-                    if (aspectRatio>1.0)
-                    {
-                        numTilesWideAtLod0 = static_cast<unsigned int>(floor(aspectRatio+0.499999));
-                        numTilesHighAtLod0 = 1;
-                    }
-                    else
-                    {
-                        numTilesWideAtLod0 = 1;
-                        numTilesHighAtLod0 = static_cast<unsigned int>(floor(1.0/aspectRatio+0.499999));
-                    }
-                    
-                    OE_DEBUG << LC << "computed numTilesWideAtLod0 = "<<numTilesWideAtLod0<<std::endl;
-                    OE_DEBUG << LC << "computed numTilesHightAtLod0 = "<<numTilesHighAtLod0<<std::endl;
-                    
-                    //if ( _options.valid() )
-                    {
-                        if ( _options.numTilesWideAtLod0().isSet() )
-                            numTilesWideAtLod0 = _options.numTilesWideAtLod0().value();
+                        double aspectRatio = (max_x-min_x)/(max_y-min_y);
 
-                        if ( _options.numTilesHighAtLod0().isSet() )
-                            numTilesHighAtLod0 = _options.numTilesHighAtLod0().value();
-                    }
+                        OE_DEBUG << LC << "aspectRatio = "<<aspectRatio<<std::endl;
 
-                    OE_DEBUG << LC << "final numTilesWideAtLod0 = "<<numTilesWideAtLod0<<std::endl;
-                    OE_DEBUG << LC << "final numTilesHightAtLod0 = "<<numTilesHighAtLod0<<std::endl;
-                   
-                    _profile = osgEarth::Profile::create( 
-                        srs,
-                        osg::RadiansToDegrees(min_x), 
-                        osg::RadiansToDegrees(min_y), 
-                        osg::RadiansToDegrees(max_x), 
-                        osg::RadiansToDegrees(max_y),
-                        "",
-                        numTilesWideAtLod0,
-                        numTilesHighAtLod0 );
+                        if (aspectRatio>1.0)
+                        {
+                            numTilesWideAtLod0 = static_cast<unsigned int>(floor(aspectRatio+0.499999));
+                            numTilesHighAtLod0 = 1;
+                        }
+                        else
+                        {
+                            numTilesWideAtLod0 = 1;
+                            numTilesHighAtLod0 = static_cast<unsigned int>(floor(1.0/aspectRatio+0.499999));
+                        }
+
+                        OE_DEBUG << LC << "computed numTilesWideAtLod0 = "<<numTilesWideAtLod0<<std::endl;
+                        OE_DEBUG << LC << "computed numTilesHightAtLod0 = "<<numTilesHighAtLod0<<std::endl;
+
+                        //if ( _options.valid() )
+                        {
+                            if ( _options.numTilesWideAtLod0().isSet() )
+                                numTilesWideAtLod0 = _options.numTilesWideAtLod0().value();
+
+                            if ( _options.numTilesHighAtLod0().isSet() )
+                                numTilesHighAtLod0 = _options.numTilesHighAtLod0().value();
+                        }
+
+                        OE_DEBUG << LC << "final numTilesWideAtLod0 = "<<numTilesWideAtLod0<<std::endl;
+                        OE_DEBUG << LC << "final numTilesHightAtLod0 = "<<numTilesHighAtLod0<<std::endl;
+
+                        _profile = osgEarth::Profile::create( 
+                            srs,
+                            osg::RadiansToDegrees(min_x), 
+                            osg::RadiansToDegrees(min_y), 
+                            osg::RadiansToDegrees(max_x), 
+                            osg::RadiansToDegrees(max_y),
+                            "",
+                            numTilesWideAtLod0,
+                            numTilesHighAtLod0 );
+                    }
                 }
                 
             }
@@ -364,8 +370,7 @@ public:
             return; //return 0;
         }        
 
-        osg::ref_ptr<osgDB::Options> localOptions = Registry::instance()->cloneOrCreateOptions();
-        CachePolicy::NO_CACHE.apply( localOptions.get() );
+        osg::ref_ptr<osgDB::Options> localOptions = Registry::instance()->cloneOrCreateOptions();        
         localOptions->setPluginData("osgearth_vpb Plugin",(void*)(1));
 
         ReadResult r = URI(filename).readNode( localOptions.get(), progress );
@@ -439,18 +444,18 @@ public:
                 _tileFIFO.pop_front();
                 _tileMap.erase(tileToRemove);
 
-                OE_DEBUG << LC << "Pruned tileID ("<<TileKey::getLOD(tileID)<<", "<<tileID.x<<", "<<tileID.y<<")"<<std::endl;
+//                OE_DEBUG << LC << "Pruned tileID ("<<TileKey::getLOD(tileID)<<", "<<tileID.x<<", "<<tileID.y<<")"<<std::endl;
             }
 
-            OE_DEBUG << LC << "insertTile ("
-                << TileKey::getLOD(tileID)<<", "<<tileID.x<<", "<<tileID.y<<") " 
-                << " tileFIFO.size()=="<<_tileFIFO.size()<<std::endl;
+            //OE_DEBUG << LC << "insertTile ("
+            //    << TileKey::getLOD(tileID)<<", "<<tileID.x<<", "<<tileID.y<<") " 
+            //    << " tileFIFO.size()=="<<_tileFIFO.size()<<std::endl;
         }
         else
         {
-            OE_DEBUG << LC << "insertTile ("
-                << TileKey::getLOD(tileID)<<", "<<tileID.x<<", "<<tileID.y<<") " 
-                << " ...already in cache!"<<std::endl;
+            //OE_DEBUG << LC << "insertTile ("
+            //    << TileKey::getLOD(tileID)<<", "<<tileID.x<<", "<<tileID.y<<") " 
+            //    << " ...already in cache!"<<std::endl;
         }
     }
 
@@ -514,15 +519,14 @@ public:
 
     Status initialize( const osgDB::Options* dbOptions )
     {
-        _dbOptions = Registry::instance()->cloneOrCreateOptions( dbOptions );
-        CachePolicy::NO_CACHE.apply( _dbOptions.get() );
+        _dbOptions = Registry::instance()->cloneOrCreateOptions( dbOptions );        
 
         _vpbDatabase->initialize( _dbOptions.get() );
 
         if ( !getProfile() )
         {
             setProfile(_vpbDatabase->_profile.get());
-        }
+        }        
 
         return STATUS_OK;
     }
diff --git a/src/osgEarthDrivers/vpb/VPBOptions b/src/osgEarthDrivers/vpb/VPBOptions
index 21ae457..7c34f3c 100644
--- a/src/osgEarthDrivers/vpb/VPBOptions
+++ b/src/osgEarthDrivers/vpb/VPBOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/wcs/ReaderWriterWCS.cpp b/src/osgEarthDrivers/wcs/ReaderWriterWCS.cpp
index 41aaf3a..70486d8 100644
--- a/src/osgEarthDrivers/wcs/ReaderWriterWCS.cpp
+++ b/src/osgEarthDrivers/wcs/ReaderWriterWCS.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/wcs/WCS11Source.cpp b/src/osgEarthDrivers/wcs/WCS11Source.cpp
index 3dc3814..2de0c87 100644
--- a/src/osgEarthDrivers/wcs/WCS11Source.cpp
+++ b/src/osgEarthDrivers/wcs/WCS11Source.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -47,8 +47,7 @@ osgEarth::TileSource::Status WCS11Source::initialize(const osgDB::Options* dbOpt
 {        
     //TODO: fetch GetCapabilities and set profile from there.
     setProfile( osgEarth::Registry::instance()->getGlobalGeodeticProfile() );
-    _dbOptions = Registry::instance()->cloneOrCreateOptions( dbOptions );
-    CachePolicy::NO_CACHE.apply( _dbOptions.get() );
+    _dbOptions = Registry::instance()->cloneOrCreateOptions( dbOptions );    
 
     return STATUS_OK;
 }
@@ -185,13 +184,10 @@ WCS11Source::createRequest( const TileKey& key ) const
     //    buf << lat_min << "," << lon_min << "," << lat_max << "," << lon_max;
     //buf << ",urn:ogc:def:crs:EPSG::4326";
 
-    double halfLon = lon_interval/2.0;
-    double halfLat = lat_interval/2.0;
-
-    //We need to shift the bounding box out by half a pixel in all directions so that the center of the edge pixels lie on
-    //the edge of this TileKey's extents.  Doing this makes neighboring tiles have the same elevation values so there is no need
-    //to run the tile edge normalization code.
-    buf << lon_min - halfLon << "," << lat_min - halfLat << "," << lon_max + halfLon << "," << lat_max + halfLat << ",EPSG:4326";
+    // there used to be code here to shift the bounding box out by half a pixel in all directions to make sure that neighboring tiles
+    // would have the same elevation values. WCS 1.1, however, samples at the edges of the bounding box, so shifting the bounding box
+    // will produce values that don't match up.
+    buf << lon_min << "," << lat_min << "," << lon_max << "," << lat_max << ",EPSG:4326";
 	std::string bufStr;
 	bufStr = buf.str();
     req.addParameter( "BOUNDINGBOX", bufStr );
diff --git a/src/osgEarthDrivers/wcs/WCS11Source.h b/src/osgEarthDrivers/wcs/WCS11Source.h
index 4ca8a73..2f7ed10 100644
--- a/src/osgEarthDrivers/wcs/WCS11Source.h
+++ b/src/osgEarthDrivers/wcs/WCS11Source.h
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/wcs/WCSOptions b/src/osgEarthDrivers/wcs/WCSOptions
index ce1a14e..e21c3cb 100644
--- a/src/osgEarthDrivers/wcs/WCSOptions
+++ b/src/osgEarthDrivers/wcs/WCSOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/wms/ReaderWriterWMS.cpp b/src/osgEarthDrivers/wms/ReaderWriterWMS.cpp
index 68a230b..8094550 100644
--- a/src/osgEarthDrivers/wms/ReaderWriterWMS.cpp
+++ b/src/osgEarthDrivers/wms/ReaderWriterWMS.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -22,6 +22,7 @@
 #include <osgEarth/Registry>
 #include <osgEarth/TimeControl>
 #include <osgEarth/XmlUtils>
+#include <osgEarth/ImageUtils>
 #include <osgEarthUtil/WMS>
 #include <osgDB/FileNameUtils>
 #include <osgDB/FileUtils>
@@ -270,8 +271,7 @@ public:
             OE_NOTICE << "[osgEarth::WMS] Profile=" << getProfile()->toString() << std::endl;
 
             // set up the cache options properly for a TileSource.
-            _dbOptions = Registry::instance()->cloneOrCreateOptions( dbOptions );
-            CachePolicy::NO_CACHE.apply( _dbOptions.get() );
+            _dbOptions = Registry::instance()->cloneOrCreateOptions( dbOptions );            
 
             return STATUS_OK;
         }
@@ -406,7 +406,7 @@ public:
     /** creates a 3D image from timestamped data. */
     osg::Image* createImageSequence( const TileKey& key, ProgressCallback* progress )
     {
-        osg::ImageSequence* seq = new SyncImageSequence();
+        osg::ref_ptr< osg::ImageSequence > seq = new SyncImageSequence();
         
         seq->setLoopingMode( osg::ImageStream::LOOPING );
         seq->setLength( _options.secondsPerFrame().value() * (double)_timesVec.size() );
@@ -425,8 +425,20 @@ public:
             }
         }
 
+        // Just return an empty image if we didn't get any images
+#if OSG_VERSION_LESS_THAN(3,1,4)
+        unsigned size = seq->getNumImages();
+#else
+        unsigned size = seq->getNumImageData();
+#endif
+
+        if (size == 0)
+        {
+            return ImageUtils::createEmptyImage();
+        }
+
         _sequenceCache.insert( seq );
-        return seq;
+        return seq.release();
     }
 
 
diff --git a/src/osgEarthDrivers/wms/TileService b/src/osgEarthDrivers/wms/TileService
index e5c7ab5..6391f96 100644
--- a/src/osgEarthDrivers/wms/TileService
+++ b/src/osgEarthDrivers/wms/TileService
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/wms/TileService.cpp b/src/osgEarthDrivers/wms/TileService.cpp
index 83cf17b..ba8b20d 100644
--- a/src/osgEarthDrivers/wms/TileService.cpp
+++ b/src/osgEarthDrivers/wms/TileService.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/wms/WMSOptions b/src/osgEarthDrivers/wms/WMSOptions
index f1d09b4..0764bf3 100644
--- a/src/osgEarthDrivers/wms/WMSOptions
+++ b/src/osgEarthDrivers/wms/WMSOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/xyz/ReaderWriterXYZ.cpp b/src/osgEarthDrivers/xyz/ReaderWriterXYZ.cpp
index 70dd78a..1372d0f 100644
--- a/src/osgEarthDrivers/xyz/ReaderWriterXYZ.cpp
+++ b/src/osgEarthDrivers/xyz/ReaderWriterXYZ.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -55,8 +55,7 @@ public:
 
     Status initialize(const osgDB::Options* dbOptions)
     {
-        _dbOptions = Registry::instance()->cloneOrCreateOptions(dbOptions);
-        CachePolicy::NO_CACHE.apply( _dbOptions.get() );
+        _dbOptions = Registry::instance()->cloneOrCreateOptions(dbOptions);        
 
         URI xyzURI = _options.url().value();
         if ( xyzURI.empty() )
@@ -102,6 +101,13 @@ public:
         }
 
         std::string location = _template;
+
+        // support OpenLayers template style:
+        replaceIn( location, "${x}", Stringify() << x );
+        replaceIn( location, "${y}", Stringify() << y );
+        replaceIn( location, "${z}", Stringify() << key.getLevelOfDetail() );
+
+        // failing that, legacy osgearth style:
         replaceIn( location, "{x}", Stringify() << x );
         replaceIn( location, "{y}", Stringify() << y );
         replaceIn( location, "{z}", Stringify() << key.getLevelOfDetail() );
diff --git a/src/osgEarthDrivers/xyz/XYZOptions b/src/osgEarthDrivers/xyz/XYZOptions
index 2c95c35..4d35e4b 100644
--- a/src/osgEarthDrivers/xyz/XYZOptions
+++ b/src/osgEarthDrivers/xyz/XYZOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthDrivers/yahoo/ReaderWriterYahoo.cpp b/src/osgEarthDrivers/yahoo/ReaderWriterYahoo.cpp
index 4272c9a..a0c4254 100644
--- a/src/osgEarthDrivers/yahoo/ReaderWriterYahoo.cpp
+++ b/src/osgEarthDrivers/yahoo/ReaderWriterYahoo.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -45,7 +45,7 @@ public:
     // Yahoo! uses spherical mercator, but the top LOD is a 2x2 tile set.
     Status initialize(const osgDB::Options* dbOptions)
     {
-        // no caching of source tiles
+        // no caching of source tiles.  Yahoo TOS does not allow it.
         _dbOptions = Registry::instance()->cloneOrCreateOptions( dbOptions );
         CachePolicy::NO_CACHE.apply( _dbOptions.get() );
 
diff --git a/src/osgEarthDrivers/yahoo/YahooOptions b/src/osgEarthDrivers/yahoo/YahooOptions
index 37d31fc..f480c3d 100644
--- a/src/osgEarthDrivers/yahoo/YahooOptions
+++ b/src/osgEarthDrivers/yahoo/YahooOptions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/AltitudeFilter b/src/osgEarthFeatures/AltitudeFilter
index a5d913e..eb2ad3f 100644
--- a/src/osgEarthFeatures/AltitudeFilter
+++ b/src/osgEarthFeatures/AltitudeFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/AltitudeFilter.cpp b/src/osgEarthFeatures/AltitudeFilter.cpp
index d54657d..5bf5eb2 100644
--- a/src/osgEarthFeatures/AltitudeFilter.cpp
+++ b/src/osgEarthFeatures/AltitudeFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -51,7 +51,6 @@ AltitudeFilter::push( FeatureList& features, FilterContext& cx )
         _altitude.valid()                                          && 
         _altitude->clamping()  != AltitudeSymbol::CLAMP_NONE       &&
         _altitude->technique() == AltitudeSymbol::TECHNIQUE_MAP    &&
-        //_altitude->technique() != AltitudeSymbol::TECHNIQUE_SCENE  &&
         cx.getSession()        != 0L                               &&
         cx.profile()           != 0L;
 
@@ -77,6 +76,13 @@ AltitudeFilter::pushAndDontClamp( FeatureList& features, FilterContext& cx )
     for( FeatureList::iterator i = features.begin(); i != features.end(); ++i )
     {
         Feature* feature = i->get();
+        
+        // run a symbol script if present.
+        if ( _altitude.valid() && _altitude->script().isSet() )
+        {
+            StringExpression temp( _altitude->script().get() );
+            feature->eval( temp, &cx );
+        }
 
         double minHAT       =  DBL_MAX;
         double maxHAT       = -DBL_MAX;
@@ -120,7 +126,8 @@ AltitudeFilter::pushAndClamp( FeatureList& features, FilterContext& cx )
 
     // the map against which we'll be doing elevation clamping
     //MapFrame mapf = session->createMapFrame( Map::ELEVATION_LAYERS );
-    MapFrame mapf = session->createMapFrame( Map::TERRAIN_LAYERS );
+    MapFrame mapf = session->createMapFrame( 
+        (Map::ModelParts)(Map::TERRAIN_LAYERS | Map::MODEL_LAYERS) );
 
     const SpatialReference* mapSRS = mapf.getProfile()->getSRS();
     osg::ref_ptr<const SpatialReference> featureSRS = cx.profile()->getSRS();
@@ -152,6 +159,14 @@ AltitudeFilter::pushAndClamp( FeatureList& features, FilterContext& cx )
     for( FeatureList::iterator i = features.begin(); i != features.end(); ++i )
     {
         Feature* feature = i->get();
+        
+        // run a symbol script if present.
+        if ( _altitude.valid() && _altitude->script().isSet() )
+        {
+            StringExpression temp( _altitude->script().get() );
+            feature->eval( temp, &cx );
+        }
+
         double maxTerrainZ  = -DBL_MAX;
         double minTerrainZ  =  DBL_MAX;
         double minHAT       =  DBL_MAX;
diff --git a/src/osgEarthFeatures/BufferFilter b/src/osgEarthFeatures/BufferFilter
index ad199f3..64317f3 100644
--- a/src/osgEarthFeatures/BufferFilter
+++ b/src/osgEarthFeatures/BufferFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/BufferFilter.cpp b/src/osgEarthFeatures/BufferFilter.cpp
index 8feed33..c5c2179 100644
--- a/src/osgEarthFeatures/BufferFilter.cpp
+++ b/src/osgEarthFeatures/BufferFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/BuildGeometryFilter b/src/osgEarthFeatures/BuildGeometryFilter
index 908a543..377cd36 100644
--- a/src/osgEarthFeatures/BuildGeometryFilter
+++ b/src/osgEarthFeatures/BuildGeometryFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -81,13 +81,23 @@ namespace osgEarth { namespace Features
         optional<GeoInterpolation> _geoInterp;
         optional<StringExpression> _featureNameExpr;
         
+        void tileAndBuildPolygon(
+            Geometry*               input,
+            const SpatialReference* featureSRS,
+            const SpatialReference* mapSRS,
+            bool                    makeECEF,
+            bool                    tessellate,
+            osg::Geometry*          osgGeom,
+            const osg::Matrixd      &world2local);
+        
         void buildPolygon(
             Geometry*               input,
             const SpatialReference* featureSRS,
             const SpatialReference* mapSRS,
             bool                    makeECEF,
             bool                    tessellate,
-            osg::Geometry*          osgGeom);
+            osg::Geometry*          osgGeom,
+            const osg::Matrixd      &world2local);
 
         osg::Geode* processPolygons        (FeatureList& input, const FilterContext& cx);
         osg::Geode* processLines           (FeatureList& input, const FilterContext& cx);
diff --git a/src/osgEarthFeatures/BuildGeometryFilter.cpp b/src/osgEarthFeatures/BuildGeometryFilter.cpp
index b3c127a..2937ae2 100644
--- a/src/osgEarthFeatures/BuildGeometryFilter.cpp
+++ b/src/osgEarthFeatures/BuildGeometryFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -17,6 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarthFeatures/BuildGeometryFilter>
+#include <osgEarthFeatures/Session>
 #include <osgEarthFeatures/FeatureSourceIndexNode>
 #include <osgEarthFeatures/PolygonizeLines>
 #include <osgEarthSymbology/TextSymbol>
@@ -25,6 +26,8 @@
 #include <osgEarthSymbology/PolygonSymbol>
 #include <osgEarthSymbology/MeshSubdivider>
 #include <osgEarthSymbology/MeshConsolidator>
+#include <osgEarthSymbology/ResourceCache>
+#include <osgEarth/Tessellator>
 #include <osgEarth/Utils>
 #include <osg/Geode>
 #include <osg/Geometry>
@@ -37,9 +40,11 @@
 #include <osgText/Text>
 #include <osgUtil/Tessellator>
 #include <osgUtil/Optimizer>
+#include <osgUtil/Simplifier>
 #include <osgUtil/SmoothingVisitor>
 #include <osgDB/WriteFile>
 #include <osg/Version>
+#include <iterator>
 
 #define LC "[BuildGeometryFilter] "
 
@@ -49,9 +54,47 @@ using namespace osgEarth;
 using namespace osgEarth::Features;
 using namespace osgEarth::Symbology;
 
+namespace
+{
+    bool isCCW(double x1, double y1, double x2, double y2, double x3, double y3)
+    {
+        return (x2 - x1) * (y3 - y1) - (y2 - y1) * (x3 - x1) > 0.0;
+    }
+
+    bool segmentsIntersect(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4)
+    {
+        return isCCW(x1, y1, x3, y3, x4, y4) != isCCW(x2, y2, x3, y3, x4, y4) && isCCW(x1, y1, x2, y2, x3, y3) != isCCW(x1, y1, x2, y2, x4, y4);
+    }
+
+    bool holeCompare(osgEarth::Symbology::Ring* i, osgEarth::Symbology::Ring* j)
+    {
+        return i->getBounds().xMax() > j->getBounds().xMax();
+    }
+
+    bool segmentsIntersect(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4, double &xi, double &yi)
+    {
+        double d = (y4-y3) * (x2-x1) - (x4-x3) * (y2-y1);
+
+        if (d == 0) return false; // parallel
+
+        double ua = ((x4-x3) * (y1-y3) - (y4-y3) * (x1-x3)) / d;
+        double ub = ((x2-x1) * (y1-y3) - (y2-y1) * (x1-x3)) / d;
+
+        if (ua >= 0.0 && ua <= 1.0 && ub >= 0.0 && ub <= 1.0)
+        {
+            xi = x1 + ua * (x2 - x1);
+            yi = y1 + ua * (y2 - y1);
+
+            return true;
+        }
+
+        return false;
+    }
+}
+
 BuildGeometryFilter::BuildGeometryFilter( const Style& style ) :
 _style        ( style ),
-_maxAngle_deg ( 1.0 ),
+_maxAngle_deg ( 180.0 ),
 _geoInterp    ( GEOINTERP_RHUMB_LINE )
 {
     //nop
@@ -78,6 +121,21 @@ BuildGeometryFilter::processPolygons(FeatureList& features, const FilterContext&
     {
         Feature* input = f->get();
 
+        // access the polygon symbol, and bail out if there isn't one
+        const PolygonSymbol* poly =
+            input->style().isSet() && input->style()->has<PolygonSymbol>() ? input->style()->get<PolygonSymbol>() :
+            _style.get<PolygonSymbol>();
+
+        if ( !poly )
+            continue;
+
+        // run a symbol script if present.
+        if ( poly->script().isSet() )
+        {
+            StringExpression temp( poly->script().get() );
+            input->eval( temp, &context );
+        }
+
         GeometryIterator parts( input->getGeometry(), false );
         while( parts.hasMore() )
         {
@@ -87,13 +145,6 @@ BuildGeometryFilter::processPolygons(FeatureList& features, const FilterContext&
             if ( part->size() < 3 )
                 continue;
 
-            // access the polygon symbol, and bail out if there isn't one
-            const PolygonSymbol* poly =
-                input->style().isSet() && input->style()->has<PolygonSymbol>() ? input->style()->get<PolygonSymbol>() :
-                _style.get<PolygonSymbol>();
-            if ( !poly )
-                continue;
-
             // resolve the color:
             osg::Vec4f primaryColor = poly->fill()->color();
             
@@ -108,37 +159,69 @@ BuildGeometryFilter::processPolygons(FeatureList& features, const FilterContext&
                 osgGeom->setName( name );
             }
 
-            // build the geometry:
-            buildPolygon(part, featureSRS, mapSRS, makeECEF, true, osgGeom);
 
-            osg::Vec3Array* allPoints = static_cast<osg::Vec3Array*>(osgGeom->getVertexArray());
-            
-            // subdivide the mesh if necessary to conform to an ECEF globe:
-            if ( makeECEF )
+            // compute localizing matrices or use globals
+            osg::Matrixd w2l, l2w;
+            if (makeECEF)
             {
-                double threshold = osg::DegreesToRadians( *_maxAngle_deg );
-                OE_DEBUG << "Running mesh subdivider with threshold " << *_maxAngle_deg << std::endl;
+                osgEarth::GeoExtent featureExtent(featureSRS);
+                featureExtent.expandToInclude(part->getBounds());
 
-                MeshSubdivider ms( _world2local, _local2world );
-                //ms.setMaxElementsPerEBO( INT_MAX );
-                if ( input->geoInterp().isSet() )
-                    ms.run( *osgGeom, threshold, *input->geoInterp() );
-                else
-                    ms.run( *osgGeom, threshold, *_geoInterp );
+                computeLocalizers(context, featureExtent, w2l, l2w);
+            }
+            else
+            {
+                w2l = _world2local;
+                l2w = _local2world;
             }
 
-            // assign the primary color array. PER_VERTEX required in order to support
-            // vertex optimization later
-            osg::Vec4Array* colors = new osg::Vec4Array;
-            colors->assign( osgGeom->getVertexArray()->getNumElements(), primaryColor );
-            osgGeom->setColorArray( colors );
-            osgGeom->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
 
-            geode->addDrawable( osgGeom );
+            // build the geometry:
+            tileAndBuildPolygon(part, featureSRS, mapSRS, makeECEF, true, osgGeom, w2l);
+            //buildPolygon(part, featureSRS, mapSRS, makeECEF, true, osgGeom, w2l);
 
-            // record the geometry's primitive set(s) in the index:
-            if ( context.featureIndex() )
-                context.featureIndex()->tagPrimitiveSets( osgGeom, input );
+            osg::Vec3Array* allPoints = static_cast<osg::Vec3Array*>(osgGeom->getVertexArray());
+            if (allPoints && allPoints->size() > 0)
+            {
+                // subdivide the mesh if necessary to conform to an ECEF globe:
+                if ( makeECEF )
+                {
+
+                    //convert back to world coords
+                    for( osg::Vec3Array::iterator i = allPoints->begin(); i != allPoints->end(); ++i )
+                    {
+                        osg::Vec3d v(*i);
+                        v = v * l2w;
+                        v = v * _world2local;
+
+                        (*i)._v[0] = v[0];
+                        (*i)._v[1] = v[1];
+                        (*i)._v[2] = v[2];
+                    }
+
+                    double threshold = osg::DegreesToRadians( *_maxAngle_deg );
+                    OE_DEBUG << "Running mesh subdivider with threshold " << *_maxAngle_deg << std::endl;
+
+                    MeshSubdivider ms( _world2local, _local2world );
+                    if ( input->geoInterp().isSet() )
+                        ms.run( *osgGeom, threshold, *input->geoInterp() );
+                    else
+                        ms.run( *osgGeom, threshold, *_geoInterp );
+                }
+
+                // assign the primary color array. PER_VERTEX required in order to support
+                // vertex optimization later
+                osg::Vec4Array* colors = new osg::Vec4Array;
+                colors->assign( osgGeom->getVertexArray()->getNumElements(), primaryColor );
+                osgGeom->setColorArray( colors );
+                osgGeom->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
+
+                geode->addDrawable( osgGeom );
+
+                // record the geometry's primitive set(s) in the index:
+                if ( context.featureIndex() )
+                    context.featureIndex()->tagPrimitiveSets( osgGeom, input );
+            }
         }
     }
     
@@ -174,9 +257,17 @@ BuildGeometryFilter::processPolygonizedLines(FeatureList&         features,
         const LineSymbol* line =
             input->style().isSet() && input->style()->has<LineSymbol>() ? input->style()->get<LineSymbol>() :
             _style.get<LineSymbol>();
+
         if ( !line )
             continue;
 
+        // run a symbol script if present.
+        if ( line->script().isSet() )
+        {
+            StringExpression temp( line->script().get() );
+            input->eval( temp, &context );
+        }
+
         // The operator we'll use to make lines into polygons.
         PolygonizeLinesOperator polygonizer( *line->stroke() );
 
@@ -215,7 +306,7 @@ BuildGeometryFilter::processPolygonizedLines(FeatureList&         features,
                 context.featureIndex()->tagPrimitiveSets( geom, input );
         }
 
-        polygonizer.installShaders( geode->getOrCreateStateSet() );
+        polygonizer.installShaders( geode );
     }
     return geode;
 }
@@ -242,6 +333,21 @@ BuildGeometryFilter::processLines(FeatureList& features, const FilterContext& co
     {
         Feature* input = f->get();
 
+        // extract the required line symbol; bail out if not found.
+        const LineSymbol* line = 
+            input->style().isSet() && input->style()->has<LineSymbol>() ? input->style()->get<LineSymbol>() :
+            _style.get<LineSymbol>();
+
+        if ( !line )
+            continue;
+
+        // run a symbol script if present.
+        if ( line->script().isSet() )
+        {
+            StringExpression temp( line->script().get() );
+            input->eval( temp, &context );
+        }
+
         GeometryIterator parts( input->getGeometry(), true );
         while( parts.hasMore() )
         {
@@ -251,13 +357,6 @@ BuildGeometryFilter::processLines(FeatureList& features, const FilterContext& co
             if ( part->size() < 2 )
                 continue;
 
-            // extract the required line symbol; bail out if not found.
-            const LineSymbol* line = 
-                input->style().isSet() && input->style()->has<LineSymbol>() ? input->style()->get<LineSymbol>() :
-                _style.get<LineSymbol>();
-            if ( !line )
-                continue;
-
             // if the underlying geometry is a ring (or a polygon), use a line loop; otherwise
             // use a line strip.
             GLenum primMode = dynamic_cast<Ring*>(part) ? GL_LINE_LOOP : GL_LINE_STRIP;
@@ -348,14 +447,11 @@ BuildGeometryFilter::processPoints(FeatureList& features, const FilterContext& c
         {
             Geometry* part = parts.next();
 
-            // skip invalid geometry for lines.
-            if ( part->size() < 2 )
-                continue;
-
-            // extract the required line symbol; bail out if not found.
+            // extract the required point symbol; bail out if not found.
             const PointSymbol* point =
                 input->style().isSet() && input->style()->has<PointSymbol>() ? input->style()->get<PointSymbol>() :
                 _style.get<PointSymbol>();
+
             if ( !point )
                 continue;
 
@@ -404,6 +500,125 @@ BuildGeometryFilter::processPoints(FeatureList& features, const FilterContext& c
     return geode;
 }
 
+#define CROP_POLYS_BEFORE_TESSELLATING 1
+
+void
+BuildGeometryFilter::tileAndBuildPolygon(Geometry*               ring,
+                                         const SpatialReference* featureSRS,
+                                         const SpatialReference* mapSRS,
+                                         bool                    makeECEF,
+                                         bool                    tessellate,
+                                         osg::Geometry*          osgGeom,
+                                         const osg::Matrixd      &world2local)
+{
+#ifdef CROP_POLYS_BEFORE_TESSELLATING
+
+#define MAX_POINTS_PER_CROP_TILE 1024
+
+    bool built = false;
+    unsigned count = ring->getTotalPointCount();
+    if ( count > MAX_POINTS_PER_CROP_TILE )
+    {
+        unsigned tiles = (count / MAX_POINTS_PER_CROP_TILE) + 1u;
+        double tx = ceil(sqrt((double)tiles));
+        double ty = tx;
+        Bounds b = ring->getBounds();
+        double tw = b.width() / tx;
+        double th = b.height() / ty;
+
+        OE_DEBUG << "Found " << count << " points; cropping to " << tx << " x " << ty << std::endl;
+
+        osg::ref_ptr<Polygon> poly = new Polygon;
+        poly->resize( 4 );
+
+        built = true;
+        for(int x=0; x<(int)tx; ++x)
+        {
+            for(int y=0; y<(int)ty; ++y)
+            {
+                (*poly)[0].set( b.xMin() + tw*(double)x,     b.yMin() + th*(double)y,     0.0 );
+                (*poly)[1].set( b.xMin() + tw*(double)(x+1), b.yMin() + th*(double)y,     0.0 );
+                (*poly)[2].set( b.xMin() + tw*(double)(x+1), b.yMin() + th*(double)(y+1), 0.0 );
+                (*poly)[3].set( b.xMin() + tw*(double)x,     b.yMin() + th*(double)(y+1), 0.0 );
+                
+                osg::ref_ptr<Geometry> ringTile;
+                if ( ring->crop(poly.get(), ringTile) )
+                {
+                    // Use an iterator since crop could return a multi-polygon
+                    GeometryIterator gi( ringTile.get(), false );
+                    while( gi.hasMore() )
+                    {
+                        Geometry* geom = gi.next();
+                        buildPolygon(geom, featureSRS, mapSRS, makeECEF, tessellate, osgGeom, world2local);
+                    }
+                }
+                else 
+                {
+                    // If crop resulted in empty geometry (valid case) ringTile will still be valid,
+                    // otherwise we need to process the entire polygon without tiling.
+                    if (!ringTile.valid())
+                    {
+                        //clean up geometry
+                        osgGeom->setVertexArray(0L);
+                        if (osgGeom->getNumPrimitiveSets())
+                            osgGeom->removePrimitiveSet(0, osgGeom->getNumPrimitiveSets());
+
+                        OE_NOTICE << LC << "GEOS crop failed, tessellating feature without tiling." << std::endl;
+
+                        built = false;
+                        break;
+                    }
+                }
+            }
+
+            // GEOS failed 
+            if (!built)
+                break;
+        }
+    }
+
+    if ( !built )
+    {
+        buildPolygon(ring, featureSRS, mapSRS, makeECEF, tessellate, osgGeom, world2local);
+    }
+    
+
+    if ( tessellate )
+    {
+        osgEarth::Tessellator oeTess;
+        if (!oeTess.tessellateGeometry(*osgGeom))
+        {
+            //fallback to osg tessellator
+            OE_DEBUG << LC << "Falling back on OSG tessellator (" << osgGeom->getName() << ")" << std::endl;
+
+            osgUtil::Tessellator tess;
+            tess.setTessellationType( osgUtil::Tessellator::TESS_TYPE_GEOMETRY );
+            tess.setWindingType( osgUtil::Tessellator::TESS_WINDING_POSITIVE );
+            tess.retessellatePolygons( *osgGeom );
+        }
+    }
+
+#else
+
+    // non-cropped way
+    buildPolygon(ring, featureSRS, mapSRS, makeECEF, tessellate, osgGeom, world2local);
+    if ( tessellate )
+    {
+        osgEarth::Tessellator oeTess;
+        if (!oeTess.tessellateGeometry(*osgGeom))
+        {
+            //fallback to osg tessellator
+            OE_INFO << LC << "OE Tessellation failed! Using OSG tessellator. (" << osgGeom->getName() << ")" << std::endl;
+
+            osgUtil::Tessellator tess;
+            tess.setTessellationType( osgUtil::Tessellator::TESS_TYPE_GEOMETRY );
+            tess.setWindingType( osgUtil::Tessellator::TESS_WINDING_POSITIVE );
+            tess.retessellatePolygons( *osgGeom );
+        }
+    }
+#endif
+}
+
 // builds and tessellates a polygon (with or without holes)
 void
 BuildGeometryFilter::buildPolygon(Geometry*               ring,
@@ -411,43 +626,166 @@ BuildGeometryFilter::buildPolygon(Geometry*               ring,
                                   const SpatialReference* mapSRS,
                                   bool                    makeECEF,
                                   bool                    tessellate,
-                                  osg::Geometry*          osgGeom)
+                                  osg::Geometry*          osgGeom,
+                                  const osg::Matrixd      &world2local)
 {
     if ( !ring->isValid() )
         return;
 
-    int totalPoints = ring->getTotalPointCount();
-    osg::Vec3Array* allPoints = new osg::Vec3Array();
-    transformAndLocalize( ring->asVector(), featureSRS, allPoints, mapSRS, _world2local, makeECEF );
+    ring->rewind(osgEarth::Symbology::Geometry::ORIENTATION_CCW);
 
-    GLenum mode = GL_LINE_LOOP;
-    osgGeom->addPrimitiveSet( new osg::DrawArrays( mode, 0, ring->size() ) );
+    osg::ref_ptr<osg::Vec3Array> allPoints = new osg::Vec3Array();
+    transformAndLocalize( ring->asVector(), featureSRS, allPoints.get(), mapSRS, world2local, makeECEF );
 
     Polygon* poly = dynamic_cast<Polygon*>(ring);
     if ( poly )
     {
-        int offset = ring->size();
+        RingCollection ordered(poly->getHoles().begin(), poly->getHoles().end());
+        std::sort(ordered.begin(), ordered.end(), holeCompare);
 
-        for( RingCollection::const_iterator h = poly->getHoles().begin(); h != poly->getHoles().end(); ++h )
+        for( RingCollection::const_iterator h = ordered.begin(); h != ordered.end(); ++h )
         {
             Geometry* hole = h->get();
             if ( hole->isValid() )
             {
-                transformAndLocalize( hole->asVector(), featureSRS, allPoints, mapSRS, _world2local, makeECEF );
-
-                osgGeom->addPrimitiveSet( new osg::DrawArrays( mode, offset, hole->size() ) );
-                offset += hole->size();
-            }            
+                hole->rewind(osgEarth::Symbology::Geometry::ORIENTATION_CW);
+
+                osg::ref_ptr<osg::Vec3Array> holePoints = new osg::Vec3Array();
+                transformAndLocalize( hole->asVector(), featureSRS, holePoints.get(), mapSRS, world2local, makeECEF );
+
+                // find the point with the highest x value
+                unsigned int hCursor = 0;
+                for (unsigned int i=1; i < holePoints->size(); i++)
+                {
+                    if ((*holePoints)[i].x() > (*holePoints)[hCursor].x())
+                      hCursor = i;
+                }
+
+                double x1 = (*holePoints)[hCursor].x();
+                double y1 = (*holePoints)[hCursor].y();
+                double y2 = (*holePoints)[hCursor].y();
+
+                unsigned int edgeCursor = UINT_MAX;
+                double edgeDistance = DBL_MAX;
+                unsigned int foundPointCursor = UINT_MAX;
+                for (unsigned int i=0; i < allPoints->size(); i++)
+                {
+                    unsigned int next = i == allPoints->size() - 1 ? 0 : i + 1;
+                    double xMax = osg::maximum((*allPoints)[i].x(), (*allPoints)[next].x());
+
+                    if (xMax > (*holePoints)[hCursor].x())
+                    {
+                        double x2 = xMax + 1.0;
+                        double x3 = (*allPoints)[i].x();
+                        double y3 = (*allPoints)[i].y();
+                        double x4 = (*allPoints)[next].x();
+                        double y4 = (*allPoints)[next].y();
+
+                        double xi=0.0, yi=0.0;
+                        bool intersects = false;
+                        unsigned int hitPointCursor = UINT_MAX;
+                        if (y1 == y3 && x3 > x1)
+                        {
+                            xi = x3;
+                            hitPointCursor = i;
+                            intersects = true;
+                        }
+                        else if (y1 == y4 && x4 > x1)
+                        {
+                            xi = x4;
+                            hitPointCursor = next;
+                            intersects = true;
+                        }
+                        else if (segmentsIntersect(x1, y1, x2, y2, x3, y3, x4, y4, xi, yi))
+                        {
+                            intersects = true;
+                        }
+
+                        double dist = (osg::Vec2d(xi, yi) - osg::Vec2d(x1, y1)).length();
+                        if (intersects && dist < edgeDistance)
+                        {
+                            foundPointCursor = hitPointCursor;
+                            edgeCursor = hitPointCursor != UINT_MAX ? hitPointCursor : (x3 >= x4 ? i : next);
+                            edgeDistance = dist;
+                        }
+                    }
+                }
+
+                if (foundPointCursor == UINT_MAX && edgeCursor != UINT_MAX)
+                {
+                    // test for intersecting edges between x1 and x2
+                    // (skipping the two segments for which edgeCursor is a vert)
+
+                    double x2 = (*allPoints)[edgeCursor].x();
+                    y2 = (*allPoints)[edgeCursor].y();
+
+                    bool foundIntersection = false;
+                    for (unsigned int i=0; i < allPoints->size(); i++)
+                    {
+                        unsigned int next = i == allPoints->size() - 1 ? 0 : i + 1;
+
+                        if (i == edgeCursor || next == edgeCursor)
+                          continue;
+
+                        double x3 = (*allPoints)[i].x();
+                        double y3 = (*allPoints)[i].y();
+                        double x4 = (*allPoints)[next].x();
+                        double y4 = (*allPoints)[next].y();
+
+                        foundIntersection = foundIntersection || segmentsIntersect(x1, y1, x2, y2, x3, y3, x4, y4);
+
+                        if (foundIntersection)
+                        {
+                            unsigned int prev = i == 0 ? allPoints->size() - 1 : i - 1;
+
+                            if (!isCCW((*allPoints)[prev].x(), (*allPoints)[prev].y(), x3, y3, x4, y4))
+                            {
+                                edgeCursor = i;
+                                x2 = (*allPoints)[edgeCursor].x();
+                                y2 = (*allPoints)[edgeCursor].y();
+                                foundIntersection = false;
+                            }
+                        }
+
+                    }
+                }
+
+                if (edgeCursor != UINT_MAX)
+                {
+                    // build array of correctly ordered new points to add to the outer loop
+                    osg::ref_ptr<osg::Vec3Array> insertPoints = new osg::Vec3Array();
+                    insertPoints->reserve(holePoints->size() + 2);
+
+                    unsigned int p = hCursor;
+                    do
+                    {
+                        insertPoints->push_back((*holePoints)[p]);
+                        p = p == holePoints->size() - 1 ? 0 : p + 1;
+                    } while(p != hCursor);
+
+                    insertPoints->push_back((*holePoints)[hCursor]);
+                    insertPoints->push_back((*allPoints)[edgeCursor]);
+                    
+                    // insert new points into outer loop
+                    osg::Vec3Array::iterator it = edgeCursor == allPoints->size() - 1 ? allPoints->end() : allPoints->begin() + (edgeCursor + 1);
+                    allPoints->insert(it, insertPoints->begin(), insertPoints->end());
+                }
+            }
         }
     }
-    osgGeom->setVertexArray( allPoints );
-
-    if ( tessellate )
+    
+    GLenum mode = GL_LINE_LOOP;
+    if ( osgGeom->getVertexArray() == 0L )
+    {
+        osgGeom->addPrimitiveSet( new osg::DrawArrays( mode, 0, allPoints->size() ) );
+        osgGeom->setVertexArray( allPoints.get() );
+    }
+    else
     {
-        osgUtil::Tessellator tess;
-        tess.setTessellationType( osgUtil::Tessellator::TESS_TYPE_GEOMETRY );
-        tess.setWindingType( osgUtil::Tessellator::TESS_WINDING_POSITIVE );
-        tess.retessellatePolygons( *osgGeom );
+        osg::Vec3Array* v = static_cast<osg::Vec3Array*>(osgGeom->getVertexArray());
+        osgGeom->addPrimitiveSet( new osg::DrawArrays( mode, v->size(), allPoints->size() ) );
+        //v->reserve(v->size() + allPoints->size());
+        std::copy(allPoints->begin(), allPoints->end(), std::back_inserter(*v));
     }
 
     //// Normal computation.
diff --git a/src/osgEarthFeatures/BuildTextFilter b/src/osgEarthFeatures/BuildTextFilter
index 638dfbc..99096ef 100644
--- a/src/osgEarthFeatures/BuildTextFilter
+++ b/src/osgEarthFeatures/BuildTextFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/BuildTextFilter.cpp b/src/osgEarthFeatures/BuildTextFilter.cpp
index c93fae4..50de374 100644
--- a/src/osgEarthFeatures/BuildTextFilter.cpp
+++ b/src/osgEarthFeatures/BuildTextFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -50,6 +50,12 @@ BuildTextFilter::push( FeatureList& input, FilterContext& context )
 
     LabelSourceOptions options;
     options.setDriver( "annotation" );
+
+    if( text && !text->provider()->empty() )
+        options.setDriver( *text->provider() );
+
+
+
     //options.setDriver( text ? (*text->provider()) : (*icon->provider()) );
     osg::ref_ptr<LabelSource> source = LabelSourceFactory::create( options );
     if ( source.valid() )
diff --git a/src/osgEarthFeatures/BuildTextOperator b/src/osgEarthFeatures/BuildTextOperator
index 7d155b8..3a954e6 100644
--- a/src/osgEarthFeatures/BuildTextOperator
+++ b/src/osgEarthFeatures/BuildTextOperator
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/BuildTextOperator.cpp b/src/osgEarthFeatures/BuildTextOperator.cpp
index fc68683..b7372b2 100644
--- a/src/osgEarthFeatures/BuildTextOperator.cpp
+++ b/src/osgEarthFeatures/BuildTextOperator.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/CMakeLists.txt b/src/osgEarthFeatures/CMakeLists.txt
index 70caec3..b84652a 100644
--- a/src/osgEarthFeatures/CMakeLists.txt
+++ b/src/osgEarthFeatures/CMakeLists.txt
@@ -21,7 +21,7 @@ SET(LIB_PUBLIC_HEADERS
     Common
     ConvertTypeFilter
     CropFilter
-    ExtrudeGeometryFilter
+    ExtrudeGeometryFilter    
     Feature
     FeatureCursor
     FeatureDisplayLayout
@@ -64,7 +64,7 @@ ADD_LIBRARY(${LIB_NAME} ${OSGEARTH_USER_DEFINED_DYNAMIC_OR_STATIC}
     CentroidFilter.cpp
     ConvertTypeFilter.cpp
     CropFilter.cpp
-    ExtrudeGeometryFilter.cpp
+    ExtrudeGeometryFilter.cpp    
     Feature.cpp
     FeatureCursor.cpp
     FeatureDisplayLayout.cpp
diff --git a/src/osgEarthFeatures/CentroidFilter b/src/osgEarthFeatures/CentroidFilter
index 9911aff..3f31f34 100644
--- a/src/osgEarthFeatures/CentroidFilter
+++ b/src/osgEarthFeatures/CentroidFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/CentroidFilter.cpp b/src/osgEarthFeatures/CentroidFilter.cpp
index 5ea9214..d2bb9b6 100644
--- a/src/osgEarthFeatures/CentroidFilter.cpp
+++ b/src/osgEarthFeatures/CentroidFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/Common b/src/osgEarthFeatures/Common
index f7b75ad..7820cb9 100644
--- a/src/osgEarthFeatures/Common
+++ b/src/osgEarthFeatures/Common
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/ConvertTypeFilter b/src/osgEarthFeatures/ConvertTypeFilter
index 329dde5..03efeb6 100644
--- a/src/osgEarthFeatures/ConvertTypeFilter
+++ b/src/osgEarthFeatures/ConvertTypeFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/ConvertTypeFilter.cpp b/src/osgEarthFeatures/ConvertTypeFilter.cpp
index 2c0147f..6853a64 100644
--- a/src/osgEarthFeatures/ConvertTypeFilter.cpp
+++ b/src/osgEarthFeatures/ConvertTypeFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/CropFilter b/src/osgEarthFeatures/CropFilter
index 62e8029..ce51978 100644
--- a/src/osgEarthFeatures/CropFilter
+++ b/src/osgEarthFeatures/CropFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/CropFilter.cpp b/src/osgEarthFeatures/CropFilter.cpp
index fe24d66..7d1658b 100644
--- a/src/osgEarthFeatures/CropFilter.cpp
+++ b/src/osgEarthFeatures/CropFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/ExtrudeGeometryFilter b/src/osgEarthFeatures/ExtrudeGeometryFilter
index bbe1d54..98cd059 100644
--- a/src/osgEarthFeatures/ExtrudeGeometryFilter
+++ b/src/osgEarthFeatures/ExtrudeGeometryFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -26,6 +26,8 @@
 #include <osgEarthSymbology/Expression>
 #include <osgEarthSymbology/Style>
 #include <osg/Geode>
+#include <vector>
+#include <list>
 
 namespace osgEarth { namespace Features 
 {
@@ -88,9 +90,63 @@ namespace osgEarth { namespace Features
         optional<bool>& useVertexBufferObjects() { return _useVertexBufferObjects;}
         const optional<bool>& useVertexBufferObjects() const { return _useVertexBufferObjects;}
 
+        /**
+         * Whether or not to use TextureArrays for the wall and roof skins
+         */
+        optional<bool>& useTextureArrays() { return _useTextureArrays;}
+        const optional<bool>& useTextureArrays() const { return _useTextureArrays;}
+
 
     protected:
 
+        // A Corner is one vertex in the source geometry, extrude from base to roof.
+        struct Corner
+        {
+            osg::Vec3d base, roof;
+            float      roofTexU, roofTexV;
+            double     offsetX;
+            float      wallTexHeightAdjusted;
+            bool       isFromSource;
+            float      cosAngle;
+        };
+        typedef std::list<Corner> Corners; // use a list to prevent iterator invalidation
+
+        // A Face joins to Corners.
+        struct Face
+        {
+            Corner left;
+            Corner right;
+            double widthM;
+        };
+        typedef std::vector<Face> Faces;
+        
+        // An Elevation is a series of related Faces.
+        struct Elevation
+        {
+            Faces  faces;
+            double texHeightAdjustedM;
+
+            unsigned getNumPoints() const {
+                return faces.size() * 6;
+            }
+        };
+        typedef std::vector<Elevation> Elevations;
+
+        // A Structure is a collection of related Elevations.
+        struct Structure
+        {
+            Elevations elevations;
+            bool       isPolygon;
+
+            unsigned getNumPoints() const {
+                unsigned c = 0;
+                for(Elevations::const_iterator e = elevations.begin(); e != elevations.end(); ++e ) {
+                    c += e->getNumPoints();
+                }
+                return c;
+            }
+        };
+
         // a set of geodes indexed by stateset pointer, for pre-sorting geodes based on 
         // their texture usage
         typedef std::map<osg::StateSet*, osg::ref_ptr<osg::Geode> > SortedGeodeMap;
@@ -110,6 +166,7 @@ namespace osgEarth { namespace Features
 
         Style                          _style;
         bool                           _styleDirty;
+        optional<bool>                 _useTextureArrays;
 
         osg::ref_ptr<const ExtrusionSymbol> _extrusionSymbol;
         osg::ref_ptr<const SkinSymbol>      _wallSkinSymbol;
@@ -132,7 +189,33 @@ namespace osgEarth { namespace Features
         bool process( 
             FeatureList&     input,
             FilterContext&   context );
-
+        
+        bool buildStructure(const Geometry*         input,
+                            double                  height,
+                            double                  heightOffset,
+                            bool                    flatten,
+                            const SkinResource*     wallSkin,
+                            const SkinResource*     roofSkin,
+                            Structure&              out_structure,
+                            FilterContext&          cx );
+
+        bool buildWallGeometry(const Structure&     structure,
+                               osg::Geometry*       walls,
+                               const osg::Vec4&     wallColor,
+                               const osg::Vec4&     wallBaseColor,
+                               const SkinResource*  wallSkin);
+
+        bool buildRoofGeometry(const Structure&     structure,
+                               osg::Geometry*       roof,
+                               const osg::Vec4&     roofColor,
+                               const SkinResource*  roofSkin);
+
+        bool buildOutlineGeometry(const Structure&  structure,
+                                  osg::Geometry*    outline,
+                                  const osg::Vec4&  outlineColor,
+                                  float             minCreaseAngleDeg);
+
+#if 0
         bool extrudeGeometry(
             const Geometry*      input,
             double               height,
@@ -147,8 +230,11 @@ namespace osgEarth { namespace Features
             const osg::Vec4&     roofColor,
             const osg::Vec4&     outlineColor,
             const SkinResource*  wallSkin,
+            int wallLayer,
             const SkinResource*  roofSkin,
+            int roofLayer,
             FilterContext&       cx );
+#endif
     };
 
 } } // namespace osgEarth::Features
diff --git a/src/osgEarthFeatures/ExtrudeGeometryFilter.cpp b/src/osgEarthFeatures/ExtrudeGeometryFilter.cpp
index 3bb2644..fa26581 100644
--- a/src/osgEarthFeatures/ExtrudeGeometryFilter.cpp
+++ b/src/osgEarthFeatures/ExtrudeGeometryFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -17,9 +17,11 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarthFeatures/ExtrudeGeometryFilter>
+#include <osgEarthFeatures/Session>
 #include <osgEarthFeatures/FeatureSourceIndexNode>
 #include <osgEarthSymbology/MeshSubdivider>
 #include <osgEarthSymbology/MeshConsolidator>
+#include <osgEarthSymbology/ResourceCache>
 #include <osgEarth/ECEF>
 #include <osgEarth/ImageUtils>
 #include <osgEarth/Registry>
@@ -77,7 +79,8 @@ _mergeGeometry      ( true ),
 _wallAngleThresh_deg( 60.0 ),
 _styleDirty         ( true ),
 _makeStencilVolume  ( false ),
-_useVertexBufferObjects( true )
+_useVertexBufferObjects( true ),
+_useTextureArrays( true )
 {
     //NOP
 }
@@ -179,175 +182,30 @@ ExtrudeGeometryFilter::reset( const FilterContext& context )
 }
 
 bool
-ExtrudeGeometryFilter::extrudeGeometry(const Geometry*         input,
-                                       double                  height,
-                                       double                  heightOffset,
-                                       bool                    flatten,
-                                       osg::Geometry*          walls,
-                                       osg::Geometry*          roof,
-                                       osg::Geometry*          base,
-                                       osg::Geometry*          outline,
-                                       const osg::Vec4&        wallColor,
-                                       const osg::Vec4&        wallBaseColor,
-                                       const osg::Vec4&        roofColor,
-                                       const osg::Vec4&        outlineColor,
-                                       const SkinResource*     wallSkin,
-                                       const SkinResource*     roofSkin,
-                                       FilterContext&          cx )
+ExtrudeGeometryFilter::buildStructure(const Geometry*         input,
+                                      double                  height,
+                                      double                  heightOffset,
+                                      bool                    flatten,
+                                      const SkinResource*     wallSkin,
+                                      const SkinResource*     roofSkin,
+                                      Structure&              structure,
+                                      FilterContext&          cx )
 {
-    bool makeECEF = false;
-    const SpatialReference* srs = 0L;
+    bool  makeECEF                 = false;
+    const SpatialReference* srs    = 0L;
     const SpatialReference* mapSRS = 0L;
 
     if ( cx.isGeoreferenced() )
     {
-       srs = cx.extent()->getSRS();
+       srs      = cx.extent()->getSRS();
        makeECEF = cx.getSession()->getMapInfo().isGeocentric();
-       mapSRS = cx.getSession()->getMapInfo().getProfile()->getSRS();
+       mapSRS   = cx.getSession()->getMapInfo().getProfile()->getSRS();
     }
 
-    bool made_geom = false;
-
-    double tex_width_m   = wallSkin ? *wallSkin->imageWidth() : 1.0;
-    double tex_height_m  = wallSkin ? *wallSkin->imageHeight() : 1.0;
-    bool   tex_repeats_y = wallSkin ? *wallSkin->isTiled() : false;
-    bool   useColor      = (!wallSkin || wallSkin->texEnvMode() != osg::TexEnv::DECAL) && !_makeStencilVolume;
-
-    bool isPolygon = input->getComponentType() == Geometry::TYPE_POLYGON;
-
-    unsigned pointCount = input->getTotalPointCount();
-    
-    // If we are extruding a polygon, and applying a wall texture, we need an extra
-    // point in the geometry in order to close the polygon and generate a unique
-    // texture coordinate for that final point.
-    bool isSkinnedPolygon = isPolygon && wallSkin != 0L;
-
-    // Total number of verts. Add 2 to close a polygon (necessary so the first and last
-    // points can have unique texture coordinates)
-    unsigned numWallVerts = 2 * pointCount + (isSkinnedPolygon? (2 * input->getNumGeometries()) : 0);
-
-    // create all the OSG geometry components
-    osg::Vec3Array* verts = new osg::Vec3Array( numWallVerts );
-    walls->setVertexArray( verts );
-
-    osg::Vec2Array* wallTexcoords = 0L;
-    if ( wallSkin )
-    { 
-        wallTexcoords = new osg::Vec2Array( numWallVerts );
-        walls->setTexCoordArray( 0, wallTexcoords );
-    }
-
-    osg::Vec4Array* colors = 0L;
-    if ( useColor )
-    {
-        // per-vertex colors are necessary if we are going to use the MeshConsolidator -gw
-        colors = new osg::Vec4Array();
-        colors->reserve( numWallVerts );
-        colors->assign( numWallVerts, wallColor );
-        walls->setColorArray( colors );
-        walls->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
-    }
-
-    // set up rooftop tessellation and texturing, if necessary:
-    osg::Vec3Array* roofVerts     = 0L;
-    osg::Vec2Array* roofTexcoords = 0L;
-    float           roofRotation  = 0.0f;
-    Bounds          roofBounds;
-    float           sinR = 0.0f, cosR = 0.0f;
-    double          roofTexSpanX = 0.0, roofTexSpanY = 0.0;
-    osg::ref_ptr<const SpatialReference> roofProjSRS;
-
-    if ( roof )
-    {
-        roofVerts = new osg::Vec3Array( pointCount );
-        roof->setVertexArray( roofVerts );
-
-        // per-vertex colors are necessary if we are going to use the MeshConsolidator -gw
-        if ( useColor )
-        {
-            osg::Vec4Array* roofColors = new osg::Vec4Array();
-            roofColors->reserve( pointCount );
-            roofColors->assign( pointCount, roofColor );
-            roof->setColorArray( roofColors );
-            roof->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
-        }
-
-        if ( roofSkin )
-        {
-            roofTexcoords = new osg::Vec2Array( pointCount );
-            roof->setTexCoordArray( 0, roofTexcoords );
-
-            roofBounds = input->getBounds();
-
-            // if our data is lat/long, we need to reproject the geometry and the bounds into a projected
-            // coordinate system in order to properly generate tex coords.
-            if ( srs && srs->isGeographic() )
-            {
-                osg::Vec2d geogCenter = roofBounds.center2d();
-                roofProjSRS = srs->createUTMFromLonLat( Angular(geogCenter.x()), Angular(geogCenter.y()) );
-                if ( roofProjSRS.valid() )
-                {
-                    roofBounds.transform( srs, roofProjSRS.get() );
-                    osg::ref_ptr<Geometry> projectedInput = input->clone();
-                    srs->transform( projectedInput->asVector(), roofProjSRS.get() );
-                    roofRotation = getApparentRotation( projectedInput.get() );
-                }
-            }
-            else
-            {
-                roofRotation = getApparentRotation( input );
-            }
-            
-            sinR = sin(roofRotation);
-            cosR = cos(roofRotation);
-
-            if ( !roofSkin->isTiled().value() )
-            {
-                //note: doesn't really work
-                roofTexSpanX = cosR*roofBounds.width() - sinR*roofBounds.height();
-                roofTexSpanY = sinR*roofBounds.width() + cosR*roofBounds.height();
-            }
-            else
-            {
-                roofTexSpanX = roofSkin->imageWidth().isSet() ? *roofSkin->imageWidth() : roofSkin->imageHeight().isSet() ? *roofSkin->imageHeight() : 10.0;
-                if ( roofTexSpanX <= 0.0 ) roofTexSpanX = 10.0;
-                roofTexSpanY = roofSkin->imageHeight().isSet() ? *roofSkin->imageHeight() : roofSkin->imageWidth().isSet() ? *roofSkin->imageWidth() : 10.0;
-                if ( roofTexSpanY <= 0.0 ) roofTexSpanY = 10.0;
-            }
-        }
-    }
-
-    osg::Vec3Array* baseVerts = NULL;
-    if ( base )
-    {
-        baseVerts = new osg::Vec3Array( pointCount );
-        base->setVertexArray( baseVerts );
-    }
-
-    osg::Vec3Array* outlineVerts = 0L;
-    osg::Vec3Array* outlineNormals = 0L;
-    if ( outline )
-    {
-        outlineVerts = new osg::Vec3Array( numWallVerts );
-        outline->setVertexArray( outlineVerts );
-
-        osg::Vec4Array* outlineColors = new osg::Vec4Array();
-        outlineColors->reserve( numWallVerts );
-        outlineColors->assign( numWallVerts, outlineColor );
-        outline->setColorArray( outlineColors );
-        outline->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
-
-        // cop out, just point all the outline normals up. fix this later.
-        outlineNormals = new osg::Vec3Array();
-        outlineNormals->reserve( numWallVerts );
-        outlineNormals->assign( numWallVerts, osg::Vec3(0,0,1) );
-        outline->setNormalArray( outlineNormals );
-    }
-
-    unsigned wallVertPtr    = 0;
-    unsigned roofVertPtr    = 0;
-    unsigned baseVertPtr    = 0;
+    // whether this is a closed polygon structure.
+    structure.isPolygon = (input->getComponentType() == Geometry::TYPE_POLYGON);
 
+    // extrusion working variables
     double     targetLen = -DBL_MAX;
     osg::Vec3d minLoc(DBL_MAX, DBL_MAX, DBL_MAX);
     double     minLoc_len = DBL_MAX;
@@ -357,7 +215,6 @@ ExtrudeGeometryFilter::extrudeGeometry(const Geometry*         input,
     // Initial pass over the geometry does two things:
     // 1: Calculate the minimum Z across all parts.
     // 2: Establish a "target length" for extrusion
-
     double absHeight = fabs(height);
 
     ConstGeometryIterator zfinder( input );
@@ -382,54 +239,102 @@ ExtrudeGeometryFilter::extrudeGeometry(const Geometry*         input,
     // apply the height offsets
     height    -= heightOffset;
     targetLen -= heightOffset;
+    
+    float   roofRotation  = 0.0f;
+    Bounds  roofBounds;
+    float   sinR = 0.0f, cosR = 0.0f;
+    double  roofTexSpanX = 0.0, roofTexSpanY = 0.0;
+    osg::ref_ptr<const SpatialReference> roofProjSRS;
+
+    if ( roofSkin )
+    {
+        roofBounds = input->getBounds();
+
+        // if our data is lat/long, we need to reproject the geometry and the bounds into a projected
+        // coordinate system in order to properly generate tex coords.
+        if ( srs && srs->isGeographic() )
+        {
+            osg::Vec2d geogCenter = roofBounds.center2d();
+            roofProjSRS = srs->createUTMFromLonLat( Angle(geogCenter.x()), Angle(geogCenter.y()) );
+            if ( roofProjSRS.valid() )
+            {
+                roofBounds.transform( srs, roofProjSRS.get() );
+                osg::ref_ptr<Geometry> projectedInput = input->clone();
+                srs->transform( projectedInput->asVector(), roofProjSRS.get() );
+                roofRotation = getApparentRotation( projectedInput.get() );
+            }
+        }
+        else
+        {
+            roofRotation = getApparentRotation( input );
+        }
+            
+        sinR = sin(roofRotation);
+        cosR = cos(roofRotation);
+
+        if ( !roofSkin->isTiled().value() )
+        {
+            //note: non-tiled roofs don't really work atm.
+            roofTexSpanX = cosR*roofBounds.width() - sinR*roofBounds.height();
+            roofTexSpanY = sinR*roofBounds.width() + cosR*roofBounds.height();
+        }
+        else
+        {
+            roofTexSpanX = roofSkin->imageWidth().isSet() ? *roofSkin->imageWidth() : roofSkin->imageHeight().isSet() ? *roofSkin->imageHeight() : 10.0;
+            if ( roofTexSpanX <= 0.0 ) roofTexSpanX = 10.0;
+            roofTexSpanY = roofSkin->imageHeight().isSet() ? *roofSkin->imageHeight() : roofSkin->imageWidth().isSet() ? *roofSkin->imageWidth() : 10.0;
+            if ( roofTexSpanY <= 0.0 ) roofTexSpanY = 10.0;
+        }
+    }
+
+    // prep for wall texture coordinate generation.
+    double texWidthM  = wallSkin ? *wallSkin->imageWidth() : 0.0;
+    double texHeightM = wallSkin ? *wallSkin->imageHeight() : 1.0;
 
-    // now generate the extruded geometry.
     ConstGeometryIterator iter( input );
     while( iter.hasMore() )
     {
         const Geometry* part = iter.next();
 
-        double tex_height_m_adj = tex_height_m;
+        // skip a part that's too small
+        if (part->size() < 2)
+            continue;
 
-        unsigned wallPartPtr = wallVertPtr;
-        unsigned roofPartPtr = roofVertPtr;
-        unsigned basePartPtr = baseVertPtr;
-        double   partLen     = 0.0;
-        double   maxHeight   = 0.0;
+        // add a new wall.
+        structure.elevations.push_back(Elevation());
+        Elevation& elevation = structure.elevations.back();
 
-        maxHeight = targetLen - minLoc.z();
+        double maxHeight = targetLen - minLoc.z();
 
         // Adjust the texture height so it is a multiple of the maximum height
-        double div = osg::round(maxHeight / tex_height_m);
-        if (div == 0) div = 1; //Prevent divide by zero
-        tex_height_m_adj = maxHeight / div;
-
-        //osg::DrawElementsUShort* idx = new osg::DrawElementsUShort( GL_TRIANGLES );
-        osg::DrawElementsUInt* idx = new osg::DrawElementsUInt( GL_TRIANGLES );
+        double div = osg::round(maxHeight / texHeightM);
+        elevation.texHeightAdjustedM = div > 0.0 ? maxHeight / div : maxHeight;
 
-        for( Geometry::const_iterator m = part->begin(); m != part->end(); ++m )
+        // Step 1 - Create the real corners and transform them into our target SRS.
+        Corners corners;
+        for(Geometry::const_iterator m = part->begin(); m != part->end(); ++m)
         {
-            osg::Vec3d basePt = *m;
-            osg::Vec3d roofPt;
+            Corners::iterator corner = corners.insert(corners.end(), Corner());
+            
+            // mark as "from source", as opposed to being inserted by the algorithm.
+            corner->isFromSource = true;
+            corner->base = *m;
 
-            if ( height >= 0 )
+            // extrude:
+            if ( height >= 0 ) // extrude up
             {
                 if ( flatten )
-                    roofPt.set( basePt.x(), basePt.y(), targetLen );
+                    corner->roof.set( corner->base.x(), corner->base.y(), targetLen );
                 else
-                    roofPt.set( basePt.x(), basePt.y(), basePt.z() + height );
+                    corner->roof.set( corner->base.x(), corner->base.y(), corner->base.z() + height );
             }
-            else // height < 0
+            else // height < 0 .. extrude down
             {
-                roofPt = *m;
-                basePt.z() += height;
+                corner->roof = *m;
+                corner->base.z() += height;
             }
-
-            // add to the approprate vertex lists:
-            int p = wallVertPtr;
-
-            // figure out the rooftop texture coordinates before doing any
-            // transformations:
+            
+            // figure out the rooftop texture coords before doing any transformation:
             if ( roofSkin && srs )
             {
                 double xr, yr;
@@ -437,177 +342,435 @@ ExtrudeGeometryFilter::extrudeGeometry(const Geometry*         input,
                 if ( srs && srs->isGeographic() && roofProjSRS )
                 {
                     osg::Vec3d projRoofPt;
-                    srs->transform( roofPt, roofProjSRS.get(), projRoofPt );
+                    srs->transform( corner->roof, roofProjSRS.get(), projRoofPt );
                     xr = (projRoofPt.x() - roofBounds.xMin());
                     yr = (projRoofPt.y() - roofBounds.yMin());
                 }
                 else
                 {
-                    xr = (roofPt.x() - roofBounds.xMin());
-                    yr = (roofPt.y() - roofBounds.yMin());
+                    xr = (corner->roof.x() - roofBounds.xMin());
+                    yr = (corner->roof.y() - roofBounds.yMin());
                 }
 
-                float u = (cosR*xr - sinR*yr) / roofTexSpanX;
-                float v = (sinR*xr + cosR*yr) / roofTexSpanY;
-
-                (*roofTexcoords)[roofVertPtr].set( u, v );
+                corner->roofTexU = (cosR*xr - sinR*yr) / roofTexSpanX;
+                corner->roofTexV = (sinR*xr + cosR*yr) / roofTexSpanY;
             }
+            
+            // transform into target SRS.
+            transformAndLocalize( corner->base, srs, corner->base, mapSRS, _world2local, makeECEF );
+            transformAndLocalize( corner->roof, srs, corner->roof, mapSRS, _world2local, makeECEF );
+        }
 
-            transformAndLocalize( basePt, srs, basePt, mapSRS, _world2local, makeECEF );
-            transformAndLocalize( roofPt, srs, roofPt, mapSRS, _world2local, makeECEF );
+        // Step 2 - Insert intermediate Corners as needed to satify texturing
+        // requirements (if necessary) and record each corner offset (horizontal distance
+        // from the beginning of the part geometry to the corner.)
+        double cornerOffset    = 0.0;
+        double nextTexBoundary = texWidthM;
 
+        for(Corners::iterator c = corners.begin(); c != corners.end(); ++c)
+        {
+            Corners::iterator this_corner = c;
 
-            if ( base )
-            {
-                (*baseVerts)[baseVertPtr] = basePt;
-            }
+            Corners::iterator next_corner = c;
+			bool isLastEdge = false;
+			if ( ++next_corner == corners.end() )
+			{
+				isLastEdge = true;
+				next_corner = corners.begin();
+			}
+
+            osg::Vec3d base_vec = next_corner->base - this_corner->base;
+            double span = base_vec.length();
+
+            this_corner->offsetX = cornerOffset;
 
-            if ( roof )
+            if (wallSkin)
             {
-                (*roofVerts)[roofVertPtr] = roofPt;
+                base_vec /= span; // normalize
+                osg::Vec3d roof_vec = next_corner->roof - this_corner->roof;
+                roof_vec.normalize();
+
+                while(nextTexBoundary < cornerOffset+span)
+                {
+                    // insert a new fake corner.
+					Corners::iterator new_corner;
+
+                    if ( isLastEdge )
+                    {
+						corners.push_back(Corner());
+						new_corner = c;
+						new_corner++;
+                    }
+                    else
+                    {
+						new_corner = corners.insert(next_corner, Corner());
+					}
+
+                    new_corner->isFromSource = false;
+                    double advance = nextTexBoundary-cornerOffset;
+                    new_corner->base = this_corner->base + base_vec*advance;
+                    new_corner->roof = this_corner->roof + roof_vec*advance;
+                    new_corner->offsetX = cornerOffset + advance;
+                    nextTexBoundary += texWidthM;
+
+                    // advance the main iterator
+                    c = new_corner;
+                }
             }
 
-            baseVertPtr++;
-            roofVertPtr++;
+            cornerOffset += span;
+        }
 
-            (*verts)[p] = roofPt;
-            (*verts)[p+1] = basePt;
+        // Step 3 - Calculate the angle of each corner.
+        osg::Vec3d prev_vec;
+        for(Corners::iterator c = corners.begin(); c != corners.end(); ++c)
+        {
+            Corners::const_iterator this_corner = c;
 
-            if ( useColor )
-            {
-                (*colors)[p+1] = wallBaseColor;
-            }
+            Corners::const_iterator next_corner = c;
+            if ( ++next_corner == corners.end() )
+                next_corner = corners.begin();
 
-            if ( outline )
+            if ( this_corner == corners.begin() )
             {
-                (*outlineVerts)[p] = roofPt;
-                (*outlineVerts)[p+1] = basePt;
+                Corners::const_iterator prev_corner = corners.end();
+                --prev_corner;
+                prev_vec = this_corner->roof - prev_corner->roof;
+                prev_vec.normalize();
             }
-            
-            partLen += wallVertPtr > wallPartPtr ? ((*verts)[p] - (*verts)[p-2]).length() : 0.0;
-            double h = tex_repeats_y ? -((*verts)[p] - (*verts)[p+1]).length() : -tex_height_m_adj;
 
-            if ( wallSkin )
+            osg::Vec3d this_vec = next_corner->roof - this_corner->roof;
+            this_vec.normalize();
+            if ( c != corners.begin() )
             {
-                (*wallTexcoords)[p].set( partLen/tex_width_m, 0.0f );
-                (*wallTexcoords)[p+1].set( partLen/tex_width_m, h/tex_height_m_adj );
+                c->cosAngle = prev_vec * this_vec;
             }
+        }
 
-            // form the 2 triangles
-            if ( (m+1) == part->end() )
+        // Step 4 - Create faces connecting each pair of Posts.
+        Faces& faces = elevation.faces;
+        for(Corners::const_iterator c = corners.begin(); c != corners.end(); ++c)
+        {
+            Corners::const_iterator this_corner = c;
+
+            Corners::const_iterator next_corner = c;
+            if ( ++next_corner == corners.end() )
+                next_corner = corners.begin();
+            
+            // only close the shape for polygons.
+            if (next_corner != corners.begin() || structure.isPolygon)
             {
-                if ( isPolygon )
-                {
-                    // end of the wall; loop around to close it off.
-                    if ( isSkinnedPolygon )
-                    {
-                        // if we requested an extra geometry point, that means we are generating
-                        // a polygon-closing line so we can have a unique texcoord for it. 
-                        idx->push_back(wallVertPtr);
-                        idx->push_back(wallVertPtr+1);
-                        idx->push_back(wallVertPtr+2);
-
-                        idx->push_back(wallVertPtr+1);
-                        idx->push_back(wallVertPtr+3);
-                        idx->push_back(wallVertPtr+2);
-
-                        (*verts)[p+2] = (*verts)[wallPartPtr];
-                        (*verts)[p+3] = (*verts)[wallPartPtr+1];
-
-                        if ( wallSkin )
-                        {
-                            partLen += ((*verts)[p+2] - (*verts)[p]).length();
-                            double h = tex_repeats_y ? -((*verts)[p+2] - (*verts)[p+3]).length() : -tex_height_m_adj;
-                            (*wallTexcoords)[p+2].set( partLen/tex_width_m, 0.0f );
-                            (*wallTexcoords)[p+3].set( partLen/tex_width_m, h/tex_height_m_adj );
-                        }
-
-                        wallVertPtr += 2;
-                    }
-                    else
-                    {
-                        // either not a poly, or no wall skin, so we can share the polygon-closing
-                        // loop point.
-                        idx->push_back(wallVertPtr); 
-                        idx->push_back(wallVertPtr+1);
-                        idx->push_back(wallPartPtr);
-
-                        idx->push_back(wallVertPtr+1);
-                        idx->push_back(wallPartPtr+1);
-                        idx->push_back(wallPartPtr);
-                    }
-                }
-                else
+                faces.push_back(Face());
+                Face& face = faces.back();
+                face.left  = *this_corner;
+                face.right = *next_corner;
+
+                // recalculate the final offset on the last face
+                if ( next_corner == corners.begin() )
                 {
-                    //nop - no elements required at the end of a line
+                    osg::Vec3d vec = next_corner->roof - this_corner->roof;
+                    face.right.offsetX = face.left.offsetX + vec.length();
                 }
+
+                face.widthM = next_corner->offsetX - this_corner->offsetX;
             }
-            else
+        }
+    }
+
+    return true;
+}
+
+
+bool
+ExtrudeGeometryFilter::buildWallGeometry(const Structure&     structure,
+                                         osg::Geometry*       walls,
+                                         const osg::Vec4&     wallColor,
+                                         const osg::Vec4&     wallBaseColor,
+                                         const SkinResource*  wallSkin)
+{
+    bool madeGeom = true;
+
+    // 6 verts per face total (3 triangles)
+    unsigned numWallVerts = 6 * structure.getNumPoints();
+
+    double texWidthM   = wallSkin ? *wallSkin->imageWidth()  : 1.0;
+    double texHeightM  = wallSkin ? *wallSkin->imageHeight() : 1.0;
+    bool   useColor    = (!wallSkin || wallSkin->texEnvMode() != osg::TexEnv::DECAL) && !_makeStencilVolume;
+    
+    // Scale and bias:
+    osg::Vec2f scale, bias;
+    float layer;
+    if ( wallSkin )
+    {
+        bias.set (wallSkin->imageBiasS().get(),  wallSkin->imageBiasT().get());
+        scale.set(wallSkin->imageScaleS().get(), wallSkin->imageScaleT().get());
+        layer = (float)wallSkin->imageLayer().get();
+    }
+
+    // create all the OSG geometry components
+    osg::Vec3Array* verts = new osg::Vec3Array( numWallVerts );
+    walls->setVertexArray( verts );
+    
+    osg::Vec3Array* tex = 0L;
+    if ( wallSkin )
+    { 
+        tex = new osg::Vec3Array( numWallVerts );
+        walls->setTexCoordArray( 0, tex );
+    }
+
+    osg::Vec4Array* colors = 0L;
+    if ( useColor )
+    {
+        // per-vertex colors are necessary if we are going to use the MeshConsolidator -gw
+        colors = new osg::Vec4Array( numWallVerts );
+        walls->setColorArray( colors );
+        walls->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
+    }
+
+    unsigned vertptr = 0;
+    bool     tex_repeats_y = wallSkin && wallSkin->isTiled() == true;
+
+    for(Elevations::const_iterator elev = structure.elevations.begin(); elev != structure.elevations.end(); ++elev)
+    {
+        osg::DrawElements* de = 
+            numWallVerts > 0xFFFF ? (osg::DrawElements*) new osg::DrawElementsUInt  ( GL_TRIANGLES ) :
+            numWallVerts > 0xFF   ? (osg::DrawElements*) new osg::DrawElementsUShort( GL_TRIANGLES ) :
+                                    (osg::DrawElements*) new osg::DrawElementsUByte ( GL_TRIANGLES );
+
+        // pre-allocate for speed
+        de->reserveElements( numWallVerts );
+
+        walls->addPrimitiveSet( de );
+
+        for(Faces::const_iterator f = elev->faces.begin(); f != elev->faces.end(); ++f, vertptr+=6)
+        {
+            // set the 6 wall verts.
+            (*verts)[vertptr+0] = f->left.roof;
+            (*verts)[vertptr+1] = f->left.base;
+            (*verts)[vertptr+2] = f->right.base;
+            (*verts)[vertptr+3] = f->right.base;
+            (*verts)[vertptr+4] = f->right.roof;
+            (*verts)[vertptr+5] = f->left.roof;
+
+            // Assign wall polygon colors.
+            if (useColor)
             {
-                idx->push_back(wallVertPtr); 
-                idx->push_back(wallVertPtr+1);
-                idx->push_back(wallVertPtr+2); 
+#if 0
+                // experimental: apply some ambient occlusion to tight inside corners                
+                float bL = f->left.cosAngle > 0.0 ? 1.0 : (1.0+f->left.cosAngle);
+                float bR = f->right.cosAngle > 0.0 ? 1.0 : (1.0+f->right.cosAngle);
+
+                osg::Vec4f leftColor      = Color(wallColor).brightness(bL);
+                osg::Vec4f leftBaseColor  = Color(wallBaseColor).brightness(bL);
+                osg::Vec4f rightColor     = Color(wallColor).brightness(bR);
+                osg::Vec4f rightBaseColor = Color(wallBaseColor).brightness(bR);
+
+                (*colors)[vertptr+0] = leftColor;
+                (*colors)[vertptr+1] = leftBaseColor;
+                (*colors)[vertptr+2] = rightBaseColor;
+                (*colors)[vertptr+3] = rightBaseColor;
+                (*colors)[vertptr+4] = rightColor;
+                (*colors)[vertptr+5] = leftColor;
+#else
+                (*colors)[vertptr+0] = wallColor;
+                (*colors)[vertptr+1] = wallBaseColor;
+                (*colors)[vertptr+2] = wallBaseColor;
+                (*colors)[vertptr+3] = wallBaseColor;
+                (*colors)[vertptr+4] = wallColor;
+                (*colors)[vertptr+5] = wallColor;
+#endif
+            }
 
-                idx->push_back(wallVertPtr+1);
-                idx->push_back(wallVertPtr+3);
-                idx->push_back(wallVertPtr+2);
+            // Calculate texture coordinates:
+            if (wallSkin)
+            {
+                // Calculate left and right corner V coordinates:
+                double hL = tex_repeats_y ? (f->left.roof - f->left.base).length()   : elev->texHeightAdjustedM;
+                double hR = tex_repeats_y ? (f->right.roof - f->right.base).length() : elev->texHeightAdjustedM;
+                
+                // Calculate the texture coordinates at each corner. The structure builder
+                // will have spaced the verts correctly for this to work.
+                float uL = fmod( f->left.offsetX, texWidthM ) / texWidthM;
+                float uR = fmod( f->right.offsetX, texWidthM ) / texWidthM;
+
+                // Correct for the case in which the rightmost corner is exactly on a
+                // texture boundary.
+                if ( uR < uL || (uL == 0.0 && uR == 0.0))
+                    uR = 1.0f;
+
+                osg::Vec2f texBaseL( uL, 0.0f );
+                osg::Vec2f texBaseR( uR, 0.0f );
+                osg::Vec2f texRoofL( uL, hL/elev->texHeightAdjustedM );
+                osg::Vec2f texRoofR( uR, hR/elev->texHeightAdjustedM );
+
+                texRoofL = bias + osg::componentMultiply(texRoofL, scale);
+                texRoofR = bias + osg::componentMultiply(texRoofR, scale);
+                texBaseL = bias + osg::componentMultiply(texBaseL, scale);
+                texBaseR = bias + osg::componentMultiply(texBaseR, scale);
+
+                (*tex)[vertptr+0].set( texRoofL.x(), texRoofL.y(), layer );
+                (*tex)[vertptr+1].set( texBaseL.x(), texBaseL.y(), layer );
+                (*tex)[vertptr+2].set( texBaseR.x(), texBaseR.y(), layer );
+                (*tex)[vertptr+3].set( texBaseR.x(), texBaseR.y(), layer );
+                (*tex)[vertptr+4].set( texRoofR.x(), texRoofR.y(), layer );
+                (*tex)[vertptr+5].set( texRoofL.x(), texRoofL.y(), layer );
             }
 
-            wallVertPtr += 2;
-            made_geom = true;
+            for(int i=0; i<6; ++i)
+                de->addElement( vertptr+i );
         }
+    }
+    
+    // generate per-vertex normals, altering the geometry as necessary to avoid
+    // smoothing around sharp corners
 
-        walls->addPrimitiveSet( idx );
+    // TODO: reconsider this, given the new Structure setup
+    // it won't actual smooth corners since we don't have shared edges.
+    osgUtil::SmoothingVisitor::smooth(
+        *walls,
+        osg::DegreesToRadians(_wallAngleThresh_deg) );
+
+    return madeGeom;
+}
 
-        if ( roof )
-        {
-            roof->addPrimitiveSet( new osg::DrawArrays(
-                osg::PrimitiveSet::LINE_LOOP,
-                roofPartPtr, roofVertPtr - roofPartPtr ) );
-        }
 
-        if ( base )
+bool
+ExtrudeGeometryFilter::buildRoofGeometry(const Structure&     structure,
+                                         osg::Geometry*       roof,
+                                         const osg::Vec4&     roofColor,
+                                         const SkinResource*  roofSkin)
+{    
+    osg::Vec3Array* verts = new osg::Vec3Array();
+    roof->setVertexArray( verts );
+
+    osg::Vec4Array* color = new osg::Vec4Array();
+    roof->setColorArray( color );
+    roof->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
+
+    osg::Vec3Array* tex = 0L;
+    if ( roofSkin )
+    {
+        tex = new osg::Vec3Array();
+        roof->setTexCoordArray(0, tex);
+    }
+
+    // Create a series of line loops that the tessellator can reorganize
+    // into polygons.
+    unsigned vertptr = 0;
+    for(Elevations::const_iterator e = structure.elevations.begin(); e != structure.elevations.end(); ++e)
+    {
+        unsigned elevptr = vertptr;
+        for(Faces::const_iterator f = e->faces.begin(); f != e->faces.end(); ++f)
         {
-            // reverse the base verts:
-            int len = baseVertPtr - basePartPtr;
-            for( int i=basePartPtr; i<len/2; i++ )
-                std::swap( (*baseVerts)[i], (*baseVerts)[basePartPtr+(len-1)-i] );
-
-            base->addPrimitiveSet( new osg::DrawArrays(
-                osg::PrimitiveSet::LINE_LOOP,
-                basePartPtr, baseVertPtr - basePartPtr ) );
+            // Only use source verts; we skip interim verts inserted by the 
+            // structure building since they are co-linear anyway and thus we don't
+            // need them for the roof line.
+            if ( f->left.isFromSource )
+            {
+                verts->push_back( f->left.roof );
+                color->push_back( roofColor );
+                if ( tex )
+                {
+                    tex->push_back( osg::Vec3f(f->left.roofTexU, f->left.roofTexV, (float)0.0f) );
+                }
+                ++vertptr;
+            }
         }
+        roof->addPrimitiveSet( new osg::DrawArrays(GL_LINE_LOOP, elevptr, vertptr-elevptr) );
+    }
+    
+
+    osg::Vec3Array* normal = new osg::Vec3Array(verts->size());
+    roof->setNormalArray( normal );
+    roof->setNormalBinding( osg::Geometry::BIND_PER_VERTEX );
+    normal->assign( verts->size(), osg::Vec3(0,0,1) );
+
+    // Tessellate the roof lines into polygons.
+    osgUtil::Tessellator tess;
+    tess.setTessellationType( osgUtil::Tessellator::TESS_TYPE_GEOMETRY );
+    tess.setWindingType( osgUtil::Tessellator::TESS_WINDING_ODD );
+    tess.retessellatePolygons( *roof );
+
+    return true;
+}
+
+
+bool
+ExtrudeGeometryFilter::buildOutlineGeometry(const Structure&  structure,
+                                            osg::Geometry*    outline,
+                                            const osg::Vec4&  outlineColor,
+                                            float             minCreaseAngleDeg)
+{
+    // minimum angle between adjacent faces for which to draw a post.
+    const float cosMinAngle = cos(osg::DegreesToRadians(minCreaseAngleDeg));
 
-        if ( outline )
+    osg::Vec3Array* verts = new osg::Vec3Array();
+    outline->setVertexArray( verts );
+
+    osg::Vec4Array* color = new osg::Vec4Array();
+    outline->setColorArray( color );
+    outline->setColorBinding( osg::Geometry::BIND_OVERALL );
+    color->push_back( outlineColor );
+
+    osg::DrawElements* de = new osg::DrawElementsUInt(GL_LINES);
+    outline->addPrimitiveSet(de);
+
+    unsigned vertptr = 0;
+    for(Elevations::const_iterator e = structure.elevations.begin(); e != structure.elevations.end(); ++e)
+    {
+        osg::Vec3d prev_vec;
+        unsigned elevptr = vertptr;
+        for(Faces::const_iterator f = e->faces.begin(); f != e->faces.end(); ++f)
         {
-            unsigned len = baseVertPtr - basePartPtr;
+            // Only use source verts for posts.
+            bool drawPost     = f->left.isFromSource;
+            bool drawCrossbar = true;
+
+            osg::Vec3d this_vec = f->right.roof - f->left.roof;
+            this_vec.normalize();
+
+            if (f->left.isFromSource && f != e->faces.begin())
+            {
+                drawPost = (this_vec * prev_vec) < cosMinAngle;
+            }
 
-            GLenum roofLineMode = isPolygon ? GL_LINE_LOOP : GL_LINE_STRIP;
-            osg::DrawElementsUInt* roofLine = new osg::DrawElementsUInt( roofLineMode );
-            roofLine->reserveElements( len );
-            for( unsigned i=0; i<len; ++i )
-                roofLine->addElement( basePartPtr + i*2 );
-            outline->addPrimitiveSet( roofLine );
+            if ( drawPost || drawCrossbar )
+            {
+                verts->push_back( f->left.roof );
+            }
 
-            // if the outline is tessellated, we only want outlines on the original 
-            // points (not the inserted points)
-            unsigned step = std::max( 1u, 
-                _outlineSymbol->tessellation().isSet() ? *_outlineSymbol->tessellation() : 1u );
+            if ( drawPost )
+            {
+                verts->push_back( f->left.base );
+                de->addElement(vertptr);
+                de->addElement(verts->size()-1);
+            }
 
-            osg::DrawElementsUInt* wallLines = new osg::DrawElementsUInt( GL_LINES );
-            wallLines->reserve( len*2 );
-            for( unsigned i=0; i<len; i+=step )
+            if ( drawCrossbar )
             {
-                wallLines->push_back( basePartPtr + i*2 );
-                wallLines->push_back( basePartPtr + i*2 + 1 );
+                verts->push_back( f->right.roof );
+
+                de->addElement(vertptr);
+                de->addElement(verts->size()-1);
             }
-            outline->addPrimitiveSet( wallLines );
 
-            applyLineSymbology( outline->getOrCreateStateSet(), _outlineSymbol.get() );
+            vertptr = verts->size();
+
+            prev_vec = this_vec;
+        }
+
+        // Draw an end-post if this isn't a closed polygon.
+        if ( !structure.isPolygon )
+        {
+            Faces::const_iterator last = e->faces.end()-1;
+            verts->push_back( last->right.roof );
+            de->addElement( verts->size()-1 );
+            verts->push_back( last->right.base );
+            de->addElement( verts->size()-1 );
         }
     }
 
-    return made_geom;
+    return true;
 }
 
 void
@@ -651,6 +814,14 @@ ExtrudeGeometryFilter::process( FeatureList& features, FilterContext& context )
     {
         Feature* input = f->get();
 
+        // run a symbol script if present.
+        if ( _extrusionSymbol->script().isSet() )
+        {
+            StringExpression temp( _extrusionSymbol->script().get() );
+            input->eval( temp, &context );
+        }
+
+        // iterator over the parts.
         GeometryIterator iter( input->getGeometry(), false );
         while( iter.hasMore() )
         {
@@ -745,12 +916,29 @@ ExtrudeGeometryFilter::process( FeatureList& features, FilterContext& context )
                 }
             }
 
-            // calculate the colors:
-            osg::Vec4f wallColor(1,1,1,0), wallBaseColor(1,1,1,0), roofColor(1,1,1,0), outlineColor(1,1,1,1);
-
-            if ( _wallPolygonSymbol.valid() )
+            // Build the data model for the structure.
+            Structure structure;
+
+            buildStructure(
+                part, 
+                height, 
+                offset, 
+                _extrusionSymbol->flatten().get(),
+                wallSkin,
+                roofSkin,
+                structure,
+                context);
+
+            // Create the walls.
+            if ( walls.valid() )
             {
-                wallColor = _wallPolygonSymbol->fill()->color();
+                osg::Vec4f wallColor(1,1,1,1), wallBaseColor(1,1,1,1);
+
+                if ( _wallPolygonSymbol.valid() )
+                {
+                    wallColor = _wallPolygonSymbol->fill()->color();
+                }
+
                 if ( _extrusionSymbol->wallGradientPercentage().isSet() )
                 {
                     wallBaseColor = Color(wallColor).brightness( 1.0 - *_extrusionSymbol->wallGradientPercentage() );
@@ -759,86 +947,81 @@ ExtrudeGeometryFilter::process( FeatureList& features, FilterContext& context )
                 {
                     wallBaseColor = wallColor;
                 }
-            }
-            if ( _roofPolygonSymbol.valid() )
-            {
-                roofColor = _roofPolygonSymbol->fill()->color();
-            }
-            if ( _outlineSymbol.valid() )
-            {
-                outlineColor = _outlineSymbol->stroke()->color();
-            }
 
-            // Create the extruded geometry!
-            if (extrudeGeometry( 
-                    part, height, offset, 
-                    *_extrusionSymbol->flatten(),
-                    walls.get(), rooflines.get(), baselines.get(), outlines.get(),
-                    wallColor, wallBaseColor, roofColor, outlineColor,
-                    wallSkin, roofSkin,
-                    context ) )
-            {      
+                buildWallGeometry(structure, walls.get(), wallColor, wallBaseColor, wallSkin);
+
                 if ( wallSkin )
                 {
-                    context.resourceCache()->getStateSet( wallSkin, wallStateSet );
+                    // Get a stateset for the individual wall stateset
+                    context.resourceCache()->getOrCreateStateSet( wallSkin, wallStateSet );
                 }
+            }
 
-                // generate per-vertex normals, altering the geometry as necessary to avoid
-                // smoothing around sharp corners
-                osgUtil::SmoothingVisitor::smooth(
-                    *walls.get(), 
-                    osg::DegreesToRadians(_wallAngleThresh_deg) );
-
-                // tessellate and add the roofs if necessary:
-                if ( rooflines.valid() )
+            // tessellate and add the roofs if necessary:
+            if ( rooflines.valid() )
+            {
+                osg::Vec4f roofColor(1,1,1,1);
+                if ( _roofPolygonSymbol.valid() )
                 {
-                    osgUtil::Tessellator tess;
-                    tess.setTessellationType( osgUtil::Tessellator::TESS_TYPE_GEOMETRY );
-                    tess.setWindingType( osgUtil::Tessellator::TESS_WINDING_ODD );
-                    tess.retessellatePolygons( *(rooflines.get()) );
+                    roofColor = _roofPolygonSymbol->fill()->color();
+                }
 
-                    // generate default normals (no crease angle necessary; they are all pointing up)
-                    // TODO do this manually; probably faster
-                    if ( !_makeStencilVolume )
-                        osgUtil::SmoothingVisitor::smooth( *rooflines.get() );
+                buildRoofGeometry(structure, rooflines.get(), roofColor, roofSkin);
 
-                    if ( roofSkin )
-                    {
-                        context.resourceCache()->getStateSet( roofSkin, roofStateSet );
-                    }
+                if ( roofSkin )
+                {
+                    // Get a stateset for the individual roof skin
+                    context.resourceCache()->getOrCreateStateSet( roofSkin, roofStateSet );
                 }
+            }
 
-                if ( baselines.valid() )
+            if ( outlines.valid() )
+            {
+                osg::Vec4f outlineColor(1,1,1,1);
+                if ( _outlineSymbol.valid() )
                 {
-                    osgUtil::Tessellator tess;
-                    tess.setTessellationType( osgUtil::Tessellator::TESS_TYPE_GEOMETRY );
-                    tess.setWindingType( osgUtil::Tessellator::TESS_WINDING_ODD );
-                    tess.retessellatePolygons( *(baselines.get()) );
+                    outlineColor = _outlineSymbol->stroke()->color();
                 }
 
-                std::string name;
-                if ( !_featureNameExpr.empty() )
-                    name = input->eval( _featureNameExpr, &context );
+                float minCreaseAngle = _outlineSymbol->creaseAngle().value();
+                buildOutlineGeometry(structure, outlines.get(), outlineColor, minCreaseAngle);
+            }
+
+            if ( baselines.valid() )
+            {
+                //TODO.
+                osgUtil::Tessellator tess;
+                tess.setTessellationType( osgUtil::Tessellator::TESS_TYPE_GEOMETRY );
+                tess.setWindingType( osgUtil::Tessellator::TESS_WINDING_ODD );
+                tess.retessellatePolygons( *(baselines.get()) );
+            }
+
+            // Set up for feature naming and feature indexing:
+            std::string name;
+            if ( !_featureNameExpr.empty() )
+                name = input->eval( _featureNameExpr, &context );
 
-                FeatureSourceIndex* index = context.featureIndex();
+            FeatureSourceIndex* index = context.featureIndex();
 
+            if ( walls.valid() )
+            {
                 addDrawable( walls.get(), wallStateSet.get(), name, input, index );
+            }
 
-                if ( rooflines.valid() )
-                {
-                    addDrawable( rooflines.get(), roofStateSet.get(), name, input, index );
-                }
+            if ( rooflines.valid() )
+            {
+                addDrawable( rooflines.get(), roofStateSet.get(), name, input, index );
+            }
 
-                if ( baselines.valid() )
-                {
-                    addDrawable( baselines.get(), 0L, name, input, index );
-                }
+            if ( baselines.valid() )
+            {
+                addDrawable( baselines.get(), 0L, name, input, index );
+            }
 
-                if ( outlines.valid() )
-                {
-                    addDrawable( outlines.get(), 0L, name, input, index );
-                }
-            }   
+            if ( outlines.valid() )
+            {
+                addDrawable( outlines.get(), 0L, name, input, index );
+            }
         }
     }
 
@@ -900,18 +1083,20 @@ ExtrudeGeometryFilter::push( FeatureList& input, FilterContext& context )
     {
         for( SortedGeodeMap::iterator i = _geodes.begin(); i != _geodes.end(); ++i )
         {
-            if ( context.featureIndex() )
+            if ( context.featureIndex() || _outlineSymbol.valid())
             {
                 // The MC will recognize the presence of feature indexing tags and
                 // preserve them. The Cache optimizer however will not, so it is
                 // out for now.
+                // The Optimizer also doesn't work with line geometry, so if we have outlines
+                // then we need to use MC.                
                 MeshConsolidator::run( *i->second.get() );
 
                 //VertexCacheOptimizer vco;
                 //i->second->accept( vco );
             }
             else
-            {
+            {                
                 //TODO: try this -- issues: it won't work on lines, and will it screw up
                 // feature indexing?
                 osgUtil::Optimizer o;
diff --git a/src/osgEarthFeatures/Feature b/src/osgEarthFeatures/Feature
index e8972ef..3353963 100644
--- a/src/osgEarthFeatures/Feature
+++ b/src/osgEarthFeatures/Feature
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -215,7 +215,7 @@ namespace osgEarth { namespace Features
 
     public:
         /** Gets a GeoJSON representation of this Feature */
-        std::string getGeoJSON();
+        std::string getGeoJSON() const;
 
         /** Gets a FeatureList as a GeoJSON FeatureCollection */
         static std::string featuresToGeoJSON( FeatureList& features);
diff --git a/src/osgEarthFeatures/Feature.cpp b/src/osgEarthFeatures/Feature.cpp
index 8fa5c06..8ef3d41 100644
--- a/src/osgEarthFeatures/Feature.cpp
+++ b/src/osgEarthFeatures/Feature.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -319,8 +319,9 @@ Feature::eval( NumericExpression& expr, FilterContext const* context ) const
           ScriptResult result = engine->run(i->first, this, context);
           if (result.success())
             val = result.asDouble();
-          else
-              OE_WARN << LC << "Script error:" << result.message() << std::endl; 
+          else {
+              OE_WARN << LC << "Feature Script error on '" << expr.expr() << "': " << result.message() << std::endl;
+          }
         }
       }
 
@@ -352,7 +353,7 @@ Feature::eval( StringExpression& expr, FilterContext const* context ) const
           if (result.success())
             val = result.asString();
           else
-              OE_WARN << LC << "Script error:" << result.message() << std::endl;
+            OE_WARN << LC << "Feature Script error on '" << expr.expr() << "': " << result.message() << std::endl;
         }
       }
 
@@ -444,7 +445,7 @@ Feature::getWorldBoundingPolytope(const SpatialReference* srs,
 
 
 std::string
-Feature::getGeoJSON()
+Feature::getGeoJSON() const
 {
     std::string geometry = GeometryUtils::geometryToGeoJSON( getGeometry() );
 
@@ -515,7 +516,6 @@ Feature::getGeoJSON()
 
     root["properties"] = props;
     return Json::FastWriter().write( root );
-    //return Json::StyledWriter().write( root );
 }
 
 std::string Feature::featuresToGeoJSON( FeatureList& features)
diff --git a/src/osgEarthFeatures/FeatureCursor b/src/osgEarthFeatures/FeatureCursor
index 7fc0f8c..5acd776 100644
--- a/src/osgEarthFeatures/FeatureCursor
+++ b/src/osgEarthFeatures/FeatureCursor
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/FeatureCursor.cpp b/src/osgEarthFeatures/FeatureCursor.cpp
index a677817..b6c50e6 100644
--- a/src/osgEarthFeatures/FeatureCursor.cpp
+++ b/src/osgEarthFeatures/FeatureCursor.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -86,7 +86,7 @@ GeometryFeatureCursor::nextFeature()
     {        
         _lastFeature = new Feature( _geom.get(), _featureProfile.valid() ? _featureProfile->getSRS() : 0L );
         FilterContext cx;
-        cx.profile() = _featureProfile.get();
+        cx.setProfile( _featureProfile.get() );
         FeatureList list;
         list.push_back( _lastFeature.get() );
         for( FeatureFilterList::const_iterator i = _filters.begin(); i != _filters.end(); ++i ) {
diff --git a/src/osgEarthFeatures/FeatureDisplayLayout b/src/osgEarthFeatures/FeatureDisplayLayout
index 9c14060..4490452 100644
--- a/src/osgEarthFeatures/FeatureDisplayLayout
+++ b/src/osgEarthFeatures/FeatureDisplayLayout
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/FeatureDisplayLayout.cpp b/src/osgEarthFeatures/FeatureDisplayLayout.cpp
index 17c14db..2794a1f 100644
--- a/src/osgEarthFeatures/FeatureDisplayLayout.cpp
+++ b/src/osgEarthFeatures/FeatureDisplayLayout.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/FeatureDrawSet b/src/osgEarthFeatures/FeatureDrawSet
index d4e71d0..c8d1bda 100644
--- a/src/osgEarthFeatures/FeatureDrawSet
+++ b/src/osgEarthFeatures/FeatureDrawSet
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/FeatureDrawSet.cpp b/src/osgEarthFeatures/FeatureDrawSet.cpp
index 458bada..541c499 100644
--- a/src/osgEarthFeatures/FeatureDrawSet.cpp
+++ b/src/osgEarthFeatures/FeatureDrawSet.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/FeatureListSource b/src/osgEarthFeatures/FeatureListSource
index c2e2464..b55a32b 100644
--- a/src/osgEarthFeatures/FeatureListSource
+++ b/src/osgEarthFeatures/FeatureListSource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/FeatureListSource.cpp b/src/osgEarthFeatures/FeatureListSource.cpp
index 50ebab1..dad3abf 100644
--- a/src/osgEarthFeatures/FeatureListSource.cpp
+++ b/src/osgEarthFeatures/FeatureListSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/FeatureModelGraph b/src/osgEarthFeatures/FeatureModelGraph
index 6fafc14..e7b77a4 100644
--- a/src/osgEarthFeatures/FeatureModelGraph
+++ b/src/osgEarthFeatures/FeatureModelGraph
@@ -1,6 +1,6 @@
 /* --*-c++-*-- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -28,6 +28,7 @@
 #include <osgEarth/NodeUtils>
 #include <osgEarth/ThreadingUtils>
 #include <osgEarth/DepthOffset>
+#include <osgDB/Callbacks>
 #include <osg/Node>
 #include <set>
 
@@ -59,11 +60,18 @@ namespace osgEarth { namespace Features
          *      Node factory that will be invoked to compile feature data into nodes
          * @param session
          *      Session under which to create elements in this graph
+         * @param preMergeOperations
+         *      Node operations that will execute in the pager thread after it finishes
+         *      building a node
+         * @param postMergeOperations
+         *      Node operations that will execute after merging a node into the graph
          */
         FeatureModelGraph(
             Session*                         session,
             const FeatureModelSourceOptions& options,
-            FeatureNodeFactory*              factory );
+            FeatureNodeFactory*              factory,
+            RefNodeOperationVector*          preMergeOperations,
+            RefNodeOperationVector*          postMergeOperations);
 
         /**
          * Loads and returns a subnode. Used internally for paging.
@@ -85,7 +93,7 @@ namespace osgEarth { namespace Features
          */
         Session* getSession() { return _session; }
 
-		/**
+        /**
          * UID given to this feature graph when registered with the pseudo-loader
          */
         UID getUID() const { return _uid; }
@@ -96,13 +104,6 @@ namespace osgEarth { namespace Features
         void dirty();
 
         /**
-         * Adds an operation that will run on new nodes that are added by the
-         * paging system. Note, the operation only runs after the pager actually
-         * added the node to the live scene graph.
-         */
-        void addPostMergeOperation( NodeOperation* op );
-
-        /**
          * Access to the features levels
          */
         const std::vector<const FeatureLevel*>& getLevels() const { return _lodmap; };
@@ -197,8 +198,12 @@ namespace osgEarth { namespace Features
         };
         OverlayChange                    _overlayChange;
 
+        osg::ref_ptr<osgDB::FileLocationCallback> _defaultFileLocationCallback;
+
+        osg::ref_ptr<RefNodeOperationVector> _preMergeOperations;
         osg::ref_ptr<RefNodeOperationVector> _postMergeOperations;
 
+        void runPreMergeOperations(osg::Node* node);
         void runPostMergeOperations(osg::Node* node);
         void checkForGlobalStyles(const Style& style);
         void changeOverlay();
diff --git a/src/osgEarthFeatures/FeatureModelGraph.cpp b/src/osgEarthFeatures/FeatureModelGraph.cpp
index 791ce7c..687a256 100644
--- a/src/osgEarthFeatures/FeatureModelGraph.cpp
+++ b/src/osgEarthFeatures/FeatureModelGraph.cpp
@@ -1,6 +1,6 @@
 /* --*-c++-*-- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -20,6 +20,9 @@
 #include <osgEarthFeatures/FeatureModelGraph>
 #include <osgEarthFeatures/CropFilter>
 #include <osgEarthFeatures/FeatureSourceIndexNode>
+#include <osgEarthFeatures/Session>
+
+#include <osgEarth/Map>
 #include <osgEarth/Capabilities>
 #include <osgEarth/ClampableNode>
 #include <osgEarth/CullingUtils>
@@ -39,6 +42,9 @@
 #include <osgDB/WriteFile>
 #include <osgUtil/Optimizer>
 
+#include <algorithm>
+#include <iterator>
+
 #define LC "[FeatureModelGraph] "
 
 using namespace osgEarth;
@@ -49,6 +55,20 @@ using namespace osgEarth::Symbology;
 #define OE_TEST OE_NULL
 //#define OE_TEST OE_NOTICE
 
+namespace
+{
+    // callback to force features onto the high-latency queue.
+    struct HighLatencyFileLocationCallback : public osgDB::FileLocationCallback
+    {
+        Location fileLocation(const std::string& filename, const osgDB::Options* options)
+        {
+            return REMOTE_FILE;
+        }
+
+        bool useFileCache() const { return false; }
+    };
+}
+
 //---------------------------------------------------------------------------
 
 // pseudo-loader for paging in feature tiles for a FeatureModelGraph.
@@ -75,7 +95,8 @@ namespace
                                 float maxRange, 
                                 float priOffset, 
                                 float priScale,
-                                RefNodeOperationVector* postMergeOps)
+                                RefNodeOperationVector* postMergeOps,
+                                osgDB::FileLocationCallback* flc)
     {
 #ifdef USE_PROXY_NODE_FOR_TESTING
         osg::ProxyNode* p = new osg::ProxyNode();
@@ -85,14 +106,18 @@ namespace
 #else
         PagedLODWithNodeOperations* p = new PagedLODWithNodeOperations(postMergeOps);
         p->setCenter( bs.center() );
-        //p->setRadius(std::max((float)bs.radius(),maxRange));
         p->setRadius( bs.radius() );
         p->setFileName( 0, uri );
-        p->setRange( 0, minRange, maxRange );
+        p->setRange( 0, minRange, maxRange + bs.radius() );
         p->setPriorityOffset( 0, priOffset );
         p->setPriorityScale( 0, priScale );
 #endif
 
+        // force onto the high-latency thread pool.
+        osgDB::Options* options = Registry::instance()->cloneOrCreateOptions();
+        options->setFileLocationCallback( flc );
+        p->setDatabaseOptions( options );
+
         return p;
     }
 }
@@ -129,7 +154,10 @@ struct osgEarthFeatureModelPseudoLoader : public osgDB::ReaderWriter
             osg::ref_ptr<const Map> map = graph->getSession()->getMap();
             if (map.valid() == true)
             {
-                return ReadResult( graph->load( lod, x, y, uri ) );
+                Registry::instance()->startActivity(uri);
+                osg::Node* node = graph->load( lod, x, y, uri );
+                Registry::instance()->endActivity(uri);
+                return ReadResult(node);
             }
         }
 
@@ -205,23 +233,36 @@ namespace
 
 FeatureModelGraph::FeatureModelGraph(Session*                         session,
                                      const FeatureModelSourceOptions& options,
-                                     FeatureNodeFactory*              factory ) :
-_session           ( session ),
-_options           ( options ),
-_factory           ( factory ),
-_dirty             ( false ),
-_pendingUpdate     ( false ),
-_overlayInstalled  ( 0L ),
-_overlayPlaceholder( 0L ),
-_clampable         ( 0L ),
-_drapeable         ( 0L ),
-_overlayChange     ( OVERLAY_NO_CHANGE )
+                                     FeatureNodeFactory*              factory,
+                                     RefNodeOperationVector*          preMergeOperations,
+                                     RefNodeOperationVector*          postMergeOperations) :
+_session            ( session ),
+_options            ( options ),
+_factory            ( factory ),
+_preMergeOperations ( preMergeOperations ),
+_postMergeOperations( postMergeOperations ),
+_dirty              ( false ),
+_pendingUpdate      ( false ),
+_overlayInstalled   ( 0L ),
+_overlayPlaceholder ( 0L ),
+_clampable          ( 0L ),
+_drapeable          ( 0L ),
+_overlayChange      ( OVERLAY_NO_CHANGE )
 {
     _uid = osgEarthFeatureModelPseudoLoader::registerGraph( this );
 
-    // operations that get applied after a new node gets merged into the 
-    // scene graph by the pager.
-    _postMergeOperations = new RefNodeOperationVector();
+    // an FLC that queues feature data on the high-latency thread.
+    _defaultFileLocationCallback = new HighLatencyFileLocationCallback();
+
+    // set up the callback queues for pre- and post-merge operations.
+
+    // per-merge ops run in the pager thread:
+    if ( !_preMergeOperations.valid())
+        _preMergeOperations = new RefNodeOperationVector();
+
+    // post-merge ops run in the update traversal:
+    if ( !_postMergeOperations.valid() )
+        _postMergeOperations = new RefNodeOperationVector();
 
     // install the stylesheet in the session if it doesn't already have one.
     if ( !session->styles() )
@@ -232,6 +273,17 @@ _overlayChange     ( OVERLAY_NO_CHANGE )
         OE_WARN << LC << "ILLEGAL: Session must have a feature source" << std::endl;
         return;
     }
+
+    // Set up a shared resource cache for the session. A session-wide cache means
+    // that all the paging threads that load data from this FMG will load resources
+    // from a single cache; e.g., once a texture is loaded in one thread, the same
+    // StateSet will be used across the entire Session. That also means that StateSets
+    // in the ResourceCache can potentially also be in the live graph; so you should
+    // take care in dealing with them in a multi-threaded environment.
+    if ( !session->getResourceCache() && _options.sessionWideResourceCache() == true )
+    {
+        session->setResourceCache( new ResourceCache(session->getDBOptions()) );
+    }
     
     // Calculate the usable extent (in both feature and map coordinates) and bounds.
     const Profile* mapProfile = session->getMapInfo().getProfile();
@@ -277,7 +329,9 @@ _overlayChange     ( OVERLAY_NO_CHANGE )
 
             OE_INFO << LC << session->getFeatureSource()->getName() 
                 << ": F.Level max=" << level->maxRange() << ", min=" << level->minRange()
-                << ", LOD=" << lod << std::endl;
+                << ", LOD=" << lod
+                << ", Tile size=" << (level->maxRange() / options.layout()->tileSizeFactor().get())
+                << std::endl;
         }
     }
 
@@ -307,7 +361,9 @@ _overlayChange     ( OVERLAY_NO_CHANGE )
     // proper fade time for paged nodes.
     if ( _options.fading().isSet() )
     {
-        addPostMergeOperation( new SetupFading() );
+        _postMergeOperations->mutex().writeLock();
+        _postMergeOperations->push_back( new SetupFading() );
+        _postMergeOperations->mutex().writeUnlock();
         OE_INFO << LC << "Added fading post-merge operation" << std::endl;
     }
 
@@ -327,13 +383,6 @@ FeatureModelGraph::dirty()
     _dirty = true;
 }
 
-void
-FeatureModelGraph::addPostMergeOperation( NodeOperation* op )
-{
-    if ( op )
-        _postMergeOperations->push_back( op );
-}
-
 osg::BoundingSphered
 FeatureModelGraph::getBoundInWorldCoords(const GeoExtent& extent,
                                          const MapFrame*  mapf ) const
@@ -415,6 +464,9 @@ FeatureModelGraph::setupPaging()
             if ( !_options.layout()->tileSizeFactor().isSet() )
             {
                 //Automatically compute the tileSizeFactor based on the max range
+                //TODO: This is no longer correct. "max_range" is actually an altitude,
+                //not a distance-from-tile, so if the user sets it we need to do something
+                //different. -gw
                 double width, height;
                 featureProfile->getProfile()->getTileDimensions(featureProfile->getFirstLevel(), width, height);
 
@@ -440,6 +492,8 @@ FeatureModelGraph::setupPaging()
     }
 
     // calculate the max range for the top-level PLOD:
+    // TODO: a user-specified maxRange is actually an altitude, so this is not
+    //       strictly correct anymore!
     float maxRange = 
         maxRangeOverride.isSet() ? *maxRangeOverride :
         bs.radius() * _options.layout()->tileSizeFactor().value();
@@ -455,7 +509,8 @@ FeatureModelGraph::setupPaging()
         maxRange, 
         *_options.layout()->priorityOffset(), 
         *_options.layout()->priorityScale(),
-        _postMergeOperations.get() );
+        _postMergeOperations.get(),
+        _defaultFileLocationCallback.get() );
 
     return pagedNode;
 }
@@ -553,7 +608,7 @@ FeatureModelGraph::load( unsigned lod, unsigned tileX, unsigned tileY, const std
                 lod > 0 ?
                 s_getTileExtent( lod, tileX, tileY, _usableFeatureExtent ) :
                 _usableFeatureExtent;
-
+                
             geometry = buildLevel( *level, tileExtent, 0 );
             result = geometry;
         }
@@ -595,6 +650,9 @@ FeatureModelGraph::load( unsigned lod, unsigned tileX, unsigned tileY, const std
         OE_DEBUG << LC << "Blacklisting: " << uri << std::endl;
     }
 
+    // Done - run the pre-merge operations.
+    runPreMergeOperations(result);
+
     return result;
 }
 
@@ -640,6 +698,7 @@ FeatureModelGraph::buildSubTilePagedLODs(unsigned        parentLOD,
                     << std::fixed
                     << "; center = " << subtile_bs.center().x() << "," << subtile_bs.center().y() << "," << subtile_bs.center().z()
                     << "; radius = " << subtile_bs.radius()
+                    << "; maxrange = " << maxRange
                     << std::endl;
 
                 osg::Group* pagedNode = createPagedNode( 
@@ -648,7 +707,8 @@ FeatureModelGraph::buildSubTilePagedLODs(unsigned        parentLOD,
                     0.0f, maxRange, 
                     *_options.layout()->priorityOffset(), 
                     *_options.layout()->priorityScale(),
-                    _postMergeOperations.get() );
+                    _postMergeOperations.get(),
+                    _defaultFileLocationCallback.get() );
 
                 parent->addChild( pagedNode );
             }
@@ -731,22 +791,15 @@ FeatureModelGraph::buildLevel( const FeatureLevel& level, const GeoExtent& exten
         // account for a min-range here. Do not address the max-range here; that happens
         // above when generating paged LOD nodes, etc.
         float minRange = level.minRange();
-
-#if 1
         if ( minRange > 0.0f )
         {
-            // minRange can't be less than the tile geometry's radius.
-            //minRange = std::max(minRange, (float)group->getBound().radius());
-            //osg::LOD* lod = new osg::LOD();
-            //lod->addChild( group.get(), minRange, FLT_MAX );
-
             ElevationLOD* lod = new ElevationLOD( _session->getMapSRS() );
             lod->setMinElevation( minRange );
             lod->addChild( group.get() );
             group = lod;
         }
-#endif
 
+        // install a cluster culler.
         if ( _session->getMapInfo().isGeocentric() && _options.clusterCulling() == true )
         {
             const FeatureProfile* featureProfile = _session->getFeatureSource()->getFeatureProfile();
@@ -1148,6 +1201,8 @@ FeatureModelGraph::checkForGlobalStyles( const Style& style )
             }
         }
     }
+    
+    const RenderSymbol* render = style.get<RenderSymbol>();
 
     if ( _clampable )
     {
@@ -1163,7 +1218,6 @@ FeatureModelGraph::checkForGlobalStyles( const Style& style )
         // check for explicit depth offset render settings (note, this could
         // override the automatic disable put in place by the presence of an
         // ExtrusionSymbol above)
-        const RenderSymbol* render = style.get<RenderSymbol>();
         if ( render && render->depthOffset().isSet() )
         {
             _clampable->setDepthOffsetOptions(*render->depthOffset());
@@ -1172,12 +1226,17 @@ FeatureModelGraph::checkForGlobalStyles( const Style& style )
 
     else 
     {
-        const RenderSymbol* render = style.get<RenderSymbol>();
         if ( render && render->depthOffset().isSet() )
         {
             _depthOffsetAdapter.setGraph( this );
             _depthOffsetAdapter.setDepthOffsetOptions( *render->depthOffset() );
         }
+
+        // apply render order when draping:
+        if ( _drapeable && render && render->order().isSet() )
+        {
+            _drapeable->setRenderOrder( render->order()->eval() );
+        }
     }
 }
 
@@ -1232,17 +1291,32 @@ FeatureModelGraph::traverse(osg::NodeVisitor& nv)
     osg::Group::traverse(nv);
 }
 
+void
+FeatureModelGraph::runPreMergeOperations(osg::Node* node)
+{
+   if ( _preMergeOperations.valid() )
+   {
+      _preMergeOperations->mutex().readLock();
+      for( NodeOperationVector::iterator i = _preMergeOperations->begin(); i != _preMergeOperations->end(); ++i )
+      {
+         i->get()->operator()( node );
+      }
+      _preMergeOperations->mutex().readUnlock();
+   }
+}
 
 void
 FeatureModelGraph::runPostMergeOperations(osg::Node* node)
 {
-    if ( _postMergeOperations.valid() )
-    {
-        for( NodeOperationVector::iterator i = _postMergeOperations->begin(); i != _postMergeOperations->end(); ++i )
-        {
-            i->get()->operator()( node );
-        }
-    }
+   if ( _postMergeOperations.valid() )
+   {
+      _postMergeOperations->mutex().readLock();
+      for( NodeOperationVector::iterator i = _postMergeOperations->begin(); i != _postMergeOperations->end(); ++i )
+      {
+         i->get()->operator()( node );
+      }
+      _postMergeOperations->mutex().readUnlock();
+   }
 }
 
 
diff --git a/src/osgEarthFeatures/FeatureModelSource b/src/osgEarthFeatures/FeatureModelSource
index 40c804d..7f789f0 100644
--- a/src/osgEarthFeatures/FeatureModelSource
+++ b/src/osgEarthFeatures/FeatureModelSource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -90,6 +90,10 @@ namespace osgEarth { namespace Features
         optional<FadeOptions>& fading() { return _fading; }
         const optional<FadeOptions>& fading() const { return _fading; }
 
+        /** Debug: whether to enable a session-wide resource cache (default=true) */
+        optional<bool>& sessionWideResourceCache() { return _sessionWideResourceCache; }
+        const optional<bool>& sessionWideResourceCache() const { return _sessionWideResourceCache; }
+
     public:
         /** A live feature source instance to use. Note, this does not serialize. */
         osg::ref_ptr<FeatureSource>& featureSource() { return _featureSource; }
@@ -123,6 +127,7 @@ namespace osgEarth { namespace Features
         optional<CachePolicy>               _cachePolicy;
         optional<FadeOptions>               _fading;
         optional<FeatureSourceIndexOptions> _featureIndexing;
+        optional<bool>                      _sessionWideResourceCache;
 
         osg::ref_ptr<StyleSheet>            _styles;
         osg::ref_ptr<FeatureSource>         _featureSource;
diff --git a/src/osgEarthFeatures/FeatureModelSource.cpp b/src/osgEarthFeatures/FeatureModelSource.cpp
index df3bd69..875457a 100644
--- a/src/osgEarthFeatures/FeatureModelSource.cpp
+++ b/src/osgEarthFeatures/FeatureModelSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -20,6 +20,7 @@
 #include <osgEarthFeatures/FeatureModelGraph>
 #include <osgEarth/SpatialReference>
 #include <osgEarth/ShaderFactory>
+#include <osgEarth/ShaderUtils>
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
 #include <osg/Notify>
@@ -39,7 +40,8 @@ _maxGranularity_deg( 1.0 ),
 _mergeGeometry     ( false ),
 _clusterCulling    ( true ),
 _backfaceCulling   ( true ),
-_alphaBlending     ( true )
+_alphaBlending     ( true ),
+_sessionWideResourceCache( true )
 {
     fromConfig( _conf );
 }
@@ -64,7 +66,8 @@ FeatureModelSourceOptions::fromConfig( const Config& conf )
     conf.getIfSet( "cluster_culling",  _clusterCulling );
     conf.getIfSet( "backface_culling", _backfaceCulling );
     conf.getIfSet( "alpha_blending",   _alphaBlending );
-
+    
+    conf.getIfSet( "session_wide_resource_cache", _sessionWideResourceCache );
 }
 
 Config
@@ -90,6 +93,8 @@ FeatureModelSourceOptions::getConfig() const
     conf.updateIfSet( "cluster_culling",  _clusterCulling );
     conf.updateIfSet( "backface_culling", _backfaceCulling );
     conf.updateIfSet( "alpha_blending",   _alphaBlending );
+    
+    conf.updateIfSet( "session_wide_resource_cache", _sessionWideResourceCache );
 
     return conf;
 }
@@ -139,6 +144,22 @@ FeatureModelSource::initialize(const osgDB::Options* dbOptions)
     if ( _features.valid() )
     {
         _features->initialize( dbOptions );
+
+        // Try to fill the DataExtent list using the FeatureProfile
+        const FeatureProfile* featureProfile = _features->getFeatureProfile();
+        if (featureProfile != NULL)
+        {
+            if (featureProfile->getProfile() != NULL)
+            {
+                // Use specified profile's GeoExtent
+                getDataExtents().push_back(DataExtent(featureProfile->getProfile()->getExtent()));
+            }
+            else if (featureProfile->getExtent().isValid() == true)
+            {
+                // Use FeatureProfile's GeoExtent
+                getDataExtents().push_back(DataExtent(featureProfile->getExtent()));
+            }
+        }
     }
     else
     {
@@ -177,14 +198,12 @@ FeatureModelSource::createNodeImplementation(const Map*            map,
     Session* session = new Session( map, _options.styles().get(), _features.get(), dbOptions );
 
     // Graph that will render feature models. May included paged data.
-    FeatureModelGraph* graph = new FeatureModelGraph( session, _options, factory );
-
-    // install any post-merge operations on the FMG so it can call them during paging:
-    const NodeOperationVector& ops = postProcessors();
-    for( NodeOperationVector::const_iterator i = ops.begin(); i != ops.end(); ++i )
-    {
-        graph->addPostMergeOperation( i->get() );
-    }
+    FeatureModelGraph* graph = new FeatureModelGraph( 
+       session,
+       _options,
+       factory,
+       _preMergeOps.get(),
+       _postMergeOps.get() );
 
     // then run the ops on the staring graph:
     firePostProcessors( graph );
@@ -235,6 +254,17 @@ FeatureNodeFactory::getOrCreateStyleGroup(const Style& style,
                 GL_CULL_FACE,
                 (render->backfaceCulling() == true ? osg::StateAttribute::ON : osg::StateAttribute::OFF) | osg::StateAttribute::OVERRIDE );
         }
+
+        if ( render->clipPlane().isSet() )
+        {
+            GLenum mode = GL_CLIP_PLANE0 + (render->clipPlane().value());
+            group->getOrCreateStateSet()->setMode(mode, 1);
+        }
+
+        if ( render->minAlpha().isSet() )
+        {
+            DiscardAlphaFragments().install( group->getOrCreateStateSet(), render->minAlpha().value() );
+        }
     }
 
     return group;
diff --git a/src/osgEarthFeatures/FeatureSource b/src/osgEarthFeatures/FeatureSource
index 29833b2..cef9764 100644
--- a/src/osgEarthFeatures/FeatureSource
+++ b/src/osgEarthFeatures/FeatureSource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/FeatureSource.cpp b/src/osgEarthFeatures/FeatureSource.cpp
index 9e0c335..f9529c2 100644
--- a/src/osgEarthFeatures/FeatureSource.cpp
+++ b/src/osgEarthFeatures/FeatureSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/FeatureSourceIndexNode b/src/osgEarthFeatures/FeatureSourceIndexNode
index 54a4961..ba7a1be 100644
--- a/src/osgEarthFeatures/FeatureSourceIndexNode
+++ b/src/osgEarthFeatures/FeatureSourceIndexNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/FeatureSourceIndexNode.cpp b/src/osgEarthFeatures/FeatureSourceIndexNode.cpp
index 828dcc3..8ef01a0 100644
--- a/src/osgEarthFeatures/FeatureSourceIndexNode.cpp
+++ b/src/osgEarthFeatures/FeatureSourceIndexNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/FeatureTileSource b/src/osgEarthFeatures/FeatureTileSource
index 0244e6c..94e566f 100644
--- a/src/osgEarthFeatures/FeatureTileSource
+++ b/src/osgEarthFeatures/FeatureTileSource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/FeatureTileSource.cpp b/src/osgEarthFeatures/FeatureTileSource.cpp
index c1deaa8..6243594 100644
--- a/src/osgEarthFeatures/FeatureTileSource.cpp
+++ b/src/osgEarthFeatures/FeatureTileSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -111,6 +111,22 @@ FeatureTileSource::initialize(const osgDB::Options* dbOptions)
     if ( _features.valid() )
     {
         _features->initialize( dbOptions );
+
+        // Try to fill the DataExtent list using the FeatureProfile
+        const FeatureProfile* featureProfile = _features->getFeatureProfile();
+        if (featureProfile != NULL)
+        {
+            if (featureProfile->getProfile() != NULL)
+            {
+                // Use specified profile's GeoExtent
+                getDataExtents().push_back(DataExtent(featureProfile->getProfile()->getExtent()));
+            }
+            else if (featureProfile->getExtent().isValid() == true)
+            {
+                // Use FeatureProfile's GeoExtent
+                getDataExtents().push_back(DataExtent(featureProfile->getExtent()));
+            }
+        }
     }
     else
     {
@@ -159,7 +175,7 @@ FeatureTileSource::createImage( const TileKey& key, ProgressCallback* progress )
         osg::ref_ptr<FeatureCursor> cursor = _features->createFeatureCursor( Query() );
         while( cursor.valid() && cursor->hasMore() )
         {
-            Feature* feature = cursor->nextFeature();
+            osg::ref_ptr< Feature > feature = cursor->nextFeature();
             if ( feature )
             {
                 FeatureList list;
diff --git a/src/osgEarthFeatures/Filter b/src/osgEarthFeatures/Filter
index 274281e..5e83573 100644
--- a/src/osgEarthFeatures/Filter
+++ b/src/osgEarthFeatures/Filter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -152,11 +152,15 @@ namespace osgEarth { namespace Features
 
         // computes the matricies required to localizer/delocalize double-precision coords
         void computeLocalizers( const FilterContext& context );
+        void computeLocalizers( const FilterContext& context, const osgEarth::GeoExtent &extent, osg::Matrixd &out_w2l, osg::Matrixd &out_l2w );
 
         /** Parents the node with a localizer group if necessary */
         osg::Node*  delocalize( osg::Node* node ) const;
+        osg::Node*  delocalize( osg::Node* node, const osg::Matrixd &local2World ) const;
         osg::Group* delocalizeAsGroup( osg::Node* node ) const;
+        osg::Group* delocalizeAsGroup( osg::Node* node, const osg::Matrixd &local2World ) const;
         osg::Group* createDelocalizeGroup() const;
+        osg::Group* createDelocalizeGroup( const osg::Matrixd &local2World) const;
 
         void transformAndLocalize(
             const std::vector<osg::Vec3d>& input,
diff --git a/src/osgEarthFeatures/Filter.cpp b/src/osgEarthFeatures/Filter.cpp
index efcddf2..daf6ed5 100644
--- a/src/osgEarthFeatures/Filter.cpp
+++ b/src/osgEarthFeatures/Filter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -24,6 +24,7 @@
 #include <osg/Point>
 #include <osg/LineWidth>
 #include <osg/LineStipple>
+#include <osgEarth/VirtualProgram>
 
 using namespace osgEarth;
 using namespace osgEarth::Features;
@@ -89,36 +90,42 @@ FeaturesToNodeFilter::~FeaturesToNodeFilter()
 void
 FeaturesToNodeFilter::computeLocalizers( const FilterContext& context )
 {
+    computeLocalizers(context, context.extent().get(), _world2local, _local2world);
+}
+
+void
+FeaturesToNodeFilter::computeLocalizers( const FilterContext& context, const osgEarth::GeoExtent &extent, osg::Matrixd &out_w2l, osg::Matrixd &out_l2w )
+{
     if ( context.isGeoreferenced() )
     {
         if ( context.getSession()->getMapInfo().isGeocentric() )
         {
             const SpatialReference* geogSRS = context.profile()->getSRS()->getGeographicSRS();
-            GeoExtent geodExtent = context.extent()->transform( geogSRS );
+            GeoExtent geodExtent = extent.transform( geogSRS );
             if ( geodExtent.width() < 180.0 )
             {
                 osg::Vec3d centroid, centroidECEF;
                 geodExtent.getCentroid( centroid.x(), centroid.y() );
                 geogSRS->transform( centroid, geogSRS->getECEF(), centroidECEF );
-                geogSRS->getECEF()->createLocalToWorld( centroidECEF, _local2world );
-                _world2local.invert( _local2world );
+                geogSRS->getECEF()->createLocalToWorld( centroidECEF, out_l2w );
+                out_w2l.invert( out_l2w );
             }
         }
 
         else // projected
         {
-            if ( context.extent().isSet() )
+            if ( extent.isValid() )
             {
                 osg::Vec3d centroid;
-                context.extent()->getCentroid(centroid.x(), centroid.y());
+                extent.getCentroid(centroid.x(), centroid.y());
 
-                context.extent()->getSRS()->transform(
+                extent.getSRS()->transform(
                     centroid,
                     context.getSession()->getMapInfo().getProfile()->getSRS(),
                     centroid );
 
-                _world2local.makeTranslate( -centroid );
-                _local2world.invert( _world2local );
+                out_w2l.makeTranslate( -centroid );
+                out_l2w.invert( out_w2l );
             }
         }
     }
@@ -231,8 +238,14 @@ FeaturesToNodeFilter::transformAndLocalize(const osg::Vec3d&              input,
 osg::Node*
 FeaturesToNodeFilter::delocalize( osg::Node* node ) const
 {
-    if ( !_local2world.isIdentity() ) 
-        return delocalizeAsGroup( node );
+    return delocalize(node, _local2world);
+}
+
+osg::Node*
+FeaturesToNodeFilter::delocalize( osg::Node* node, const osg::Matrixd &local2world) const
+{
+    if ( !local2world.isIdentity() ) 
+        return delocalizeAsGroup( node, local2world );
     else
         return node;
 }
@@ -240,7 +253,13 @@ FeaturesToNodeFilter::delocalize( osg::Node* node ) const
 osg::Group*
 FeaturesToNodeFilter::delocalizeAsGroup( osg::Node* node ) const
 {
-    osg::Group* group = createDelocalizeGroup();
+    return delocalizeAsGroup( node, _local2world );
+}
+
+osg::Group*
+FeaturesToNodeFilter::delocalizeAsGroup( osg::Node* node, const osg::Matrixd &local2world ) const
+{
+    osg::Group* group = createDelocalizeGroup(local2world);
     if ( node )
         group->addChild( node );
     return group;
@@ -249,9 +268,15 @@ FeaturesToNodeFilter::delocalizeAsGroup( osg::Node* node ) const
 osg::Group*
 FeaturesToNodeFilter::createDelocalizeGroup() const
 {
-    osg::Group* group = _local2world.isIdentity() ?
+    return createDelocalizeGroup( _local2world );
+}
+
+osg::Group*
+FeaturesToNodeFilter::createDelocalizeGroup( const osg::Matrixd &local2world ) const
+{
+    osg::Group* group = local2world.isIdentity() ?
         new osg::Group() :
-        new osg::MatrixTransform( _local2world );
+        new osg::MatrixTransform( local2world );
 
     return group;
 }
@@ -274,9 +299,26 @@ FeaturesToNodeFilter::applyLineSymbology(osg::StateSet*    stateset,
 
         if ( line->stroke()->stipplePattern().isSet() )
         {
-            stateset->setAttributeAndModes( new osg::LineStipple(
-                line->stroke()->stippleFactor().value(),
-                line->stroke()->stipplePattern().value() ) );
+#if 1
+            stateset->setAttributeAndModes(
+                new osg::LineStipple(
+                    line->stroke()->stippleFactor().value(),
+                    line->stroke()->stipplePattern().value()),
+                osg::StateAttribute::ON );
+#else
+            // goofing around...
+            const char* frag =
+                "#version 110\n"
+                "void oe_stipple_frag(inout vec4 color) {\n"
+                "    float x = mod(gl_FragCoord.x, 5.0);\n"
+                "    float y = mod(gl_FragCoord.y, 5.0);\n"
+                "    if (x < y)\n"
+                "        color.a = 0.0;\n"
+                "}\n";
+
+            VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
+            vp->setFunction("oe_stipple_frag", frag, ShaderComp::LOCATION_FRAGMENT_COLORING);
+#endif
         }
     }
 }
diff --git a/src/osgEarthFeatures/FilterContext b/src/osgEarthFeatures/FilterContext
index 8c6a1fc..d48688c 100644
--- a/src/osgEarthFeatures/FilterContext
+++ b/src/osgEarthFeatures/FilterContext
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -64,6 +64,11 @@ namespace osgEarth { namespace Features
          */
         void setFeatureIndex( FeatureSourceIndex* value ) { _index = value; }
 
+        /**
+         * Sets the profile (SRS etc) of the feature data
+         */
+        void setProfile( const FeatureProfile* profile );
+
 
     public: // properties
 
@@ -82,7 +87,6 @@ namespace osgEarth { namespace Features
          * The spatial profile of the feature data in this context.
          */
         const FeatureProfile* profile() const { return _profile.get(); }
-        osg::ref_ptr<const FeatureProfile>& profile() { return _profile; }
 
         /**
          * The spatial extent of the working cell
diff --git a/src/osgEarthFeatures/FilterContext.cpp b/src/osgEarthFeatures/FilterContext.cpp
index 2a2426f..14baaa3 100644
--- a/src/osgEarthFeatures/FilterContext.cpp
+++ b/src/osgEarthFeatures/FilterContext.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -34,7 +34,17 @@ _isGeocentric( false ),
 _index       ( index ),
 _shaderPolicy( osgEarth::SHADERPOLICY_GENERATE )
 {
-    _resourceCache = new ResourceCache( session ? session->getDBOptions() : 0L );
+    if ( session )
+    {
+        if ( session->getResourceCache() )
+        {
+            _resourceCache = session->getResourceCache();
+        }
+        else
+        {
+            _resourceCache = new ResourceCache( session->getDBOptions() );
+        }
+    }
 
     // attempt to establish a working extent if we don't have one:
 
@@ -67,6 +77,12 @@ _shaderPolicy         ( rhs._shaderPolicy )
     //nop
 }
 
+void
+FilterContext::setProfile(const FeatureProfile* value)
+{
+    _profile = value;
+}
+
 const osgDB::Options*
 FilterContext::getDBOptions() const
 {
diff --git a/src/osgEarthFeatures/GeometryCompiler b/src/osgEarthFeatures/GeometryCompiler
index f7e5e75..4e8ab90 100644
--- a/src/osgEarthFeatures/GeometryCompiler
+++ b/src/osgEarthFeatures/GeometryCompiler
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -34,7 +34,13 @@ namespace osgEarth { namespace Features
     class OSGEARTHFEATURES_EXPORT GeometryCompilerOptions : public ConfigOptions
     {
     public:
-        GeometryCompilerOptions( const ConfigOptions& conf =ConfigOptions() );
+        /**
+         * Set the global default values for the options.
+         */
+        static void setDefaults(const GeometryCompilerOptions& defaults);
+
+    public:
+        GeometryCompilerOptions(const ConfigOptions& conf =ConfigOptions());
         
         virtual ~GeometryCompilerOptions() { }
 
@@ -74,12 +80,22 @@ namespace osgEarth { namespace Features
         optional<double>& resampleMaxLength() { return _resampleMaxLength; }
         const optional<double>& resampleMaxLength() const { return _resampleMaxLength;}
 
+        /** @deprecated Whether to use VBOs in geometry */
         optional<bool>& useVertexBufferObjects() { return _useVertexBufferObjects;}
         const optional<bool>& useVertexBufferObjects() const { return _useVertexBufferObjects;}
 
+        /** Whether to combine resource library textures into a single texture array */        
+        optional<bool>& useTextureArrays() { return _useTextureArrays;}
+        const optional<bool>& useTextureArrays() const { return _useTextureArrays;}
+
+        /** Whether to generate shader components on compiled geometry */
         optional<ShaderPolicy>& shaderPolicy() { return _shaderPolicy; }
         const optional<ShaderPolicy>& shaderPolicy() const { return _shaderPolicy; }
 
+        /** Whether to run consolidate equivalent state attributes for better performance. */
+        optional<bool>& optimizeStateSharing() { return _optimizeStateSharing; }
+        const optional<bool>& optimizeStateSharing() const { return _optimizeStateSharing; }
+
 
     public:
         Config getConfig() const;
@@ -97,8 +113,15 @@ namespace osgEarth { namespace Features
         optional<bool>                 _ignoreAlt;
         optional<bool>                 _useVertexBufferObjects;
         optional<ShaderPolicy>         _shaderPolicy;
+        optional<bool>                 _useTextureArrays;
+        optional<bool>                 _optimizeStateSharing;
 
         void fromConfig( const Config& conf );
+
+        static GeometryCompilerOptions s_defaults;
+
+    public:
+       GeometryCompilerOptions(bool); // internal
     };
 
     /**
diff --git a/src/osgEarthFeatures/GeometryCompiler.cpp b/src/osgEarthFeatures/GeometryCompiler.cpp
index e0fbfee..4386034 100644
--- a/src/osgEarthFeatures/GeometryCompiler.cpp
+++ b/src/osgEarthFeatures/GeometryCompiler.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -22,16 +22,18 @@
 #include <osgEarthFeatures/AltitudeFilter>
 #include <osgEarthFeatures/CentroidFilter>
 #include <osgEarthFeatures/ExtrudeGeometryFilter>
-#include <osgEarthFeatures/PolygonizeLines>
 #include <osgEarthFeatures/ScatterFilter>
 #include <osgEarthFeatures/SubstituteModelFilter>
 #include <osgEarthFeatures/TessellateOperator>
+#include <osgEarthFeatures/Session>
+#include <osgEarth/Utils>
 #include <osgEarth/AutoScale>
 #include <osgEarth/CullingUtils>
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
 #include <osgEarth/ShaderGenerator>
 #include <osgEarth/ShaderUtils>
+#include <osgEarth/Utils>
 #include <osg/MatrixTransform>
 #include <osg/Timer>
 #include <osgDB/WriteFile>
@@ -42,6 +44,8 @@ using namespace osgEarth;
 using namespace osgEarth::Features;
 using namespace osgEarth::Symbology;
 
+//#define PROFILING 1
+
 //-----------------------------------------------------------------------
 
 namespace
@@ -65,7 +69,9 @@ namespace
             for( unsigned i=0; i<geode.getNumDrawables(); ++i )
             {
                 osg::Drawable* d = geode.getDrawable(i);
-                osg::BoundingBox bbox = d->getBound();
+
+                osg::BoundingBox bbox = Utils::getBoundingBox(d);
+
                 if ( bbox.zMin() > _low )
                     bbox.expandBy( osg::Vec3f(bbox.xMin(), bbox.yMin(), _low) );
                 if ( bbox.zMax() < _high )
@@ -79,18 +85,46 @@ namespace
 
 //-----------------------------------------------------------------------
 
-GeometryCompilerOptions::GeometryCompilerOptions( const ConfigOptions& conf ) :
-ConfigOptions      ( conf ),
-_maxGranularity_deg( 1.0 ),
-_mergeGeometry     ( false ),
-_clustering        ( false ),
-_instancing        ( false ),
-_ignoreAlt         ( false ),
+GeometryCompilerOptions GeometryCompilerOptions::s_defaults(true);
+
+void
+GeometryCompilerOptions::setDefaults(const GeometryCompilerOptions& defaults)
+{
+   s_defaults = defaults;
+}
+
+// defaults.
+GeometryCompilerOptions::GeometryCompilerOptions(bool stockDefaults) :
+_maxGranularity_deg    ( 10.0 ),
+_mergeGeometry         ( false ),
+_clustering            ( false ),
+_instancing            ( false ),
+_ignoreAlt             ( false ),
 _useVertexBufferObjects( true ),
-_shaderPolicy      ( SHADERPOLICY_GENERATE )
+_useTextureArrays      ( true ),
+_shaderPolicy          ( SHADERPOLICY_GENERATE ),
+_geoInterp             ( GEOINTERP_GREAT_CIRCLE ),
+_optimizeStateSharing  ( true )
+{
+   //nop
+}
+
+//-----------------------------------------------------------------------
+
+GeometryCompilerOptions::GeometryCompilerOptions(const ConfigOptions& conf) :
+ConfigOptions          ( conf ),
+_maxGranularity_deg    ( s_defaults.maxGranularity().value() ),
+_mergeGeometry         ( s_defaults.mergeGeometry().value() ),
+_clustering            ( s_defaults.clustering().value() ),
+_instancing            ( s_defaults.instancing().value() ),
+_ignoreAlt             ( s_defaults.ignoreAltitudeSymbol().value() ),
+_useVertexBufferObjects( s_defaults.useVertexBufferObjects().value() ),
+_useTextureArrays      ( s_defaults.useTextureArrays().value() ),
+_shaderPolicy          ( s_defaults.shaderPolicy().value() ),
+_geoInterp             ( s_defaults.geoInterp().value() ),
+_optimizeStateSharing  ( s_defaults.optimizeStateSharing().value() )
 {
     fromConfig(_conf);
-    _useVertexBufferObjects = !Registry::capabilities().preferDisplayListsForStaticGeometry();
 }
 
 void
@@ -105,6 +139,8 @@ GeometryCompilerOptions::fromConfig( const Config& conf )
     conf.getIfSet   ( "geo_interpolation", "great_circle", _geoInterp, GEOINTERP_GREAT_CIRCLE );
     conf.getIfSet   ( "geo_interpolation", "rhumb_line",   _geoInterp, GEOINTERP_RHUMB_LINE );
     conf.getIfSet   ( "use_vbo", _useVertexBufferObjects);
+    conf.getIfSet   ( "use_texture_arrays", _useTextureArrays );
+    conf.getIfSet   ( "optimize_state_sharing", _optimizeStateSharing );
 
     conf.getIfSet( "shader_policy", "disable",  _shaderPolicy, SHADERPOLICY_DISABLE );
     conf.getIfSet( "shader_policy", "inherit",  _shaderPolicy, SHADERPOLICY_INHERIT );
@@ -124,6 +160,8 @@ GeometryCompilerOptions::getConfig() const
     conf.addIfSet   ( "geo_interpolation", "great_circle", _geoInterp, GEOINTERP_GREAT_CIRCLE );
     conf.addIfSet   ( "geo_interpolation", "rhumb_line",   _geoInterp, GEOINTERP_RHUMB_LINE );
     conf.addIfSet   ( "use_vbo", _useVertexBufferObjects);
+    conf.addIfSet   ( "use_texture_arrays", _useTextureArrays );
+    conf.addIfSet   ( "optimize_state_sharing", _optimizeStateSharing );
 
     conf.addIfSet( "shader_policy", "disable",  _shaderPolicy, SHADERPOLICY_DISABLE );
     conf.addIfSet( "shader_policy", "inherit",  _shaderPolicy, SHADERPOLICY_INHERIT );
@@ -288,7 +326,8 @@ GeometryCompiler::compile(FeatureList&          workingSet,
         altitude && (
             altitude->clamping() != AltitudeSymbol::CLAMP_NONE ||
             altitude->verticalOffset().isSet() ||
-            altitude->verticalScale().isSet() );
+            altitude->verticalScale().isSet() ||
+            altitude->script().isSet() );
 
     // marker substitution -- to be deprecated in favor of model/icon
     if ( marker )
@@ -406,6 +445,12 @@ GeometryCompiler::compile(FeatureList&          workingSet,
         ExtrudeGeometryFilter extrude;
         extrude.setStyle( style );
 
+        // Activate texture arrays if the GPU supports them and if the user
+        // hasn't disabled them.        
+        extrude.useTextureArrays() =
+            Registry::capabilities().supportsTextureArrays() &&
+            _options.useTextureArrays() == true;
+
         // apply per-feature naming if requested.
         if ( _options.featureName().isSet() )
             extrude.setFeatureNameExpr( *_options.featureName() );
@@ -417,6 +462,7 @@ GeometryCompiler::compile(FeatureList&          workingSet,
         {
             resultGroup->addChild( node );
         }
+        
     }
 
     // simple geometry
@@ -431,10 +477,9 @@ GeometryCompiler::compile(FeatureList&          workingSet,
         }
 
         BuildGeometryFilter filter( style );
-        if ( _options.maxGranularity().isSet() )
-            filter.maxGranularity() = *_options.maxGranularity();
-        if ( _options.geoInterp().isSet() )
-            filter.geoInterp() = *_options.geoInterp();
+        filter.maxGranularity() = *_options.maxGranularity();
+        filter.geoInterp()      = *_options.geoInterp();
+
         if ( _options.featureName().isSet() )
             filter.featureName() = *_options.featureName();
 
@@ -463,21 +508,14 @@ GeometryCompiler::compile(FeatureList&          workingSet,
         }
     }
 
-    // Common state set cache?
-    osg::ref_ptr<StateSetCache> sscache;
-    if ( sharedCX.getSession() )
-        sscache = sharedCX.getSession()->getStateSetCache();
-    else 
-        sscache = new StateSetCache();
-
-    // Generate shaders, if necessary
     if (Registry::capabilities().supportsGLSL())
     {
         if ( _options.shaderPolicy() == SHADERPOLICY_GENERATE )
         {
             // no ss cache because we will optimize later.
-            ShaderGenerator gen;
-            gen.run( resultGroup.get() );
+            Registry::shaderGenerator().run( 
+                resultGroup.get(),
+                "osgEarth.GeomCompiler" );
         }
         else if ( _options.shaderPolicy() == SHADERPOLICY_DISABLE )
         {
@@ -488,15 +526,47 @@ GeometryCompiler::compile(FeatureList&          workingSet,
     }
 
     // Optimize stateset sharing.
-    sscache->optimize( resultGroup.get() );
+    if ( _options.optimizeStateSharing() == true )
+    {
+        // Common state set cache?
+        osg::ref_ptr<StateSetCache> sscache;
+        if ( sharedCX.getSession() )
+        {
+            // with a shared cache, don't combine statesets. They may be
+            // in the live graph
+            sscache = sharedCX.getSession()->getStateSetCache();
+            sscache->consolidateStateAttributes( resultGroup.get() );
+        }
+        else 
+        {
+            // isolated: perform full optimization
+            sscache = new StateSetCache();
+            sscache->optimize( resultGroup.get() );
+        }
+    }
 
+    //test: dump the tile to disk
     //osgDB::writeNodeFile( *(resultGroup.get()), "out.osg" );
 
 #ifdef PROFILING
+    static double totalTime = 0.0;
+    static Threading::Mutex totalTimeMutex;
     osg::Timer_t p_end = osg::Timer::instance()->tick();
+    double t = osg::Timer::instance()->delta_s(p_start, p_end);
+    totalTimeMutex.lock();
+    totalTime += t;
+    totalTimeMutex.unlock();
     OE_INFO << LC
-        << "features = " << p_features <<
-        << " ,time = " << osg::Timer::instance()->delta_s(p_start, p_end) << " s." << std::endl;
+        << "features = " << p_features
+        << ", time = " << t << " s.  cummulative = " 
+        << totalTime << " s."
+        << std::endl;
+#endif
+
+#if 0
+    //test: run the geometry validator to make sure geometry it legal
+    osgEarth::GeometryValidator validator;
+    resultGroup->accept(validator);
 #endif
 
     return resultGroup.release();
diff --git a/src/osgEarthFeatures/GeometryUtils b/src/osgEarthFeatures/GeometryUtils
index 96f2c52..b162391 100644
--- a/src/osgEarthFeatures/GeometryUtils
+++ b/src/osgEarthFeatures/GeometryUtils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -29,13 +29,15 @@ namespace osgEarth { namespace Features
 
     namespace GeometryUtils
     {
-        extern OSGEARTHFEATURES_EXPORT std::string geometryToWKT( Geometry* geometry );
+        extern OSGEARTHFEATURES_EXPORT std::string geometryToWKT( const Geometry* geometry );
         extern OSGEARTHFEATURES_EXPORT Geometry*   geometryFromWKT( const std::string& wkt );
 
-        extern OSGEARTHFEATURES_EXPORT std::string geometryToGeoJSON( Geometry* geometry );
-        extern OSGEARTHFEATURES_EXPORT std::string geometryToKML( Geometry* geometry );
-        extern OSGEARTHFEATURES_EXPORT std::string geometryToGML( Geometry* geometry );
-        extern OSGEARTHFEATURES_EXPORT double getGeometryArea( Geometry* geometry );
+        extern OSGEARTHFEATURES_EXPORT std::string geometryToGeoJSON( const Geometry* geometry );
+        extern OSGEARTHFEATURES_EXPORT Geometry*   geometryFromGeoJSON( const std::string& geojson );
+
+        extern OSGEARTHFEATURES_EXPORT std::string geometryToKML( const Geometry* geometry );
+        extern OSGEARTHFEATURES_EXPORT std::string geometryToGML( const Geometry* geometry );
+        extern OSGEARTHFEATURES_EXPORT double getGeometryArea( const Geometry* geometry );
     }
 
 } } // namespace osgEarth::Features
diff --git a/src/osgEarthFeatures/GeometryUtils.cpp b/src/osgEarthFeatures/GeometryUtils.cpp
index 236eb75..e55f1a7 100644
--- a/src/osgEarthFeatures/GeometryUtils.cpp
+++ b/src/osgEarthFeatures/GeometryUtils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -24,7 +24,7 @@ using namespace osgEarth::Features;
 using namespace osgEarth::Symbology;
 
 std::string
-osgEarth::Features::GeometryUtils::geometryToWKT( Geometry* geometry )
+osgEarth::Features::GeometryUtils::geometryToWKT( const Geometry* geometry )
 {
     OGRGeometryH g = OgrUtils::createOgrGeometry( geometry );
     std::string result;
@@ -42,7 +42,7 @@ osgEarth::Features::GeometryUtils::geometryToWKT( Geometry* geometry )
 }
 
 std::string 
-osgEarth::Features::GeometryUtils::geometryToGeoJSON( Geometry* geometry )
+osgEarth::Features::GeometryUtils::geometryToGeoJSON( const Geometry* geometry )
 {
     OGRGeometryH g = OgrUtils::createOgrGeometry( geometry );
     std::string result;
@@ -60,8 +60,21 @@ osgEarth::Features::GeometryUtils::geometryToGeoJSON( Geometry* geometry )
     return result;
 }
 
+Geometry*
+osgEarth::Features::GeometryUtils::geometryFromGeoJSON(const std::string& geojson)
+{
+    Geometry* result = 0L;
+    OGRGeometryH g = OGR_G_CreateGeometryFromJson(geojson.c_str());
+    if ( g )
+    {
+        result = OgrUtils::createGeometry( g );
+        OGR_G_DestroyGeometry( g );
+    }
+    return result;
+}
+
 std::string 
-osgEarth::Features::GeometryUtils::geometryToKML( Geometry* geometry )
+osgEarth::Features::GeometryUtils::geometryToKML( const Geometry* geometry )
 {
     OGRGeometryH g = OgrUtils::createOgrGeometry( geometry );
     std::string result;
@@ -80,7 +93,7 @@ osgEarth::Features::GeometryUtils::geometryToKML( Geometry* geometry )
 }
 
 std::string 
-osgEarth::Features::GeometryUtils::geometryToGML( Geometry* geometry )
+osgEarth::Features::GeometryUtils::geometryToGML( const Geometry* geometry )
 {
     OGRGeometryH g = OgrUtils::createOgrGeometry( geometry );
     std::string result;
@@ -134,7 +147,7 @@ osgEarth::Features::GeometryUtils::geometryFromWKT( const std::string& wkt )
 }
 
 double
-osgEarth::Features::GeometryUtils::getGeometryArea( Geometry* geometry )
+osgEarth::Features::GeometryUtils::getGeometryArea( const Geometry* geometry )
 {
     OGRGeometryH g = OgrUtils::createOgrGeometry( geometry );
     
diff --git a/src/osgEarthFeatures/LabelSource b/src/osgEarthFeatures/LabelSource
index 2c845c3..53362e1 100644
--- a/src/osgEarthFeatures/LabelSource
+++ b/src/osgEarthFeatures/LabelSource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/LabelSource.cpp b/src/osgEarthFeatures/LabelSource.cpp
index d9b2cdc..168284b 100644
--- a/src/osgEarthFeatures/LabelSource.cpp
+++ b/src/osgEarthFeatures/LabelSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -18,6 +18,7 @@
  */
 #include <osgEarthFeatures/LabelSource>
 #include <osgEarth/Registry>
+#include <osgDB/ReadFile>
 
 using namespace osgEarth;
 using namespace osgEarth::Features;
diff --git a/src/osgEarthFeatures/MeshClamper b/src/osgEarthFeatures/MeshClamper
index 77d786d..adb047a 100644
--- a/src/osgEarthFeatures/MeshClamper
+++ b/src/osgEarthFeatures/MeshClamper
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/MeshClamper.cpp b/src/osgEarthFeatures/MeshClamper.cpp
index 1956385..1678cc3 100644
--- a/src/osgEarthFeatures/MeshClamper.cpp
+++ b/src/osgEarthFeatures/MeshClamper.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/OgrUtils b/src/osgEarthFeatures/OgrUtils
index 8935335..8aebfda 100644
--- a/src/osgEarthFeatures/OgrUtils
+++ b/src/osgEarthFeatures/OgrUtils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -38,11 +38,11 @@ struct OSGEARTHFEATURES_EXPORT OgrUtils
        
     static Symbology::Geometry* createGeometry( OGRGeometryH geomHandle );
 
-    static OGRGeometryH encodePart( Geometry* geometry, OGRwkbGeometryType part_type );
+    static OGRGeometryH encodePart( const Geometry* geometry, OGRwkbGeometryType part_type );
 
-    static OGRGeometryH encodeShape( Geometry* geometry, OGRwkbGeometryType shape_type, OGRwkbGeometryType part_type );    
+    static OGRGeometryH encodeShape( const Geometry* geometry, OGRwkbGeometryType shape_type, OGRwkbGeometryType part_type );    
 
-    static OGRGeometryH createOgrGeometry(osgEarth::Symbology::Geometry* geometry, OGRwkbGeometryType requestedType = wkbUnknown);
+    static OGRGeometryH createOgrGeometry(const Geometry* geometry, OGRwkbGeometryType requestedType = wkbUnknown);
     
     static Feature* createFeature( OGRFeatureH handle, const SpatialReference* srs );
     
diff --git a/src/osgEarthFeatures/OgrUtils.cpp b/src/osgEarthFeatures/OgrUtils.cpp
index 610d6aa..f0110a7 100644
--- a/src/osgEarthFeatures/OgrUtils.cpp
+++ b/src/osgEarthFeatures/OgrUtils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -24,7 +24,7 @@ using namespace osgEarth::Features;
 
 
 void
-    OgrUtils::populate( OGRGeometryH geomHandle, Symbology::Geometry* target, int numPoints )
+OgrUtils::populate( OGRGeometryH geomHandle, Symbology::Geometry* target, int numPoints )
 {
     for( int v = numPoints-1; v >= 0; v-- ) // reverse winding.. we like ccw
     {
@@ -37,7 +37,7 @@ void
 }
 
 Symbology::Polygon*
-    OgrUtils::createPolygon( OGRGeometryH geomHandle )
+OgrUtils::createPolygon( OGRGeometryH geomHandle )
 {
     Symbology::Polygon* output = 0L;
 
@@ -76,7 +76,7 @@ Symbology::Polygon*
 }
 
 Symbology::Geometry*
-    OgrUtils::createGeometry( OGRGeometryH geomHandle )
+OgrUtils::createGeometry( OGRGeometryH geomHandle )
 {
     Symbology::Geometry* output = 0L;
 
@@ -141,7 +141,7 @@ Symbology::Geometry*
 }
 
 OGRGeometryH
-    OgrUtils::encodePart( Geometry* geometry, OGRwkbGeometryType part_type )
+OgrUtils::encodePart( const Geometry* geometry, OGRwkbGeometryType part_type )
 {
     OGRGeometryH part_handle = OGR_G_CreateGeometry( part_type );
 
@@ -156,15 +156,15 @@ OGRGeometryH
 
 
 OGRGeometryH
-    OgrUtils::encodeShape( Geometry* geometry, OGRwkbGeometryType shape_type, OGRwkbGeometryType part_type )
+OgrUtils::encodeShape( const Geometry* geometry, OGRwkbGeometryType shape_type, OGRwkbGeometryType part_type )
 {
     OGRGeometryH shape_handle = OGR_G_CreateGeometry( shape_type );
     if ( shape_handle )
     {
-        GeometryIterator itr(geometry, true);
+        ConstGeometryIterator itr(geometry, true);
         while (itr.hasMore())
         {
-            Geometry* geom = itr.next();
+            const Geometry* geom = itr.next();
             OGRGeometryH part_handle = encodePart( geom, part_type );
             if ( part_handle )
             {
@@ -176,7 +176,7 @@ OGRGeometryH
 }
 
 OGRGeometryH
-    OgrUtils::createOgrGeometry(osgEarth::Symbology::Geometry* geometry, OGRwkbGeometryType requestedType)
+OgrUtils::createOgrGeometry(const osgEarth::Symbology::Geometry* geometry, OGRwkbGeometryType requestedType)
 {
     if (!geometry) return NULL;
 
@@ -200,7 +200,7 @@ OGRGeometryH
         case Geometry::TYPE_UNKNOWN: break;
         case Geometry::TYPE_MULTI: 
             {
-                osgEarth::Symbology::MultiGeometry* multi = dynamic_cast<MultiGeometry*>(geometry);
+                const osgEarth::Symbology::MultiGeometry* multi = dynamic_cast<const MultiGeometry*>(geometry);
                 osgEarth::Symbology::Geometry::Type componentType = multi->getComponentType();
                 requestedType = componentType == Geometry::TYPE_POLYGON ? wkbMultiPolygon : 
                     componentType == Geometry::TYPE_POINTSET ? wkbMultiPoint :
@@ -231,13 +231,13 @@ OGRGeometryH
     //OE_NOTICE << "shape_type = " << shape_type << " part_type=" << part_type << std::endl;
 
 
-    osgEarth::Symbology::MultiGeometry* multi = dynamic_cast<MultiGeometry*>(geometry);
+    const osgEarth::Symbology::MultiGeometry* multi = dynamic_cast<const MultiGeometry*>(geometry);
 
     if ( multi )
     {
         OGRGeometryH group_handle = OGR_G_CreateGeometry( wkbGeometryCollection );
 
-        for (GeometryCollection::iterator itr = multi->getComponents().begin(); itr != multi->getComponents().end(); ++itr)
+        for (GeometryCollection::const_iterator itr = multi->getComponents().begin(); itr != multi->getComponents().end(); ++itr)
         {
             OGRGeometryH shape_handle = encodeShape( itr->get(), shape_type, part_type );
             if ( shape_handle )
@@ -261,7 +261,7 @@ OGRGeometryH
 }
 
 Feature*
-    OgrUtils::createFeature( OGRFeatureH handle, const SpatialReference* srs )
+OgrUtils::createFeature( OGRFeatureH handle, const SpatialReference* srs )
 {
     long fid = OGR_F_GetFID( handle );
 
@@ -283,8 +283,7 @@ Feature*
 
         // get the field name and convert to lower case:
         const char* field_name = OGR_Fld_GetNameRef( field_handle_ref ); 
-        std::string name = std::string( field_name ); 
-        std::transform( name.begin(), name.end(), name.begin(), ::tolower ); 
+        std::string name = osgEarth::toLower( std::string(field_name) );
 
         // get the field type and set the value appropriately
         OGRFieldType field_type = OGR_Fld_GetType( field_handle_ref );        
@@ -334,7 +333,8 @@ Feature*
     return feature;
 }
 
-AttributeType OgrUtils::getAttributeType( OGRFieldType type )
+AttributeType
+OgrUtils::getAttributeType( OGRFieldType type )
 {
     switch (type)
     {
diff --git a/src/osgEarthFeatures/OptimizerHints b/src/osgEarthFeatures/OptimizerHints
index 6298e4a..208647a 100644
--- a/src/osgEarthFeatures/OptimizerHints
+++ b/src/osgEarthFeatures/OptimizerHints
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/OptimizerHints.cpp b/src/osgEarthFeatures/OptimizerHints.cpp
index 3542c1b..83c540a 100644
--- a/src/osgEarthFeatures/OptimizerHints.cpp
+++ b/src/osgEarthFeatures/OptimizerHints.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/PolygonizeLines b/src/osgEarthFeatures/PolygonizeLines
index d153984..ff624e5 100644
--- a/src/osgEarthFeatures/PolygonizeLines
+++ b/src/osgEarthFeatures/PolygonizeLines
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -63,7 +63,7 @@ namespace osgEarth { namespace Features
         /**
          * Installs an auto-scaling shader on a stateset.
          */
-        void installShaders(osg::StateSet* stateset) const;
+        void installShaders(osg::Node* node) const;
 
     protected:
         Stroke _stroke;
diff --git a/src/osgEarthFeatures/PolygonizeLines.cpp b/src/osgEarthFeatures/PolygonizeLines.cpp
index 6fa4449..749ba8a 100644
--- a/src/osgEarthFeatures/PolygonizeLines.cpp
+++ b/src/osgEarthFeatures/PolygonizeLines.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -18,9 +18,11 @@
  */
 #include <osgEarthFeatures/PolygonizeLines>
 #include <osgEarthFeatures/FeatureSourceIndexNode>
+#include <osgEarthFeatures/Session>
 #include <osgEarthSymbology/MeshConsolidator>
 #include <osgEarth/VirtualProgram>
 #include <osgEarth/Utils>
+#include <osgEarth/CullingUtils>
 
 #define LC "[PolygonizeLines] "
 
@@ -225,6 +227,7 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
             // flip it depending on the current side.
             //osg::Vec3 bufVecUnit = (s==0) ? normal ^ dir : dir ^ normal;
             osg::Vec3 bufVecUnit = (side < 0.0f) ? normal ^ dir : dir ^ normal;
+            bufVecUnit.normalize();
 
             // scale the buffering vector to half the stroke width.
             osg::Vec3 bufVec = bufVecUnit * halfWidth;
@@ -445,14 +448,40 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
 
 #define SHADER_NAME "osgEarth::PolygonizeLinesAutoScale"
 
+namespace
+{
+    struct PixelSizeVectorCullCallback : public osg::NodeCallback
+    {
+        PixelSizeVectorCullCallback(osg::StateSet* stateset)
+        {
+            _pixelSizeVectorUniform = new osg::Uniform(osg::Uniform::FLOAT_VEC4, "oe_PixelSizeVector");
+            stateset->addUniform( _pixelSizeVectorUniform.get() );
+        }
+
+        void operator()(osg::Node* node, osg::NodeVisitor* nv)
+        {
+            osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
+            _pixelSizeVectorUniform->set( cv->getCurrentCullingSet().getPixelSizeVector() );
+            traverse(node, nv);
+        }
+
+        osg::ref_ptr<osg::Uniform> _pixelSizeVectorUniform;
+    };
+}
+
 
 void
-PolygonizeLinesOperator::installShaders(osg::StateSet* stateset) const
+PolygonizeLinesOperator::installShaders(osg::Node* node) const
 {
+    if ( !node )
+        return;
+
     float minPixels = _stroke.minPixels().getOrUse( 0.0f );
     if ( minPixels <= 0.0f )
         return;
 
+    osg::StateSet* stateset = node->getOrCreateStateSet();
+
     VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
 
     // bail if already installed.
@@ -464,34 +493,27 @@ PolygonizeLinesOperator::installShaders(osg::StateSet* stateset) const
     const char* vs =
         "#version " GLSL_VERSION_STR "\n"
         GLSL_DEFAULT_PRECISION_FLOAT "\n"
-        "attribute vec3   oe_polyline_center; \n"
-        "uniform   float  oe_polyline_scale;  \n"
-        "uniform   float  oe_polyline_min_pixels; \n"
-        "uniform   mat3   oe_WindowScaleMatrix; \n"
+        "attribute vec3 oe_polyline_center; \n"
+        "uniform float oe_polyline_scale;  \n"
+        "uniform float oe_polyline_min_pixels; \n"
+        "uniform vec4 oe_PixelSizeVector; \n"
 
-        "void oe_polyline_scalelines(inout vec4 VertexMODEL) \n"
+        "void oe_polyline_scalelines(inout vec4 vertex_model4) \n"
         "{ \n"
-        //"   if ( oe_polyline_scale != 1.0 || oe_polyline_min_pixels > 0.0 ) \n"
+        "   vec4  center_model = vec4(oe_polyline_center, 1.0); \n"
+        "   vec4  vertex_model = vertex_model4/vertex_model4.w; \n"
+        "   vec3  vector_model = vertex_model.xyz - center_model.xyz; \n"
+        
+        "   float r = length(vector_model); \n"
+
+        "   if ( r > 0.0 && oe_polyline_min_pixels > 0.0 ) \n"
         "   { \n"
-        "       vec4  center_model = vec4(oe_polyline_center*VertexMODEL.w, VertexMODEL.w); \n"
-        "       vec4  vector_model = VertexMODEL - center_model; \n"
-        "       if ( length(vector_model.xyz) > 0.0 ) \n"
-        "       { \n"
-        "           float scale = oe_polyline_scale; \n"
-
-        "           vec4 vertex_clip = gl_ModelViewProjectionMatrix * VertexMODEL; \n"
-        "           vec4 center_clip = gl_ModelViewProjectionMatrix * center_model; \n"
-        "           vec4 vector_clip = vertex_clip - center_clip; \n"
-
-        "           if ( oe_polyline_min_pixels > 0.0 ) \n"
-        "           { \n"
-        "               vec3 vector_win = oe_WindowScaleMatrix * (vertex_clip.xyz/vertex_clip.w - center_clip.xyz/center_clip.w); \n"
-        "               float min_scale = max( (0.5*oe_polyline_min_pixels)/length(vector_win.xy), 1.0 ); \n"
-        "               scale = max( scale, min_scale ); \n"
-        "           } \n"
-
-        "           VertexMODEL = center_model + vector_model*scale; \n"
-        "        } \n"
+        "       float pixelSize = abs(r/dot(center_model, oe_PixelSizeVector)); \n"
+        "       float min_scale = max( oe_polyline_min_pixels/pixelSize, 1.0 ); \n"
+        "       float scale = max( oe_polyline_scale, min_scale ); \n"
+
+        "       vertex_model.xyz += vector_model*scale; \n"
+        "       vertex_model4 = vec4(vertex_model.xyz, 1.0); \n"
         "    } \n"
         "} \n";
 
@@ -509,6 +531,13 @@ PolygonizeLinesOperator::installShaders(osg::StateSet* stateset) const
     osg::Uniform* minPixelsU = new osg::Uniform(osg::Uniform::FLOAT, "oe_polyline_min_pixels");
     minPixelsU->set( minPixels );
     stateset->addUniform( minPixelsU, 1 );
+
+    // this will install and update the oe_PixelSizeVector uniform.
+    node->addCullCallback( new PixelSizeVectorCullCallback(stateset) );
+
+    // DYNAMIC since we will be altering the uniforms and we don't want 
+    // the stateset to get shared via statesetcache optimization.
+    stateset->setDataVariance(osg::Object::DYNAMIC);
 }
 
 
@@ -587,7 +616,7 @@ PolygonizeLinesFilter::push(FeatureList& input, FilterContext& cx)
     geode->accept( vco );
 
     // If we're auto-scaling, we need a shader
-    polygonize.installShaders( geode->getOrCreateStateSet() );
+    polygonize.installShaders( geode );
 
     return delocalize( geode );
 }
diff --git a/src/osgEarthFeatures/ResampleFilter b/src/osgEarthFeatures/ResampleFilter
index f0393bf..a6c6d64 100644
--- a/src/osgEarthFeatures/ResampleFilter
+++ b/src/osgEarthFeatures/ResampleFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/ResampleFilter.cpp b/src/osgEarthFeatures/ResampleFilter.cpp
index 9f4692e..d7c76ec 100644
--- a/src/osgEarthFeatures/ResampleFilter.cpp
+++ b/src/osgEarthFeatures/ResampleFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/ScaleFilter b/src/osgEarthFeatures/ScaleFilter
index baf1b8e..2c8d107 100644
--- a/src/osgEarthFeatures/ScaleFilter
+++ b/src/osgEarthFeatures/ScaleFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/ScaleFilter.cpp b/src/osgEarthFeatures/ScaleFilter.cpp
index c83b120..700de8e 100644
--- a/src/osgEarthFeatures/ScaleFilter.cpp
+++ b/src/osgEarthFeatures/ScaleFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/ScatterFilter b/src/osgEarthFeatures/ScatterFilter
index 6f48cd9..d75f6c0 100644
--- a/src/osgEarthFeatures/ScatterFilter
+++ b/src/osgEarthFeatures/ScatterFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/ScatterFilter.cpp b/src/osgEarthFeatures/ScatterFilter.cpp
index f4b263a..44cd8a7 100644
--- a/src/osgEarthFeatures/ScatterFilter.cpp
+++ b/src/osgEarthFeatures/ScatterFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/Script b/src/osgEarthFeatures/Script
index 707ca71..ce6a2f6 100644
--- a/src/osgEarthFeatures/Script
+++ b/src/osgEarthFeatures/Script
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/ScriptEngine b/src/osgEarthFeatures/ScriptEngine
index e40ac67..a1e0a30 100644
--- a/src/osgEarthFeatures/ScriptEngine
+++ b/src/osgEarthFeatures/ScriptEngine
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -63,17 +63,34 @@ namespace osgEarth { namespace Features
   class OSGEARTHFEATURES_EXPORT ScriptEngine : public osg::Object
   {
   public:
-    ScriptEngine(const ScriptEngineOptions& options =ScriptEngineOptions()) : _script(options.script()) {}
+    ScriptEngine(const ScriptEngineOptions& options =ScriptEngineOptions()) :
+        _script(options.script()) {}
 
     virtual ~ScriptEngine() { }
 
+    /** Whether the engine supports the specified scripting language */
     virtual bool supported(std::string lang) =0;
-    virtual bool supported(Script* script) =0;
 
-    virtual ScriptResult run(Script* script, Feature const* feature=0L, FilterContext const* context=0L) =0;
+    /** Whether the engine can run the specified script */
+    virtual bool supported(Script* script)
+    {
+        return script ? supported(script->getLanguage()) : false;
+    }
+
+    /** Runs a code snippet. */
     virtual ScriptResult run(const std::string& code, Feature const* feature=0L, FilterContext const* context=0L) =0;
 
-    virtual ScriptResult call(const std::string& function, Feature const* feature=0L, FilterContext const* context=0L) =0;
+    /** Runs a script */
+    virtual ScriptResult run(Script* script, Feature const* feature=0L, FilterContext const* context=0L)
+    {
+        return script ? run(script->getCode(), feature, context) : ScriptResult("", false);
+    }
+
+    /** deprecated */
+    virtual ScriptResult call(const std::string& function, Feature const* feature=0L, FilterContext const* context=0L)
+    {
+        return ScriptResult("", false, "ScriptResult::call not implemented");
+    }
 
   public:
     // META_Object specialization:
@@ -84,7 +101,8 @@ namespace osgEarth { namespace Features
     virtual const char* libraryName() const { return "osgEarth::Features"; }
 
   protected:
-    optional<Script>  _script;
+    // common script to be included in each call to run()
+    optional<Script> _script;
   };
 
   typedef std::list< osg::ref_ptr<ScriptEngine> > ScriptEngineList;
@@ -105,9 +123,9 @@ namespace osgEarth { namespace Features
 	public:
     static ScriptEngineFactory* instance();
 
-    static ScriptEngine* create( const std::string& language, const std::string& engineName="" );
-    static ScriptEngine* create( const Script& script, const std::string& engineName="" );
-    static ScriptEngine* create( const ScriptEngineOptions& options );
+    static ScriptEngine* create( const std::string& language, const std::string& engineName="", bool quiet =false);
+    static ScriptEngine* create( const Script& script, const std::string& engineName="", bool quiet =false);
+    static ScriptEngine* create( const ScriptEngineOptions& options, bool quiet =false);
 
   protected:
     ScriptEngineFactory() { }
diff --git a/src/osgEarthFeatures/ScriptEngine.cpp b/src/osgEarthFeatures/ScriptEngine.cpp
index 0c4966c..5599ba5 100644
--- a/src/osgEarthFeatures/ScriptEngine.cpp
+++ b/src/osgEarthFeatures/ScriptEngine.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -88,26 +88,26 @@ ScriptEngineFactory::instance()
 }
 
 ScriptEngine*
-ScriptEngineFactory::create( const std::string& language, const std::string& engineName )
+ScriptEngineFactory::create( const std::string& language, const std::string& engineName, bool quiet)
 {
   ScriptEngineOptions opts;
   opts.setDriver(language + (engineName.empty() ? "" : (std::string("_") + engineName)));
 
-  return create(opts);
+  return create(opts, quiet);
 }
 
 ScriptEngine*
-ScriptEngineFactory::create( const Script& script, const std::string& engineName )
+ScriptEngineFactory::create( const Script& script, const std::string& engineName, bool quiet)
 {
   ScriptEngineOptions opts;
   opts.setDriver(script.getLanguage() + (engineName.empty() ? "" : (std::string("_") + engineName)));
   opts.script() = script;
 
-  return create(opts);
+  return create(opts, quiet);
 }
 
 ScriptEngine*
-ScriptEngineFactory::create( const ScriptEngineOptions& options )
+ScriptEngineFactory::create( const ScriptEngineOptions& options, bool quiet)
 {
     ScriptEngine* scriptEngine = 0L;
 
@@ -127,7 +127,9 @@ ScriptEngineFactory::create( const ScriptEngineOptions& options )
             }
             else
             {
-                OE_WARN << "FAIL, unable to load ScriptEngine driver for \"" << options.getDriver() << "\"" << std::endl;
+                if (!quiet)
+                    OE_WARN << "FAIL, unable to load ScriptEngine driver for \"" << options.getDriver() << "\"" << std::endl;
+
                 instance()->_failedDrivers.push_back(options.getDriver());
             }
         }
@@ -138,7 +140,8 @@ ScriptEngineFactory::create( const ScriptEngineOptions& options )
     }
     else
     {
-        OE_WARN << LC << "FAIL, illegal null driver specification" << std::endl;
+        if (!quiet)
+            OE_WARN << LC << "FAIL, illegal null driver specification" << std::endl;
     }
 
     return scriptEngine;
diff --git a/src/osgEarthFeatures/Session b/src/osgEarthFeatures/Session
index 66515f6..1029ef8 100644
--- a/src/osgEarthFeatures/Session
+++ b/src/osgEarthFeatures/Session
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -22,6 +22,7 @@
 
 #include <osgEarthFeatures/Common>
 #include <osgEarthFeatures/ScriptEngine>
+#include <osgEarthSymbology/ResourceCache>
 #include <osgEarthSymbology/StyleSheet>
 #include <osgEarth/StateSetCache>
 #include <osgEarth/ThreadingUtils>
@@ -92,6 +93,10 @@ namespace osgEarth { namespace Features
         /** The I/O options for operations within this session */
         const osgDB::Options* getDBOptions() const;
 
+        /** Shared resource cache (optional) */
+        void setResourceCache(ResourceCache* cache);
+        ResourceCache* getResourceCache();
+
     public:
         template<typename T>
         struct CreateFunctor {
@@ -109,7 +114,6 @@ namespace osgEarth { namespace Features
          */
         template<typename T>
         T* putObject( const std::string& key, T* object, bool overwrite =true ) {
-            //Threading::ScopedWriteLock lock( _objMapMutex );
             Threading::ScopedMutexLock lock( _objMapMutex );
             ObjectMap::iterator i = _objMap.find(key);
             if ( i != _objMap.end() && !overwrite )
@@ -124,7 +128,6 @@ namespace osgEarth { namespace Features
          */
         template<typename T>
         osg::ref_ptr<T> getObject( const std::string& key ) {
-            //Threading::ScopedReadLock lock( _objMapMutex );
             Threading::ScopedMutexLock lock( _objMapMutex );
             ObjectMap::const_iterator i = _objMap.find(key);
             return i != _objMap.end() ? dynamic_cast<T*>( i->second.get() ) : 0L;
@@ -165,7 +168,6 @@ namespace osgEarth { namespace Features
     private:
         typedef std::map<std::string, osg::ref_ptr<osg::Referenced> > ObjectMap;
         ObjectMap                    _objMap;
-        //Threading::ReadWriteMutex    _objMapMutex;
         Threading::Mutex             _objMapMutex;
 
         URIContext                         _uriContext;
@@ -176,6 +178,7 @@ namespace osgEarth { namespace Features
         osg::ref_ptr<ScriptEngine>         _styleScriptEngine;
         osg::ref_ptr<FeatureSource>        _featureSource;
         osg::ref_ptr<StateSetCache>        _stateSetCache;
+        osg::ref_ptr<ResourceCache>        _resourceCache;
     };
 
 } }
diff --git a/src/osgEarthFeatures/Session.cpp b/src/osgEarthFeatures/Session.cpp
index 79fe906..ed14d08 100644
--- a/src/osgEarthFeatures/Session.cpp
+++ b/src/osgEarthFeatures/Session.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,6 +21,7 @@
 #include <osgEarthFeatures/Script>
 #include <osgEarthFeatures/ScriptEngine>
 #include <osgEarthFeatures/FeatureSource>
+#include <osgEarthSymbology/ResourceCache>
 #include <osgEarth/FileUtils>
 #include <osgEarth/StringUtils>
 #include <osgEarth/Registry>
@@ -47,10 +48,6 @@ _dbOptions     ( dbOptions )
     else
         _styles = new StyleSheet();
 
-    // if no script engine was created when the style was set above, create a default javascript one
-    if (!_styleScriptEngine.valid())
-      _styleScriptEngine = ScriptEngineFactory::create("javascript");
-
     // if the caller did not provide a dbOptions, take it from the map.
     if ( map && !dbOptions )
         _dbOptions = map->getDBOptions();
@@ -63,6 +60,7 @@ _dbOptions     ( dbOptions )
 
 Session::~Session()
 {
+    //nop
 }
 
 const osgDB::Options*
@@ -71,6 +69,18 @@ Session::getDBOptions() const
     return _dbOptions.get();
 }
 
+void
+Session::setResourceCache(ResourceCache* cache)
+{
+    _resourceCache = cache;
+}
+
+ResourceCache*
+Session::getResourceCache()
+{
+    return _resourceCache.get();
+}
+
 MapFrame
 Session::createMapFrame( Map::ModelParts parts ) const
 {
@@ -81,7 +91,6 @@ void
 Session::removeObject( const std::string& key )
 {
     Threading::ScopedMutexLock lock( _objMapMutex );
-    //Threading::ScopedWriteLock lock( _objMapMutex );
     _objMap.erase( key );
 }
 
@@ -89,22 +98,36 @@ void
 Session::setStyles( StyleSheet* value )
 {
     _styles = value ? value : new StyleSheet();
-
-    // Go ahead and create the script engine for the StyleSheet
-    if (_styles && _styles->script())
-      _styleScriptEngine = ScriptEngineFactory::create(Script(_styles->script()->code, _styles->script()->language, _styles->script()->name));
-    else
-      _styleScriptEngine = 0L;
+    _styleScriptEngine = 0L;
+
+    // Create a script engine for the StyleSheet
+    if (_styles)
+    {
+        if (_styles->script())
+        {
+            _styleScriptEngine = ScriptEngineFactory::create( Script(
+                _styles->script()->code, 
+                _styles->script()->language, 
+                _styles->script()->name ) );
+        }
+        else
+        {
+            // If the stylesheet has no script set, create a default JS engine
+            // This enables the use of "inline" scripting in StringExpression
+            // and NumericExpression style values.
+            _styleScriptEngine = ScriptEngineFactory::create("javascript", "", true);
+        }
+    }
 }
 
 ScriptEngine*
 Session::getScriptEngine() const
 {
-  return _styleScriptEngine.get();
+    return _styleScriptEngine.get();
 }
 
 FeatureSource*
 Session::getFeatureSource() const 
 { 
-	return _featureSource.get(); 
+    return _featureSource.get(); 
 }
diff --git a/src/osgEarthFeatures/SubstituteModelFilter b/src/osgEarthFeatures/SubstituteModelFilter
index f1af4ba..d162365 100644
--- a/src/osgEarthFeatures/SubstituteModelFilter
+++ b/src/osgEarthFeatures/SubstituteModelFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/SubstituteModelFilter.cpp b/src/osgEarthFeatures/SubstituteModelFilter.cpp
index 4306b7b..259f8a7 100644
--- a/src/osgEarthFeatures/SubstituteModelFilter.cpp
+++ b/src/osgEarthFeatures/SubstituteModelFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -18,6 +18,8 @@
  */
 #include <osgEarthFeatures/SubstituteModelFilter>
 #include <osgEarthFeatures/FeatureSourceIndexNode>
+#include <osgEarthFeatures/Session>
+#include <osgEarthFeatures/GeometryUtils>
 #include <osgEarthSymbology/MeshConsolidator>
 #include <osgEarth/ECEF>
 #include <osgEarth/VirtualProgram>
@@ -33,6 +35,7 @@
 #include <osg/MatrixTransform>
 #include <osg/NodeVisitor>
 #include <osg/ShapeDrawable>
+#include <osg/AlphaFunc>
 
 #include <osgDB/FileNameUtils>
 #include <osgDB/Registry>
@@ -135,6 +138,9 @@ SubstituteModelFilter::process(const FeatureList&           features,
     // factor to any AutoTransforms directly (cloning them as necessary)
     std::map< std::pair<URI, float>, osg::ref_ptr<osg::Node> > uniqueModels;
 
+    // URI cache speeds up URI creation since it can be slow.
+    osgEarth::fast_map<std::string, URI> uriCache;
+
     // keep track of failed URIs so we don't waste time or warning messages on them
     std::set< URI > missing;
 
@@ -152,9 +158,20 @@ SubstituteModelFilter::process(const FeatureList&           features,
     {
         Feature* input = f->get();
 
-        // evaluate the instance URI expression:
-        StringExpression uriEx = *symbol->url();
-        URI instanceURI( input->eval(uriEx, &context), uriEx.uriContext() );
+        // Run a feature pre-processing script.
+        if ( symbol->script().isSet() )
+        {
+            StringExpression scriptExpr(symbol->script().get());
+            input->eval( scriptExpr, &context );
+        }
+
+		// evaluate the instance URI expression:
+		const std::string& st = input->eval(uriEx, &context);
+		URI& instanceURI = uriCache[st];
+		if(instanceURI.empty()) // Create a map, to reuse URI's, since they take a long time to create
+		{
+			instanceURI = URI( st, uriEx.uriContext() );
+		}
 
         // find the corresponding marker in the cache
         osg::ref_ptr<InstanceResource> instance;
@@ -164,7 +181,6 @@ SubstituteModelFilter::process(const FeatureList&           features,
         // evalute the scale expression (if there is one)
         float scale = 1.0f;
         osg::Matrixd scaleMatrix;
-
         if ( symbol->scale().isSet() )
         {
             scale = input->eval( scaleEx, &context );
@@ -176,21 +192,22 @@ SubstituteModelFilter::process(const FeatureList&           features,
         }
         
         osg::Matrixd rotationMatrix;
-
         if ( modelSymbol && modelSymbol->heading().isSet() )
         {
             float heading = input->eval(headingEx, &context);
             rotationMatrix.makeRotate( osg::Quat(osg::DegreesToRadians(heading), osg::Vec3(0,0,1)) );
         }
 
-        // how that we have a marker source, create a node for it
-        std::pair<URI,float> key( instanceURI, scale );
+		// how that we have a marker source, create a node for it
+		std::pair<URI,float> key( instanceURI, iconSymbol? scale : 1.0f );//use 1.0 for models, since we don't want unique models based on scaling
 
         // cache nodes per instance.
         osg::ref_ptr<osg::Node>& model = uniqueModels[key];
         if ( !model.valid() )
         {
-            context.resourceCache()->getInstanceNode( instance.get(), model );
+            // Always clone the cached instance so we're not processing data that's
+            // already in the scene graph. -gw
+            context.resourceCache()->cloneOrCreateInstanceNode(instance.get(), model);
 
             // if icon decluttering is off, install an AutoTransform.
             if ( iconSymbol )
@@ -227,6 +244,23 @@ SubstituteModelFilter::process(const FeatureList&           features,
                 {
                     osg::Matrixd mat;
 
+                    // need to recalcluate expression-based data per-point, not just per-feature!
+                    if ( symbol->scale().isSet() )
+                    {
+                        scale = input->eval(scaleEx, &context);
+                        if ( scale == 0.0 )
+                            scale = 1.0;
+                        if ( scale != 1.0 )
+                            _normalScalingRequired = true;
+                        scaleMatrix = osg::Matrix::scale( scale, scale, scale );
+                    }
+
+                    if ( modelSymbol->heading().isSet() )
+                    {
+                        float heading = input->eval(headingEx, &context);
+                        rotationMatrix.makeRotate( osg::Quat(osg::DegreesToRadians(heading), osg::Vec3(0,0,1)) );
+                    }
+
                     osg::Vec3d point = (*geom)[i];
                     if ( makeECEF )
                     {
@@ -318,7 +352,11 @@ struct ClusterVisitor : public osg::NodeVisitor
     void apply( osg::Geode& geode )
     {
         // save the geode's drawables..
-        osg::Geode::DrawableList old_drawables = geode.getDrawableList();
+        typedef std::vector<osg::ref_ptr<osg::Drawable> > Drawables;        
+        Drawables old_drawables;
+        old_drawables.reserve( geode.getNumDrawables() );
+        for(unsigned i=0; i<geode.getNumDrawables(); ++i)
+            old_drawables.push_back( geode.getDrawable(i) );
 
         //OE_DEBUG << "ClusterVisitor geode " << &geode << " featureNode=" << _featureNode << " drawables=" << old_drawables.size() << std::endl;
 
@@ -326,7 +364,7 @@ struct ClusterVisitor : public osg::NodeVisitor
         geode.removeDrawables( 0, geode.getNumDrawables() );
 
         // foreach each drawable that was originally in the geode...
-        for( osg::Geode::DrawableList::iterator i = old_drawables.begin(); i != old_drawables.end(); i++ )
+        for( Drawables::iterator i = old_drawables.begin(); i != old_drawables.end(); i++ )
         {
             osg::Geometry* originalDrawable = dynamic_cast<osg::Geometry*>( i->get() );
             if ( !originalDrawable )
@@ -574,6 +612,7 @@ SubstituteModelFilter::push(FeatureList& features, FilterContext& context)
     // return proper context
     context = newContext;
 
+#if 0
     // TODO: OBE due to shader pipeline
     // see if we need normalized normals
     if ( _normalScalingRequired )
@@ -587,6 +626,7 @@ SubstituteModelFilter::push(FeatureList& features, FilterContext& context)
             group->getOrCreateStateSet()->setMode( GL_NORMALIZE, osg::StateAttribute::ON );
         }
     }
+#endif
 
     return group;
 }
diff --git a/src/osgEarthFeatures/TessellateOperator b/src/osgEarthFeatures/TessellateOperator
index b9fc400..8f8c014 100644
--- a/src/osgEarthFeatures/TessellateOperator
+++ b/src/osgEarthFeatures/TessellateOperator
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/TessellateOperator.cpp b/src/osgEarthFeatures/TessellateOperator.cpp
index 3fd393b..679d4ab 100644
--- a/src/osgEarthFeatures/TessellateOperator.cpp
+++ b/src/osgEarthFeatures/TessellateOperator.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -18,6 +18,7 @@
  */
 #include <osgEarthFeatures/TessellateOperator>
 #include <osgEarthSymbology/Geometry>
+#include <osgEarth/GeoMath>
 
 using namespace osgEarth;
 using namespace osgEarth::Features;
diff --git a/src/osgEarthFeatures/TextSymbolizer b/src/osgEarthFeatures/TextSymbolizer
index 214c00e..dddc54f 100644
--- a/src/osgEarthFeatures/TextSymbolizer
+++ b/src/osgEarthFeatures/TextSymbolizer
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/TextSymbolizer.cpp b/src/osgEarthFeatures/TextSymbolizer.cpp
index fadd985..5d0aa95 100644
--- a/src/osgEarthFeatures/TextSymbolizer.cpp
+++ b/src/osgEarthFeatures/TextSymbolizer.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -71,13 +71,24 @@ TextSymbolizer::create(Feature*             feature,
     //TODO: resonsider defaults here
     t->setCharacterSizeMode( osgText::Text::OBJECT_COORDS );
 
-    t->setCharacterSize( _symbol.valid() && _symbol->size().isSet() ? *_symbol->size() : 16.0f );
+    float size = 16.0f;
+    if (_symbol->size().isSet())
+    {
+        NumericExpression sizeExpr = _symbol->size().value();
+        size = feature ? feature->eval(sizeExpr, context) : sizeExpr.eval();
+    }
+    t->setCharacterSize( size );
 
     t->setColor( _symbol.valid() && _symbol->fill().isSet() ? _symbol->fill()->color() : Color::White );
 
     osgText::Font* font = 0L;
     if ( _symbol.valid() && _symbol->font().isSet() )
+    {
         font = osgText::readFontFile( *_symbol->font() );
+        // mitigates mipmapping issues that cause rendering artifacts for some fonts/placement
+        if ( font )
+            font->setGlyphImageMargin( 2 );
+    }
     if ( !font )
         font = Registry::instance()->getDefaultFont();
     if ( font )
diff --git a/src/osgEarthFeatures/TransformFilter b/src/osgEarthFeatures/TransformFilter
index 5e28c27..ff529d8 100644
--- a/src/osgEarthFeatures/TransformFilter
+++ b/src/osgEarthFeatures/TransformFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/TransformFilter.cpp b/src/osgEarthFeatures/TransformFilter.cpp
index 214bf14..aa6a56a 100644
--- a/src/osgEarthFeatures/TransformFilter.cpp
+++ b/src/osgEarthFeatures/TransformFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -132,9 +132,9 @@ TransformFilter::push( FeatureList& input, FilterContext& incx )
     if ( _outputSRS.valid() )
     {
         if ( incx.extent()->isValid() )
-            outcx.profile() = new FeatureProfile( incx.extent()->transform( _outputSRS.get() ) );
+            outcx.setProfile( new FeatureProfile( incx.extent()->transform( _outputSRS.get()) ) );
         else
-            outcx.profile() = new FeatureProfile( incx.profile()->getExtent().transform( _outputSRS.get() ) );
+            outcx.setProfile( new FeatureProfile( incx.profile()->getExtent().transform( _outputSRS.get()) ) );
     }
 
     // set the reference frame to shift data to the centroid. This will
@@ -151,7 +151,7 @@ TransformFilter::push( FeatureList& input, FilterContext& incx )
         {
             localizeGeometry( i->get(), localizer );
         }
-        outcx.setReferenceFrame( localizer );
+        //outcx.setReferenceFrame( localizer );
     }
 
     return outcx;
diff --git a/src/osgEarthFeatures/VirtualFeatureSource b/src/osgEarthFeatures/VirtualFeatureSource
index afdc0d3..7b77ef4 100644
--- a/src/osgEarthFeatures/VirtualFeatureSource
+++ b/src/osgEarthFeatures/VirtualFeatureSource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthFeatures/VirtualFeatureSource.cpp b/src/osgEarthFeatures/VirtualFeatureSource.cpp
index de19ea3..324ff91 100644
--- a/src/osgEarthFeatures/VirtualFeatureSource.cpp
+++ b/src/osgEarthFeatures/VirtualFeatureSource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/Actions b/src/osgEarthQt/Actions
index 807da30..3bdd4b1 100644
--- a/src/osgEarthQt/Actions
+++ b/src/osgEarthQt/Actions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/AnnotationDialogs b/src/osgEarthQt/AnnotationDialogs
index 59bcd84..a03cf8e 100644
--- a/src/osgEarthQt/AnnotationDialogs
+++ b/src/osgEarthQt/AnnotationDialogs
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/AnnotationDialogs.cpp b/src/osgEarthQt/AnnotationDialogs.cpp
index e1cd32a..b6994f1 100644
--- a/src/osgEarthQt/AnnotationDialogs.cpp
+++ b/src/osgEarthQt/AnnotationDialogs.cpp
@@ -1,5 +1,5 @@
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/AnnotationListWidget b/src/osgEarthQt/AnnotationListWidget
index d6384fa..ecd55df 100644
--- a/src/osgEarthQt/AnnotationListWidget
+++ b/src/osgEarthQt/AnnotationListWidget
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/AnnotationListWidget.cpp b/src/osgEarthQt/AnnotationListWidget.cpp
index 7f441b7..149650c 100644
--- a/src/osgEarthQt/AnnotationListWidget.cpp
+++ b/src/osgEarthQt/AnnotationListWidget.cpp
@@ -1,5 +1,5 @@
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -370,4 +370,5 @@ void AnnotationListWidget::onAddFinished(int result)
 
   if (result == QDialog::Accepted)
     refresh();
-}
\ No newline at end of file
+}
+
diff --git a/src/osgEarthQt/AnnotationToolbar b/src/osgEarthQt/AnnotationToolbar
index d8cb82a..aecd4a3 100644
--- a/src/osgEarthQt/AnnotationToolbar
+++ b/src/osgEarthQt/AnnotationToolbar
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/AnnotationToolbar.cpp b/src/osgEarthQt/AnnotationToolbar.cpp
index 8acc748..d2ab30b 100644
--- a/src/osgEarthQt/AnnotationToolbar.cpp
+++ b/src/osgEarthQt/AnnotationToolbar.cpp
@@ -1,5 +1,5 @@
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/CMakeLists.txt b/src/osgEarthQt/CMakeLists.txt
index cc3a3d6..dd451bb 100644
--- a/src/osgEarthQt/CMakeLists.txt
+++ b/src/osgEarthQt/CMakeLists.txt
@@ -8,8 +8,6 @@ SET(LIB_NAME osgEarthQt)
 
 SET(HEADER_PATH ${OSGEARTH_SOURCE_DIR}/include/${LIB_NAME})
 
-INCLUDE( ${QT_USE_FILE} )
-
 # Header files that need moc'd
 set(LIB_MOC_HDRS
     AnnotationDialogs
@@ -36,14 +34,23 @@ set(LIB_QT_UIS
     ui/LOSCreationDialog.ui
 )
 
-QT4_ADD_RESOURCES( LIB_RC_SRCS ${LIB_QT_RCS} )
-
-QT4_WRAP_UI( LIB_UI_HDRS ${LIB_QT_UIS} )
-
-QT4_WRAP_CPP( LIB_UI_SRCS ${LIB_UI_HDRS} OPTIONS "-f" )
-
-QT4_WRAP_CPP( LIB_MOC_SRCS ${LIB_MOC_HDRS} OPTIONS "-f" )
-
+IF(Qt5Widgets_FOUND)
+    QT5_ADD_RESOURCES( LIB_RC_SRCS ${LIB_QT_RCS} )
+    QT5_WRAP_UI( LIB_UI_HDRS ${LIB_QT_UIS} )
+    IF(Qt5Widgets_VERSION VERSION_LESS 5.2.0)
+        QT5_WRAP_CPP( LIB_UI_SRCS ${LIB_UI_HDRS} OPTIONS "-f" )
+        QT5_WRAP_CPP( LIB_MOC_SRCS ${LIB_MOC_HDRS} OPTIONS "-f" )
+    ELSE()
+        QT5_WRAP_CPP( LIB_UI_SRCS ${LIB_UI_HDRS} )
+        QT5_WRAP_CPP( LIB_MOC_SRCS ${LIB_MOC_HDRS} )
+    ENDIF()
+ELSE()
+    INCLUDE( ${QT_USE_FILE} )
+    QT4_ADD_RESOURCES( LIB_RC_SRCS ${LIB_QT_RCS} )
+    QT4_WRAP_UI( LIB_UI_HDRS ${LIB_QT_UIS} )
+    QT4_WRAP_CPP( LIB_UI_SRCS ${LIB_UI_HDRS} OPTIONS "-f" )
+    QT4_WRAP_CPP( LIB_MOC_SRCS ${LIB_MOC_HDRS} OPTIONS "-f" )
+ENDIF()
 
 SET(LIB_PUBLIC_HEADERS
 #   header files go here
@@ -110,4 +117,8 @@ LINK_WITH_VARIABLES(${LIB_NAME} OSG_LIBRARY OSGWIDGET_LIBRARY OSGUTIL_LIBRARY OS
 
 LINK_CORELIB_DEFAULT(${LIB_NAME} ${CMAKE_THREAD_LIBS_INIT} ${MATH_LIBRARY})
 
+IF ( Qt5Widgets_FOUND )
+    qt5_use_modules( ${LIB_NAME} Gui Widgets OpenGL )
+ENDIF( Qt5Widgets_FOUND )
+
 INCLUDE(ModuleInstall OPTIONAL)
diff --git a/src/osgEarthQt/CollapsiblePairWidget b/src/osgEarthQt/CollapsiblePairWidget
index 42a524d..925f3b7 100644
--- a/src/osgEarthQt/CollapsiblePairWidget
+++ b/src/osgEarthQt/CollapsiblePairWidget
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/CollapsiblePairWidget.cpp b/src/osgEarthQt/CollapsiblePairWidget.cpp
index 2f3802f..9c87496 100644
--- a/src/osgEarthQt/CollapsiblePairWidget.cpp
+++ b/src/osgEarthQt/CollapsiblePairWidget.cpp
@@ -1,5 +1,5 @@
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/Common b/src/osgEarthQt/Common
index 665b5e0..7e354ee 100644
--- a/src/osgEarthQt/Common
+++ b/src/osgEarthQt/Common
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/DataManager b/src/osgEarthQt/DataManager
index fdf0f9a..53c3b9b 100644
--- a/src/osgEarthQt/DataManager
+++ b/src/osgEarthQt/DataManager
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/DataManager.cpp b/src/osgEarthQt/DataManager.cpp
index b80550b..9b7dffb 100644
--- a/src/osgEarthQt/DataManager.cpp
+++ b/src/osgEarthQt/DataManager.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/GuiActions b/src/osgEarthQt/GuiActions
index 7c983a7..6aa0ce6 100644
--- a/src/osgEarthQt/GuiActions
+++ b/src/osgEarthQt/GuiActions
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/LOSControlWidget b/src/osgEarthQt/LOSControlWidget
index 5e0b19d..d5d9fd2 100644
--- a/src/osgEarthQt/LOSControlWidget
+++ b/src/osgEarthQt/LOSControlWidget
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/LOSControlWidget.cpp b/src/osgEarthQt/LOSControlWidget.cpp
index 08c8979..9832a1b 100644
--- a/src/osgEarthQt/LOSControlWidget.cpp
+++ b/src/osgEarthQt/LOSControlWidget.cpp
@@ -1,5 +1,5 @@
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/LOSCreationDialog b/src/osgEarthQt/LOSCreationDialog
index 21c9192..d0f2f87 100644
--- a/src/osgEarthQt/LOSCreationDialog
+++ b/src/osgEarthQt/LOSCreationDialog
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/LOSCreationDialog.cpp b/src/osgEarthQt/LOSCreationDialog.cpp
index c145422..59cbdc8 100644
--- a/src/osgEarthQt/LOSCreationDialog.cpp
+++ b/src/osgEarthQt/LOSCreationDialog.cpp
@@ -1,5 +1,5 @@
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/LayerManagerWidget b/src/osgEarthQt/LayerManagerWidget
index 8119c8a..6d66be3 100644
--- a/src/osgEarthQt/LayerManagerWidget
+++ b/src/osgEarthQt/LayerManagerWidget
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -35,6 +35,7 @@
 #include <QSlider>
 #include <QVBoxLayout>
 #include <QWidget>
+#include <QMimeData>
 
 namespace osgEarth { namespace QtGui 
 {
diff --git a/src/osgEarthQt/LayerManagerWidget.cpp b/src/osgEarthQt/LayerManagerWidget.cpp
index 8272742..0e3ce65 100644
--- a/src/osgEarthQt/LayerManagerWidget.cpp
+++ b/src/osgEarthQt/LayerManagerWidget.cpp
@@ -1,5 +1,5 @@
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -41,6 +41,7 @@
 #include <QStandardItemModel>
 #include <QVBoxLayout>
 #include <QWidget>
+#include <QDrag>
 
 using namespace osgEarth;
 using namespace osgEarth::QtGui;
@@ -615,7 +616,7 @@ Action* ModelLayerControlWidget::getDoubleClickAction(const ViewVector& views)
 {
   if (!_doubleClick.valid() && _layer.valid() && _map.valid())
   {
-    osg::ref_ptr<osg::Node> temp = _layer->createSceneGraph( _map.get(), _map->getDBOptions(), 0L );
+    osg::ref_ptr<osg::Node> temp = _layer->getOrCreateSceneGraph( _map.get(), _map->getDBOptions(), 0L );
     if (temp.valid())
     {
       osg::NodePathList nodePaths = temp->getParentalNodePaths();
diff --git a/src/osgEarthQt/MapCatalogWidget b/src/osgEarthQt/MapCatalogWidget
index 73c46c7..4fa4bdc 100644
--- a/src/osgEarthQt/MapCatalogWidget
+++ b/src/osgEarthQt/MapCatalogWidget
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/MapCatalogWidget.cpp b/src/osgEarthQt/MapCatalogWidget.cpp
index 0c946f9..f00b55d 100644
--- a/src/osgEarthQt/MapCatalogWidget.cpp
+++ b/src/osgEarthQt/MapCatalogWidget.cpp
@@ -1,5 +1,5 @@
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -109,7 +109,7 @@ namespace
           osgEarth::ModelLayer* model = dynamic_cast<osgEarth::ModelLayer*>(_layer.get());
           if (model && _map.valid())
           {
-            osg::ref_ptr<osg::Node> temp = model->createSceneGraph( _map.get(), _map->getDBOptions(), 0L );
+            osg::ref_ptr<osg::Node> temp = model->getOrCreateSceneGraph( _map.get(), _map->getDBOptions(), 0L );
             if (temp.valid())
             {
               osg::NodePathList nodePaths = temp->getParentalNodePaths();
diff --git a/src/osgEarthQt/TerrainProfileGraph b/src/osgEarthQt/TerrainProfileGraph
index 06b362c..3d120a0 100644
--- a/src/osgEarthQt/TerrainProfileGraph
+++ b/src/osgEarthQt/TerrainProfileGraph
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -25,6 +25,7 @@
 
 #include <osgEarth/Map>
 #include <osgEarthUtil/TerrainProfile>
+#include <osgEarthUtil/Formatter>
 
 #include <QGraphicsLineItem>
 #include <QGraphicsScene>
@@ -61,6 +62,8 @@ namespace osgEarth { namespace QtGui
 
       virtual ~TerrainProfileGraph();
 
+      void clear();
+
       void setBackgroundColor(const QColor& color);
       const QColor& getBackgroundColor() { return _backgroundColor; }
 
@@ -76,6 +79,8 @@ namespace osgEarth { namespace QtGui
       void setGraphFillColor(const QColor& color);
       const QColor& getGraphFillColor() { return _graphFillColor; }
 
+      void setCoordinateFormatter(osgEarth::Util::Formatter* formatter);
+
       void notifyTerrainGraphChanged() { emit onNotifyTerrainGraphChanged(); }
 
     signals:
@@ -97,6 +102,9 @@ namespace osgEarth { namespace QtGui
       void drawHoverCursor(const QPointF& position);
       void drawSelectionBox(double position);
 
+    public slots:
+      void onCopyToClipboard();
+
     private slots:
       void onTerrainGraphChanged();
 
@@ -104,6 +112,7 @@ namespace osgEarth { namespace QtGui
       osg::ref_ptr<osgEarth::Util::TerrainProfileCalculator> _calculator;
       osg::ref_ptr<TerrainProfilePositionCallback> _positionCallback;
       osg::ref_ptr<TerrainProfileCalculator::ChangedCallback> _graphChangedCallback;
+      osg::ref_ptr<osgEarth::Util::Formatter> _coordinateFormatter;
       QGraphicsScene* _scene;
       QList<QLineF> _graphLines;
       QGraphicsLineItem* _hoverLine;
diff --git a/src/osgEarthQt/TerrainProfileGraph.cpp b/src/osgEarthQt/TerrainProfileGraph.cpp
index af787d4..fca6485 100644
--- a/src/osgEarthQt/TerrainProfileGraph.cpp
+++ b/src/osgEarthQt/TerrainProfileGraph.cpp
@@ -1,5 +1,5 @@
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -21,7 +21,11 @@
 #include <osgEarthQt/GuiActions>
 
 #include <osgEarthUtil/TerrainProfile>
+#include <osgEarthUtil/LatLongFormatter>
 
+#include <QApplication>
+#include <QClipboard>
+#include <QMimeData>
 #include <QGraphicsLineItem>
 #include <QGraphicsRectItem>
 #include <QGraphicsScene>
@@ -36,7 +40,6 @@
 #include <QResizeEvent>
 #include <QToolTip>
 
-
 using namespace osgEarth;
 using namespace osgEarth::Util;
 using namespace osgEarth::QtGui;
@@ -84,8 +87,8 @@ const int TerrainProfileGraph::OVERLAY_Z = 30;
 
 
 TerrainProfileGraph::TerrainProfileGraph(TerrainProfileCalculator* calculator, TerrainProfilePositionCallback* callback)
-  : QGraphicsView(), _calculator(calculator), _positionCallback(callback), _graphFont(tr("Helvetica,Verdana,Arial"), 8),
-    _backgroundColor(128, 128, 128), _fieldColor(204, 204, 204), _axesColor(255, 255, 255), 
+  : QGraphicsView(), _calculator(calculator), _positionCallback(callback), 
+    _backgroundColor(128, 128, 128), _fieldColor(204, 204, 204), _axesColor(255, 255, 255),
     _graphColor(0, 128, 0), _graphFillColor(128, 255, 128, 192), _graphField(0, 0, 0, 0),
     _totalDistance(0.0), _graphMinY(0), _graphMaxY(0), _graphWidth(500), _graphHeight(309),
     _selecting(false)
@@ -93,6 +96,8 @@ TerrainProfileGraph::TerrainProfileGraph(TerrainProfileCalculator* calculator, T
   setMouseTracking(true);
 
   _graphFont.setStyleHint(QFont::SansSerif);
+  _graphFont.setFamily(_graphFont.defaultFamily());
+  _graphFont.setPixelSize( 10 );
 
   _linePen.setWidth(2);
   _linePen.setBrush(QBrush(_graphColor));
@@ -106,14 +111,20 @@ TerrainProfileGraph::TerrainProfileGraph(TerrainProfileCalculator* calculator, T
   _scene = new QGraphicsScene(0, 0, _graphWidth, _graphHeight);
   _scene->setBackgroundBrush(QBrush(_backgroundColor));
   setScene(_scene);
-  
+
   redrawGraph();
 
   _graphChangedCallback = new TerrainGraphChangedShim(this);
   connect(this, SIGNAL(onNotifyTerrainGraphChanged()), this, SLOT(onTerrainGraphChanged()), Qt::QueuedConnection);
   //connect(_graphChangedCallback, SIGNAL(graphChanged()), this, SLOT(onGraphChanged()), Qt::QueuedConnection);
   if (_calculator.valid())
-    _calculator->addChangedCallback(_graphChangedCallback);
+  {
+      _calculator->addChangedCallback(_graphChangedCallback);
+  }
+
+  _coordinateFormatter = new osgEarth::Util::LatLongFormatter(
+    osgEarth::Util::LatLongFormatter::FORMAT_DECIMAL_DEGREES,
+    osgEarth::Util::LatLongFormatter::USE_COLONS);
 }
 
 TerrainProfileGraph::~TerrainProfileGraph()
@@ -123,6 +134,46 @@ TerrainProfileGraph::~TerrainProfileGraph()
   //  _calculator->removeChangedCallback(_graphChangedCallback);
 }
 
+void TerrainProfileGraph::clear()
+{
+  _scene->clear();
+  _graphLines.clear();
+  _graphField.setCoords(0, 0, 0, 0);
+  _hoverLine = 0L;
+}
+
+void TerrainProfileGraph::onCopyToClipboard()
+{
+  const osgEarth::Util::TerrainProfile profile = _calculator->getProfile();
+  if (profile.getNumElevations() > 0)
+  {
+    const QLatin1String fieldSeparator(",");
+    GeoPoint startPt = _calculator->getStart(ALTMODE_ABSOLUTE);
+    GeoPoint endPt = _calculator->getEnd(ALTMODE_ABSOLUTE);
+    QString profileInfo = QString("Start:,%1,%2\nEnd:,%3,%4\n")
+      .arg(_coordinateFormatter->format(startPt).c_str()).arg(startPt.alt())
+      .arg(_coordinateFormatter->format(endPt).c_str()).arg(endPt.alt());
+    QString distanceInfo("Distance:");
+    QString elevationInfo("Elevation:");
+    for (unsigned int i = 0; i < profile.getNumElevations(); i++)
+    {
+      distanceInfo += fieldSeparator + QString::number(profile.getDistance(i));
+      elevationInfo += fieldSeparator + QString::number(profile.getElevation(i));
+    }
+    profileInfo += distanceInfo + QString("\n") + elevationInfo;
+
+    QImage graphImage(_graphWidth, _graphHeight, QImage::Format_RGB32);
+    QPainter p;
+    p.begin(&graphImage);
+    _scene->render(&p);
+    p.end();
+    QMimeData* clipData = new QMimeData();
+    clipData->setText(profileInfo);
+    clipData->setImageData(graphImage);
+    QApplication::clipboard()->setMimeData(clipData);
+  }
+}
+
 void TerrainProfileGraph::setBackgroundColor(const QColor& color)
 {
   _backgroundColor = color;
@@ -157,6 +208,11 @@ void TerrainProfileGraph::setGraphFillColor(const QColor& color)
   redrawGraph();
 }
 
+void TerrainProfileGraph::setCoordinateFormatter(osgEarth::Util::Formatter* formatter)
+{
+  _coordinateFormatter = formatter;
+}
+
 void TerrainProfileGraph::resizeEvent(QResizeEvent* e)
 {
   _graphWidth = e->size().width() - 20;
@@ -192,8 +248,8 @@ void TerrainProfileGraph::mouseReleaseEvent(QMouseEvent* e)
     double endDistanceFactor = ((zoomEnd - _graphField.x()) / (double)_graphField.width());
 
     osg::Vec3d worldStart, worldEnd;
-    _calculator->getStart().toWorld(worldStart);
-    _calculator->getEnd().toWorld(worldEnd);
+    _calculator->getStart(ALTMODE_ABSOLUTE).toWorld(worldStart);
+    _calculator->getEnd(ALTMODE_ABSOLUTE).toWorld(worldEnd);
 
     double newStartWorldX = (worldEnd.x() - worldStart.x()) * startDistanceFactor + worldStart.x();
     double newStartWorldY = (worldEnd.y() - worldStart.y()) * startDistanceFactor + worldStart.y();
@@ -240,6 +296,10 @@ void TerrainProfileGraph::redrawGraph()
     _totalDistance = profile.getTotalDistance();
 
     int mag = (int)pow(10.0, (double)((int)log10(maxElevation - minElevation)));
+    if( mag == 0 )
+    {
+        mag = 1;
+    }
     _graphMinY = ((int)(minElevation / mag) - (minElevation < 0 ? 1 : 0)) * mag;
     _graphMaxY = ((int)(maxElevation / mag) + (maxElevation < 0 ? 0 : 1)) * mag;
 
@@ -434,8 +494,8 @@ void TerrainProfileGraph::drawHoverCursor(const QPointF& position)
       double distanceFactor = ((xPos - _graphField.x()) / (double)_graphField.width());
 
       osg::Vec3d worldStart, worldEnd;
-      _calculator->getStart().toWorld(worldStart);
-      _calculator->getEnd().toWorld(worldEnd);
+      _calculator->getStart(ALTMODE_ABSOLUTE).toWorld(worldStart);
+      _calculator->getEnd(ALTMODE_ABSOLUTE).toWorld(worldEnd);
 
       double worldX = (worldEnd.x() - worldStart.x()) * distanceFactor + worldStart.x();
       double worldY = (worldEnd.y() - worldStart.y()) * distanceFactor + worldStart.y();
@@ -463,7 +523,14 @@ void TerrainProfileGraph::drawHoverCursor(const QPointF& position)
   distanceText->setBrush(QBrush(_axesColor));
   distanceText->setFont(_graphFont);
   distanceText->setZValue(OVERLAY_Z);
-  distanceText->setPos(xPos - 2 - distanceText->boundingRect().width(), _graphField.y() + _graphField.height() + 2);
+  if(xPos - 2 - distanceText->boundingRect().width() > _graphField.x())
+  {
+      distanceText->setPos(xPos - 2 - distanceText->boundingRect().width(), _graphField.y() + _graphField.height() + 2);
+  }
+  else
+  {
+      distanceText->setPos(xPos + 2, _graphField.y() + _graphField.height() + 2);
+  }
   distanceText->setParentItem(_hoverLine);
 
   // Draw selection box
diff --git a/src/osgEarthQt/TerrainProfileWidget b/src/osgEarthQt/TerrainProfileWidget
index 53d94bb..79961ba 100644
--- a/src/osgEarthQt/TerrainProfileWidget
+++ b/src/osgEarthQt/TerrainProfileWidget
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -50,6 +50,8 @@ namespace osgEarth { namespace QtGui
 
       virtual ~TerrainProfileWidget();
 
+      void setCoordinateFormatter(osgEarth::Util::Formatter* coordinateFormatter);
+
       void setActiveView(osgViewer::View* view);
 
       void setActiveViews(const ViewVector& views);
@@ -64,6 +66,9 @@ namespace osgEarth { namespace QtGui
     signals:
         void onNotifyTerrainProfileChanged();
 
+    public slots:
+      void onClearProfiles();
+
     private slots:
       void onCaptureToggled(bool checked);
       void onUndoZoom();
@@ -100,6 +105,8 @@ namespace osgEarth { namespace QtGui
       TerrainProfileGraph*                                    _graph;
       QAction*                                                _captureAction;
       QAction*                                                _undoZoomAction;
+      QAction*                                                _clearProfilesAction;
+      QAction*                                                _copyClipboardAction;
       osg::ref_ptr<osgGA::GUIEventHandler>                    _guiHandler;
       std::vector<StartEndPair>                               _profileStack;
       osg::ref_ptr<osgEarth::Annotation::FeatureNode>         _lineNode;
diff --git a/src/osgEarthQt/TerrainProfileWidget.cpp b/src/osgEarthQt/TerrainProfileWidget.cpp
index aed2dac..4b17ba2 100644
--- a/src/osgEarthQt/TerrainProfileWidget.cpp
+++ b/src/osgEarthQt/TerrainProfileWidget.cpp
@@ -1,5 +1,5 @@
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -94,17 +94,23 @@ void TerrainProfileWidget::initialize()
   _captureAction = new QAction(QIcon(":/images/crosshair.png"), tr(""), this);
   _captureAction->setToolTip(tr("Capture map clicks"));
   _captureAction->setCheckable(true);
-  connect(_captureAction, SIGNAL(toggled(bool)), this, SLOT(onCaptureToggled(bool)));
 
   _undoZoomAction = new QAction(QIcon(":/images/undo.png"), tr(""), this);
   _undoZoomAction->setToolTip(tr("Undo zoom"));
-  connect(_undoZoomAction, SIGNAL(triggered()), this, SLOT(onUndoZoom()));
   _undoZoomAction->setEnabled(false);
 
+  _clearProfilesAction = new QAction(QIcon(":/images/close.png"), tr(""), this);
+  _clearProfilesAction->setToolTip(tr("Clear profiles"));
+
+  _copyClipboardAction = new QAction(QIcon(":/images/copy.png"), tr(""), this);
+  _copyClipboardAction->setToolTip(tr("Copy"));
+
   // create toolbar
   QToolBar *buttonToolbar = new QToolBar(tr("Action Toolbar"));
   buttonToolbar->setIconSize(QSize(24, 24));
   buttonToolbar->addAction(_captureAction);
+  buttonToolbar->addAction(_clearProfilesAction);
+  buttonToolbar->addAction(_copyClipboardAction);
   buttonToolbar->addSeparator();
   buttonToolbar->addAction(_undoZoomAction);
   vStack->addWidget(buttonToolbar);
@@ -113,11 +119,17 @@ void TerrainProfileWidget::initialize()
   _graph = new TerrainProfileGraph(_calculator, new UpdatePositionShim(this));
   vStack->addWidget(_graph);
 
+  // Connect the action signals/slots
+  connect(_captureAction, SIGNAL(toggled(bool)), this, SLOT(onCaptureToggled(bool)));
+  connect(_undoZoomAction, SIGNAL(triggered()), this, SLOT(onUndoZoom()));
+  connect(_clearProfilesAction, SIGNAL(triggered()), this, SLOT(onClearProfiles()));
+  connect(_copyClipboardAction, SIGNAL(triggered()), _graph, SLOT(onCopyToClipboard()));
+
   // define a style for the line
   osgEarth::Symbology::LineSymbol* ls = _lineStyle.getOrCreateSymbol<osgEarth::Symbology::LineSymbol>();
   ls->stroke()->color() = osgEarth::Symbology::Color::White;
   ls->stroke()->width() = 2.0f;
-  ls->tessellation() = 20;
+  ls->tessellation() = 500;
   _lineStyle.getOrCreate<osgEarth::Symbology::AltitudeSymbol>()->clamping() = osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
 
   // load marker image
@@ -145,6 +157,14 @@ void TerrainProfileWidget::initialize()
   _placeStyle.getOrCreate<AltitudeSymbol>()->clamping() = AltitudeSymbol::CLAMP_TO_TERRAIN;
 }
 
+void TerrainProfileWidget::setCoordinateFormatter(osgEarth::Util::Formatter* coordinateFormatter)
+{
+  if(_graph)
+  {
+    _graph->setCoordinateFormatter(coordinateFormatter);
+  }
+}
+
 void TerrainProfileWidget::setActiveView(osgViewer::View* view)
 {
   removeViews();
@@ -257,6 +277,25 @@ void TerrainProfileWidget::onCaptureToggled(bool checked)
   ((TerrainProfileMouseHandler*)_guiHandler.get())->setCapturing(checked);
 }
 
+void TerrainProfileWidget::onClearProfiles()
+{
+  if (_lineNode.valid())
+  {
+    _root->removeChild(_lineNode.get());
+    _lineNode = 0;
+  }
+  if (_markerNode.valid())
+  {
+    _root->removeChild(_markerNode.get());
+    _markerNode = 0;
+  }
+  _profileStack.clear();
+  _undoZoomAction->setEnabled(false);
+  _calculator->setStartEnd(GeoPoint::INVALID, GeoPoint::INVALID);
+  _graph->clear();
+  refreshViews();
+}
+
 void TerrainProfileWidget::onUndoZoom()
 {
   _profileStack.pop_back();
@@ -270,7 +309,7 @@ void TerrainProfileWidget::drawProfileLine()
   line->push_back( _calculator->getEnd().vec3d() );
 
   osgEarth::Features::Feature* feature = new osgEarth::Features::Feature(line, _mapNode->getMapSRS());
-  feature->geoInterp() = osgEarth::GEOINTERP_GREAT_CIRCLE;    
+  feature->geoInterp() = osgEarth::GEOINTERP_GREAT_CIRCLE;
   feature->style() = _lineStyle;
 
   if (!_lineNode.valid())
diff --git a/src/osgEarthQt/ViewWidget b/src/osgEarthQt/ViewWidget
index 88df14e..11c2bac 100644
--- a/src/osgEarthQt/ViewWidget
+++ b/src/osgEarthQt/ViewWidget
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/ViewWidget.cpp b/src/osgEarthQt/ViewWidget.cpp
index b0a139b..833c438 100644
--- a/src/osgEarthQt/ViewWidget.cpp
+++ b/src/osgEarthQt/ViewWidget.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -21,8 +21,7 @@
 #include <osgQt/GraphicsWindowQt>
 #include <osgViewer/View>
 
-#include <QtGui>
-#include <QtGui/QWidget>
+#include <QWidget>
 
 #define LC "[ViewWidget] "
 
diff --git a/src/osgEarthQt/ViewerWidget b/src/osgEarthQt/ViewerWidget
index f657e15..75af407 100644
--- a/src/osgEarthQt/ViewerWidget
+++ b/src/osgEarthQt/ViewerWidget
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthQt/ViewerWidget.cpp b/src/osgEarthQt/ViewerWidget.cpp
index 2779128..164712b 100644
--- a/src/osgEarthQt/ViewerWidget.cpp
+++ b/src/osgEarthQt/ViewerWidget.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -27,7 +27,7 @@
 
 #include <QtGui>
 #include <QtCore/QTimer>
-#include <QtGui/QWidget>
+#include <QWidget>
 
 using namespace osgEarth;
 using namespace osgEarth::QtGui;
diff --git a/src/osgEarthQt/images.qrc b/src/osgEarthQt/images.qrc
index 2f37408..e6965ef 100644
--- a/src/osgEarthQt/images.qrc
+++ b/src/osgEarthQt/images.qrc
@@ -3,6 +3,7 @@
         <file>images/minus.png</file>
         <file>images/plus.png</file>
         <file>images/edit.png</file>
+        <file>images/copy.png</file>
         <file>images/crosshair.png</file>
         <file>images/undo.png</file>
         <file>images/marker.png</file>
diff --git a/src/osgEarthQt/images/copy.png b/src/osgEarthQt/images/copy.png
new file mode 100644
index 0000000..4ba5ad7
Binary files /dev/null and b/src/osgEarthQt/images/copy.png differ
diff --git a/src/osgEarthSymbology/AltitudeSymbol b/src/osgEarthSymbology/AltitudeSymbol
index 6cdbe79..b84b735 100644
--- a/src/osgEarthSymbology/AltitudeSymbol
+++ b/src/osgEarthSymbology/AltitudeSymbol
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/AltitudeSymbol.cpp b/src/osgEarthSymbology/AltitudeSymbol.cpp
index f4a299c..2fb3373 100644
--- a/src/osgEarthSymbology/AltitudeSymbol.cpp
+++ b/src/osgEarthSymbology/AltitudeSymbol.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -29,7 +29,7 @@ Symbol             ( conf ),
 _clamping          ( CLAMP_NONE ),
 _technique         ( TECHNIQUE_MAP ),
 _binding           ( BINDING_VERTEX ),
-_resolution        ( 0.001f ),
+_resolution        ( 0.0 ), //0.001f ),
 _verticalScale     ( NumericExpression(1.0) ),
 _verticalOffset    ( NumericExpression(0.0) )
 {
@@ -39,7 +39,8 @@ _verticalOffset    ( NumericExpression(0.0) )
 Config 
 AltitudeSymbol::getConfig() const
 {
-    Config conf;
+    Config conf = Symbol::getConfig();
+
     conf.key() = "altitude";
     conf.addIfSet   ( "clamping",  "none",       _clamping, CLAMP_NONE );
     conf.addIfSet   ( "clamping",  "terrain",    _clamping, CLAMP_TO_TERRAIN );
@@ -129,4 +130,7 @@ AltitudeSymbol::parseSLD(const Config& c, Style& style)
     else if ( match(c.key(), "altitude-scale") ) {
         style.getOrCreate<AltitudeSymbol>()->verticalScale() = NumericExpression( c.value() );
     }
+    else if ( match(c.key(), "altitude-script") ) {
+        style.getOrCreate<AltitudeSymbol>()->script() = StringExpression(c.value());
+    }
 }
diff --git a/src/osgEarthSymbology/Color b/src/osgEarthSymbology/Color
index dd3cbbd..e52c82c 100644
--- a/src/osgEarthSymbology/Color
+++ b/src/osgEarthSymbology/Color
@@ -1,6 +1,6 @@
 /* --*-c++-*-- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/Color.cpp b/src/osgEarthSymbology/Color.cpp
index acb866e..85fb7bb 100644
--- a/src/osgEarthSymbology/Color.cpp
+++ b/src/osgEarthSymbology/Color.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -124,8 +124,7 @@ osg::Vec4f( rhs )
 /** Parses a hex color string ("#rrggbb", "#rrggbbaa", "0xrrggbb", etc.) into an OSG color. */
 Color::Color( const std::string& input, Format format )
 {
-    std::string t = input;
-    std::transform( t.begin(), t.end(), t.begin(), ::tolower );
+    std::string t = osgEarth::toLower(input);
     osg::Vec4ub c(0,0,0,255);
 
     unsigned e = 
diff --git a/src/osgEarthSymbology/Common b/src/osgEarthSymbology/Common
index f25f83d..75be97e 100644
--- a/src/osgEarthSymbology/Common
+++ b/src/osgEarthSymbology/Common
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/CssUtils b/src/osgEarthSymbology/CssUtils
index 3952a76..4b99cc7 100644
--- a/src/osgEarthSymbology/CssUtils
+++ b/src/osgEarthSymbology/CssUtils
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/CssUtils.cpp b/src/osgEarthSymbology/CssUtils.cpp
index 3367aaf..f88eb10 100644
--- a/src/osgEarthSymbology/CssUtils.cpp
+++ b/src/osgEarthSymbology/CssUtils.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/Expression b/src/osgEarthSymbology/Expression
index decffaa..c2992e1 100644
--- a/src/osgEarthSymbology/Expression
+++ b/src/osgEarthSymbology/Expression
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/Expression.cpp b/src/osgEarthSymbology/Expression.cpp
index 4fbff7b..f23c1a4 100644
--- a/src/osgEarthSymbology/Expression.cpp
+++ b/src/osgEarthSymbology/Expression.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/ExtrusionSymbol b/src/osgEarthSymbology/ExtrusionSymbol
index 4197995..685d59f 100644
--- a/src/osgEarthSymbology/ExtrusionSymbol
+++ b/src/osgEarthSymbology/ExtrusionSymbol
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/ExtrusionSymbol.cpp b/src/osgEarthSymbology/ExtrusionSymbol.cpp
index 433860d..f4e836d 100644
--- a/src/osgEarthSymbology/ExtrusionSymbol.cpp
+++ b/src/osgEarthSymbology/ExtrusionSymbol.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -82,4 +82,7 @@ ExtrusionSymbol::parseSLD(const Config& c, Style& style)
     else if ( match(c.key(), "extrusion-wall-gradient") ) {
         style.getOrCreate<ExtrusionSymbol>()->wallGradientPercentage() = as<float>(c.value(), 0.0f);
     }
+    else if ( match(c.key(), "extrusion-script") ) {
+        style.getOrCreate<ExtrusionSymbol>()->script() = StringExpression(c.value());
+    }
 }
diff --git a/src/osgEarthSymbology/Fill b/src/osgEarthSymbology/Fill
index 333fe7b..542f3c9 100644
--- a/src/osgEarthSymbology/Fill
+++ b/src/osgEarthSymbology/Fill
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/Fill.cpp b/src/osgEarthSymbology/Fill.cpp
index 5ef83d0..fc02379 100644
--- a/src/osgEarthSymbology/Fill.cpp
+++ b/src/osgEarthSymbology/Fill.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/GEOS b/src/osgEarthSymbology/GEOS
index de52156..bd11e43 100644
--- a/src/osgEarthSymbology/GEOS
+++ b/src/osgEarthSymbology/GEOS
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -31,14 +31,21 @@ namespace osgEarth { namespace Symbology
 {
     using namespace osgEarth;
 
-    class GEOSUtils // not exported
+    class GEOSContext
     {
     public:
+        GEOSContext();
+        ~GEOSContext();
 
-        static Symbology::Geometry* exportGeometry( const geos::geom::Geometry* input );
+    public:
+        Symbology::Geometry* exportGeometry(const geos::geom::Geometry* input);
+
+        geos::geom::Geometry* importGeometry( const Symbology::Geometry* input );
 
-        static geos::geom::Geometry* importGeometry( const Symbology::Geometry* input );
+        void disposeGeometry(geos::geom::Geometry* input);
 
+    protected:
+        geos::geom::GeometryFactory* _factory;
     };
 
 } } // namespace osgEarth::Features
diff --git a/src/osgEarthSymbology/GEOS.cpp b/src/osgEarthSymbology/GEOS.cpp
index 097fdee..6f7f1bd 100644
--- a/src/osgEarthSymbology/GEOS.cpp
+++ b/src/osgEarthSymbology/GEOS.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -43,190 +43,207 @@ using namespace geos;
 using namespace geos::operation;
 
 
-static
-geom::CoordinateSequence*
-vec3dArray2CoordSeq( const Symbology::Geometry* input, bool close, const geom::CoordinateSequenceFactory* factory )
-{   
-    bool needToClose = close && input->size() > 2 && input->front() != input->back();
-
-    std::vector<geos::geom::Coordinate>* coords = new std::vector<geom::Coordinate>();
-    coords->reserve( input->size() + (needToClose ? 1 : 0) );
-    for( osg::Vec3dArray::const_iterator i = input->begin(); i != input->end(); ++i )
-    {
-        coords->push_back( geom::Coordinate( i->x(), i->y() ) ); //, i->z() ));
-    }
-    if ( needToClose )
-    {
-        coords->push_back( coords->front() );
-    }
-    geom::CoordinateSequence* seq = factory->create( coords );
-
-    return seq;
-}
-
-static geom::Geometry*
-import( const Symbology::Geometry* input, const geom::GeometryFactory* f )
+namespace
 {
-    geom::Geometry* output = 0L;
+    geom::CoordinateSequence*
+    vec3dArray2CoordSeq( const Symbology::Geometry* input, bool close, const geom::CoordinateSequenceFactory* factory )
+    {   
+        bool needToClose = close && input->size() > 2 && input->front() != input->back();
 
-    if ( input->getType() == Symbology::Geometry::TYPE_UNKNOWN )
-    {
-        output = 0L;
+        std::vector<geos::geom::Coordinate>* coords = new std::vector<geom::Coordinate>();
+        coords->reserve( input->size() + (needToClose ? 1 : 0) );
+        for( osg::Vec3dArray::const_iterator i = input->begin(); i != input->end(); ++i )
+        {
+            coords->push_back( geom::Coordinate( i->x(), i->y() ) ); //, i->z() ));
+        }
+        if ( needToClose )
+        {
+            coords->push_back( coords->front() );
+        }
+        geom::CoordinateSequence* seq = factory->create( coords );
+
+        return seq;
     }
-    else if ( input->getType() == Symbology::Geometry::TYPE_MULTI )
-    {
-        const Symbology::MultiGeometry* multi = static_cast<const Symbology::MultiGeometry*>( input );
 
-        Symbology::Geometry::Type compType = multi->getComponentType();
+    geom::Geometry*
+    import( const Symbology::Geometry* input, const geom::GeometryFactory* f )
+    {
+        geom::Geometry* output = 0L;
 
-        std::vector<geom::Geometry*>* children = new std::vector<geom::Geometry*>();
-        for( Symbology::GeometryCollection::const_iterator i = multi->getComponents().begin(); i != multi->getComponents().end(); ++i ) 
+        if ( input->getType() == Symbology::Geometry::TYPE_UNKNOWN )
         {
-            geom::Geometry* child = import( i->get(), f );
-            if ( child )
-                children->push_back( child );
+            output = 0L;
         }
-        if ( children->size() > 0 )
+        else if ( input->getType() == Symbology::Geometry::TYPE_MULTI )
         {
-            if ( compType == Symbology::Geometry::TYPE_POLYGON )
-                output = f->createMultiPolygon( children );
-            else if ( compType == Symbology::Geometry::TYPE_LINESTRING )
-                output = f->createMultiLineString( children );
-            else if ( compType == Symbology::Geometry::TYPE_POINTSET )
-                output = f->createMultiPoint( children );
+            const Symbology::MultiGeometry* multi = static_cast<const Symbology::MultiGeometry*>( input );
+
+            Symbology::Geometry::Type compType = multi->getComponentType();
+
+            std::vector<geom::Geometry*>* children = new std::vector<geom::Geometry*>();
+            for( Symbology::GeometryCollection::const_iterator i = multi->getComponents().begin(); i != multi->getComponents().end(); ++i ) 
+            {
+                geom::Geometry* child = import( i->get(), f );
+                if ( child )
+                    children->push_back( child );
+            }
+            if ( children->size() > 0 )
+            {
+                if ( compType == Symbology::Geometry::TYPE_POLYGON )
+                    output = f->createMultiPolygon( children );
+                else if ( compType == Symbology::Geometry::TYPE_LINESTRING )
+                    output = f->createMultiLineString( children );
+                else if ( compType == Symbology::Geometry::TYPE_POINTSET )
+                    output = f->createMultiPoint( children );
+                else
+                    output = f->createGeometryCollection( children );
+            }
             else
-                output = f->createGeometryCollection( children );
+                delete children;
         }
         else
-            delete children;
-    }
-    else
-    {
-        // any other type will at least contain points:
-        geom::CoordinateSequence* seq = 0L;
-        try
         {
-            switch( input->getType() )
+            // any other type will at least contain points:
+            geom::CoordinateSequence* seq = 0L;
+            try
             {
-            case Symbology::Geometry::TYPE_UNKNOWN: break;
-            case Symbology::Geometry::TYPE_MULTI: break;
-            case Symbology::Geometry::TYPE_POINTSET:
-                seq = vec3dArray2CoordSeq( input, false, f->getCoordinateSequenceFactory() );
-                if ( seq ) output = f->createPoint( seq );
-                break;
-
-            case Symbology::Geometry::TYPE_LINESTRING:
-                seq = vec3dArray2CoordSeq( input, false, f->getCoordinateSequenceFactory() );
-                if ( seq ) output = f->createLineString( seq );
-                break;
-
-            case Symbology::Geometry::TYPE_RING:
-                seq = vec3dArray2CoordSeq( input, true, f->getCoordinateSequenceFactory() );
-                if ( seq ) output = f->createLinearRing( seq );
-                break;
-
-            case Symbology::Geometry::TYPE_POLYGON:
-                seq = vec3dArray2CoordSeq( input, true, f->getCoordinateSequenceFactory() );
-                geom::LinearRing* shell = 0L;
-                if ( seq )
-                    shell = f->createLinearRing( seq );
-
-                if ( shell )
+                switch( input->getType() )
                 {
-                    const Symbology::Polygon* poly = static_cast<const Symbology::Polygon*>(input);
-                    std::vector<geom::Geometry*>* holes = poly->getHoles().size() > 0 ? new std::vector<geom::Geometry*>() : 0L;
-                    for( Symbology::RingCollection::const_iterator r = poly->getHoles().begin(); r != poly->getHoles().end(); ++r )
-                    {
-                        geom::Geometry* hole = import( r->get(), f );
-                        if ( hole ) holes->push_back( hole );
-                    }
-                    if ( holes && holes->size() == 0 )
+                case Symbology::Geometry::TYPE_UNKNOWN: 
+                    break;
+                case Symbology::Geometry::TYPE_MULTI: break;
+
+                case Symbology::Geometry::TYPE_POINTSET:
+                    seq = vec3dArray2CoordSeq( input, false, f->getCoordinateSequenceFactory() );
+                    if ( seq ) output = f->createPoint( seq );
+                    break;
+
+                case Symbology::Geometry::TYPE_LINESTRING:
+                    seq = vec3dArray2CoordSeq( input, false, f->getCoordinateSequenceFactory() );
+                    if ( seq ) output = f->createLineString( seq );
+                    break;
+
+                case Symbology::Geometry::TYPE_RING:
+                    seq = vec3dArray2CoordSeq( input, true, f->getCoordinateSequenceFactory() );
+                    if ( seq ) output = f->createLinearRing( seq );
+                    break;
+
+                case Symbology::Geometry::TYPE_POLYGON:
+                    seq = vec3dArray2CoordSeq( input, true, f->getCoordinateSequenceFactory() );
+                    geom::LinearRing* shell = 0L;
+                    if ( seq )
+                        shell = f->createLinearRing( seq );
+
+                    if ( shell )
                     {
-                        delete holes;
-                        holes = 0L;
+                        const Symbology::Polygon* poly = static_cast<const Symbology::Polygon*>(input);
+                        std::vector<geom::Geometry*>* holes = poly->getHoles().size() > 0 ? new std::vector<geom::Geometry*>() : 0L;
+                        for( Symbology::RingCollection::const_iterator r = poly->getHoles().begin(); r != poly->getHoles().end(); ++r )
+                        {
+                            geom::Geometry* hole = import( r->get(), f );
+                            if ( hole ) holes->push_back( hole );
+                        }
+                        if ( holes && holes->size() == 0 )
+                        {
+                            delete holes;
+                            holes = 0L;
+                        }
+                        output = f->createPolygon( shell, holes );
                     }
-                    output = f->createPolygon( shell, holes );
-                }
                 
-                break;
+                    break;
+                }
+            }
+            catch( util::IllegalArgumentException )
+            {
+                // catch GEOS exceptions..
+                //if ( seq )
+                //    delete seq;
+
+                OE_NOTICE << "GEOS::import: Removed degenerate geometry" << std::endl;
             }
         }
-        catch( util::IllegalArgumentException )
+
+        return output;
+    }
+
+    Symbology::Geometry*
+    exportPolygon( const geom::Polygon* input )
+    {
+        Symbology::Polygon* output = 0L;
+
+        const geom::LineString* outerRing = input->getExteriorRing();
+        if ( outerRing )
         {
-            // catch GEOS exceptions..
-            //if ( seq )
-            //    delete seq;
+            // leaks here:
+            const geom::CoordinateSequence* s = outerRing->getCoordinatesRO();
+
+            output = new Symbology::Polygon( s->getSize() );
+
+            for( unsigned int j=0; j<s->getSize(); j++ ) 
+            {
+                const geom::Coordinate& c = s->getAt( j );
+                output->push_back( osg::Vec3d( c.x, c.y, 0 ) ); 
+            }
+            output->rewind( Symbology::Ring::ORIENTATION_CCW );
 
-            OE_NOTICE << "GEOS::import: Removed degenerate geometry" << std::endl;
+            for( unsigned k=0; k < input->getNumInteriorRing(); k++ )
+            {
+                const geom::LineString* inner = input->getInteriorRingN( k );
+                const geom::CoordinateSequence* s = inner->getCoordinatesRO();
+                Symbology::Ring* hole = new Symbology::Ring( s->getSize() );
+                for( unsigned int m = 0; m<s->getSize(); m++ )
+                {
+                    const geom::Coordinate& c = s->getAt( m );
+                    hole->push_back( osg::Vec3d( c.x, c.y, 0 ) );
+                }
+                hole->rewind( Symbology::Ring::ORIENTATION_CW );
+                output->getHoles().push_back( hole );
+            }
         }
+        return output;
     }
+}
 
-    return output;
+
+GEOSContext::GEOSContext()
+{
+    // double-precison:
+    geos::geom::PrecisionModel* pm = new geos::geom::PrecisionModel(geom::PrecisionModel::FLOATING);
+
+    // Factory will clone the PM
+    _factory = new geos::geom::GeometryFactory( pm );
+
+    // Delete the template.
+    delete pm;
+}
+
+GEOSContext::~GEOSContext()
+{
+    delete _factory;
 }
 
 geom::Geometry*
-GEOSUtils::importGeometry( const Symbology::Geometry* input )
+GEOSContext::importGeometry(const Symbology::Geometry* input)
 {
     geom::Geometry* output = 0L;
     if ( input && input->isValid() )
     {
-        geom::PrecisionModel* pm = new geom::PrecisionModel( geom::PrecisionModel::FLOATING );
-        const geom::GeometryFactory* f = new geom::GeometryFactory( pm );
-
-        output = import( input, f );
+        output = import( input, _factory );
 
         // if output is ok, it will have a pointer to f. this is probably a leak.
         // TODO: Check whether this is a leak!! -gw
-        if ( !output )
-            delete f;
+        //if ( !output )
+        //    delete f;
     }
     return output;
 }
 
-static Symbology::Geometry*
-exportPolygon( const geom::Polygon* input )
-{
-    Symbology::Polygon* output = 0L;
-    const geom::LineString* outerRing = input->getExteriorRing();
-    if ( outerRing )
-    {
-        const geom::CoordinateSequence* s = outerRing->getCoordinates();
-        output = new Symbology::Polygon( s->getSize() );
-        for( unsigned int j=0; j<s->getSize(); j++ ) 
-        {
-            const geom::Coordinate& c = s->getAt( j );
-            output->push_back( osg::Vec3d( c.x, c.y, 0 ) ); 
-        }
-        output->rewind( Symbology::Ring::ORIENTATION_CCW );
 
-        for( unsigned k=0; k < input->getNumInteriorRing(); k++ )
-        {
-            const geom::LineString* inner = input->getInteriorRingN( k );
-            const geom::CoordinateSequence* s = inner->getCoordinates();
-            Symbology::Ring* hole = new Symbology::Ring( s->getSize() );
-            for( unsigned int m = 0; m<s->getSize(); m++ )
-            {
-                const geom::Coordinate& c = s->getAt( m );
-                hole->push_back( osg::Vec3d( c.x, c.y, 0 ) );
-            }
-            hole->rewind( Symbology::Ring::ORIENTATION_CW );
-            output->getHoles().push_back( hole );
-        }
-    }
-    return output;
-}
 
 Symbology::Geometry*
-GEOSUtils::exportGeometry( const geom::Geometry* input )
+GEOSContext::exportGeometry(const geom::Geometry* input)
 {
-    //// first verify that the input is valid.
-    //valid::IsValidOp validator( input );
-    //if ( !validator.isValid() )
-    //{
-    //    OE_NOTICE << "GEOS: discarding invalid geometry" << std::endl;
-    //    return 0L;
-    //}
-
     Symbology::GeometryCollection parts;
 
     if ( dynamic_cast<const geom::Point*>( input ) )
@@ -284,7 +301,7 @@ GEOSUtils::exportGeometry( const geom::Geometry* input )
 
     if ( parts.size() == 1 )
     {
-        osg::ref_ptr<Symbology::Geometry> part = parts.front();
+        osg::ref_ptr<Symbology::Geometry> part = parts.front().get();
         parts.clear();
         return part.release();
     }
@@ -298,5 +315,18 @@ GEOSUtils::exportGeometry( const geom::Geometry* input )
     }
 }
 
+
+void
+GEOSContext::disposeGeometry(geom::Geometry* input)
+{
+    if (input)
+    {
+        geom::GeometryFactory* f = const_cast<geom::GeometryFactory*>(input->getFactory());
+        _factory->destroyGeometry(input);
+        if ( f != _factory )
+            delete f;
+    }
+}
+
 #endif // OSGEARTH_HAVE_GEOS
 
diff --git a/src/osgEarthSymbology/Geometry b/src/osgEarthSymbology/Geometry
index 552d5ae..21e6894 100644
--- a/src/osgEarthSymbology/Geometry
+++ b/src/osgEarthSymbology/Geometry
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/Geometry.cpp b/src/osgEarthSymbology/Geometry.cpp
index 432047b..1ebb2e2 100644
--- a/src/osgEarthSymbology/Geometry.cpp
+++ b/src/osgEarthSymbology/Geometry.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -153,7 +153,9 @@ Geometry::buffer(double distance,
 {
 #ifdef OSGEARTH_HAVE_GEOS   
 
-    geom::Geometry* inGeom = GEOSUtils::importGeometry( this );
+    GEOSContext gc;
+
+    geom::Geometry* inGeom = gc.importGeometry( this );
     if ( inGeom )
     {
         buffer::BufferParameters::EndCapStyle geosEndCap =
@@ -186,27 +188,34 @@ Geometry::buffer(double distance,
         {
             if (params._singleSided)
             {
-                outGeom = bufBuilder.bufferLineSingleSided(inGeom, distance, params._leftSide );
+                outGeom = bufBuilder.bufferLineSingleSided(inGeom, distance, params._leftSide);
             }
             else
             {
-                outGeom = bufBuilder.buffer(inGeom, distance );
+                outGeom = bufBuilder.buffer(inGeom, distance);
             }
         }
-        catch( const util::TopologyException& ex )
+        catch(const geos::util::GEOSException& ex)
         {
-            OE_WARN << LC << "GEOS buffer: " << ex.what() << std::endl;
+            OE_NOTICE << LC << "buffer(GEOS): "
+                << (ex.what()? ex.what() : " no error message")
+                << std::endl;
             outGeom = 0L;
         }
 
+        bool sharedFactory = 
+            inGeom && outGeom &&
+            inGeom->getFactory() == outGeom->getFactory();
+
         if ( outGeom )
         {
-            output = GEOSUtils::exportGeometry( outGeom );
-            outGeom->getFactory()->destroyGeometry( outGeom );
+            output = gc.exportGeometry( outGeom );
+            gc.disposeGeometry( outGeom );
         }
 
-        inGeom->getFactory()->destroyGeometry( inGeom );
+        gc.disposeGeometry( inGeom );
     }
+
     return output.valid();
 
 #else // OSGEARTH_HAVE_GEOS
@@ -221,43 +230,66 @@ bool
 Geometry::crop( const Polygon* cropPoly, osg::ref_ptr<Geometry>& output ) const
 {
 #ifdef OSGEARTH_HAVE_GEOS
+    bool success = false;
+    output = 0L;
 
-    geom::GeometryFactory* f = new geom::GeometryFactory();
+    GEOSContext gc;
 
     //Create the GEOS Geometries
-    geom::Geometry* inGeom = GEOSUtils::importGeometry( this );
-    geom::Geometry* cropGeom = GEOSUtils::importGeometry( cropPoly );
+    geom::Geometry* inGeom   = gc.importGeometry( this );
+    geom::Geometry* cropGeom = gc.importGeometry( cropPoly );
 
     if ( inGeom )
     {    
         geom::Geometry* outGeom = 0L;
         try {
             outGeom = overlay::OverlayOp::overlayOp(
-                inGeom, cropGeom,
+                inGeom,
+                cropGeom,
                 overlay::OverlayOp::opINTERSECTION );
         }
-        catch( ... ) {
+        catch(const geos::util::GEOSException& ex) {
+            OE_NOTICE << LC << "Crop(GEOS): "
+                << (ex.what()? ex.what() : " no error message")
+                << std::endl;
             outGeom = 0L;
-            OE_NOTICE << LC << "::crop, GEOS overlay op exception, skipping feature" << std::endl;
         }
 
         if ( outGeom )
         {
-            output = GEOSUtils::exportGeometry( outGeom );
-            f->destroyGeometry( outGeom );
-            if ( output.valid() && !output->isValid() )
+            output = gc.exportGeometry( outGeom );
+
+            if ( output.valid())
             {
-                output = 0L;
+                if ( output->isValid() )
+                {
+                    success = true;
+                }
+                else
+                {
+                    // GEOS result is invalid
+                    output = 0L;
+                }
             }
+            else
+            {
+                // set output to empty geometry to indicate the (valid) empty case,
+                // still returning false but allows for check.
+                if (outGeom->getNumPoints() == 0)
+                {
+                    output = new osgEarth::Symbology::Geometry();
+                }
+            }
+
+            gc.disposeGeometry( outGeom );
         }
     }
 
     //Destroy the geometry
-    f->destroyGeometry( cropGeom );
-    f->destroyGeometry( inGeom );
+    gc.disposeGeometry( cropGeom );
+    gc.disposeGeometry( inGeom );
 
-    delete f;
-    return output.valid();
+    return success;
 
 #else // OSGEARTH_HAVE_GEOS
 
@@ -272,29 +304,33 @@ Geometry::difference( const Polygon* diffPolygon, osg::ref_ptr<Geometry>& output
 {
 #ifdef OSGEARTH_HAVE_GEOS
 
-    geom::GeometryFactory* f = new geom::GeometryFactory();
+    GEOSContext gc;
 
     //Create the GEOS Geometries
-    geom::Geometry* inGeom = GEOSUtils::importGeometry( this );
-    geom::Geometry* diffGeom = GEOSUtils::importGeometry( diffPolygon );
+    geom::Geometry* inGeom   = gc.importGeometry( this );
+    geom::Geometry* diffGeom = gc.importGeometry( diffPolygon );
 
     if ( inGeom )
     {    
         geom::Geometry* outGeom = 0L;
         try {
             outGeom = overlay::OverlayOp::overlayOp(
-                inGeom, diffGeom,
+                inGeom,
+                diffGeom,
                 overlay::OverlayOp::opDIFFERENCE );
         }
-        catch( ... ) {
+        catch(const geos::util::GEOSException& ex) {
+            OE_NOTICE << LC << "Diff(GEOS): "
+                << (ex.what()? ex.what() : " no error message")
+                << std::endl;
             outGeom = 0L;
-            OE_NOTICE << LC << "::difference, GEOS overlay op exception, skipping feature" << std::endl;
         }
 
         if ( outGeom )
         {
-            output = GEOSUtils::exportGeometry( outGeom );
-            f->destroyGeometry( outGeom );
+            output = gc.exportGeometry( outGeom );
+            gc.disposeGeometry( outGeom );
+
             if ( output.valid() && !output->isValid() )
             {
                 output = 0L;
@@ -303,10 +339,9 @@ Geometry::difference( const Polygon* diffPolygon, osg::ref_ptr<Geometry>& output
     }
 
     //Destroy the geometry
-    f->destroyGeometry( diffGeom );
-    f->destroyGeometry( inGeom );
+    gc.disposeGeometry( diffGeom );
+    gc.disposeGeometry( inGeom );
 
-    delete f;
     return output.valid();
 
 #else // OSGEARTH_HAVE_GEOS
diff --git a/src/osgEarthSymbology/GeometryFactory b/src/osgEarthSymbology/GeometryFactory
index b51edb3..f58bf70 100644
--- a/src/osgEarthSymbology/GeometryFactory
+++ b/src/osgEarthSymbology/GeometryFactory
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/GeometryFactory.cpp b/src/osgEarthSymbology/GeometryFactory.cpp
index d5ed9f0..fe48641 100644
--- a/src/osgEarthSymbology/GeometryFactory.cpp
+++ b/src/osgEarthSymbology/GeometryFactory.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -237,8 +237,8 @@ GeometryFactory::createEllipticalArc(const osg::Vec3d& center,
         numSegments = (unsigned)::ceil(circumference / segLen);
     }
 
-    double startRad = std::min( start.as(Units::RADIANS), end.as(Units::RADIANS) ) - osg::PI_2;
-    double endRad   = std::max( start.as(Units::RADIANS), end.as(Units::RADIANS) ) - osg::PI_2;
+    double startRad = std::min( start.as(Units::RADIANS), end.as(Units::RADIANS) );// - osg::PI_2;
+    double endRad   = std::max( start.as(Units::RADIANS), end.as(Units::RADIANS) );// - osg::PI_2;
 
     if ( endRad == startRad )
     {
@@ -275,16 +275,15 @@ GeometryFactory::createEllipticalArc(const osg::Vec3d& center,
     {
         double a = radiusMajor.as(Units::METERS);
         double b = radiusMinor.as(Units::METERS);
-        double g = rotationAngle.as(Units::RADIANS) - osg::PI_2;
+        double g = rotationAngle.as(Units::RADIANS);
         double sing = sin(g), cosg = cos(g);
 
         for( unsigned i=0; i<=numSegments; i++ )
         {
             double angle = startRad + step*(double)i;
-            double t = angle - osg::PI_2;
-            double cost = cos(t), sint = sin(t);
-            double x = center.x() + a*cost*cosg - b*sint*sing;
-            double y = center.y() + a*cost*sing + b*sint*cosg;
+            double cost = cos(angle), sint = sin(angle);
+            double x = center.x() + a*sint*cosg + b*cost*sing;
+            double y = center.y() + b*cost*cosg - a*sint*sing;
 
             geom->push_back( osg::Vec3d(x, y, center.z()) );
         }
diff --git a/src/osgEarthSymbology/GeometryRasterizer b/src/osgEarthSymbology/GeometryRasterizer
index 4455987..bd8f990 100644
--- a/src/osgEarthSymbology/GeometryRasterizer
+++ b/src/osgEarthSymbology/GeometryRasterizer
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/GeometryRasterizer.cpp b/src/osgEarthSymbology/GeometryRasterizer.cpp
index fc9aac6..1e8e9c0 100644
--- a/src/osgEarthSymbology/GeometryRasterizer.cpp
+++ b/src/osgEarthSymbology/GeometryRasterizer.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/IconResource b/src/osgEarthSymbology/IconResource
index 7882671..69361db 100644
--- a/src/osgEarthSymbology/IconResource
+++ b/src/osgEarthSymbology/IconResource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/IconResource.cpp b/src/osgEarthSymbology/IconResource.cpp
index 1c172a8..36f16f3 100644
--- a/src/osgEarthSymbology/IconResource.cpp
+++ b/src/osgEarthSymbology/IconResource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -150,6 +150,9 @@ IconResource::createNodeFromURI( const URI& uri, const osgDB::Options* dbOptions
     ReadResult r = uri.readImage( dbOptions );
     if ( r.succeeded() )
     {
+        OE_INFO << LC << "Loaded " << uri.base() << "(from " << (r.isFromCache()? "cache" : "source") << ")"
+            << std::endl;
+
         if ( r.getImage() )
         {
             node = buildIconModel( r.releaseImage() );
diff --git a/src/osgEarthSymbology/IconSymbol b/src/osgEarthSymbology/IconSymbol
index 03058fc..07600d3 100644
--- a/src/osgEarthSymbology/IconSymbol
+++ b/src/osgEarthSymbology/IconSymbol
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/IconSymbol.cpp b/src/osgEarthSymbology/IconSymbol.cpp
index a0299b7..89560a3 100644
--- a/src/osgEarthSymbology/IconSymbol.cpp
+++ b/src/osgEarthSymbology/IconSymbol.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -196,4 +196,7 @@ IconSymbol::parseSLD(const Config& c, Style& style)
     else if ( match(c.key(), "icon-occlusion-cull-altitude") ) {
         style.getOrCreate<IconSymbol>()->occlusionCullAltitude() = as<float>(c.value(), *defaults.occlusionCullAltitude());
     }
+    else if ( match(c.key(), "icon-script") ) {
+        style.getOrCreate<IconSymbol>()->script() = StringExpression(c.value());
+    }
 }
diff --git a/src/osgEarthSymbology/InstanceResource b/src/osgEarthSymbology/InstanceResource
index 1f0c997..907b52b 100644
--- a/src/osgEarthSymbology/InstanceResource
+++ b/src/osgEarthSymbology/InstanceResource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/InstanceResource.cpp b/src/osgEarthSymbology/InstanceResource.cpp
index f65343e..b7e02c4 100644
--- a/src/osgEarthSymbology/InstanceResource.cpp
+++ b/src/osgEarthSymbology/InstanceResource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/InstanceSymbol b/src/osgEarthSymbology/InstanceSymbol
index 641095b..5523de2 100644
--- a/src/osgEarthSymbology/InstanceSymbol
+++ b/src/osgEarthSymbology/InstanceSymbol
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -45,12 +45,12 @@ namespace osgEarth { namespace Symbology
          */
         enum Placement
         {
+            /** Places an instance at each feature point (default) */
+            PLACEMENT_VERTEX,
+
             /** Places one instance at the centroid of the feature. */
             PLACEMENT_CENTROID,
 
-            /** Places an instance at each feature point. */
-            PLACEMENT_VERTEX,
-
             /** Places instances at regular intervals within/along the feature geometry,
                 according to density. */
             PLACEMENT_INTERVAL,
@@ -91,6 +91,12 @@ namespace osgEarth { namespace Symbology
         optional<URIAliasMap>& uriAliasMap() { return _uriAliasMap; }
         const optional<URIAliasMap>& uriAliasMap() const { return _uriAliasMap; }
 
+        /** Expression that returns custom geometry as a GeoJSON string. 
+            You will typically use this as a script that takes the input geometry
+            to dumps out a new geometry for model substitution. */
+        optional<StringExpression>& script() { return _script; }
+        const optional<StringExpression>& script() const { return _script; }
+
     public: // conversions to built-in base classes, for convenience.
 
         const IconSymbol* asIcon() const;
@@ -115,6 +121,7 @@ namespace osgEarth { namespace Symbology
         optional<float>              _density;
         optional<unsigned>           _randomSeed;
         optional<URIAliasMap>        _uriAliasMap;
+        optional<StringExpression>   _script;
     };
 
 } } // namespace osgEarth::Symbology
diff --git a/src/osgEarthSymbology/InstanceSymbol.cpp b/src/osgEarthSymbology/InstanceSymbol.cpp
index 085468b..b1977b7 100644
--- a/src/osgEarthSymbology/InstanceSymbol.cpp
+++ b/src/osgEarthSymbology/InstanceSymbol.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -25,7 +25,7 @@ using namespace osgEarth::Symbology;
 
 InstanceSymbol::InstanceSymbol( const Config& conf ) :
 Symbol     ( conf ),
-_placement ( PLACEMENT_CENTROID ),
+_placement ( PLACEMENT_VERTEX ),
 _density   ( 25.0f ),
 _randomSeed( 0 ),
 _scale     ( NumericExpression(1.0) )
@@ -41,9 +41,11 @@ InstanceSymbol::getConfig() const
     conf.addObjIfSet( "url", _url );
     conf.addObjIfSet( "library", _libraryName );
     conf.addObjIfSet( "scale", _scale );
-    conf.addIfSet   ( "placement", "vertex",   _placement, PLACEMENT_VERTEX );
-    conf.addIfSet   ( "placement", "interval", _placement, PLACEMENT_INTERVAL );
-    conf.addIfSet   ( "placement", "random",   _placement, PLACEMENT_RANDOM );
+    conf.addObjIfSet( "script", _script );
+    conf.addIfSet   ( "placement", "vertex",    _placement, PLACEMENT_VERTEX );
+    conf.addIfSet   ( "placement", "interval",  _placement, PLACEMENT_INTERVAL );
+    conf.addIfSet   ( "placement", "random",    _placement, PLACEMENT_RANDOM );
+    conf.addIfSet   ( "placement", "centroid",  _placement, PLACEMENT_CENTROID );
     conf.addIfSet   ( "density", _density );
     conf.addIfSet   ( "random_seed", _randomSeed );
     return conf;
@@ -55,9 +57,11 @@ InstanceSymbol::mergeConfig( const Config& conf )
     conf.getObjIfSet( "url", _url );
     conf.getObjIfSet( "library", _libraryName );
     conf.getObjIfSet( "scale", _scale );
+    conf.getObjIfSet( "script", _script );
     conf.getIfSet   ( "placement", "vertex",   _placement, PLACEMENT_VERTEX );
     conf.getIfSet   ( "placement", "interval", _placement, PLACEMENT_INTERVAL );
     conf.getIfSet   ( "placement", "random",   _placement, PLACEMENT_RANDOM );
+    conf.getIfSet   ( "placement", "centroid", _placement, PLACEMENT_CENTROID );
     conf.getIfSet   ( "density", _density );
     conf.getIfSet   ( "random_seed", _randomSeed );
 }
diff --git a/src/osgEarthSymbology/LineSymbol b/src/osgEarthSymbology/LineSymbol
index 2d44b39..4f3384d 100644
--- a/src/osgEarthSymbology/LineSymbol
+++ b/src/osgEarthSymbology/LineSymbol
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -46,6 +46,10 @@ namespace osgEarth { namespace Symbology
         optional<unsigned>& tessellation() { return _tessellation; }
         const optional<unsigned>& tessellation() const { return _tessellation; }
 
+        /** Minimum angle (deg) for which to create creases where applicable (like when outlining) */
+        optional<float>& creaseAngle() { return _creaseAngle; }
+        const optional<float>& creaseAngle() const { return _creaseAngle; }
+
     public:
         virtual Config getConfig() const;
         virtual void mergeConfig( const Config& conf );
@@ -54,6 +58,7 @@ namespace osgEarth { namespace Symbology
     protected:
         optional<Stroke>   _stroke;
         optional<unsigned> _tessellation;
+        optional<float>    _creaseAngle;
     };
 
 } } // namespace osgEarth::Symbology
diff --git a/src/osgEarthSymbology/LineSymbol.cpp b/src/osgEarthSymbology/LineSymbol.cpp
index 51eeba6..0e23796 100644
--- a/src/osgEarthSymbology/LineSymbol.cpp
+++ b/src/osgEarthSymbology/LineSymbol.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -27,7 +27,8 @@ OSGEARTH_REGISTER_SIMPLE_SYMBOL(line, LineSymbol);
 LineSymbol::LineSymbol( const Config& conf ) :
 Symbol       ( conf ),
 _stroke      ( Stroke() ),
-_tessellation( 0 )
+_tessellation( 0 ),
+_creaseAngle ( 0.0f )
 {
     mergeConfig(conf);
 }
@@ -39,6 +40,7 @@ LineSymbol::getConfig() const
     conf.key() = "line";
     conf.addObjIfSet("stroke",       _stroke);
     conf.addIfSet   ("tessellation", _tessellation);
+    conf.addIfSet   ("crease_angle", _creaseAngle);
     return conf;
 }
 
@@ -47,6 +49,7 @@ LineSymbol::mergeConfig( const Config& conf )
 {
     conf.getObjIfSet("stroke",       _stroke);
     conf.getIfSet   ("tessellation", _tessellation);
+    conf.getIfSet   ("crease_angle", _creaseAngle);
 }
 
 void
@@ -95,4 +98,10 @@ LineSymbol::parseSLD(const Config& c, Style& style)
               match(c.key(), "stroke-stipple") ) {
         style.getOrCreate<LineSymbol>()->stroke()->stipplePattern() = as<unsigned short>(c.value(), 0xFFFF);
     }
+    else if ( match(c.key(), "stroke-crease-angle") ) {
+        style.getOrCreate<LineSymbol>()->creaseAngle() = as<float>(c.value(), 0.0);
+    }
+    else if ( match(c.key(), "stroke-script") ) {
+        style.getOrCreate<LineSymbol>()->script() = StringExpression(c.value());
+    }
 }
diff --git a/src/osgEarthSymbology/MarkerResource b/src/osgEarthSymbology/MarkerResource
index 4122c13..beb5300 100644
--- a/src/osgEarthSymbology/MarkerResource
+++ b/src/osgEarthSymbology/MarkerResource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/MarkerResource.cpp b/src/osgEarthSymbology/MarkerResource.cpp
index 171a9b5..baf057c 100644
--- a/src/osgEarthSymbology/MarkerResource.cpp
+++ b/src/osgEarthSymbology/MarkerResource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -22,6 +22,7 @@
 
 #include <osg/AutoTransform>
 #include <osg/Depth>
+#include <osg/Geode>
 #include <osg/Geometry>
 #include <osg/TextureRectangle>
 #include <osg/Program>
@@ -123,7 +124,10 @@ MarkerResource::createNodeFromURI( const URI& uri, const osgDB::Options* dbOptio
 
     ReadResult r = uri.readObject( dbOptions );
     if ( r.succeeded() )
-    {
+    {     
+        OE_INFO << LC << "Loaded " << uri.base() << "(from " << (r.isFromCache()? "cache" : "source") << ")"
+            << std::endl;
+
         if ( r.getImage() )
         {
             node = buildImageModel( r.getImage() );
@@ -142,12 +146,5 @@ MarkerResource::createNodeFromURI( const URI& uri, const osgDB::Options* dbOptio
             return createNodeFromURI( URI(tok[1]), dbOptions );
     }
 
-    // for now, disable any shaders on an imported resource until we do something about it
-    if ( node )
-    {
-        // disable shaders. perhaps later we can run a shadergen or something.
-        node->getOrCreateStateSet()->setAttributeAndModes( new osg::Program(), osg::StateAttribute::OFF );
-    }
-
     return node;
 }
diff --git a/src/osgEarthSymbology/MarkerSymbol b/src/osgEarthSymbology/MarkerSymbol
index ed1a9da..923f762 100644
--- a/src/osgEarthSymbology/MarkerSymbol
+++ b/src/osgEarthSymbology/MarkerSymbol
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/MarkerSymbol.cpp b/src/osgEarthSymbology/MarkerSymbol.cpp
index 08a99f1..0e22347 100644
--- a/src/osgEarthSymbology/MarkerSymbol.cpp
+++ b/src/osgEarthSymbology/MarkerSymbol.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -277,4 +277,5 @@ MarkerSymbol::parseSLD(const Config& c, Style& style)
         else if ( match(c.value(), "right-bottom") ) 
             style.getOrCreate<MarkerSymbol>()->alignment() = MarkerSymbol::ALIGN_RIGHT_BOTTOM;
     }
-}
\ No newline at end of file
+}
+
diff --git a/src/osgEarthSymbology/MeshConsolidator b/src/osgEarthSymbology/MeshConsolidator
index 277041a..3b75507 100644
--- a/src/osgEarthSymbology/MeshConsolidator
+++ b/src/osgEarthSymbology/MeshConsolidator
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/MeshConsolidator.cpp b/src/osgEarthSymbology/MeshConsolidator.cpp
index 8c7e2aa..9a41646 100644
--- a/src/osgEarthSymbology/MeshConsolidator.cpp
+++ b/src/osgEarthSymbology/MeshConsolidator.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -37,76 +37,6 @@ using namespace osgEarth;
 
 namespace
 {
-    struct GeometryValidator : public osg::NodeVisitor
-    {
-        template<typename DE>
-        void validateDE( DE* de, unsigned maxIndex, unsigned numVerts )
-        {
-            for( unsigned i=0; i<de->getNumIndices(); ++i )
-            {
-                typename DE::value_type index = de->getElement(i);
-                if ( index > maxIndex )
-                {
-                    OE_WARN << "MAXIMUM Index exceeded in DrawElements" << std::endl;
-                    break;
-                }
-                else if ( index > numVerts-1 )
-                {
-                    OE_WARN << "INDEX OUT OF Range in DrawElements" << std::endl;
-                }
-            }
-        }
-
-        void apply(osg::Geometry& geom)
-        {
-            unsigned numVerts = geom.getVertexArray()->getNumElements();
-
-            if ( geom.getColorArray() )
-            {
-                if ( geom.getColorBinding() == osg::Geometry::BIND_OVERALL && geom.getColorArray()->getNumElements() != 1 )
-                {
-                    OE_WARN << "BIND_OVERALL with wrong number of elements" << std::endl;
-                }
-                else if ( geom.getColorBinding() == osg::Geometry::BIND_PER_VERTEX && geom.getColorArray()->getNumElements() != numVerts )
-                {
-                    OE_WARN << "BIND_PER_VERTEX with color.size != verts.size" << std::endl;
-                }
-
-                const osg::Geometry::PrimitiveSetList& plist = geom.getPrimitiveSetList();
-                for( osg::Geometry::PrimitiveSetList::const_iterator p = plist.begin(); p != plist.end(); ++p )
-                {
-                    osg::PrimitiveSet* pset = p->get();
-
-                    osg::DrawElementsUByte* de_byte = dynamic_cast<osg::DrawElementsUByte*>(pset);
-                    if ( de_byte )
-                    {
-                        if ( numVerts > 0xFF )
-                        {
-                            OE_WARN << "DrawElementsUByte used when numVerts > 0xFF" << std::endl;
-                        }
-                        validateDE(de_byte, 0xFF, numVerts );
-                    }
-
-                    osg::DrawElementsUShort* de_short = dynamic_cast<osg::DrawElementsUShort*>(pset);
-                    if ( de_short )
-                    {
-                        if ( numVerts > 0xFFFF )
-                        {
-                            OE_WARN << "DrawElementsUShort used when numVerts > 0xFFFF" << std::endl;
-                        }
-                        validateDE(de_short, 0xFFFF, numVerts );
-                    }
-
-                    osg::DrawElementsUInt* de_int = dynamic_cast<osg::DrawElementsUInt*>(pset);
-                    if ( de_int )
-                    {
-                        validateDE(de_int, 0xFFFFFFFF, numVerts );
-                    }
-                }
-            }
-        }
-    };
-
     template<typename T>
     struct Collector
     {
@@ -188,14 +118,6 @@ namespace
         // just for now.... TODO: allow thi later
         if ( geom.getVertexAttribArrayList().size() > 0 )
             return false;
-        //{
-        //    unsigned n = geom.getVertexAttribArrayList().size();
-        //    for( unsigned i=0; i<n; ++i ) 
-        //    {
-        //        if ( geom.getVertexAttribBinding( i ) != osg::Geometry::BIND_PER_VERTEX )
-        //            return false;
-        //    }
-        //}
 
         // check that all primitive sets share the same user data
         osg::Geometry::PrimitiveSetList& pslist = geom.getPrimitiveSetList();
@@ -298,8 +220,7 @@ MeshConsolidator::convertToTriangles( osg::Geometry& geom, bool force )
     geom.setPrimitiveSetList( nonTriSets );
 }
 
-
-typedef osg::Geode::DrawableList DrawableList;
+typedef std::vector<osg::ref_ptr<osg::Drawable> > DrawableList;
 
 namespace
 {
@@ -311,13 +232,33 @@ namespace
         unsigned                      numNormals,
         const std::vector<unsigned>&  texCoordArrayUnits,
         bool                          useVBOs,
-        osg::Geode::DrawableList&     results )
+        DrawableList&                 results )
     {
         osg::Geometry::AttributeBinding newColorsBinding, newNormalsBinding;
 
         osg::Vec3Array* newVerts = new osg::Vec3Array();
         newVerts->reserve( numVerts );
 
+        // Determine if we need to use 3D texture coordinates or not.
+        bool use3DTextureCoords = false;
+        for( DrawableList::iterator i = start; i != end; ++i )
+        {
+            for( unsigned a=0; a<texCoordArrayUnits.size(); ++a )
+            {
+                unsigned unit = texCoordArrayUnits[a];
+
+                osg::Vec3Array* texCoords = dynamic_cast<osg::Vec3Array*>(i->get()->asGeometry()->getTexCoordArray( unit ));
+                if (texCoords)
+                {
+                    use3DTextureCoords = true;
+                    break;
+                }
+            }
+
+            if (use3DTextureCoords) break;
+        }
+
+
         osg::Vec4Array* newColors =0L;
         if ( numColors > 0 )
         {
@@ -338,11 +279,22 @@ namespace
             //newNormalsBinding = numNormals==numVerts? osg::Geometry::BIND_PER_VERTEX : osg::Geometry::BIND_OVERALL;
         }
 
-        std::vector<osg::Vec2Array*> newTexCoordsArrays;
+        std::vector<osg::Array*> newTexCoordsArrays;
         for( unsigned i=0; i<texCoordArrayUnits.size(); ++i )
         {
-            osg::Vec2Array* newTexCoords = new osg::Vec2Array();
-            newTexCoords->reserve( numVerts );
+            osg::Array* newTexCoords;
+            if (use3DTextureCoords)
+            {
+                osg::Vec3Array* texCoords3D = new osg::Vec3Array;
+                texCoords3D->reserve( numVerts );
+                newTexCoords = texCoords3D;                                    
+            }
+            else
+            {
+                osg::Vec2Array* texCoords2D = new osg::Vec2Array;
+                texCoords2D->reserve( numVerts );
+                newTexCoords = texCoords2D;                                    
+            }                        
             newTexCoordsArrays.push_back( newTexCoords );
         }
 
@@ -406,12 +358,39 @@ namespace
                     for( unsigned a=0; a<texCoordArrayUnits.size(); ++a )
                     {
                         unsigned unit = texCoordArrayUnits[a];
-                        osg::Vec2Array* texCoords = dynamic_cast<osg::Vec2Array*>( geom->getTexCoordArray(unit) );
-                        if ( texCoords )
+
+                        // We are just using 2D texture coordinates so only check for the 2D case
+                        if (!use3DTextureCoords)
                         {
-                            osg::Vec2Array* newTexCoords = newTexCoordsArrays[a];
-                            std::copy( texCoords->begin(), texCoords->end(), std::back_inserter(*newTexCoords) );
+                            osg::Vec2Array* texCoords2D = dynamic_cast<osg::Vec2Array*>( geom->getTexCoordArray(unit) );
+                            if (texCoords2D)
+                            {
+                                osg::Vec2Array* newTexCoords = static_cast< osg::Vec2Array*>( newTexCoordsArrays[a] );
+                                std::copy( texCoords2D->begin(), texCoords2D->end(), std::back_inserter(*newTexCoords) );
+                            }                                                           
                         }
+                        else
+                        {
+                            // We are using 3D coordinates, so check for both 2D and 3D coordinates.  We will consolidate them all into 3D coordinates.
+                            osg::Vec2Array* texCoords2D = dynamic_cast<osg::Vec2Array*>( geom->getTexCoordArray(unit) );
+                            osg::Vec3Array* texCoords3D = dynamic_cast<osg::Vec3Array*>( geom->getTexCoordArray(unit) );
+                            if (texCoords2D || texCoords3D)
+                            {
+                                osg::Vec3Array* newTexCoords = static_cast< osg::Vec3Array*>( newTexCoordsArrays[a] );
+                                if (texCoords3D)
+                                {
+                                    std::copy( texCoords3D->begin(), texCoords3D->end(), std::back_inserter(*newTexCoords) );
+                                }
+                                else
+                                {
+                                    // Convert 2D to 3D coords
+                                    for (osg::Vec2Array::iterator itr = texCoords2D->begin(); itr != texCoords2D->end(); ++itr)
+                                    {                                        
+                                        newTexCoords->push_back( osg::Vec3(itr->x(), itr->y(), 0) );
+                                    }
+                                }
+                            }
+                        }                                                
                     }
                 }
 
@@ -517,7 +496,7 @@ MeshConsolidator::run( osg::Geode& geode )
         {
             if ( canOptimize(*geom) )
             {
-                // convert all primitives to triangles.
+                // convert all surface primitives to triangles.
                 convertToTriangles( *geom );
 
                 // NOTE!! tex/attrib array counts much already be equal.
diff --git a/src/osgEarthSymbology/MeshSubdivider b/src/osgEarthSymbology/MeshSubdivider
index c0c971b..f35028d 100644
--- a/src/osgEarthSymbology/MeshSubdivider
+++ b/src/osgEarthSymbology/MeshSubdivider
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/MeshSubdivider.cpp b/src/osgEarthSymbology/MeshSubdivider.cpp
index 351c993..55eae49 100644
--- a/src/osgEarthSymbology/MeshSubdivider.cpp
+++ b/src/osgEarthSymbology/MeshSubdivider.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -24,12 +24,18 @@
 #include <climits>
 #include <queue>
 #include <map>
+#include <algorithm>
+#include <iterator>
 
 #define LC "[MeshSubdivider] "
 
 using namespace osgEarth;
 using namespace osgEarth::Symbology;
 
+// Define this to create line strips after tessellating line geometry.
+// This is necessary if you intend to use stippling!
+#define STRIPIFY_LINES 1
+
 //------------------------------------------------------------------------
 
 namespace
@@ -316,6 +322,39 @@ namespace
     typedef std::queue<Line>  LineQueue;
     typedef std::vector<Line> LineVector;
 
+    void stripifyLines(LineVector& input, std::vector<LineVector>& strips, LineVector& segments)
+    {
+        LineVector source(input);
+        while(source.size() > 0)
+        {
+            LineVector next;
+            std::deque<Line> lineStrip;
+            lineStrip.push_back(source[0]);
+            for(unsigned i=1; i<source.size(); ++i)
+            {
+                if ( source[i]._i0 == lineStrip.back()._i1 )
+                    lineStrip.push_back(source[i]);
+                else if ( source[i]._i1 == lineStrip.front()._i0 )
+                    lineStrip.push_front(source[i]);
+                else
+                    next.push_back(source[i]);                    
+            }
+
+            if ( lineStrip.size() > 1 )
+            {
+                strips.push_back(LineVector());
+                strips.back().reserve(lineStrip.size());
+                std::copy(lineStrip.begin(), lineStrip.end(), std::back_inserter(strips.back()));
+            }
+            else
+            {
+                segments.push_back( lineStrip.front() );
+            }
+
+            source.swap(next);
+        }
+    }
+
     struct LineData
     {
         typedef std::map<osg::Vec3,GLuint> VertMap;        
@@ -448,11 +487,47 @@ namespace
         {
             geom.addPrimitiveSet( ebo );
         }
+    }       
+    
+    /**
+     * Populates the geometry object with a collection of GL_LINE_STRIP index elements primitives.
+     */
+    template<typename ETYPE, typename VTYPE>
+    void populateLineStrip( osg::Geometry& geom, const LineVector& strip, unsigned maxElementsPerEBO )
+    {
+        unsigned numElementsTotal = (unsigned)strip.size() + 1;
+        unsigned numElementsWritten = 0;
+        unsigned numElementsInCurrentEBO = maxElementsPerEBO;
+
+        ETYPE* ebo = 0L;
+
+        for( LineVector::const_iterator i = strip.begin(); i != strip.end(); ++i )
+        {
+            if ( !ebo )
+            {
+                ebo = new ETYPE( GL_LINE_STRIP );
+                ebo->reserve( std::min(numElementsTotal-numElementsWritten, maxElementsPerEBO+1) );
+                numElementsInCurrentEBO = 0;
+            }
+
+            ebo->push_back( static_cast<VTYPE>(i->_i0) );
+            ++numElementsInCurrentEBO;
+            ++numElementsWritten;
+
+            if (numElementsInCurrentEBO+1 >= maxElementsPerEBO ||
+                (i+1) == strip.end())
+            {
+                ebo->push_back( static_cast<VTYPE>(i->_i1) );
+                geom.addPrimitiveSet( ebo );
+                ebo = 0L;
+            }
+        }
     }
 
     static const osg::Vec3d s_pole(0,0,1);
     static const double s_maxLatAdjustment(0.75);
 
+
     /**
      * Collects all the line segments from the geometry, coalesces them into a single
      * line set, subdivides it according to the granularity threshold, and replaces
@@ -535,12 +610,39 @@ namespace
             if ( data._colors )
                 geom.setColorArray( data._colors );
 
+#ifdef STRIPIFY_LINES
+            // detect and assemble line strips/loop
+            std::vector<LineVector> strips;
+            LineVector              segments;
+            stripifyLines(done, strips, segments);
+
+            if ( segments.size() > 0 )
+            {
+                if ( data._verts->size() < 256 )
+                    populateLines<osg::DrawElementsUByte,GLubyte>( geom, done, maxElementsPerEBO );
+                else if ( data._verts->size() < 65536 )
+                    populateLines<osg::DrawElementsUShort,GLushort>( geom, done, maxElementsPerEBO );
+                else
+                    populateLines<osg::DrawElementsUInt,GLuint>( geom, done, maxElementsPerEBO );
+            }
+
+            for(unsigned i=0; i<strips.size(); ++i)
+            {
+                if ( data._verts->size() < 256 )
+                    populateLineStrip<osg::DrawElementsUByte,GLubyte>( geom, strips[i], maxElementsPerEBO );
+                else if ( data._verts->size() < 65536 )
+                    populateLineStrip<osg::DrawElementsUShort,GLushort>( geom, strips[i], maxElementsPerEBO );
+                else
+                    populateLineStrip<osg::DrawElementsUInt,GLuint>( geom, strips[i], maxElementsPerEBO );
+            }
+#else
             if ( data._verts->size() < 256 )
                 populateLines<osg::DrawElementsUByte,GLubyte>( geom, done, maxElementsPerEBO );
             else if ( data._verts->size() < 65536 )
                 populateLines<osg::DrawElementsUShort,GLushort>( geom, done, maxElementsPerEBO );
             else
                 populateLines<osg::DrawElementsUInt,GLuint>( geom, done, maxElementsPerEBO );
+#endif
         }
     }
 
@@ -761,13 +863,7 @@ namespace
         else
         {
             subdivideTriangles( granularity, interp, geom, W2L, L2W, maxElementsPerEBO );
-
-            //osgUtil::VertexCacheVisitor cacheOptimizer;
-            //cacheOptimizer.optimizeVertices( geom );
         }
-        
-        //osgUtil::VertexAccessOrderVisitor orderOptimizer;
-        //orderOptimizer.optimizeOrder( geom );
     }
 }
 
diff --git a/src/osgEarthSymbology/ModelResource b/src/osgEarthSymbology/ModelResource
index f2b9621..9c27cdb 100644
--- a/src/osgEarthSymbology/ModelResource
+++ b/src/osgEarthSymbology/ModelResource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/ModelResource.cpp b/src/osgEarthSymbology/ModelResource.cpp
index cc65e5f..75f074a 100644
--- a/src/osgEarthSymbology/ModelResource.cpp
+++ b/src/osgEarthSymbology/ModelResource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -18,8 +18,10 @@
  */
 #include <osgEarthSymbology/ModelResource>
 #include <osgEarth/StringUtils>
-#include <osgUtil/Optimizer>
 #include <osgEarth/Utils>
+#include <osgEarth/Registry>
+#include <osgEarth/Capabilities>
+#include <osgUtil/Optimizer>
 
 #define LC "[ModelResource] "
 
@@ -54,36 +56,29 @@ ModelResource::createNodeFromURI( const URI& uri, const osgDB::Options* dbOption
 {
     osg::Node* node = 0L;
 
-    ReadResult r = uri.getNode( dbOptions );
+    ReadResult r = uri.readNode( dbOptions );
     if ( r.succeeded() )
     {
         node = r.releaseNode();
+        
+        OE_INFO << LC << "Loaded " << uri.base() << "(from " << (r.isFromCache()? "cache" : "source") << ")"
+            << std::endl;
 
-        OE_DEBUG << LC << "Loading " << uri.full() << std::endl;
-
-#if 1
         osgUtil::Optimizer o;
         o.optimize( node,
             o.DEFAULT_OPTIMIZATIONS |
             o.INDEX_MESH |
             o.VERTEX_PRETRANSFORM |
             o.VERTEX_POSTTRANSFORM );
-            
-#else
-        osgUtil::Optimizer o;
-        o.optimize( node, osgUtil::Optimizer::INDEX_MESH );
-
-        // GPU performance optimization:
-        VertexCacheOptimizer vco;
-        node->accept( vco );
-#endif
     }
     else // failing that, fall back on the old encoding format..
     {
         StringVector tok;
         StringTokenizer( *uri, tok, "()" );
         if (tok.size() >= 2)
-            return createNodeFromURI( URI(tok[1]), dbOptions );
+        {
+            node = createNodeFromURI( URI(tok[1]), dbOptions );
+        }
     }
 
     return node;
diff --git a/src/osgEarthSymbology/ModelSymbol b/src/osgEarthSymbology/ModelSymbol
index 4afc6cb..e96cad8 100644
--- a/src/osgEarthSymbology/ModelSymbol
+++ b/src/osgEarthSymbology/ModelSymbol
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -59,6 +59,7 @@ namespace osgEarth { namespace Symbology
         optional<bool>& autoScale() { return _autoScale; }
         const optional<bool>& autoScale() const { return _autoScale; }
         
+        
     public: // non-serialized properties (for programmatic use only)
 
         /** Explicit model to use for model placement */
diff --git a/src/osgEarthSymbology/ModelSymbol.cpp b/src/osgEarthSymbology/ModelSymbol.cpp
index 43b4cbc..ec02819 100644
--- a/src/osgEarthSymbology/ModelSymbol.cpp
+++ b/src/osgEarthSymbology/ModelSymbol.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -76,6 +76,9 @@ ModelSymbol::parseSLD(const Config& c, Style& style)
     if ( match(c.key(), "model") ) {
         style.getOrCreate<ModelSymbol>()->url() = c.value();
         style.getOrCreate<ModelSymbol>()->url()->setURIContext( c.referrer() );
+    }    
+    else if ( match(c.key(),"model-library") ) {
+        style.getOrCreate<ModelSymbol>()->libraryName() = StringExpression(c.value());
     }
     else if ( match(c.key(), "model-placement") ) {
         if      ( match(c.value(), "vertex") )   
@@ -102,5 +105,8 @@ ModelSymbol::parseSLD(const Config& c, Style& style)
     else if ( match(c.key(), "model-heading") ) {
         style.getOrCreate<ModelSymbol>()->heading() = NumericExpression(c.value());
     }
+    else if ( match(c.key(), "model-script") ) {
+        style.getOrCreate<ModelSymbol>()->script() = StringExpression(c.value());
+    }
+}
 
-}
\ No newline at end of file
diff --git a/src/osgEarthSymbology/PointSymbol b/src/osgEarthSymbology/PointSymbol
index 2cc067b..dc8a185 100644
--- a/src/osgEarthSymbology/PointSymbol
+++ b/src/osgEarthSymbology/PointSymbol
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/PointSymbol.cpp b/src/osgEarthSymbology/PointSymbol.cpp
index 17b0d0b..d8c6170 100644
--- a/src/osgEarthSymbology/PointSymbol.cpp
+++ b/src/osgEarthSymbology/PointSymbol.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -62,4 +62,8 @@ PointSymbol::parseSLD(const Config& c, Style& style)
     else if ( match(c.key(), "point-size") ) {
         style.getOrCreate<PointSymbol>()->size() = as<float>(c.value(), 1.0f);
     }
-}
\ No newline at end of file
+    else if ( match(c.key(), "point-script") ) {
+        style.getOrCreate<PointSymbol>()->script() = StringExpression(c.value());
+    }
+}
+
diff --git a/src/osgEarthSymbology/PolygonSymbol b/src/osgEarthSymbology/PolygonSymbol
index a0e4932..e966920 100644
--- a/src/osgEarthSymbology/PolygonSymbol
+++ b/src/osgEarthSymbology/PolygonSymbol
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/PolygonSymbol.cpp b/src/osgEarthSymbology/PolygonSymbol.cpp
index 0adf402..e051f26 100644
--- a/src/osgEarthSymbology/PolygonSymbol.cpp
+++ b/src/osgEarthSymbology/PolygonSymbol.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -55,4 +55,7 @@ PolygonSymbol::parseSLD(const Config& c, Style& style)
     else if ( match(c.key(), "fill-opacity") ) {
         style.getOrCreate<PolygonSymbol>()->fill()->color().a() = as<float>( c.value(), 1.0f );
     }
+    else if ( match(c.key(), "fill-script") ) {
+        style.getOrCreate<PolygonSymbol>()->script() = StringExpression(c.value());
+    }
 }
diff --git a/src/osgEarthSymbology/Query b/src/osgEarthSymbology/Query
index d314c76..be0f9c9 100644
--- a/src/osgEarthSymbology/Query
+++ b/src/osgEarthSymbology/Query
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/Query.cpp b/src/osgEarthSymbology/Query.cpp
index ed97472..bd8dfcf 100644
--- a/src/osgEarthSymbology/Query.cpp
+++ b/src/osgEarthSymbology/Query.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/RenderSymbol b/src/osgEarthSymbology/RenderSymbol
index df289d5..5ba1fc0 100644
--- a/src/osgEarthSymbology/RenderSymbol
+++ b/src/osgEarthSymbology/RenderSymbol
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -22,8 +22,7 @@
 
 #include <osgEarth/Common>
 #include <osgEarth/DepthOffset>
-#include <osgEarth/GeoMath>
-#include <osgEarthSymbology/Common>
+#include <osgEarthSymbology/Expression>
 #include <osgEarthSymbology/Symbol>
 #include <osg/Referenced>
 #include <vector>
@@ -57,6 +56,18 @@ namespace osgEarth { namespace Symbology
         optional<bool>& backfaceCulling() { return _backfaceCulling; }
         const optional<bool>& backfaceCulling() const { return _backfaceCulling; }
 
+        /** applies a rendering order to affected geometry */
+        optional<NumericExpression>& order() { return _order; }
+        const optional<NumericExpression>& order() const { return _order; }
+
+        /** clip plane number to activate. */
+        optional<unsigned>& clipPlane() { return _clipPlane; }
+        const optional<unsigned>& clipPlane() const { return _clipPlane; }
+
+        /** discard fragments with alpha < threshold */
+        optional<float>& minAlpha() { return _minAlpha; }
+        const optional<float>& minAlpha() const { return _minAlpha; }
+
     public:
         virtual Config getConfig() const;
         virtual void mergeConfig( const Config& conf );
@@ -67,6 +78,9 @@ namespace osgEarth { namespace Symbology
         optional<bool>               _lighting;
         optional<DepthOffsetOptions> _depthOffset;
         optional<bool>               _backfaceCulling;
+        optional<NumericExpression>  _order;
+        optional<unsigned>           _clipPlane;
+        optional<float>              _minAlpha;
         
         /** dtor */
         virtual ~RenderSymbol() { }
diff --git a/src/osgEarthSymbology/RenderSymbol.cpp b/src/osgEarthSymbology/RenderSymbol.cpp
index a7c6d71..f05ba83 100644
--- a/src/osgEarthSymbology/RenderSymbol.cpp
+++ b/src/osgEarthSymbology/RenderSymbol.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -28,7 +28,10 @@ RenderSymbol::RenderSymbol(const Config& conf) :
 Symbol          ( conf ),
 _depthTest      ( true ),
 _lighting       ( true ),
-_backfaceCulling( true )
+_backfaceCulling( true ),
+_order          ( 0 ),
+_clipPlane      ( 0 ),
+_minAlpha       ( 0.0f )
 {
     mergeConfig(conf);
 }
@@ -42,6 +45,9 @@ RenderSymbol::getConfig() const
     conf.addIfSet   ( "lighting",         _lighting );
     conf.addObjIfSet( "depth_offset",     _depthOffset );
     conf.addIfSet   ( "backface_culling", _backfaceCulling );
+    conf.addObjIfSet( "order",            _order );
+    conf.addIfSet   ( "clip_plane",       _clipPlane );
+    conf.addIfSet   ( "min_alpha",        _minAlpha );
     return conf;
 }
 
@@ -52,6 +58,9 @@ RenderSymbol::mergeConfig( const Config& conf )
     conf.getIfSet   ( "lighting",         _lighting );
     conf.getObjIfSet( "depth_offset",     _depthOffset );
     conf.getIfSet   ( "backface_culling", _backfaceCulling );
+    conf.getObjIfSet( "order",            _order );
+    conf.getIfSet   ( "clip_plane",       _clipPlane );
+    conf.getIfSet   ( "min_alpha",        _minAlpha );
 }
 
 void
@@ -87,4 +96,13 @@ RenderSymbol::parseSLD(const Config& c, Style& style)
     else if ( match(c.key(), "render-backface-culling") ) {
         style.getOrCreate<RenderSymbol>()->backfaceCulling() = as<bool>(c.value(), *defaults.backfaceCulling() );
     }
+    else if ( match(c.key(), "render-order") ) {
+        style.getOrCreate<RenderSymbol>()->order() = !c.value().empty() ? NumericExpression(c.value()) : *defaults.order();
+    }
+    else if ( match(c.key(), "render-clip-plane") ) {
+        style.getOrCreate<RenderSymbol>()->clipPlane() = as<unsigned>(c.value(), *defaults.clipPlane() );
+    }
+    else if ( match(c.key(), "render-min-alpha") ) {
+        style.getOrCreate<RenderSymbol>()->minAlpha() = as<float>(c.value(), *defaults.minAlpha() );
+    }
 }
diff --git a/src/osgEarthSymbology/Resource b/src/osgEarthSymbology/Resource
index 724fde6..bada14c 100644
--- a/src/osgEarthSymbology/Resource
+++ b/src/osgEarthSymbology/Resource
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/Resource.cpp b/src/osgEarthSymbology/Resource.cpp
index 6b64ef3..85ac9a1 100644
--- a/src/osgEarthSymbology/Resource.cpp
+++ b/src/osgEarthSymbology/Resource.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/ResourceCache b/src/osgEarthSymbology/ResourceCache
index 8393792..011f1a3 100644
--- a/src/osgEarthSymbology/ResourceCache
+++ b/src/osgEarthSymbology/ResourceCache
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -24,6 +24,7 @@
 #include <osgEarthSymbology/Skins>
 #include <osgEarthSymbology/MarkerResource>
 #include <osgEarthSymbology/InstanceResource>
+#include <osgEarthSymbology/ResourceLibrary>
 #include <osgEarth/Containers>
 #include <osgEarth/ThreadingUtils>
 
@@ -35,28 +36,23 @@ namespace osgEarth { namespace Symbology
      * Caches the runtime objects created by resources, so we can avoid creating them
      * each time they are referenced.
      *
-     * This object is intended for use by a FilterContext, and therefore will only
-     * run in an isolated thread. No thread-safety is required in that scenario.
+     * This object is thread-safe.
      */
     class OSGEARTHSYMBOLOGY_EXPORT ResourceCache : public osg::Referenced
     {
     public:
         /** 
          * Constructs a new resource cache.
-         * @param threadSafe Whether to protect access to the cache so that you can
-         *        use it from multiple threads (default = false)
          */
         ResourceCache( 
-            const osgDB::Options* dbOptions,
-            bool                  threadSafe =false );
-
-        /** dtor */
-        virtual ~ResourceCache() { }
+            const osgDB::Options* dbOptions );
 
         /**
          * Fetches the StateSet implementation corresponding to a Skin.
+         * @param skin   Skin resource for which to get or create a state set.
+         * @param output Result goes here.
          */
-        bool getStateSet( SkinResource* skin, osg::ref_ptr<osg::StateSet>& output );
+        bool getOrCreateStateSet( SkinResource* skin, osg::ref_ptr<osg::StateSet>& output );
 
         /**
          * Get the statistics collected from the skin cache.
@@ -64,31 +60,38 @@ namespace osgEarth { namespace Symbology
         const CacheStats getSkinStats() const { return _skinCache.getStats(); }
 
         /**
-         * Gets a node corresponding to a marker.
-         * @deprecated
+         * Gets a node corresponding to an instance resource.
+         * @param skin   Instance resource for which to get or create a Node.
+         * @param output Result goes here.
          */
-        bool getMarkerNode( MarkerResource* marker, osg::ref_ptr<osg::Node>& output );
+        bool getOrCreateInstanceNode( InstanceResource* instance, osg::ref_ptr<osg::Node>& output );
+        bool cloneOrCreateInstanceNode( InstanceResource* instance, osg::ref_ptr<osg::Node>& output );
 
         /**
-         * Gets a node corresponding to an instance resource.
+         * Fetches the StateSet implemention for an entire ResourceLibrary.  This will contain a Texture2DArray with all of the skins merged into it.
+         * @param library    The library 
+         * @param output     Result goes here. 
          */
-        bool getInstanceNode( InstanceResource* instance, osg::ref_ptr<osg::Node>& output );
+        bool getOrCreateStateSet( ResourceLibrary* library,  osg::ref_ptr<osg::StateSet>& output );
 
     protected:
+        virtual ~ResourceCache() { }
+
         osg::ref_ptr<const osgDB::Options> _dbOptions;
-        bool                               _threadSafe;
 
+        //typedef LRUCache<std::string, osg::observer_ptr<osg::StateSet> > SkinCache;
         typedef LRUCache<std::string, osg::ref_ptr<osg::StateSet> > SkinCache;
-        SkinCache _skinCache;
-        Threading::ReadWriteMutex _skinMutex;
-
-        typedef LRUCache<std::string, osg::ref_ptr<osg::Node> > MarkerCache;
-        MarkerCache _markerCache;
-        Threading::ReadWriteMutex _markerMutex;
+        SkinCache        _skinCache;
+        Threading::Mutex _skinMutex;
 
+        //typedef LRUCache<std::string, osg::observer_ptr<osg::Node> > InstanceCache;
         typedef LRUCache<std::string, osg::ref_ptr<osg::Node> > InstanceCache;
-        InstanceCache _instanceCache;
-        Threading::ReadWriteMutex _instanceMutex;
+        InstanceCache    _instanceCache;
+        Threading::Mutex _instanceMutex;
+
+        typedef LRUCache<std::string, osg::ref_ptr<osg::StateSet> > ResourceLibraryCache;
+        ResourceLibraryCache  _resourceLibraryCache;
+        Threading::Mutex      _resourceLibraryMutex;
     };
 
 } } // namespace osgEarth::Symbology
diff --git a/src/osgEarthSymbology/ResourceCache.cpp b/src/osgEarthSymbology/ResourceCache.cpp
index ca99dbd..4ff88c3 100644
--- a/src/osgEarthSymbology/ResourceCache.cpp
+++ b/src/osgEarthSymbology/ResourceCache.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,70 +21,48 @@
 using namespace osgEarth;
 using namespace osgEarth::Symbology;
 
-ResourceCache::ResourceCache(const osgDB::Options* dbOptions,
-                             bool                  threadSafe ) :
+
+// internal thread-safety not required since we mutex it in this object.
+ResourceCache::ResourceCache(const osgDB::Options* dbOptions ) :
 _dbOptions    ( dbOptions ),
-_threadSafe   ( threadSafe ),
 _skinCache    ( false ),
-_markerCache  ( false ),
-_instanceCache( false )
+_instanceCache( false ),
+_resourceLibraryCache( false )
 {
     //nop
 }
 
 bool
-ResourceCache::getStateSet(SkinResource*                skin,
-                           osg::ref_ptr<osg::StateSet>& output)
+ResourceCache::getOrCreateStateSet(SkinResource*                skin,
+                                   osg::ref_ptr<osg::StateSet>& output)
 {
     output = 0L;
-    std::string key = skin->getConfig().toJSON(false);
-
-    if ( _threadSafe )
-    {
-        // first check if it exists
-        {
-            Threading::ScopedReadLock shared( _skinMutex );
-
-            SkinCache::Record rec;
-            if ( _skinCache.get(key, rec) )
-            {
-                output = rec.value().get();
-            }
-        }
+    //std::string key = skin->getConfig().toJSON(false);
 
-        // no? exclusive lock and create it.
-        if ( !output.valid() )
-        {
-            Threading::ScopedWriteLock exclusive( _skinMutex );
-            
-            // double check to avoid race condition
-            SkinCache::Record rec;
-            if ( _skinCache.get(key, rec) )
-            {
-                output = rec.value().get();
-            }
-            else
-            {
-                // still not there, make it.
-                output = skin->createStateSet( _dbOptions.get() );
-                if ( output.valid() )
-                    _skinCache.insert( key, output.get() );
-            }
-        }
-    }
+    // Note: we use the imageURI as the basis for the caching key since 
+    // it's the only property used by Skin->createStateSet(). If that
+    // changes, we need to address it here. It might be better it SkinResource
+    // were to provide a unique key.
+    std::string key = skin->getUniqueID();
 
-    else
+    // exclusive lock (since it's an LRU)
     {
-        SkinCache::Record rec;
-        if ( _skinCache.get(key, rec) )
+        Threading::ScopedMutexLock exclusive( _skinMutex );
+            
+        // double check to avoid race condition
+        SkinCache::Record rec;       
+        if ( _skinCache.get(key, rec) && rec.value().valid() )
         {
-            output = rec.value();
+            output = rec.value().get();
         }
         else
         {
+            // still not there, make it.
             output = skin->createStateSet( _dbOptions.get() );
             if ( output.valid() )
+            {
                 _skinCache.insert( key, output.get() );
+            }
         }
     }
 
@@ -93,118 +71,62 @@ ResourceCache::getStateSet(SkinResource*                skin,
 
 
 bool
-ResourceCache::getInstanceNode(InstanceResource*        res,
-                               osg::ref_ptr<osg::Node>& output)
+ResourceCache::getOrCreateInstanceNode(InstanceResource*        res,
+                                       osg::ref_ptr<osg::Node>& output)
 {
     output = 0L;
     std::string key = res->getConfig().toJSON(false);
 
-    if ( _threadSafe )
+    // exclusive lock (since it's an LRU)
     {
-        // first check if it exists
-        {
-            Threading::ScopedReadLock shared( _instanceMutex );
-
-            InstanceCache::Record rec;
-            if ( _instanceCache.get(key, rec) )
-            {
-                output = rec.value().get();
-            }
-        }
-
-        // no? exclusive lock and create it.
-        if ( !output.valid() )
-        {
-            Threading::ScopedWriteLock exclusive( _instanceMutex );
-            
-            // double check to avoid race condition
-            InstanceCache::Record rec;
-            if ( _instanceCache.get(key, rec) )
-            {
-                output = rec.value().get();
-            }
-            else
-            {
-                // still not there, make it.
-                output = res->createNode( _dbOptions.get() );
-                if ( output.valid() )
-                    _instanceCache.insert( key, output.get() );
-            }
-        }
-    }
+        Threading::ScopedMutexLock exclusive( _instanceMutex );
 
-    else
-    {
+        // double check to avoid race condition
         InstanceCache::Record rec;
-        if ( _instanceCache.get(key, rec) )
+        if ( _instanceCache.get(key, rec) && rec.value().valid() )
         {
             output = rec.value().get();
         }
         else
         {
+            // still not there, make it.
             output = res->createNode( _dbOptions.get() );
             if ( output.valid() )
+            {
                 _instanceCache.insert( key, output.get() );
+            }
         }
     }
 
     return output.valid();
 }
 
-
 bool
-ResourceCache::getMarkerNode(MarkerResource*          marker,
-                             osg::ref_ptr<osg::Node>& output)
+ResourceCache::cloneOrCreateInstanceNode(InstanceResource*        res,
+                                         osg::ref_ptr<osg::Node>& output)
 {
     output = 0L;
-    std::string key = marker->getConfig().toJSON(false);
+    std::string key = res->getConfig().toJSON(false);
 
-    if ( _threadSafe )
+    // exclusive lock (since it's an LRU)
     {
-        // first check if it exists
-        {
-            Threading::ScopedReadLock shared( _markerMutex );
-
-            MarkerCache::Record rec;
-            if ( _markerCache.get(key, rec) )
-            {
-                output = rec.value().get();
-            }
-        }
-
-        // no? exclusive lock and create it.
-        if ( !output.valid() )
-        {
-            Threading::ScopedWriteLock exclusive( _markerMutex );
-            
-            // double check to avoid race condition
-            MarkerCache::Record rec;
-            if ( _markerCache.get( key, rec ) )
-            {
-                output = rec.value().get();
-            }
-            else
-            {
-                // still not there, make it.
-                output = marker->createNode( _dbOptions.get() );
-                if ( output.valid() )
-                    _markerCache.insert( key, output.get() );
-            }
-        }
-    }
+        Threading::ScopedMutexLock exclusive( _instanceMutex );
 
-    else
-    {
-        MarkerCache::Record rec;
-        if ( _markerCache.get( key, rec ) )
+        // double check to avoid race condition
+        InstanceCache::Record rec;
+        if ( _instanceCache.get(key, rec) && rec.value().valid() )
         {
-            output = rec.value().get();
+            output = osg::clone(rec.value().get(), osg::CopyOp::DEEP_COPY_ALL);
         }
         else
         {
-            output = marker->createNode( _dbOptions.get() );
+            // still not there, make it.
+            output = res->createNode( _dbOptions.get() );
             if ( output.valid() )
-                _markerCache.insert( key, output.get() );
+            {
+                _instanceCache.insert( key, output.get() );
+                output = osg::clone(output.get(), osg::CopyOp::DEEP_COPY_ALL);
+            }
         }
     }
 
diff --git a/src/osgEarthSymbology/ResourceLibrary b/src/osgEarthSymbology/ResourceLibrary
index e4c83af..e0064f1 100644
--- a/src/osgEarthSymbology/ResourceLibrary
+++ b/src/osgEarthSymbology/ResourceLibrary
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -59,6 +59,11 @@ namespace osgEarth { namespace Symbology
         /** dtor */
         virtual ~ResourceLibrary() { }
 
+        /** 
+         * Initialize the catalog by loading its contents into memory
+         */
+        bool initialize( const osgDB::Options* options );
+
         /**
          * Gets the name of the lib.
          */
@@ -128,9 +133,11 @@ namespace osgEarth { namespace Symbology
         void mergeConfig( const Config& conf );
         Config getConfig() const;
 
+        optional<URI>& uri() { return _uri; }
+        const optional<URI>& uri() const { return _uri; }
+
     protected:
         typedef std::map< const Symbol*, Random > RandomMap;
-
         optional<URI>                      _uri;
         std::string                        _name;
         bool                               _initialized;
@@ -141,8 +148,6 @@ namespace osgEarth { namespace Symbology
         ResourceMap<InstanceResource>      _instances;
 
         bool matches( const SkinSymbol* symbol, SkinResource* skin ) const;
-
-        void initialize( const osgDB::Options* options );
     };
 
 
diff --git a/src/osgEarthSymbology/ResourceLibrary.cpp b/src/osgEarthSymbology/ResourceLibrary.cpp
index 670ce53..ec54360 100644
--- a/src/osgEarthSymbology/ResourceLibrary.cpp
+++ b/src/osgEarthSymbology/ResourceLibrary.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -82,7 +82,7 @@ ResourceLibrary::mergeConfig( const Config& conf )
 Config
 ResourceLibrary::getConfig() const
 {
-    Config conf;
+    Config conf("resources");
     {
         Threading::ScopedReadLock shared( const_cast<ResourceLibrary*>(this)->_mutex );
 
@@ -166,19 +166,25 @@ ResourceLibrary::removeResource( Resource* resource )
 
 static Threading::Mutex s_initMutex;
 
-void
+bool
 ResourceLibrary::initialize( const osgDB::Options* dbOptions )
 {
+    bool ok = true;
+
     if ( !_initialized )
     {
         Threading::ScopedMutexLock exclusive(s_initMutex);
         if ( !_initialized )
         {
+            ok = false;
+
             if ( _uri.isSet() )
             {
                 osg::ref_ptr<XmlDocument> xml = XmlDocument::load( *_uri, dbOptions );
                 if ( xml.valid() )
                 {
+                    ok = true;
+
                     Config conf = xml->getConfig();
                     if ( conf.key() == "resources" )
                     {
@@ -195,6 +201,8 @@ ResourceLibrary::initialize( const osgDB::Options* dbOptions )
             _initialized = true;
         }
     }
+
+    return ok;
 }
 
 SkinResource*
@@ -236,6 +244,12 @@ SkinResource*
 ResourceLibrary::getSkin( const SkinSymbol* symbol, Random& prng, const osgDB::Options* dbOptions ) const
 {
     const_cast<ResourceLibrary*>(this)->initialize( dbOptions );
+
+    if (symbol->name().isSet())
+    {
+        return getSkin(symbol->name()->eval(), dbOptions);
+    }
+
     SkinResourceVector candidates;
     getSkins( symbol, candidates );
     unsigned size = candidates.size();
@@ -256,6 +270,11 @@ ResourceLibrary::getSkin( const SkinSymbol* symbol, Random& prng, const osgDB::O
 bool
 ResourceLibrary::matches( const SkinSymbol* q, SkinResource* s ) const
 {
+    if ( q->name().isSet() )
+    {
+        return osgEarth::ciEquals(q->name()->eval(), s->name());
+    }
+
     if (q->objectHeight().isSet())
     {
         if (s->minObjectHeight().isSet() && 
diff --git a/src/osgEarthSymbology/Skins b/src/osgEarthSymbology/Skins
index 72bedab..80d1e50 100644
--- a/src/osgEarthSymbology/Skins
+++ b/src/osgEarthSymbology/Skins
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -25,6 +25,7 @@
 #include <osgEarthSymbology/Symbol>
 #include <osgEarth/URI>
 #include <osg/TexEnv>
+#include <osg/Texture2DArray>
 
 namespace osgEarth { namespace Symbology
 {
@@ -48,6 +49,17 @@ namespace osgEarth { namespace Symbology
          */
         osg::StateSet* createStateSet( const osgDB::Options* dbOptions ) const;
 
+        /**
+         * Creates an image for this SkinResource.
+         */
+        osg::Image* createImage( const osgDB::Options* options ) const;
+
+        /** 
+         * A key string that can uniquely identify this object for the purposes
+         * of creating its state set (e.g., for a cache)
+         */
+        std::string getUniqueID() const;
+
     public:
         /** Source location of the actual texture image.  */
         optional<URI>& imageURI() { return _imageURI; }
@@ -73,6 +85,26 @@ namespace osgEarth { namespace Symbology
         optional<bool>& isTiled() { return _isTiled; }
         const optional<bool>& isTiled() const { return _isTiled; }
 
+        /** Image offset within a source atlas (S dimension [0..1]) */
+        optional<float>& imageBiasS() { return _imageBiasS; }
+        const optional<float>& imageBiasS() const { return _imageBiasS; }
+
+        /** Image offset (pixels) within a source atlas (T dimension [0..1]) */
+        optional<float>& imageBiasT() { return _imageBiasT; }
+        const optional<float>& imageBiasT() const { return _imageBiasT; }
+
+        /** Image layer index within a source atlas (R dimension) */
+        optional<unsigned>& imageLayer() { return _imageLayer; }
+        const optional<unsigned>& imageLayer() const { return _imageLayer; }
+
+        /** Image scalke factor within a source atlas (S dimension) */
+        optional<float>& imageScaleS() { return _imageScaleS; }
+        const optional<float>& imageScaleS() const { return _imageScaleS; }
+
+        /** Image scalke factor within a source atlas (T dimension) */
+        optional<float>& imageScaleT() { return _imageScaleT; }
+        const optional<float>& imageScaleT() const { return _imageScaleT; }
+
         /** GL texture application mode */
         optional<osg::TexEnv::Mode>& texEnvMode() { return _texEnvMode; }
         const optional<osg::TexEnv::Mode>& texEnvMode() const { return _texEnvMode; }
@@ -88,12 +120,11 @@ namespace osgEarth { namespace Symbology
 
     protected:
 
-        osg::StateSet* createStateSet( osg::Image* image ) const;
-
-        osg::Image* createImage( const osgDB::Options* options ) const;
+        osg::StateSet* createStateSet( osg::Image* image ) const;        
 
     protected:
 
+        optional<std::string>       _name;
         optional<URI>               _imageURI;
         optional<float>             _imageWidth;
         optional<float>             _imageHeight;
@@ -102,6 +133,11 @@ namespace osgEarth { namespace Symbology
         optional<bool>              _isTiled;
         optional<osg::TexEnv::Mode> _texEnvMode;
         optional<unsigned>          _maxTexSpan;
+        optional<float>             _imageBiasS;
+        optional<float>             _imageBiasT;
+        optional<unsigned>          _imageLayer;
+        optional<float>             _imageScaleS;
+        optional<float>             _imageScaleT;
     };
 
 
@@ -145,6 +181,10 @@ namespace osgEarth { namespace Symbology
         optional<unsigned>& randomSeed() { return _randomSeed; }
         const optional<unsigned>& randomSeed() const { return _randomSeed; }
 
+        /** Name of a specific skin in the catalog */
+        optional<StringExpression>& name() { return _name; }
+        const optional<StringExpression>& name() const { return _name; }
+
     public:
         void mergeConfig(const Config& conf);
         Config getConfig() const;
@@ -157,10 +197,43 @@ namespace osgEarth { namespace Symbology
         optional<float>       _maxObjHeight;
         optional<bool>        _isTiled;
         optional<unsigned>    _randomSeed;
+        optional<StringExpression> _name;
     };
 
     typedef std::vector< osg::ref_ptr<SkinResource> > SkinResourceVector;
 
+#if 0
+    /**
+     * Utility class that builds a Texture2DArray from a list of SkinResources
+     */
+    class OSGEARTHSYMBOLOGY_EXPORT SkinTextureArray : public osg::Referenced
+    {
+    public:
+        SkinTextureArray();
+
+        /**
+         * Gets the Texture2DArray for this SkinTextureArray
+         */
+        osg::Texture2DArray* getTexture();
+
+        /**
+         * Gets the index into the texture array for the given SkinResource.
+         */
+        int getSkinIndex( const SkinResource* skin );
+
+        /**
+         * Builds a Texture2DArray from the given SkinResources.
+         */
+        void build(SkinResourceVector& skins, const osgDB::Options* dbOptions);
+
+    protected:
+
+        typedef std::map< std::string, int > LayerIndex;
+        LayerIndex _layerIndex;
+        osg::ref_ptr< osg::Texture2DArray > _texture;
+    };
+#endif
+
 } } // namespace osgEarth::Symbology
 
 #endif // OSGEARTHSYMBOLOGY_SKIN_RESOURCE_H
diff --git a/src/osgEarthSymbology/Skins.cpp b/src/osgEarthSymbology/Skins.cpp
index aa1c39a..eca3b47 100644
--- a/src/osgEarthSymbology/Skins.cpp
+++ b/src/osgEarthSymbology/Skins.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -25,6 +25,8 @@
 #include <osg/BlendFunc>
 #include <osg/Texture2D>
 
+#define LC "[SkinResource] "
+
 using namespace osgEarth;
 using namespace osgEarth::Symbology;
 
@@ -38,7 +40,12 @@ _minObjHeight     ( 0.0f ),
 _maxObjHeight     ( FLT_MAX ),
 _isTiled          ( false ),
 _texEnvMode       ( osg::TexEnv::MODULATE ),
-_maxTexSpan       ( 1024 )
+_maxTexSpan       ( 1024 ),
+_imageBiasS       ( 0.0f ),
+_imageBiasT       ( 0.0f ),
+_imageLayer       ( 0 ),
+_imageScaleS      ( 1.0f ),
+_imageScaleT      ( 1.0f )
 {
     mergeConfig( conf );
 }
@@ -58,6 +65,13 @@ SkinResource::mergeConfig( const Config& conf )
     conf.getIfSet( "texture_mode", "modulate", _texEnvMode, osg::TexEnv::MODULATE );
     conf.getIfSet( "texture_mode", "replace",  _texEnvMode, osg::TexEnv::REPLACE );
     conf.getIfSet( "texture_mode", "blend",    _texEnvMode, osg::TexEnv::BLEND );
+
+    // texture atlas support
+    conf.getIfSet( "image_bias_s",        _imageBiasS );
+    conf.getIfSet( "image_bias_t",        _imageBiasT );
+    conf.getIfSet( "image_layer",         _imageLayer );
+    conf.getIfSet( "image_scale_s",       _imageScaleS );
+    conf.getIfSet( "image_scale_t",       _imageScaleT );
 }
 
 Config
@@ -73,18 +87,32 @@ SkinResource::getConfig() const
     conf.updateIfSet( "max_object_height",   _maxObjHeight );
     conf.updateIfSet( "tiled",               _isTiled );
     conf.updateIfSet( "max_texture_span",    _maxTexSpan );
-
+    
     conf.updateIfSet( "texture_mode", "decal",    _texEnvMode, osg::TexEnv::DECAL );
     conf.updateIfSet( "texture_mode", "modulate", _texEnvMode, osg::TexEnv::MODULATE );
     conf.updateIfSet( "texture_mode", "replace",  _texEnvMode, osg::TexEnv::REPLACE );
     conf.updateIfSet( "texture_mode", "blend",    _texEnvMode, osg::TexEnv::BLEND );
 
+    // texture atlas support
+    conf.updateIfSet( "image_bias_s",        _imageBiasS );
+    conf.updateIfSet( "image_bias_t",        _imageBiasT );
+    conf.updateIfSet( "image_layer",         _imageLayer );
+    conf.updateIfSet( "image_scale_s",       _imageScaleS );
+    conf.updateIfSet( "image_scale_t",       _imageScaleT );
+
     return conf;
 }
 
+std::string
+SkinResource::getUniqueID() const
+{
+    return imageURI()->full();
+}
+
 osg::StateSet*
 SkinResource::createStateSet( const osgDB::Options* dbOptions ) const
 {
+    OE_DEBUG << LC << "Creating skin state set for " << imageURI()->full() << std::endl;
     return createStateSet( createImage(dbOptions) );
 }
 
@@ -95,19 +123,49 @@ SkinResource::createStateSet( osg::Image* image ) const
     if ( image )
     {
         stateSet = new osg::StateSet();
+        
+        osg::Texture* tex;
+
+        if ( image->r() > 1 )
+        {
+            osg::Texture2DArray* ta = new osg::Texture2DArray();
+            
+            ta->setTextureDepth( image->r() );
+            ta->setTextureWidth( image->s() );
+            ta->setTextureHeight( image->t() );
+            ta->setInternalFormatMode(osg::Texture::USE_IMAGE_DATA_FORMAT);
+            tex = ta;
 
-        osg::Texture* tex = new osg::Texture2D( image );
+            std::vector<osg::ref_ptr<osg::Image> > layers;
+            ImageUtils::flattenImage(image, layers);
+            for(unsigned i=0; i<layers.size(); ++i)
+            {
+                tex->setImage(i, layers[i].get());
+            }
+            tex->setWrap( osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE );
+            tex->setWrap( osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE );
+            stateSet->setTextureAttribute( 0, tex, osg::StateAttribute::ON );
+        }
+        else
+        {
+            tex = new osg::Texture2D( image );
+            tex->setWrap( osg::Texture::WRAP_S, osg::Texture::REPEAT );
+            tex->setWrap( osg::Texture::WRAP_T, osg::Texture::REPEAT );     
+            stateSet->setTextureAttributeAndModes( 0, tex, osg::StateAttribute::ON );
+        }
+        
+        tex->setFilter(osg::Texture::MIN_FILTER,osg::Texture::LINEAR_MIPMAP_LINEAR);
+        tex->setFilter(osg::Texture::MAG_FILTER,osg::Texture::LINEAR);
+
+        tex->setUnRefImageDataAfterApply(true);
         tex->setResizeNonPowerOfTwoHint(false);
-        tex->setWrap( osg::Texture::WRAP_S, osg::Texture::REPEAT );
-        tex->setWrap( osg::Texture::WRAP_T, osg::Texture::REPEAT );
-        stateSet->setTextureAttributeAndModes( 0, tex, osg::StateAttribute::ON );
 
         if ( _texEnvMode.isSet() )
         {
             osg::TexEnv* texenv = new osg::TexEnv();
             texenv = new osg::TexEnv();
             texenv->setMode( *_texEnvMode );
-            stateSet->setTextureAttribute( 0, texenv, osg::StateAttribute::ON );
+            stateSet->setTextureAttributeAndModes( 0, texenv, osg::StateAttribute::ON );
         }
 
         if ( ImageUtils::hasAlphaChannel( image ) )
@@ -152,6 +210,7 @@ SkinSymbol::mergeConfig( const Config& conf )
     conf.getIfSet( "max_object_height",   _maxObjHeight );
     conf.getIfSet( "tiled",               _isTiled );
     conf.getIfSet( "random_seed",         _randomSeed );
+    conf.getObjIfSet( "name",                _name );
 
     addTags( conf.value("tags" ) );
 }
@@ -168,6 +227,7 @@ SkinSymbol::getConfig() const
     conf.addIfSet( "max_object_height",   _maxObjHeight );
     conf.addIfSet( "tiled",               _isTiled );
     conf.addIfSet( "random_seed",         _randomSeed );
+    conf.addObjIfSet( "name",                _name );
 
     std::string tagstring = this->tagString();
     if ( !tagstring.empty() )
@@ -202,4 +262,87 @@ SkinSymbol::parseSLD(const Config& c, Style& style)
     else if (match(c.key(), "skin-random-seed") ) {
         style.getOrCreate<SkinSymbol>()->randomSeed() = as<unsigned>( c.value(), 0u );
     }
+    else if (match(c.key(), "skin-name")) {
+        style.getOrCreate<SkinSymbol>()->name() = StringExpression(c.value());
+    }
+}
+
+#if 0
+//---------------------------------------------------------------------------
+SkinTextureArray::SkinTextureArray()
+{        
+}
+
+osg::Texture2DArray* SkinTextureArray::getTexture()
+{
+    return _texture.get();
+}
+
+int SkinTextureArray::getSkinIndex( const SkinResource* skin )
+{
+    LayerIndex::iterator itr = _layerIndex.find( skin->name());
+    if (itr != _layerIndex.end())
+    {
+        return itr->second;
+    }        
+    return -1;
+}
+
+void SkinTextureArray::build(SkinResourceVector& skins, const osgDB::Options* dbOptions)
+{        
+    _texture = 0;
+    _layerIndex.clear();
+
+    unsigned int maxWidth = 0;
+    unsigned int maxHeight = 0;
+
+    std::vector< osg::ref_ptr< osg::Image > > images;
+
+    for (unsigned int i = 0; i < skins.size(); i++)
+    {
+        osg::ref_ptr< osg::Image > image = skins[i]->createImage( dbOptions );
+        if (image.valid())
+        {
+            if (maxWidth < image->s()) maxWidth = image->s();
+            if (maxHeight < image->t()) maxHeight = image->t();            
+            _layerIndex[ skins[i]->name() ] = i;
+            images.push_back( image.get() );
+        }
+    }
+
+    // Now resize all the images to the largest size.
+    for (unsigned int i = 0; i < images.size(); i++)
+    {
+        osg::ref_ptr< osg::Image> resized;
+        if (images[i]->s() != maxWidth || images[i]->t() != maxHeight)
+        {            
+            OE_DEBUG << "resizing image to " << maxWidth << "x" << maxHeight << std::endl;
+            ImageUtils::resizeImage( images[i].get(), maxWidth, maxHeight, resized, 0, true);
+        }        
+        else
+        {
+             resized = images[i].get();
+        }
+        resized = ImageUtils::convertToRGBA8( resized.get() );
+        images[i] = resized.get();
+    }
+
+    osg::Texture2DArray* texture = new osg::Texture2DArray();
+    texture->setTextureDepth( images.size() );
+    texture->setTextureWidth( maxWidth );
+    texture->setTextureHeight( maxHeight );
+    texture->setSourceFormat( GL_RGBA );
+    texture->setInternalFormat( GL_RGBA8 );
+
+    texture->setFilter(osg::Texture::MIN_FILTER,osg::Texture::LINEAR_MIPMAP_LINEAR);
+    texture->setFilter(osg::Texture::MAG_FILTER,osg::Texture::LINEAR);
+    texture->setWrap(osg::Texture::WRAP_S,osg::Texture::REPEAT);
+    texture->setWrap(osg::Texture::WRAP_T,osg::Texture::REPEAT);
+    texture->setResizeNonPowerOfTwoHint(false);
+    for (unsigned int i = 0; i < images.size(); i++)
+    {
+        texture->setImage(i, images[i].get() );
+    }
+    _texture = texture;
 }
+#endif
diff --git a/src/osgEarthSymbology/StencilVolumeNode b/src/osgEarthSymbology/StencilVolumeNode
index 3afa0cf..b9fe08c 100644
--- a/src/osgEarthSymbology/StencilVolumeNode
+++ b/src/osgEarthSymbology/StencilVolumeNode
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/StencilVolumeNode.cpp b/src/osgEarthSymbology/StencilVolumeNode.cpp
index b4d1847..cb91515 100644
--- a/src/osgEarthSymbology/StencilVolumeNode.cpp
+++ b/src/osgEarthSymbology/StencilVolumeNode.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/Stroke b/src/osgEarthSymbology/Stroke
index 64dd83c..d803cf2 100644
--- a/src/osgEarthSymbology/Stroke
+++ b/src/osgEarthSymbology/Stroke
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/Stroke.cpp b/src/osgEarthSymbology/Stroke.cpp
index 0573655..8be1c8d 100644
--- a/src/osgEarthSymbology/Stroke.cpp
+++ b/src/osgEarthSymbology/Stroke.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -55,13 +55,15 @@ Stroke::Stroke(const Stroke& rhs)
 void
 Stroke::init()
 {
-    _color.set         ( 1.0f, 1.0f, 1.0f, 1.0f );
-    _lineCap.init      ( LINECAP_FLAT );
-    _lineJoin.init     ( LINEJOIN_ROUND );
-    _width.init        ( 1.0f );
-    _widthUnits.init   ( Units::PIXELS );
-    _roundingRatio.init( 0.4f );
-    _minPixels.init    ( 0.0f );
+    _color.set          ( 1.0f, 1.0f, 1.0f, 1.0f );
+    _lineCap.init       ( LINECAP_FLAT );
+    _lineJoin.init      ( LINEJOIN_ROUND );
+    _width.init         ( 1.0f );
+    _widthUnits.init    ( Units::PIXELS );
+    _roundingRatio.init ( 0.4f );
+    _minPixels.init     ( 0.0f );
+    _stipplePattern.init( 0xFFFF );
+    _stippleFactor.init ( 1u );
 }
 
 Config 
diff --git a/src/osgEarthSymbology/Style b/src/osgEarthSymbology/Style
index 8e2a96c..2f965a8 100644
--- a/src/osgEarthSymbology/Style
+++ b/src/osgEarthSymbology/Style
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -33,7 +33,6 @@
 #include <osgEarthSymbology/TextSymbol>
 #include <osgEarthSymbology/Skins>
 #include <osgEarthSymbology/RenderSymbol>
-#include <osgEarthSymbology/ResourceLibrary>
 
 #include <osgEarth/Config>
 #include <osg/Object>
@@ -109,7 +108,7 @@ namespace osgEarth { namespace Symbology
 
         /** Whether the style contains a symbol of the template type */
         template<typename T> bool has() { return get<T>() != 0L; }
-        template<typename T> const bool has() const { return get<T>() != 0L; }
+        template<typename T> bool has() const { return get<T>() != 0L; }
 
         /** Gets a typed symbol from the style (the first one found) */
         template<typename T>
diff --git a/src/osgEarthSymbology/Style.cpp b/src/osgEarthSymbology/Style.cpp
index 9d07735..af21d34 100644
--- a/src/osgEarthSymbology/Style.cpp
+++ b/src/osgEarthSymbology/Style.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/StyleSelector b/src/osgEarthSymbology/StyleSelector
index f8000ac..906e0e7 100644
--- a/src/osgEarthSymbology/StyleSelector
+++ b/src/osgEarthSymbology/StyleSelector
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/StyleSelector.cpp b/src/osgEarthSymbology/StyleSelector.cpp
index 1d68e03..be55dd6 100644
--- a/src/osgEarthSymbology/StyleSelector.cpp
+++ b/src/osgEarthSymbology/StyleSelector.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthSymbology/StyleSheet b/src/osgEarthSymbology/StyleSheet
index f97934f..ca9e02b 100644
--- a/src/osgEarthSymbology/StyleSheet
+++ b/src/osgEarthSymbology/StyleSheet
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -36,14 +36,16 @@ namespace osgEarth { namespace Symbology
       /**
        * A simple representation of a script used locally
        */
-      struct Script : osg::Referenced
+      struct ScriptDef : osg::Referenced
       {
-        Script(const std::string& code, const std::string& language="javascript", const std::string& name="") :
-          code(code), language(language), name(name) { }
+          ScriptDef() { }
+          ScriptDef(const std::string& code, const std::string& language="javascript", const std::string& name="") :
+            code(code), language(language), name(name) { }
 
         std::string code;
         std::string language;
         std::string name;
+        optional<URI> uri;
       };
 
     public:
@@ -84,8 +86,8 @@ namespace osgEarth { namespace Symbology
         ResourceLibrary* getResourceLibrary( const std::string& name ) const;
 
         /** Script accessors */
-        void setScript( Script* script );
-        Script* script() const { return _script.get(); }
+        void setScript( ScriptDef* script );
+        ScriptDef* script() const { return _script.get(); }
 
     public: // serialization functions
 
@@ -94,7 +96,7 @@ namespace osgEarth { namespace Symbology
 
     protected:
         URIContext                   _uriContext;
-        osg::ref_ptr<Script>         _script;
+        osg::ref_ptr<ScriptDef>      _script;
         StyleSelectorList            _selectors;
         StyleMap                     _styles;
         Style                        _emptyStyle;
diff --git a/src/osgEarthSymbology/StyleSheet.cpp b/src/osgEarthSymbology/StyleSheet.cpp
index 49275d6..eb96486 100644
--- a/src/osgEarthSymbology/StyleSheet.cpp
+++ b/src/osgEarthSymbology/StyleSheet.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -154,7 +154,7 @@ StyleSheet::getResourceLibrary( const std::string& name ) const
         return 0L;
 }
 
-void StyleSheet::setScript( Script* script )
+void StyleSheet::setScript( ScriptDef* script )
 {
   _script = script;
 }
@@ -191,11 +191,14 @@ StyleSheet::getConfig() const
     if ( _script.valid() )
     {
         Config scriptConf("script");
+
         if ( !_script->name.empty() )
             scriptConf.set( "name", _script->name );
         if ( !_script->language.empty() )
             scriptConf.set( "language", _script->language );
-        if ( !_script->code.empty() )
+        if ( !_script->uri.isSet() )
+            scriptConf.set( "url", _script->uri->base() );
+        else if ( !_script->code.empty() )
             scriptConf.value() = _script->code;
 
         conf.add( scriptConf );
@@ -221,19 +224,25 @@ StyleSheet::mergeConfig( const Config& conf )
     ConfigSet scripts = conf.children( "script" );
     for( ConfigSet::iterator i = scripts.begin(); i != scripts.end(); ++i )
     {
-        // get the script code
-        std::string code = i->value();
+        _script = new ScriptDef();
+
+        // load the code from a URI if there is one:
+        if ( i->hasValue("url") )
+        {
+            _script->uri = URI( i->value("url"), _uriContext );
+            OE_INFO << LC << "Loading script from \"" << _script->uri->full() << std::endl;
+            _script->code = _script->uri->getString();
+        }
+        else
+        {
+            _script->code = i->value();
+        }
 
         // name is optional and unused at the moment
-        std::string name = i->value("name");
+        _script->name = i->value("name");
 
         std::string lang = i->value("language");
-        if ( lang.empty() ) {
-            // default to javascript
-            lang = "javascript";
-        }
-
-        _script = new Script(code, lang, name);
+        _script->language = lang.empty() ? "javascript" : lang;
     }
 
     // read any style class definitions. either "class" or "selector" is allowed
diff --git a/src/osgEarthSymbology/Symbol b/src/osgEarthSymbology/Symbol
index 436735d..3c819ab 100644
--- a/src/osgEarthSymbology/Symbol
+++ b/src/osgEarthSymbology/Symbol
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -21,6 +21,7 @@
 #define OSGEARTHSYMBOLOGY_SYMBOL_H 1
 
 #include <osgEarthSymbology/Common>
+#include <osgEarthSymbology/Expression>
 #include <osgEarth/Config>
 #include <osgEarth/URI>
 #include <osg/Referenced>
@@ -41,9 +42,14 @@ namespace osgEarth { namespace Symbology
     class OSGEARTHSYMBOLOGY_EXPORT Symbol : public osg::Referenced
     {
     public:
+        /** Script expression, optionally implemented by symbolizers. */
+        optional<StringExpression>& script() { return _script; }
+        const optional<StringExpression>& script() const { return _script; }
+
+        /** URI context (relative paths, etc) associated wtih this symbol */
         const URIContext& uriContext() const { return _uriContext; }
 
-        virtual Config getConfig() const { return Config(); }
+        virtual Config getConfig() const;
 
     public: // META_Symbol methods
         virtual bool isSameKindAs(const Symbol* sym) const =0;
@@ -51,15 +57,19 @@ namespace osgEarth { namespace Symbology
 
     protected:
         URIContext _uriContext;
+        optional<StringExpression> _script;
 
         static bool match(const std::string& key, const char* pattern);
 
         Symbol( const Config& conf =Config() );
 
+        void mergeConfig(const Config& conf);
+
         virtual ~Symbol() { }
     };
     typedef std::vector<osg::ref_ptr<Symbol> > SymbolList;
 
+
     /**
      * A Factory that can create a Symbol from a Config
      */
diff --git a/src/osgEarthSymbology/Symbol.cpp b/src/osgEarthSymbology/Symbol.cpp
index c4cc78c..65714ee 100644
--- a/src/osgEarthSymbology/Symbol.cpp
+++ b/src/osgEarthSymbology/Symbol.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -69,9 +69,25 @@ void SymbolRegistry::parseSLD(const Config& c, class Style& style) const
 
 //------------------------------------------------------------------------
 
-Symbol::Symbol( const Config& conf )
+Symbol::Symbol(const Config& conf) :
+_script( StringExpression("{}") )
 {
     _uriContext = URIContext(conf.referrer());
+    mergeConfig(conf);
+}
+
+void
+Symbol::mergeConfig(const Config& conf)
+{
+    conf.getObjIfSet("script", _script);
+}
+
+Config
+Symbol::getConfig() const
+{
+    Config conf;
+    conf.addObjIfSet("script", _script);
+    return conf;
 }
 
 bool
diff --git a/src/osgEarthSymbology/Tags b/src/osgEarthSymbology/Tags
index ba6a3f2..965388a 100644
--- a/src/osgEarthSymbology/Tags
+++ b/src/osgEarthSymbology/Tags
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -103,9 +103,7 @@ namespace osgEarth { namespace Symbology
     private:
 
         std::string normalize( const std::string& input ) const {
-            std::string output = input;
-            std::transform( output.begin(), output.end(), output.begin(), tolower );
-            return output;
+            return osgEarth::toLower(input);
         }
     };
 
diff --git a/src/osgEarthSymbology/TextSymbol b/src/osgEarthSymbology/TextSymbol
index 50bba2f..dbb718a 100644
--- a/src/osgEarthSymbology/TextSymbol
+++ b/src/osgEarthSymbology/TextSymbol
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -103,8 +103,8 @@ namespace osgEarth { namespace Symbology
         const optional<NumericExpression>& priority() const { return _priority; }
 
         /** Font size. */
-        optional<float>& size() { return _size; }
-        const optional<float>& size() const { return _size; }
+        optional<NumericExpression>& size() { return _size; }
+        const optional<NumericExpression>& size() const { return _size; }
 
         /** Pixel offset from the center point. */
         optional<osg::Vec2s>& pixelOffset() { return _pixelOffset; }
@@ -152,7 +152,7 @@ namespace osgEarth { namespace Symbology
         optional<Stroke>            _halo;
         optional<float>             _haloOffset;
         optional<std::string>       _font;
-        optional<float>             _size;
+        optional<NumericExpression> _size;
         optional<StringExpression>  _content;
         optional<NumericExpression> _priority;
         optional<bool>              _removeDuplicateLabels;
diff --git a/src/osgEarthSymbology/TextSymbol.cpp b/src/osgEarthSymbology/TextSymbol.cpp
index fbf4f67..b42409f 100644
--- a/src/osgEarthSymbology/TextSymbol.cpp
+++ b/src/osgEarthSymbology/TextSymbol.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -51,7 +51,7 @@ TextSymbol::getConfig() const
     conf.addObjIfSet( "halo", _halo );
     conf.addIfSet( "halo_offset", _haloOffset );
     conf.addIfSet( "font", _font );
-    conf.addIfSet( "size", _size );
+    conf.addObjIfSet( "size", _size );
     conf.addObjIfSet( "content", _content );
     conf.addObjIfSet( "priority", _priority );
     conf.addIfSet( "remove_duplicate_labels", _removeDuplicateLabels );
@@ -103,7 +103,7 @@ TextSymbol::mergeConfig( const Config& conf )
     conf.getObjIfSet( "halo", _halo );
     conf.getIfSet( "halo_offset", _haloOffset );
     conf.getIfSet( "font", _font );
-    conf.getIfSet( "size", _size );
+    conf.getObjIfSet( "size", _size );
     conf.getObjIfSet( "content", _content );
     conf.getObjIfSet( "priority", _priority );
     conf.getIfSet( "remove_duplicate_labels", _removeDuplicateLabels );
@@ -157,7 +157,7 @@ TextSymbol::parseSLD(const Config& c, Style& style)
         style.getOrCreate<TextSymbol>()->fill()->color().a() = as<float>( c.value(), 1.0f );
     }
     else if ( match(c.key(), "text-size") ) {
-        style.getOrCreate<TextSymbol>()->size() = as<float>(c.value(), 32.0f);
+        style.getOrCreate<TextSymbol>()->size() = NumericExpression( c.value() );
     }
     else if ( match(c.key(), "text-font") ) {
         style.getOrCreate<TextSymbol>()->font() = c.value();
@@ -246,4 +246,7 @@ TextSymbol::parseSLD(const Config& c, Style& style)
     else if ( match(c.key(), "text-occlusion-cull-altitude") ) {
         style.getOrCreate<TextSymbol>()->occlusionCullAltitude() = as<double>(c.value(), 200000.0);
     }
+    else if ( match(c.key(), "text-script") ) {
+        style.getOrCreate<TextSymbol>()->script() = StringExpression(c.value());
+    }
 }
diff --git a/src/osgEarthUtil/Formatter b/src/osgEarthUtil/ActivityMonitorTool
similarity index 53%
copy from src/osgEarthUtil/Formatter
copy to src/osgEarthUtil/ActivityMonitorTool
index dc26879..e63d12a 100644
--- a/src/osgEarthUtil/Formatter
+++ b/src/osgEarthUtil/ActivityMonitorTool
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -16,29 +16,36 @@
  * You should have received a copy of the GNU Lesser General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
-#ifndef OSGEARTH_UTIL_FORMATTER_H
-#define OSGEARTH_UTIL_FORMATTER_H
+
+#ifndef OSGEARTHUTIL_ACTIVITY_MONITOR_TOOL_H
+#define OSGEARTHUTIL_ACTIVITY_MONITOR_TOOL_H 1
 
 #include <osgEarthUtil/Common>
-#include <osgEarth/GeoData>
+#include <osgEarthUtil/Controls>
+#include <osgGA/GUIEventHandler>
+#include <set>
 
 namespace osgEarth { namespace Util
 {
-    using namespace osgEarth;
+    using namespace Controls;
 
     /**
-     * Interface class for coordinate formatters.
+     * Tool that displays the contents of the registry's activity set.
      */
-    class Formatter : public osg::Referenced
+    class OSGEARTHUTIL_EXPORT ActivityMonitorTool : public osgGA::GUIEventHandler
     {
     public:
-        virtual std::string format( const GeoPoint& mapCoords ) const =0;
-        std::string operator()(const GeoPoint& p) const { return format(p); }
+        ActivityMonitorTool(VBox* vbox);
+        virtual ~ActivityMonitorTool() { }
+
+    public: // GUIEventHandler
+        bool handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa );
 
-        /** dtor */
-        virtual ~Formatter() { }
+    protected:
+        osg::observer_ptr<VBox> _vbox;
+        std::set<std::string>   _prev;
     };
 
-} } // namespace osgEarth::Util
+} } // namespace osgEarthUtil
 
-#endif // OSGEARTH_UTIL_FORMATTER_H
+#endif // OSGEARTHUTIL_MOUSE_COORDS_TOOL_H
diff --git a/src/osgEarthUtil/ActivityMonitorTool.cpp b/src/osgEarthUtil/ActivityMonitorTool.cpp
new file mode 100644
index 0000000..3be8c20
--- /dev/null
+++ b/src/osgEarthUtil/ActivityMonitorTool.cpp
@@ -0,0 +1,58 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+#include <osgEarthUtil/ActivityMonitorTool>
+#include <osgEarth/Registry>
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+using namespace osgEarth::Util::Controls;
+
+//-----------------------------------------------------------------------
+
+ActivityMonitorTool::ActivityMonitorTool(VBox* vbox) :
+_vbox( vbox )
+{
+    //nop
+}
+
+bool
+ActivityMonitorTool::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
+{
+    if (ea.getEventType() == ea.FRAME)
+    {
+        osg::ref_ptr<VBox> vbox;
+        if ( _vbox.lock(vbox) )
+        {
+            std::set<std::string> activity;
+            Registry::instance()->getActivities(activity);
+            if ( activity != _prev )
+            {            
+                _vbox->clearControls();
+                for(std::set<std::string>::const_iterator i = activity.begin(); i != activity.end(); ++i)
+                {
+                    _vbox->addControl( new LabelControl(*i) );
+                }
+                _prev = activity;
+            }
+        }        
+    }
+
+    return false;
+}
diff --git a/src/osgEarthUtil/AnnotationEvents b/src/osgEarthUtil/AnnotationEvents
index fb2d4d3..0b97278 100644
--- a/src/osgEarthUtil/AnnotationEvents
+++ b/src/osgEarthUtil/AnnotationEvents
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/AnnotationEvents.cpp b/src/osgEarthUtil/AnnotationEvents.cpp
index 07626b7..55659b8 100644
--- a/src/osgEarthUtil/AnnotationEvents.cpp
+++ b/src/osgEarthUtil/AnnotationEvents.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -43,21 +43,34 @@ AnnotationEventCallback::addHandler( AnnotationEventHandler* handler )
 }
 
 void
+AnnotationEventCallback::setHoverEnabled( bool hoverEnabled )
+{
+    // Does not unhover currently hovered annotations, so don't turn hovering off if there
+    // are currently items hovered
+    if(hoverEnabled || !_hovered.size() )
+    {
+      _hoverEnabled = hoverEnabled;
+    }
+}
+
+void
 AnnotationEventCallback::operator()( osg::Node* node, osg::NodeVisitor* nv )
 {
     osgGA::EventVisitor* ev = static_cast<osgGA::EventVisitor*>(nv);
-    osgGA::EventVisitor::EventList& events = ev->getEvents();
+    osgGA::EventQueue::Events& events = ev->getEvents();
     osgViewer::View* view = static_cast<osgViewer::View*>(ev->getActionAdapter());
 
-    for( osgGA::EventVisitor::EventList::const_iterator e = events.begin(); e != events.end(); ++e )
+    for( osgGA::EventQueue::Events::const_iterator e = events.begin(); e != events.end(); ++e )
     {
-        osgGA::GUIEventAdapter* ea = e->get();
+        osgGA::GUIEventAdapter* ea = dynamic_cast<osgGA::GUIEventAdapter*>(e->get());
+        if ( !ea )
+            continue;
 
         if ( ea->getEventType() == osgGA::GUIEventAdapter::MOVE ||
              ea->getEventType() == osgGA::GUIEventAdapter::DRAG )
         {
             _args.x = ea->getX();
-            _args.y = ea->getY();        
+            _args.y = ea->getY();
         }
 
         else if ( ea->getEventType() == osgGA::GUIEventAdapter::PUSH )
diff --git a/src/osgEarthUtil/ArcGIS b/src/osgEarthUtil/ArcGIS
index 4bf999c..e5795e9 100644
--- a/src/osgEarthUtil/ArcGIS
+++ b/src/osgEarthUtil/ArcGIS
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -28,9 +28,7 @@
 
 #include <osgDB/ReaderWriter>
 #include <osg/Version>
-#if OSG_MIN_VERSION_REQUIRED(2,9,5)
 #include <osgDB/Options>
-#endif
 
 
 #include <string>
diff --git a/src/osgEarthUtil/ArcGIS.cpp b/src/osgEarthUtil/ArcGIS.cpp
index 99ea342..fc88eb0 100644
--- a/src/osgEarthUtil/ArcGIS.cpp
+++ b/src/osgEarthUtil/ArcGIS.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/AtlasBuilder b/src/osgEarthUtil/AtlasBuilder
new file mode 100644
index 0000000..de2f6f5
--- /dev/null
+++ b/src/osgEarthUtil/AtlasBuilder
@@ -0,0 +1,83 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTHUTIL_ATLAS_BUILDER_H
+#define OSGEARTHUTIL_ATLAS_BUILDER_H
+
+#include <osgEarthUtil/Common>
+#include <osgEarthSymbology/ResourceLibrary>
+#include <osg/Vec4f>
+#include <vector>
+
+namespace osgDB {
+    class Options;
+}
+
+namespace osgEarth { namespace Util
+{
+    /**
+     * Compiles a resource library into a texture atlas.
+     */
+    class OSGEARTHUTIL_EXPORT AtlasBuilder
+    {
+    public:
+        struct Atlas
+        {
+            // resulting atlas images. The first one is the master atlas image;
+            // any following ones correspond to images created based on auxiliary
+            // patterns added with addAuxFilePattern.
+            std::vector<osg::ref_ptr<osg::Image> >             _images;
+
+            // resulting resource library for the atlas
+            osg::ref_ptr<osgEarth::Symbology::ResourceLibrary> _lib;
+        };
+
+    public:
+        /** Construct a new atlas builder */
+        AtlasBuilder(const osgDB::Options* options =0L);
+
+        /** Sets the maximum size of each layer in the output atlas */
+        void setSize(unsigned width, unsigned height);
+
+        /** Adds an auxiliary file pattern that should be built, along with the
+            default RGBA to use when the aux file doesn't exist. */
+        void addAuxFilePattern(const std::string& pattern, const osg::Vec4f& defRGBA);
+
+        /** List of aux file patterns */
+        const std::vector<std::string>& auxFilePatterns() const { return _auxPatterns; }
+        const std::vector<osg::Vec4f>& auxDefaultValues() const { return _auxDefaults; }
+
+        /** Builds an atlas. */
+        bool build(
+            const osgEarth::Symbology::ResourceLibrary* input,
+            const std::string&                          newAtlasURI,
+            Atlas&                                      output) const;
+
+
+    protected:
+        unsigned _width;
+        unsigned _height;
+        osg::ref_ptr<const osgDB::Options> _options;
+        std::vector<std::string> _auxPatterns;
+        std::vector<osg::Vec4f>  _auxDefaults;
+        bool _debug;
+    };
+
+} } // namespace osgEarth::Util
+
+#endif // OSGEARTHUTIL_ATLAS_BUILDER_H
diff --git a/src/osgEarthUtil/AtlasBuilder.cpp b/src/osgEarthUtil/AtlasBuilder.cpp
new file mode 100644
index 0000000..78d09a6
--- /dev/null
+++ b/src/osgEarthUtil/AtlasBuilder.cpp
@@ -0,0 +1,324 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osgEarthUtil/AtlasBuilder>
+#include <osgEarthSymbology/Skins>
+#include <osgEarth/ImageUtils>
+#include <osgDB/Options>
+#include <osgDB/FileNameUtils>
+#include <osgDB/ReadFile>
+#include <osgDB/WriteFile>
+#include <osgUtil/Optimizer>
+#include <vector>
+#include <string>
+#include <stdlib.h> // for ::getenv
+
+#define LC "[AtlasBuilder] "
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+using namespace osgEarth::Symbology;
+
+
+namespace
+{
+    /** Subclass so we can expose the internal lists. */
+    struct TextureAtlasBuilderEx : public osgUtil::Optimizer::TextureAtlasBuilder
+    {
+        typedef AtlasList AtlasListEx;
+        const AtlasListEx& getAtlasList()
+        {
+            return _atlasList;
+        }
+        
+        typedef SourceList SourceListEx;
+        typedef Source SourceEx;
+        const SourceListEx& getSourceList()
+        {
+            return _sourceList;
+        }
+    };
+    
+    /** PixelVisitor functor to fill an image with a value */
+    struct SetDefaults
+    {
+        osg::Vec4f _value;
+        bool operator()(osg::Vec4f& inout) {
+            inout = _value;
+            return true;
+        }
+    };
+}
+
+
+AtlasBuilder::AtlasBuilder(const osgDB::Options* options) :
+_options( options ),
+_width  ( 1024 ),
+_height ( 1024 ),
+_debug  ( false )
+{
+    //nop
+    if (::getenv("OSGEARTH_ATLAS_DEBUG"))
+        _debug = true;
+}
+
+void
+AtlasBuilder::setSize(unsigned width, unsigned height)
+{
+    _width = width;
+    _height = height;
+}
+
+void
+AtlasBuilder::addAuxFilePattern(const std::string& pattern,
+                                const osg::Vec4f&  defaults)
+{
+    _auxPatterns.push_back(pattern);
+    _auxDefaults.push_back(defaults);
+}
+
+bool
+AtlasBuilder::build(const ResourceLibrary* inputLib,
+                    const std::string&     newAtlasURI,
+                    Atlas&                 out          ) const
+{
+    if ( !inputLib )
+        return false;
+
+    // prepare an atlaser:
+    typedef std::vector<TextureAtlasBuilderEx> TABs;
+    TABs tabs(1 + _auxPatterns.size());
+    TABs::iterator maintab = tabs.begin();
+
+    for(TABs::iterator tab = tabs.begin(); tab != tabs.end(); ++tab)
+    {
+        // maximum size of the texture (x,y)
+        tab->setMaximumAtlasSize( (int)_width, (int)_height );
+
+        // texels between atlased images
+        tab->setMargin( 1 );
+    }
+
+    // clone the Resource library so we can re-write the URIs and add 
+    // texture matrix information.
+    out._lib = new ResourceLibrary( inputLib->getConfig() );
+    out._lib->initialize( _options );
+    out._lib->uri().unset();
+
+    // store a mapping from atlasbuilder source to skin.
+    typedef std::map<TextureAtlasBuilderEx::SourceEx*, SkinResource*> SourceSkinMap;
+    SourceSkinMap sourceSkins;
+
+    // fetch all the skins from the catalog:
+    SkinResourceVector skins;
+    out._lib->getSkins( skins );
+    for(SkinResourceVector::iterator i = skins.begin(); i != skins.end(); ++i)
+    {
+        SkinResource* skin = i->get();
+
+        // skip tiled skins for now.
+        if ( skin->isTiled() == true )
+        {
+            continue;
+        }
+
+        osg::ref_ptr<osg::Image> image = skin->createImage( _options );
+        if ( image.valid() )
+        {
+            OE_INFO << LC << "Loaded skin file: " << skin->imageURI()->full() << std::endl;
+
+            // ensure we're not trying to atlas an atlas.
+            if ( image->r() > 1 )
+            {
+                OE_WARN << LC <<
+                    "Found an image with more than one layer. You cannot create an "
+                    "altas from another atlas. Stopping." << std::endl;
+                return false;
+            }
+
+            // normalize to RGBA8
+            image = ImageUtils::convertToRGBA8(image);
+
+            maintab->addSource( image );
+
+            // for each aux pattern, either load and resize the aux image or create
+            // an empty placeholder.
+            TABs::iterator tab = maintab;
+            ++tab;
+            for(int a=0; a<_auxPatterns.size(); ++a, ++tab)
+            {
+                const std::string& pattern      = _auxPatterns[a];
+                const osg::Vec4f&  defaultValue = _auxDefaults[a];
+
+                std::string base = osgDB::getNameLessExtension(skin->imageURI()->full());
+                std::string ext  = osgDB::getFileExtension(skin->imageURI()->base());
+                std::string auxFile = base + "_" + pattern + "." + ext;
+
+                // read in the auxiliary image:
+                osg::ref_ptr<osg::Image> auxImage;
+                auxImage = osgDB::readImageFile( auxFile, _options.get() );
+
+                // if that didn't work, try alternate extensions:
+                const char* alternateExtensions[3] = {"png", "jpg", "osgb"};
+                for(int b = 0; b < 3 && !auxImage.valid(); ++b)
+                {
+                    auxFile = base + "_" + pattern + "." + alternateExtensions[b];
+                    auxImage = osgDB::readImageFile( auxFile, _options.get() );
+                }
+
+                if ( auxImage.valid() )
+                {
+                    OE_INFO << LC << "  Found aux file: " << auxFile << std::endl;
+                    auxImage = ImageUtils::convertToRGBA8(auxImage);
+                }
+                else
+                {
+                    // failing that, create an empty one as a placeholder.
+                    auxImage = new osg::Image();
+                    auxImage->allocateImage(image->s(), image->t(), 1, GL_RGBA, GL_UNSIGNED_BYTE);
+                    ImageUtils::PixelVisitor<SetDefaults> filler;
+                    filler._value = defaultValue;
+                    filler.accept(auxImage.get());
+                }
+
+                if ( auxImage->s() != image->s() || auxImage->t() != image->t() )
+                {
+                    osg::ref_ptr<osg::Image> temp;
+                    osgEarth::ImageUtils::resizeImage(auxImage.get(), image->s(), image->t(), temp);
+                    auxImage = temp.get();
+                    OE_INFO << "  ...resized " << auxFile << " to match atlas size" << std::endl;
+                }
+
+                if ( !ImageUtils::sameFormat(image, auxImage.get()) ) 
+                {
+                    auxImage = ImageUtils::convertToRGBA8(auxImage.get());
+                }
+
+                tab->addSource( auxImage.get() );
+            }
+
+            // re-write the URI to point at our new atlas:
+            skin->imageURI() = newAtlasURI;
+
+            // save the associate so we can come back later:
+            sourceSkins[maintab->getSourceList().back().get()] = skin;
+
+            //OE_INFO << LC << "Added skin: \"" << skin->name() << "\"" << std::endl;
+        }
+        else
+        {
+            OE_WARN << LC << "Failed to load image from catalog: \""
+                << skin->name() << "\" ... skipped" << std::endl;
+        }
+    }
+
+    unsigned numSources = maintab->getNumSources();
+    OE_INFO << LC << "Added " << numSources << " images ... building atlas ..." << std::endl;
+
+    // build the atlas images.
+    for(TABs::iterator tab = tabs.begin(); tab != tabs.end(); ++tab )
+    {
+        tab->buildAtlas();
+    }
+
+    const TextureAtlasBuilderEx::AtlasListEx& mainAtlasList = maintab->getAtlasList();
+
+    // Atlas images are not all the same size. Figure out the largest size
+    unsigned maxS=0, maxT=0;
+
+    for(unsigned r=0; r<mainAtlasList.size(); ++r)
+    {
+        unsigned s = mainAtlasList[r]->_image->s();
+        unsigned t = mainAtlasList[r]->_image->t();
+
+        OE_INFO << LC
+            << "Altas image " << r << ": size = (" << s << ", " << t << ")" << std::endl;
+
+        if ( s > maxS )
+            maxS = s;
+        if ( t > maxT )
+            maxT = t;
+    }
+
+    OE_INFO << LC <<
+        "Final atlas size will be (" << maxS << ", " << maxT << ")" << std::endl;
+
+    
+    for(TABs::iterator tab = tabs.begin(); tab != tabs.end(); ++tab )
+    {
+        const TextureAtlasBuilderEx::AtlasListEx& atlasList = tab->getAtlasList();
+
+        // create the target multi-layer image.
+        osg::Image* imageArray = new osg::Image();
+
+        imageArray->allocateImage(
+            maxS,
+            maxT,
+            atlasList.size(),
+            GL_RGBA,
+            GL_UNSIGNED_BYTE);
+
+        // initialize to all zeros
+        memset(imageArray->data(), 0, imageArray->getTotalSizeInBytesIncludingMipmaps());
+
+        // combine each of the atlas images into the corresponding "r" slot of the composed image:
+        for(int r=0; r<(int)atlasList.size(); ++r)
+        {
+            // copy the atlas image into the image array:
+            osg::Image* atlasImage = atlasList[r]->_image.get();
+
+            if ( _debug )
+            {
+                std::string name = Stringify() << "image_" << (int)(tab-tabs.begin()) << "_" << r << ".png";
+                osgDB::writeImageFile(*atlasImage, name);
+            }
+
+            ImageUtils::PixelReader read (atlasImage);
+            ImageUtils::PixelWriter write(imageArray);
+
+            for(int s=0; s<atlasImage->s(); ++s)
+                for(int t=0; t<atlasImage->t(); ++t)
+                    write(read(s, t, 0), s, t, r);
+        }
+
+        out._images.push_back(imageArray);
+    }
+
+    
+    // for each source in this atlas layer, apply its texture matrix info
+    // to the new catalog.
+    for(int r=0; r<(int)mainAtlasList.size(); ++r)
+    {
+        for(int k=0; k<mainAtlasList[r]->_sourceList.size(); ++k)
+        {
+            TextureAtlasBuilderEx::SourceEx* source = mainAtlasList[r]->_sourceList[k].get();
+            SourceSkinMap::iterator n = sourceSkins.find(source);
+            if ( n != sourceSkins.end() )
+            {
+                SkinResource* skin = n->second;
+                skin->imageLayer()  = r;
+                skin->imageBiasS()  = (float)source->_x/(float)maxS;
+                skin->imageBiasT()  = (float)source->_y/(float)maxT;
+                skin->imageScaleS() = (float)source->_image->s()/(float)maxS;
+                skin->imageScaleT() = (float)source->_image->t()/(float)maxT;
+            }
+        }
+    }
+
+    return true;
+}
diff --git a/src/osgEarthUtil/AutoClipPlaneHandler b/src/osgEarthUtil/AutoClipPlaneHandler
index 1f44143..808123d 100644
--- a/src/osgEarthUtil/AutoClipPlaneHandler
+++ b/src/osgEarthUtil/AutoClipPlaneHandler
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/AutoClipPlaneHandler.cpp b/src/osgEarthUtil/AutoClipPlaneHandler.cpp
index 480a191..761cf27 100644
--- a/src/osgEarthUtil/AutoClipPlaneHandler.cpp
+++ b/src/osgEarthUtil/AutoClipPlaneHandler.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/BrightnessContrastColorFilter b/src/osgEarthUtil/BrightnessContrastColorFilter
index 6c3f995..df85cd7 100644
--- a/src/osgEarthUtil/BrightnessContrastColorFilter
+++ b/src/osgEarthUtil/BrightnessContrastColorFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/BrightnessContrastColorFilter.cpp b/src/osgEarthUtil/BrightnessContrastColorFilter.cpp
index 1e4aa9c..2f9f312 100644
--- a/src/osgEarthUtil/BrightnessContrastColorFilter.cpp
+++ b/src/osgEarthUtil/BrightnessContrastColorFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/CMYKColorFilter b/src/osgEarthUtil/CMYKColorFilter
index 2b28404..64ac220 100644
--- a/src/osgEarthUtil/CMYKColorFilter
+++ b/src/osgEarthUtil/CMYKColorFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/CMYKColorFilter.cpp b/src/osgEarthUtil/CMYKColorFilter.cpp
index 5db9dd8..1afe79d 100644
--- a/src/osgEarthUtil/CMYKColorFilter.cpp
+++ b/src/osgEarthUtil/CMYKColorFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/CMakeLists.txt b/src/osgEarthUtil/CMakeLists.txt
index 5a05f0c..1d38842 100644
--- a/src/osgEarthUtil/CMakeLists.txt
+++ b/src/osgEarthUtil/CMakeLists.txt
@@ -9,9 +9,11 @@ SET(LIB_NAME osgEarthUtil)
 SET(HEADER_PATH ${OSGEARTH_SOURCE_DIR}/include/${LIB_NAME})
 
 SET(HEADERS_ROOT
+	ActivityMonitorTool
     AnnotationEvents
-    AutoClipPlaneHandler
+    AutoClipPlaneHandler	
     ArcGIS
+    AtlasBuilder
     Common
     Controls
     ContourMap
@@ -20,10 +22,12 @@ SET(HEADERS_ROOT
     DateTime
     DetailTexture
     EarthManipulator
+	Ephemeris
     ExampleResources
     Export
     FeatureManipTool
     FeatureQueryTool
+    Fog
     Formatter
     GeodeticGraticule
     HTM
@@ -31,18 +35,23 @@ SET(HEADERS_ROOT
     LineOfSight
     LinearLineOfSight
     LODBlending
+	LogarithmicDepthBuffer
     MeasureTool
     MGRSFormatter
     MGRSGraticule
     MouseCoordsTool
     NormalMap
     ObjectLocator
+    Ocean
     PolyhedralLineOfSight
     RadialLineOfSight
-    SkyNode
+	Shadowing
+	SimplexNoise
+    Sky
     SpatialData
     StarData
     TerrainProfile
+	TextureSplatter
     TileIndex
     TileIndexBuilder
     TFS
@@ -55,43 +64,47 @@ SET(HEADERS_ROOT
     WFS
     WMS
 )
-IF(NOT ${OPENSCENEGRAPH_VERSION} VERSION_LESS "3.1.0")
-    SET(HEADERS_ROOT ${HEADERS_ROOT}
-        ShadowUtils)
-ENDIF()
 SOURCE_GROUP( Headers FILES ${HEADERS_ROOT} )
 
 
 SET(SOURCES_ROOT
+	ActivityMonitorTool.cpp
     AnnotationEvents.cpp
     ArcGIS.cpp
+    AtlasBuilder.cpp
     AutoClipPlaneHandler.cpp
     ClampCallback.cpp
     Controls.cpp
     ContourMap.cpp
-    DataScanner.cpp
-    #DateTime.cpp #moved to osgEarth namespace
+    DataScanner.cpp    
     DetailTexture.cpp
     EarthManipulator.cpp
+	Ephemeris.cpp
     ExampleResources.cpp
     FeatureManipTool.cpp
     FeatureQueryTool.cpp
+    Fog.cpp
     GeodeticGraticule.cpp
     HTM.cpp
     LatLongFormatter.cpp
     LinearLineOfSight.cpp
     LODBlending.cpp
+	LogarithmicDepthBuffer.cpp
     MeasureTool.cpp
     MGRSFormatter.cpp
     MGRSGraticule.cpp
     MouseCoordsTool.cpp
     NormalMap.cpp
     ObjectLocator.cpp
+    Ocean.cpp
     PolyhedralLineOfSight.cpp
     RadialLineOfSight.cpp
+	Shadowing.cpp
+	SimplexNoise.cpp
     SpatialData.cpp
-    SkyNode.cpp
+    Sky.cpp
     TerrainProfile.cpp
+	TextureSplatter.cpp
     TileIndex.cpp
     TileIndexBuilder.cpp
     TFS.cpp
@@ -104,10 +117,6 @@ SET(SOURCES_ROOT
     WFS.cpp
     WMS.cpp
 )
-IF(NOT ${OPENSCENEGRAPH_VERSION} VERSION_LESS "3.1.0")
-    SET(SOURCES_ROOT ${SOURCES_ROOT} 
-        ShadowUtils.cpp)
-ENDIF()
 
 SOURCE_GROUP( Sources FILES ${SOURCES_ROOT} )
 
diff --git a/src/osgEarthUtil/ChromaKeyColorFilter b/src/osgEarthUtil/ChromaKeyColorFilter
index 2f1a71d..f8d7b4a 100644
--- a/src/osgEarthUtil/ChromaKeyColorFilter
+++ b/src/osgEarthUtil/ChromaKeyColorFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/ChromaKeyColorFilter.cpp b/src/osgEarthUtil/ChromaKeyColorFilter.cpp
index d092593..cf2d116 100644
--- a/src/osgEarthUtil/ChromaKeyColorFilter.cpp
+++ b/src/osgEarthUtil/ChromaKeyColorFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/ClampCallback b/src/osgEarthUtil/ClampCallback
index a7503f6..a513d2b 100644
--- a/src/osgEarthUtil/ClampCallback
+++ b/src/osgEarthUtil/ClampCallback
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/ClampCallback.cpp b/src/osgEarthUtil/ClampCallback.cpp
index 5e31c6c..5278419 100644
--- a/src/osgEarthUtil/ClampCallback.cpp
+++ b/src/osgEarthUtil/ClampCallback.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/Common b/src/osgEarthUtil/Common
index 34b9f92..358df1e 100644
--- a/src/osgEarthUtil/Common
+++ b/src/osgEarthUtil/Common
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/Controls b/src/osgEarthUtil/Controls
index d17f8f7..24f5568 100644
--- a/src/osgEarthUtil/Controls
+++ b/src/osgEarthUtil/Controls
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -23,6 +23,7 @@
 #include <osgEarth/Common>
 #include <osgEarth/Units>
 #include <osgEarth/AlphaEffect>
+#include <osgEarth/Containers>
 #include <osg/Drawable>
 #include <osg/Geode>
 #include <osg/MatrixTransform>
@@ -227,10 +228,10 @@ namespace osgEarth { namespace Util { namespace Controls
         bool horizFill() const { return _hfill; }
 
         void setVertFill( bool value, float minHeight =0.0f );
-        const bool vertFill() const { return _vfill; }
+        bool vertFill() const { return _vfill; }
 
         void setVisible( bool value );
-        const bool visible() const { return _visible; }
+        bool visible() const { return _visible; }
         bool parentIsVisible() const;
 
         void setForeColor( const osg::Vec4f& value );
@@ -374,10 +375,10 @@ namespace osgEarth { namespace Util { namespace Controls
         const optional<osg::Vec4f>& haloColor() const { return _haloColor; }
 
         void setTextBackdropType(osgText::Text::BackdropType value);
-        const osgText::Text::BackdropType getTextBackdropType() const { return _backdropType; }
+        osgText::Text::BackdropType getTextBackdropType() const { return _backdropType; }
 
         void setTextBackdropImplementation(osgText::Text::BackdropImplementation value);
-        const osgText::Text::BackdropImplementation getTextBackdropImplementation() const { return _backdropImpl; }
+        osgText::Text::BackdropImplementation getTextBackdropImplementation() const { return _backdropImpl; }
 
         void setTextBackdropOffset(float offsetValue);
         float getTextBackdropOffset() const { return _backdropOffset; }
@@ -738,7 +739,7 @@ namespace osgEarth { namespace Util { namespace Controls
     {
     public:
         /** Constructs a new control node with an embedded control. */
-        ControlNode( Control* control, float priority =0.0f );
+        ControlNode(Control* control, float priority =0.0f);
 
         /** dtor */
         virtual ~ControlNode() { }
@@ -762,9 +763,9 @@ namespace osgEarth { namespace Util { namespace Controls
 
     protected:
 
-        struct PerViewData
+        struct TravSpecificData
         {
-            PerViewData();
+            TravSpecificData();
             bool                              _obscured;
             osg::Vec3f                        _screenPos;
             float                             _visibleTime;
@@ -772,14 +773,14 @@ namespace osgEarth { namespace Util { namespace Controls
             osg::ref_ptr<osg::Uniform>        _uniform;
             osg::observer_ptr<osg::Camera>    _canvas;
         };
-        typedef std::map<osg::View*,PerViewData> PerViewDataMap;
+        typedef osgEarth::fast_map<osg::Camera*,TravSpecificData> TravSpecificDataMap;
 
-        PerViewDataMap         _perViewData;
+        TravSpecificDataMap    _travDataMap;
         osg::ref_ptr<Control>  _control;
         float                  _priority;
         optional<osg::Vec2f>   _anchor;
 
-        PerViewData& getData( osg::View* view ) { return _perViewData[view]; }
+        TravSpecificData& getData(osg::Camera* key) { return _travDataMap[key]; }
 
         friend class ControlNodeBin;
     };
@@ -835,7 +836,10 @@ namespace osgEarth { namespace Util { namespace Controls
     {
     public:
         /** Accesses the control canvas attached the the specified view. */
-        static ControlCanvas* get( osg::View* view, bool installCanvasInSceneData =false );
+        static ControlCanvas* get(osg::View* view);
+        
+        /** Accesses the control canvas attached the the specified view. */
+        static ControlCanvas* getOrCreate(osg::View* view);
 
     public:
         /** adds a top-level control to this surface. */
@@ -860,15 +864,14 @@ namespace osgEarth { namespace Util { namespace Controls
         void setControlContext( const ControlContext& );
 
     public:
-        virtual void traverse( osg::NodeVisitor& nv ); // override
+        virtual void traverse(osg::NodeVisitor& nv); // override
 
         virtual ~ControlCanvas();
 
         /**
-         * Constructs a new canvas and attaches it to a view. Call
-         * getOrCreateControlCanvas(view) to create one.
+         * Constructs a new canvas.
          */
-        ControlCanvas( osgViewer::View* view );
+        ControlCanvas();
 
     protected:
 
@@ -884,21 +887,26 @@ namespace osgEarth { namespace Util { namespace Controls
         Control* addControlImpl( Control* control );
 
     private:
-        friend struct ControlCanvasEventHandler;
         friend class ControlNode;
-
-        ControlCanvas( osgViewer::View* view, bool registerCanvas );
-        void init( osgViewer::View* view, bool registerCanvas );
+        void init();
 
         bool handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa );
 
-        // a static registry of canvases in the scene graph.
-        typedef std::map<osg::View*, ControlCanvas*> ViewCanvasMap;
-        static ViewCanvasMap      _viewCanvasMap;        
-        static OpenThreads::Mutex _viewCanvasMapMutex;
-
         /** Accesses the priority rendering bin for this canvas. */
         ControlNodeBin* getControlNodeBin() { return _controlNodeBin.get(); }
+
+        // internal class
+        class EventCallback : public osg::NodeCallback
+        {
+        public:
+            EventCallback(ControlCanvas*);
+            void operator()(osg::Node*, osg::NodeVisitor*);
+            void handleResize(osg::View* view, ControlCanvas* canvas);
+        protected:
+            osg::observer_ptr<ControlCanvas> _canvas;
+            bool _firstTime;
+            int _width, _height;
+        };
     };
 
 
diff --git a/src/osgEarthUtil/Controls.cpp b/src/osgEarthUtil/Controls.cpp
index 9f94822..37eb220 100755
--- a/src/osgEarthUtil/Controls.cpp
+++ b/src/osgEarthUtil/Controls.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -669,9 +669,12 @@ Control::handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa,
         {            
             if ( ea.getEventType() == osgGA::GUIEventAdapter::RELEASE )
             {
+              float canvasY = cx._vp->height() - (ea.getY() - cx._view->getCamera()->getViewport()->y());
+              float canvasX = ea.getX() - cx._view->getCamera()->getViewport()->x();
+
                 for( ControlEventHandlerList::const_iterator i = _eventHandlers.begin(); i != _eventHandlers.end(); ++i )
                 {
-                    osg::Vec2f relXY( ea.getX() - _renderPos.x(), cx._vp->height() - ea.getY() - _renderPos.y() );
+                    osg::Vec2f relXY( canvasX - _renderPos.x(), canvasY - _renderPos.y() );
                     i->get()->onClick( this, relXY, ea.getButtonMask() );
                     aa.requestRedraw();
                 }
@@ -1286,7 +1289,8 @@ HSliderControl::handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapte
 
     if ( ea.getEventType() == osgGA::GUIEventAdapter::DRAG )
     {
-        float relX = ea.getX() - _renderPos.x();
+        float canvasX = ea.getX() - cx._view->getCamera()->getViewport()->x();
+        float relX = canvasX - _renderPos.x();
 
         if ( _min < _max )
             setValue( osg::clampBetween(_min + (_max-_min) * ( relX/_renderSize.x() ), _min, _max) );
@@ -1611,6 +1615,10 @@ Container::handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa
         return false;
 
     bool handled = false;
+
+    float canvasY = cx._vp->height() - (ea.getY() - cx._view->getCamera()->getViewport()->y());
+    float canvasX = ea.getX() - cx._view->getCamera()->getViewport()->x();
+
     std::vector<Control*> children;
     getChildren( children );
     //OE_NOTICE << "handling " << children.size() << std::endl;
@@ -1620,7 +1628,7 @@ Container::handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa
         //Control* child = dynamic_cast<Control*>( getChild(i) );
         if ( child )
         {
-            if (ea.getEventType() == osgGA::GUIEventAdapter::FRAME || child->intersects( ea.getX(), cx._vp->height() - ea.getY() ) )
+            if (ea.getEventType() == osgGA::GUIEventAdapter::FRAME || child->intersects( canvasX, canvasY ) )
                 handled = child->handle( ea, aa, cx );
             if ( handled )
                 break;
@@ -2165,119 +2173,105 @@ Grid::draw( const ControlContext& cx )
 
 // ---------------------------------------------------------------------------
 
-namespace osgEarth { namespace Util { namespace Controls
+ControlCanvas::EventCallback::EventCallback(ControlCanvas* canvas) : 
+_canvas   ( canvas ),
+_firstTime( true ),
+_width    ( 0 ),
+_height   ( 0 )
+{
+    //nop
+}
+
+// version helper.
+#if OSG_VERSION_GREATER_THAN(3,3,0)
+#   define AS_ADAPTER(e) e->asGUIEventAdapter()
+#else
+#   define AS_ADAPTER(e) e
+#endif
+
+void
+ControlCanvas::EventCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
 {
-    // This handler keeps an eye on the viewport and informs the control surface when it changes.
-    // We need this info since controls position from the upper-left corner.
-    struct ViewportHandler : public osgGA::GUIEventHandler
+    osgGA::EventVisitor* ev = static_cast<osgGA::EventVisitor*>(nv);
+
+    osg::ref_ptr<ControlCanvas> canvas;
+    if ( _canvas.lock(canvas) )
     {
-        ViewportHandler( ControlCanvas* cs ) : _cs(cs), _width(0), _height(0), _first(true) { }
+        if ( _firstTime )
+        {
+            handleResize( ev->getActionAdapter()->asView(), canvas.get() );
+        }
 
-        bool handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa )
+        const osgGA::EventQueue::Events& events = ev->getEvents();
+        if ( events.size() > 0 )
         {
-            if ( ea.getEventType() == osgGA::GUIEventAdapter::RESIZE || _first )
+            osg::ref_ptr<ControlCanvas> canvas;
+            if ( _canvas.lock(canvas) )
             {
-                osg::Camera* cam = aa.asView()->getCamera();
-                if ( cam && cam->getViewport() )
+                osgGA::GUIActionAdapter* aa = ev->getActionAdapter();
+
+                for(osgGA::EventQueue::Events::const_iterator e = events.begin(); e != events.end(); ++e)
                 {
-                    const osg::Viewport* vp = cam->getViewport();
-                    if ( _first || vp->width() != _width || vp->height() != _height )
+                    osgGA::GUIEventAdapter* ea = AS_ADAPTER(e->get());
+
+                    if (!_firstTime && ea->getEventType() == osgGA::GUIEventAdapter::RESIZE)
                     {
-                        _cs->setProjectionMatrix(osg::Matrix::ortho2D( 0, vp->width()-1, 0, vp->height()-1 ) );
-
-                        ControlContext cx;
-                        cx._view = aa.asView();
-                        cx._vp = new osg::Viewport( 0, 0, vp->width(), vp->height() );
-                        
-                        osg::View* view = aa.asView();
-                        osg::GraphicsContext* gc = view->getCamera()->getGraphicsContext();
-                        if ( !gc && view->getNumSlaves() > 0 )
-                            gc = view->getSlave(0)._camera->getGraphicsContext();
-
-                        if ( gc )
-                            cx._viewContextID = gc->getState()->getContextID();
-                        else
-                            cx._viewContextID = ~0u;
-
-                        _cs->setControlContext( cx );
-
-                        _width  = (int)vp->width();
-                        _height = (int)vp->height();
+                        handleResize( aa->asView(), canvas.get() );
                     }
-                    if ( vp->width() != 0 && vp->height() != 0 )
+
+                    if (canvas->handle( *ea, *aa ))
                     {
-                        _first = false;
+                        e->get()->setHandled(true);
                     }
                 }
             }
-            return false;
         }
-        ControlCanvas* _cs;
-        int _width, _height;
-        bool _first;
-    };
-
-    struct ControlCanvasEventHandler : public osgGA::GUIEventHandler
-    {
-        ControlCanvasEventHandler( ControlCanvas* cs ) : _cs(cs) { }
+    }
 
-        bool handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa )
-        {
-            return _cs->handle( ea, aa );
-        }
+    traverse(node,nv);
+}
 
-        ControlCanvas* _cs;
-    };
+void 
+ControlCanvas::EventCallback::handleResize(osg::View* view, ControlCanvas* canvas)
+{
+    osg::Camera* cam = view->getCamera();
 
-    // This callback installs a control canvas under a view.
-    struct CanvasInstaller : public osg::NodeCallback
+    if ( cam && cam->getViewport() )
     {
-        CanvasInstaller( ControlCanvas* canvas, osg::Camera* camera )
-            : _canvas( canvas )
+        const osg::Viewport* vp = cam->getViewport();
+        if ( _firstTime || vp->width() != _width || vp->height() != _height )
         {
-            _oldCallback = camera->getUpdateCallback();
-            camera->setUpdateCallback( this );
-        }
-        
-        void operator()( osg::Node* node, osg::NodeVisitor* nv )
-        {
-            osg::Camera* camera = static_cast<osg::Camera*>(node);
-            osgViewer::View* view2 = dynamic_cast<osgViewer::View*>(camera->getView());
-            install( view2, _canvas.get() );
-            camera->setUpdateCallback( _oldCallback.get() );
-        }
+            canvas->setProjectionMatrix(osg::Matrix::ortho2D( 0, vp->width()-1, 0, vp->height()-1 ) );
 
-        static void install( osgViewer::View* view2, ControlCanvas* canvas )
-        {
-            osg::Node* node = view2->getSceneData();
-            osg::Group* group = new osg::Group();
-            if ( node )
-                group->addChild( node );
-            group->addChild( canvas );
-            
-            // must save the manipulator matrix b/c calling setSceneData causes
-            // the view to call home() on the manipulator.
-            osg::Matrixd savedMatrix;
-            osgGA::CameraManipulator* manip = view2->getCameraManipulator();
-            if ( manip )
-                savedMatrix = manip->getMatrix();
+            ControlContext cx;
+            cx._view = view;
+            cx._vp = new osg::Viewport( 0, 0, vp->width(), vp->height() );
 
-            view2->setSceneData( group );
+            osg::GraphicsContext* gc = view->getCamera()->getGraphicsContext();
+            if ( !gc && view->getNumSlaves() > 0 )
+                gc = view->getSlave(0)._camera->getGraphicsContext();
 
-            // restore it
-            if ( manip )
-                manip->setByMatrix( savedMatrix );
-        }
+            if ( gc )
+                cx._viewContextID = gc->getState()->getContextID();
+            else
+                cx._viewContextID = ~0u;
 
-        osg::ref_ptr<ControlCanvas>     _canvas;
-        osg::ref_ptr<osg::NodeCallback> _oldCallback;
-    };
+            canvas->setControlContext( cx );
+
+            _width  = (int)vp->width();
+            _height = (int)vp->height();
+        }
 
-} } } // namespace osgEarth::Util::Controls
+        if ( vp->width() != 0 && vp->height() != 0 )
+        {
+            _firstTime = false;
+        }
+    }
+}
 
 // ---------------------------------------------------------------------------
 
-ControlNode::ControlNode( Control* control, float priority ) :
+ControlNode::ControlNode(Control* control, float priority ) :
 _control ( control ),
 _priority( priority )
 {
@@ -2300,13 +2294,13 @@ ControlNode::traverse( osg::NodeVisitor& nv )
         osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
 
         // pull up the per-view data for this view:
-        PerViewData& data = _perViewData[cv->getCurrentCamera()->getView()];
+        TravSpecificData& data = _travDataMap[cv->getCurrentCamera()];
 
         // if it's uninitialized, find the corresponding control canvas and 
         // cache a reference to its control node bin:
         if ( !data._canvas.valid() )
         {
-            data._canvas = ControlCanvas::get( cv->getCurrentCamera()->getView(), true );
+            data._canvas = osgEarth::findTopMostNodeOfType<ControlCanvas>( cv->getCurrentCamera() );
             if ( data._canvas.valid() )
             {
                 ControlNodeBin* bin = static_cast<ControlCanvas*>(data._canvas.get())->getControlNodeBin();
@@ -2339,7 +2333,7 @@ ControlNode::traverse( osg::NodeVisitor& nv )
     osg::Node::traverse(nv);
 }
 
-ControlNode::PerViewData::PerViewData() :
+ControlNode::TravSpecificData::TravSpecificData() :
 _obscured   ( true ),
 _visibleTime( 0.0 ),
 _screenPos  ( 0.0, 0.0, 0.0 )
@@ -2421,7 +2415,7 @@ ControlNodeBin::draw( const ControlContext& context, bool newContext, int bin )
             }
             else
             {
-                ControlNode::PerViewData& nodeData = node->getData( context._view );
+                ControlNode::TravSpecificData& nodeData = node->getData( context._view->getCamera() );
                 byDepth.insert( ControlNodePair(nodeData._screenPos.z(), node) );
             }
         }
@@ -2443,7 +2437,7 @@ ControlNodeBin::draw( const ControlContext& context, bool newContext, int bin )
 
         if ( nodeActive )
         {
-          ControlNode::PerViewData& nodeData = node->getData( context._view );
+          ControlNode::TravSpecificData& nodeData = node->getData( context._view->getCamera() );
           Control* control = node->getControl();
 
           // if the context changed (e.g., viewport resize), we need to mark all nodes as dirty
@@ -2588,55 +2582,70 @@ ControlNodeBin::addNode( ControlNode* controlNode )
 
 // ---------------------------------------------------------------------------
 
-ControlCanvas::ViewCanvasMap ControlCanvas::_viewCanvasMap;
-OpenThreads::Mutex           ControlCanvas::_viewCanvasMapMutex;
-
 ControlCanvas*
-ControlCanvas::get( osg::View* view, bool installInSceneData )
+ControlCanvas::getOrCreate(osg::View* view)
 {
-    ControlCanvas* canvas = 0L;
+    if ( !view )
+        return 0L;
 
-    OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _viewCanvasMapMutex );
+    if ( !view->getCamera() )
+        return 0L;
 
-    ViewCanvasMap::iterator i = _viewCanvasMap.find( view );
-    if ( i != _viewCanvasMap.end() )
-    {
-        canvas = i->second;
-    }
+    ControlCanvas* canvas = osgEarth::findTopMostNodeOfType<ControlCanvas>(view->getCamera());
+    if ( canvas )
+        return canvas;
 
-    else
+    canvas = new ControlCanvas();
+
+    // ControlCanvas does NOT work as a direct child of the View's camera.
+    osg::Group* group = 0L;
+    if ( view->getCamera()->getNumChildren() > 0 )
     {
-        // Not found, so create one. If requested, add a callback that will
-        // automatically install it in the view's scene data during the 
-        // next update traversal.
-        osgViewer::View* view2 = dynamic_cast<osgViewer::View*>(view);
-        if ( view2 )
+        group = view->getCamera()->getChild(0)->asGroup();
+        if ( !group )
         {
-            canvas = new ControlCanvas( view2, false );
-            _viewCanvasMap[view] = canvas;
-
-            if ( installInSceneData )
-                new CanvasInstaller( canvas, view->getCamera() );
+            group = new osg::Group();
+            osgEarth::insertGroup(group, view->getCamera());
         }
     }
+    else
+    {
+        group = new osg::Group();
+        view->getCamera()->addChild(group);
+    }
 
+    group->addChild( canvas );
     return canvas;
 }
 
-// ---------------------------------------------------------------------------
-
-ControlCanvas::ControlCanvas( osgViewer::View* view )
+ControlCanvas*
+ControlCanvas::get(osg::View* view)
 {
-    init( view, true );
+#if 1 // for now, to avoid breaking lots of code
+    return getOrCreate(view);
+#else
+    if ( !view )
+        return 0L;
+
+    if ( !view->getCamera() )
+        return 0L;
+
+    ControlCanvas* canvas = osgEarth::findTopMostNodeOfType<ControlCanvas>(view->getCamera());
+    if ( canvas )
+        return canvas;
+
+    return 0L;
+#endif
 }
 
-ControlCanvas::ControlCanvas( osgViewer::View* view, bool registerCanvas )
+
+ControlCanvas::ControlCanvas()
 {
-    init( view, registerCanvas );
+    init();
 }
 
 void
-ControlCanvas::init( osgViewer::View* view, bool registerCanvas )
+ControlCanvas::init()
 {
     _contextDirty  = true;
     _updatePending = false;
@@ -2644,14 +2653,7 @@ ControlCanvas::init( osgViewer::View* view, bool registerCanvas )
     // deter the optimizer
     this->setDataVariance( osg::Object::DYNAMIC );
 
-    osg::ref_ptr<osgGA::GUIEventHandler> pViewportHandler = new ViewportHandler(this);
-    osg::ref_ptr<osgGA::GUIEventHandler> pControlCanvasEventHandler = new ControlCanvasEventHandler(this);
-
-    _eventHandlersMap[pViewportHandler] = view;
-    _eventHandlersMap[pControlCanvasEventHandler] = view;
-
-    view->addEventHandler( pViewportHandler );
-    view->addEventHandler( pControlCanvasEventHandler );
+    this->addEventCallback( new EventCallback(this) );
 
     setReferenceFrame(osg::Transform::ABSOLUTE_RF);
     setViewMatrix(osg::Matrix::identity());
@@ -2668,41 +2670,20 @@ ControlCanvas::init( osgViewer::View* view, bool registerCanvas )
     ss->setAttributeAndModes( new osg::Depth( osg::Depth::ALWAYS, 0, 1, false ) );
     ss->setRenderBinDetails( 0, "TraversalOrderBin" );
 
-#if 0
-    // come on we don't really need this...gw
-    // keeps the control bin shaders from "leaking out" into the scene graph :/
-    if ( Registry::capabilities().supportsGLSL() )
-    {
-        ss->setAttributeAndModes( new osg::Program(), osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED );
-    }
-#endif
-
     _controlNodeBin = new ControlNodeBin();
     this->addChild( _controlNodeBin->getControlGroup() );
-
-    // register this canvas.
-    if ( registerCanvas )
-    {
-        OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _viewCanvasMapMutex );
-        _viewCanvasMap[view] = this;
-    }
+   
+#ifndef OSG_GLES2_AVAILABLE
+    // don't use shaders unless we have to.
+    this->getOrCreateStateSet()->setAttributeAndModes(
+        new osg::Program(), 
+        osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE);
+#endif
 }
 
 ControlCanvas::~ControlCanvas()
 {
-    OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _viewCanvasMapMutex );
-    _viewCanvasMap.erase( _context._view );
-
-    EventHandlersMap::iterator itr;
-    for (itr = _eventHandlersMap.begin(); itr != _eventHandlersMap.end(); ++itr)
-    {
-        osgGA::GUIEventHandler* pGUIEventHandler = itr->first.get();
-        osgViewer::View* pView = itr->second.get();
-        if ( (pView != NULL) && (pGUIEventHandler != NULL) )
-        {
-            pView->removeEventHandler(pGUIEventHandler);
-        }
-    }
+    //nop
 }
 
 void
@@ -2740,7 +2721,8 @@ ControlCanvas::getControlAtMouse( float x, float y )
 }
 
 bool
-ControlCanvas::handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa )
+ControlCanvas::handle(const osgGA::GUIEventAdapter& ea,
+                      osgGA::GUIActionAdapter&      aa)
 {
     if ( !_context._vp )
         return false;
@@ -2756,6 +2738,7 @@ ControlCanvas::handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter
     }
 
     bool handled = false;
+
     //Send a frame event to all controls
     if ( ea.getEventType() == osgGA::GUIEventAdapter::FRAME )
     {
@@ -2768,13 +2751,13 @@ ControlCanvas::handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter
     }
 
 
-    float invY = _context._vp->height() - ea.getY();
+    float canvasY = _context._vp->height() - (ea.getY() - _context._view->getCamera()->getViewport()->y());
+    float canvasX = ea.getX() - _context._view->getCamera()->getViewport()->x();
 
     for( unsigned i=getNumChildren()-1; i>0; --i )
     {
         Control* control = static_cast<Control*>( getChild(i) );
-
-        if ( control->intersects( ea.getX(), invY ) )
+        if ( control->intersects( canvasX, canvasY ) )
         {
             handled = control->handle( ea, aa, _context );
             if ( handled )
@@ -2790,7 +2773,7 @@ ControlCanvas::handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter
 
     if ( _context._active.size() > 0 )
     {
-        bool hit = _context._active.front()->intersects( ea.getX(), invY );
+        bool hit = _context._active.front()->intersects( canvasX, canvasY );
         _context._active.front()->setActive( hit );
         if ( !hit )
             _context._active.pop();
@@ -2800,7 +2783,7 @@ ControlCanvas::handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter
 }
 
 void
-ControlCanvas::update( const osg::FrameStamp* frameStamp )
+ControlCanvas::update(const osg::FrameStamp* frameStamp)
 {
     _context._frameStamp = frameStamp;
 
@@ -2830,17 +2813,18 @@ ControlCanvas::update( const osg::FrameStamp* frameStamp )
         _controlNodeBin->draw( _context, _contextDirty, bin );
     }
 
+#ifdef OSG_GLES2_AVAILABLE
     // shaderize.
     // we don't really need to rebuild shaders on every dirty; we could probably
     // just do it on add/remove controls; but that's an optimization for later
-    ShaderGenerator shaderGen;
-    shaderGen.run( this );
+    Registry::shaderGenerator().run( this, "osgEarth.ControlCanvas" );
+#endif
 
     _contextDirty = false;
 }
 
 void
-ControlCanvas::traverse( osg::NodeVisitor& nv )
+ControlCanvas::traverse(osg::NodeVisitor& nv)
 {
     switch( nv.getVisitorType() )
     {
diff --git a/src/osgEarthUtil/DataScanner b/src/osgEarthUtil/DataScanner
index 70dcfcb..b02e18e 100644
--- a/src/osgEarthUtil/DataScanner
+++ b/src/osgEarthUtil/DataScanner
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/DataScanner.cpp b/src/osgEarthUtil/DataScanner.cpp
index 875d1be..2ed45cf 100644
--- a/src/osgEarthUtil/DataScanner.cpp
+++ b/src/osgEarthUtil/DataScanner.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/DateTime b/src/osgEarthUtil/DateTime
index 2352564..b13576a 100644
--- a/src/osgEarthUtil/DateTime
+++ b/src/osgEarthUtil/DateTime
@@ -19,9 +19,9 @@
 #ifndef OSGEARTHUTIL_DATE_TIME_H
 #define OSGEARTHUTIL_DATE_TIME_H
 
+#if 0
 #include <osgEarthUtil/Common>
 #include <osgEarth/DateTime>
-
 namespace osgEarth { namespace Util
 {
     // for backwards-compability.
@@ -29,5 +29,6 @@ namespace osgEarth { namespace Util
     typedef osgEarth::DateTime DateTime;
 
 } } // namespace osgEarth::Util
+#endif
 
 #endif // OSGEARTHUTIL_DATE_TIME_H
diff --git a/src/osgEarthUtil/DetailTexture b/src/osgEarthUtil/DetailTexture
index 604d2de..128a779 100644
--- a/src/osgEarthUtil/DetailTexture
+++ b/src/osgEarthUtil/DetailTexture
@@ -21,12 +21,18 @@
 
 #include <osgEarthUtil/Common>
 #include <osgEarth/TerrainEffect>
+#include <osgEarth/ImageLayer>
 #include <osgEarth/URI>
 #include <osg/Image>
 #include <osg/Uniform>
-#include <osg/Texture2D>
+#include <osg/Texture2DArray>
 
-namespace osgEarth { namespace Util
+using namespace osgEarth;
+
+namespace osgEarth {
+class Map;
+
+namespace Util
 {
     /**
      * Controller that applies a detail texture to the terrain.
@@ -37,6 +43,11 @@ namespace osgEarth { namespace Util
         /** construct a new detail texture controller */
         DetailTexture();
 
+        /** Sets the image layer that generates the normal map. 
+            You must call this prior to installing the effect. */
+        void setMaskLayer(ImageLayer* layer) { _maskLayer = layer; }
+        ImageLayer* getMaskLayer() { return _maskLayer.get(); }
+
         /** Sets the LOD at which to start detailing */
         void setStartLOD( unsigned lod );
         unsigned getStartLOD() const { return _startLOD.get(); }
@@ -45,9 +56,18 @@ namespace osgEarth { namespace Util
         void setIntensity( float value );
         float getIntensity() const { return _intensity.get(); }
 
-        /** Sets the image to use as the detail texture */
-        void setImage( const osg::Image* image );
-        const osg::Image* getImage() const { return _texture->getImage(); }
+        /** Sets the attenuation distance, i.e. the distance from the camera
+            at which the detail texture will fade out completely. */
+        void setAttenuationDistance( float value );
+        float getAttenuationDistance() const { return _attenuationDistance.get(); }
+
+        /** Sets the scale factor for the texture (1..) */
+        void setScale( float value );
+        float getScale() const { return _scale.get(); }
+
+        /** Sets the number of detail octaves */
+        void setNumOctaves( unsigned value );
+        float getNumOctaves() const { return _octaves.get(); }
 
     public: // TerrainEffect interface
 
@@ -56,7 +76,7 @@ namespace osgEarth { namespace Util
 
     public: // serialization
 
-        DetailTexture(const Config& conf);
+        DetailTexture(const Config& conf, const Map* map);
         void mergeConfig(const Config& conf);
         virtual Config getConfig() const;
 
@@ -64,15 +84,29 @@ namespace osgEarth { namespace Util
         virtual ~DetailTexture() { }
         void init();
 
-        optional<float>    _intensity;
-        optional<unsigned> _startLOD;
-        optional<URI>      _imageURI;
+        optional<float>          _intensity;
+        optional<unsigned>       _startLOD;
+        optional<std::string>    _maskLayerName;
+        optional<float>          _scale;
+        optional<float>          _attenuationDistance;
+        optional<unsigned>       _octaves;
+
+        struct TextureSource {
+            std::string _tag;
+            std::string _url;
+        };
+        std::vector<TextureSource> _textures;
 
-        osg::ref_ptr<osg::Uniform>   _intensityUniform;
-        osg::ref_ptr<osg::Uniform>   _startLODUniform;
-        osg::ref_ptr<osg::Uniform>   _samplerUniform;
-        osg::ref_ptr<osg::Texture2D> _texture;
-        int                          _unit;
+        osg::ref_ptr<osg::Uniform>         _intensityUniform;
+        osg::ref_ptr<osg::Uniform>         _startLODUniform;
+        osg::ref_ptr<osg::Uniform>         _scaleUniform;
+        osg::ref_ptr<osg::Uniform>         _attenuationDistanceUniform;
+        osg::ref_ptr<osg::Uniform>         _samplerUniform;
+        osg::ref_ptr<osg::Uniform>         _maskUniform;
+        osg::ref_ptr<osg::Texture2DArray>  _texture;
+        int                                _unit;
+        osg::observer_ptr<ImageLayer>      _maskLayer;
+        osg::ref_ptr<osgDB::Options>       _dbOptions;
     };
 
 } } // namespace osgEarth::Util
diff --git a/src/osgEarthUtil/DetailTexture.cpp b/src/osgEarthUtil/DetailTexture.cpp
index 7d67a3b..3517369 100644
--- a/src/osgEarthUtil/DetailTexture.cpp
+++ b/src/osgEarthUtil/DetailTexture.cpp
@@ -21,6 +21,8 @@
 #include <osgEarth/Capabilities>
 #include <osgEarth/VirtualProgram>
 #include <osgEarth/TerrainEngineNode>
+#include <osgEarth/ImageUtils>
+#include <osgEarth/URI>
 
 #define LC "[DetailTexture] "
 
@@ -29,16 +31,78 @@ using namespace osgEarth::Util;
 
 namespace
 {
+    const char* snoise =
+        "vec3 mod289(vec3 x) {\n"
+        "   return x - floor(x * (1.0 / 289.0)) * 289.0;\n"
+        "}\n"
+        "vec2 mod289(vec2 x) {\n"
+        "   return x - floor(x * (1.0 / 289.0)) * 289.0;\n"
+        "}\n"
+        "vec3 permute(vec3 x) {\n"
+        "   return mod289(((x*34.0)+1.0)*x);\n"
+        "}\n"
+        "float snoise(vec2 v)\n"
+        "{\n"
+        "   const vec4 C = vec4(0.211324865405187,  // (3.0-sqrt(3.0))/6.0 \n"
+        "                        0.366025403784439,  // 0.5*(sqrt(3.0)-1.0) \n"
+        "                       -0.577350269189626,  // -1.0 + 2.0 * C.x \n"
+        "                        0.024390243902439); // 1.0 / 41.0 \n"
+        "   // First corner\n"
+        "   vec2 i  = floor(v + dot(v, C.yy) );\n"
+        "   vec2 x0 = v -   i + dot(i, C.xx);\n"
+        "   // Other corners\n"
+        "   vec2 i1;\n"
+        "//i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0\n"
+        "//i1.y = 1.0 - i1.x;\n"
+        "i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);\n"
+        "// x0 = x0 - 0.0 + 0.0 * C.xx ;\n"
+        "// x1 = x0 - i1 + 1.0 * C.xx ;\n"
+        "// x2 = x0 - 1.0 + 2.0 * C.xx ;\n"
+        "vec4 x12 = x0.xyxy + C.xxzz;\n"
+        "x12.xy -= i1;\n"
+
+        "// Permutations\n"
+        "i = mod289(i); // Avoid truncation effects in permutation\n"
+        "vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))\n"
+        "              + i.x + vec3(0.0, i1.x, 1.0 ));\n"
+
+        "vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);\n"
+        "m = m*m ;\n"
+        "m = m*m ;\n"
+
+        "// Gradients: 41 points uniformly over a line, mapped onto a diamond.\n"
+        "// The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287)\n"
+
+        "vec3 x = 2.0 * fract(p * C.www) - 1.0;\n"
+        "vec3 h = abs(x) - 0.5;\n"
+        "vec3 ox = floor(x + 0.5);\n"
+        "vec3 a0 = x - ox;\n"
+
+        "// Normalise gradients implicitly by scaling m\n"
+        "// Approximation of: m *= inversesqrt( a0*a0 + h*h );\n"
+        "m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );\n"
+
+        "// Compute final noise value at P\n"
+        "vec3 g;\n"
+        "g.x  = a0.x  * x0.x  + h.x  * x0.y;\n"
+        "g.yz = a0.yz * x12.xz + h.yz * x12.yw;\n"
+        "return 130.0 * dot(m, g);\n"
+        "}\n";
+
     const char* vs =
         "#version " GLSL_VERSION_STR "\n"
         GLSL_DEFAULT_PRECISION_FLOAT "\n"
 
-        "uniform vec4  oe_tile_key; \n"
-        "varying vec4  oe_layer_tilec; \n"
-        "uniform float oe_dtex_L0; \n"
-        "varying vec2  oe_dtex_tc; \n"
+        "uniform vec4 oe_tile_key; \n"
+        "uniform float oe_detail_L0; \n"
+        "uniform float oe_detail_scale; \n"
+        "uniform float oe_detail_attenuation_distance; \n"
 
-        "int oe_dtex_ipow(in int x, in int y) { \n"
+        "varying vec4 oe_layer_tilec; \n"
+        "varying vec2 oe_detail_tc; \n"
+        "varying float oe_detail_atten_factor; \n"
+
+        "int oe_detail_ipow(in int x, in int y) { \n"
         "   int r = 1; \n"
         "   while( y > 0 ) { \n"
         "       r *= x; \n"
@@ -47,10 +111,10 @@ namespace
         "   return r; \n"
         "}\n"
 
-        "void oe_dtex_vertex(inout vec4 VertexMODEL) \n"
+        "void oe_detail_vertex(inout vec4 VertexVIEW) \n"
         "{ \n"
-        "    float dL = oe_tile_key.z - oe_dtex_L0; \n"
-        "    float twoPowDeltaL = float(oe_dtex_ipow(2, int(abs(dL)))); \n"
+        "    float dL = oe_tile_key.z - oe_detail_L0; \n"
+        "    float twoPowDeltaL = float(oe_detail_ipow(2, int(abs(dL)))); \n"
         "    float factor = dL >= 0.0 ? twoPowDeltaL : 1.0/twoPowDeltaL; \n"
 
         "    vec2 a = floor(oe_tile_key.xy / factor); \n"
@@ -59,57 +123,144 @@ namespace
         "    vec2 offset = (oe_tile_key.xy-b)/(c-b); \n"
         "    vec2 scale = vec2(1.0/factor); \n"
 
-        "    oe_dtex_tc = (oe_layer_tilec.st * scale) + offset; \n"
+        "    float tscale = pow(2.0, oe_detail_scale-1.0); \n"
+        "    oe_detail_tc = tscale * ((oe_layer_tilec.st * scale) + offset); \n"
+        
+        "    float r = 1.0-((-VertexVIEW.z/VertexVIEW.w)/oe_detail_attenuation_distance);\n"
+        "    oe_detail_atten_factor = clamp(r, 0.0, 1.0); \n"
         "} \n";
 
-    const char* fs =
-        "#version " GLSL_VERSION_STR "\n"
-        GLSL_DEFAULT_PRECISION_FLOAT "\n"
 
-        "uniform vec4      oe_tile_key; \n"
-        "uniform float     oe_dtex_L0; \n"
-        "uniform sampler2D oe_dtex_tex; \n"
-        "uniform float     oe_dtex_intensity; \n"
-        "varying vec2      oe_dtex_tc; \n"
+    std::string generateFragmentShader(unsigned numTextures, unsigned numOctaves)
+    {
+        std::stringstream buf;
+
+        buf <<
+            "#version " GLSL_VERSION_STR "\n"
+            "#extension GL_EXT_texture_array : enable\n"
+            GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+            "uniform vec4 oe_tile_key; \n"
+            "uniform float oe_detail_L0; \n"
+            "uniform sampler2D oe_detail_mask; \n"
+            "uniform sampler2DArray oe_detail_tex; \n"
+            "uniform float oe_detail_intensity; \n"
+            "varying vec2 oe_detail_tc; \n"
+            "varying vec4 oe_layer_tilec; \n"
+            "varying float oe_detail_atten_factor; \n"
+
+            "float snoise(vec2 v);\n"
+
+            "void oe_detail_fragment(inout vec4 color) \n"
+            "{ \n"
+            "    if ( oe_tile_key.z >= oe_detail_L0 ) \n"
+            "    { \n"
+            //"        vec4 m = texture2D(oe_detail_mask, oe_layer_tilec.st); \n"
+            "        vec4 m = 0.25 * (\n"
+            //"                 texture2D(oe_detail_mask, oe_layer_tilec.st) + \n"
+            "                 texture2D(oe_detail_mask, oe_layer_tilec.st + vec2(-0.4,0.0)) + \n"
+            "                 texture2D(oe_detail_mask, oe_layer_tilec.st + vec2( 0.4,0.0)) + \n"
+            "                 texture2D(oe_detail_mask, oe_layer_tilec.st + vec2( 0.0,-0.4)) + \n"
+            "                 texture2D(oe_detail_mask, oe_layer_tilec.st + vec2( 0.0, 0.4))); \n"
+            "        vec4 detail = vec4(0.0); \n";
+
+#if 0
+        buf <<
+            "        float nz = snoise(oe_detail_tc.st); \n"
+            "        if ( nz < -0.5 ) m[0] = 1.0; else m[0] = 0.0; \n"
+            "        if ( nz >= -0.5 && nz < 0.0 ) m[1] = 1.0; else m[1] = 0.0;  \n"
+            "        if ( nz >= 0.0 && nz < 0.5 ) m[2] = 1.0; else m[2] = 0.0; \n"
+            "        if ( nz >= 0.5 ) m[3] = 1.0; else m[3] = 0.0; \n";
+#endif
+
+        if ( numOctaves > 1 )
+        {
+            buf <<
+                "        float weight[" << numOctaves << "];\n"
+                "        float interval = 1.0/" << numOctaves << ".0;\n"
+                "        float d, w;\n"
+                "        float total = 0.0;\n"
+                "        float a = oe_detail_atten_factor; \n"
+                "        a = a*a*a*a*a*a*a; \n"; // weights the transitions to be close to the eyepoint
+
+            for(unsigned oct=0; oct<numOctaves; ++oct)
+            {
+                buf <<
+                    "        d = abs( (interval+(interval*" << oct << ".0)) - a);\n"
+                    "        w = (1.0-d)*(1.0-d)*(1.0-d); \n"
+                    "        total += w*w; \n"
+                    "        weight[" << oct << "] = w;\n";
+            }
+                
+            buf <<
+                "        float sqrttotal = sqrt(total);\n";
+
+            for(unsigned oct=0; oct<numOctaves; ++oct)
+            {
+                buf <<
+                    "        w = weight["<<oct<<"] / sqrttotal; \n";
+
+                unsigned ofx = 1+(5*oct); // todo: make that octave jump configurable?
+                for(unsigned i=0; i<numTextures; ++i)
+                {
+                    buf <<
+                    "        detail += w * m[" << i << "] * texture2DArray(oe_detail_tex, vec3(oe_detail_tc*" << ofx << ".0," << i << ")); \n";
+                }
+            }
+        }
+        else
+        {
+            for(unsigned i=0; i<numTextures; ++i)
+            {
+                buf <<
+                    "        detail += m[" << i << "] * texture2DArray(oe_detail_tex, vec3(oe_detail_tc," << i << ")); \n";
+            }
+        }
 
-        "void oe_dtex_fragment(inout vec4 color) \n"
-        "{ \n"
-        "    if ( oe_tile_key.z >= oe_dtex_L0 ) \n"
-        "    { \n"
-        "        vec4 texel = texture2D(oe_dtex_tex, oe_dtex_tc); \n"
-        "        if ( oe_tile_key.z >= oe_dtex_L0+3.0 ) \n"
-        "        { \n"
-        "            texel += texture2D(oe_dtex_tex, oe_dtex_tc*8.0)-0.5; \n"
-        "            if ( oe_tile_key.z >= oe_dtex_L0+6.0 ) \n"
-        "            { \n"
-        "                texel += texture2D(oe_dtex_tex, oe_dtex_tc*32.0)-0.5; \n"
-        "                if ( oe_tile_key.z >= oe_dtex_L0+9.0 ) \n"
-        "                { \n"
-        "                    texel += texture2D(oe_dtex_tex, oe_dtex_tc*64.0)-0.5; \n"
-        "                } \n"
-        "            } \n"
-        "        } \n"
-        "        texel.rgb -= 0.5; \n"
-        "        color.rgb = clamp( color.rgb + (texel.rgb*oe_dtex_intensity), 0.0, 1.0 ); \n"
-        "    } \n"
-        "} \n";
+        buf <<
+            //"        vec3 luminosity = vec3(detail.r*0.2125 + detail.g*0.7154 + detail.b*0.0721);\n"
+            "        color = mix(color, detail, oe_detail_intensity * oe_detail_atten_factor);\n"
+            "    } \n"
+            "} \n";
+
+        std::string r;
+        r = buf.str();
+        return r;
+    }
 }
 
 
 DetailTexture::DetailTexture() :
 TerrainEffect(),
-_startLOD    ( 8 ),
-_intensity   ( 0.25f )
+_startLOD    ( 10 ),
+_intensity   ( 1.0f ),
+_scale       ( 1.0f ),
+_attenuationDistance( FLT_MAX ),
+_octaves     ( 1 )
 {
     init();
 }
 
-DetailTexture::DetailTexture(const Config& conf) :
+DetailTexture::DetailTexture(const Config& conf, const Map* map) :
 TerrainEffect(),
-_startLOD    ( 8 ),
-_intensity   ( 0.25f )
+_startLOD    ( 10 ),
+_intensity   ( 1.0f ),
+_scale       ( 1.0f ),
+_attenuationDistance( FLT_MAX ),
+_octaves     ( 1 )
 {
     mergeConfig(conf);
+
+    if ( map )
+    {
+        if ( _maskLayerName.isSet() )
+        {
+            _maskLayer = map->getImageLayerByName(*_maskLayerName);
+        }
+
+        _dbOptions = Registry::instance()->cloneOrCreateOptions(map->getDBOptions());        
+    }
+
     init();
 }
 
@@ -120,25 +271,17 @@ DetailTexture::init()
     // negative means unset:
     _unit = -1;
 
-    _startLODUniform   = new osg::Uniform(osg::Uniform::FLOAT, "oe_dtex_L0");
+    _startLODUniform   = new osg::Uniform(osg::Uniform::FLOAT, "oe_detail_L0");
     _startLODUniform->set( (float)_startLOD.get() );
 
-    _intensityUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_dtex_intensity");
+    _intensityUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_detail_intensity");
     _intensityUniform->set( _intensity.get() );
-    
-    _texture = new osg::Texture2D();
-    _texture->setWrap( osg::Texture::WRAP_S, osg::Texture::REPEAT );
-    _texture->setWrap( osg::Texture::WRAP_T, osg::Texture::REPEAT );
-    _texture->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR );
-    _texture->setFilter( osg::Texture::MAG_FILTER, osg::Texture::LINEAR );
-    _texture->setResizeNonPowerOfTwoHint( false );
-
-    if ( _imageURI.isSet() )
-    {
-        osg::Image* image = _imageURI->getImage();
-        if ( image )
-            _texture->setImage( image );
-    }
+
+    _scaleUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_detail_scale");
+    _scaleUniform->set( _scale.get() );
+
+    _attenuationDistanceUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_detail_attenuation_distance");
+    _attenuationDistanceUniform->set( _attenuationDistance.get() );
 }
 
 
@@ -162,35 +305,86 @@ DetailTexture::setIntensity(float intensity)
 
 
 void
-DetailTexture::setImage(const osg::Image* image)
+DetailTexture::setScale(float scale)
 {
-    if ( image )
-    {
-        _texture->setImage( const_cast<osg::Image*>(image) );
-    }
+    _scale = osg::clampAbove( scale, 1.0f );
+    _scaleUniform->set( _scale.get() );
 }
 
 
 void
+DetailTexture::setAttenuationDistance(float value)
+{
+    _attenuationDistance = osg::clampAbove(value, 1.0f);
+    _attenuationDistanceUniform->set( _attenuationDistance.get() );
+}
+
+void
 DetailTexture::onInstall(TerrainEngineNode* engine)
 {
     if ( engine )
     {
+        if ( !_texture.valid() )
+        {
+            _texture = new osg::Texture2DArray();
+            _texture->setTextureSize(1024, 1024, _textures.size());
+            _texture->setWrap( osg::Texture::WRAP_S, osg::Texture::REPEAT );
+            _texture->setWrap( osg::Texture::WRAP_T, osg::Texture::REPEAT );
+            _texture->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR );
+            _texture->setFilter( osg::Texture::MAG_FILTER, osg::Texture::LINEAR );
+            _texture->setResizeNonPowerOfTwoHint( false );
+
+            for(unsigned i=0; i<_textures.size(); ++i)
+            {
+                const TextureSource& ts = _textures[i];
+                osg::ref_ptr<osg::Image> image = URI(ts._url).getImage(_dbOptions.get());
+                if ( image->s() != 1024 || image->t() != 1024 )
+                {
+                    osg::ref_ptr<osg::Image> imageResized;
+                    ImageUtils::resizeImage( image.get(), 1024, 1024, imageResized );
+                    _texture->setImage( i, imageResized.get() );
+                }
+                else
+                {
+                    _texture->setImage( i, image.get() );
+                }
+            }
+        }
+
         osg::StateSet* stateset = engine->getOrCreateStateSet();
 
         if ( engine->getTextureCompositor()->reserveTextureImageUnit(_unit) )
         {
-            _samplerUniform = stateset->getOrCreateUniform( "oe_dtex_tex", osg::Uniform::SAMPLER_2D );
+            _samplerUniform = stateset->getOrCreateUniform( "oe_detail_tex", osg::Uniform::SAMPLER_2D_ARRAY );
             _samplerUniform->set( _unit );
-            stateset->setTextureAttributeAndModes( _unit, _texture.get() );
+            stateset->setTextureAttribute( _unit, _texture.get(), osg::StateAttribute::ON ); // don't use "..andModes"
+        }
+
+        if ( _maskLayer.valid() )
+        {
+            int unit = *_maskLayer->shareImageUnit();
+            _maskUniform = stateset->getOrCreateUniform("oe_detail_mask", osg::Uniform::SAMPLER_2D);
+            _maskUniform->set(unit);
+            OE_NOTICE << LC << "Installed layer " << _maskLayer->getName() << " as texture mask on unit " << unit << std::endl;
+        }
+        else
+        {
+            exit(-1);
         }
 
         stateset->addUniform( _startLODUniform.get() );
         stateset->addUniform( _intensityUniform.get() );
+        stateset->addUniform( _scaleUniform.get() );
+        stateset->addUniform( _attenuationDistanceUniform.get() );
 
+        std::string fs = generateFragmentShader( _textures.size(), _octaves.value() );
         VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
-        vp->setFunction( "oe_dtex_vertex",   vs, ShaderComp::LOCATION_VERTEX_MODEL );
-        vp->setFunction( "oe_dtex_fragment", fs, ShaderComp::LOCATION_FRAGMENT_COLORING );
+        vp->setFunction( "oe_detail_vertex",   vs, ShaderComp::LOCATION_VERTEX_VIEW );
+        vp->setFunction( "oe_detail_fragment", fs, ShaderComp::LOCATION_FRAGMENT_COLORING );
+
+        vp->setShader(
+            "simplexNoise",
+            new osg::Shader(osg::Shader::FRAGMENT, snoise) );
     }
 }
 
@@ -203,6 +397,8 @@ DetailTexture::onUninstall(TerrainEngineNode* engine)
     {
         stateset->removeUniform( _startLODUniform.get() );
         stateset->removeUniform( _intensityUniform.get() );
+        stateset->removeUniform( _scaleUniform.get() );
+        stateset->removeUniform( _attenuationDistanceUniform.get() );
 
         if ( _samplerUniform.valid() )
         {
@@ -215,8 +411,8 @@ DetailTexture::onUninstall(TerrainEngineNode* engine)
         VirtualProgram* vp = VirtualProgram::get(stateset);
         if ( vp )
         {
-            vp->removeShader( "oe_dtex_vertex" );
-            vp->removeShader( "oe_dtex_fragment" );
+            vp->removeShader( "oe_detail_vertex" );
+            vp->removeShader( "oe_detail_fragment" );
         }
     }
 
@@ -235,18 +431,48 @@ DetailTexture::onUninstall(TerrainEngineNode* engine)
 void
 DetailTexture::mergeConfig(const Config& conf)
 {
-    conf.getIfSet( "start_lod", _startLOD );
-    conf.getIfSet( "intensity", _intensity );
-    conf.getIfSet( "image",     _imageURI );
+    conf.getIfSet( "start_lod",  _startLOD );
+    conf.getIfSet( "intensity",  _intensity );
+    conf.getIfSet( "scale",      _scale );
+    conf.getIfSet( "attenuation_distance", _attenuationDistance );
+    conf.getIfSet( "mask_layer", _maskLayerName );
+    conf.getIfSet( "octaves",    _octaves );
+
+    ConfigSet textures = conf.child("textures").children("texture");
+    for(ConfigSet::iterator i = textures.begin(); i != textures.end(); ++i)
+    {
+        _textures.push_back(TextureSource());
+        _textures.back()._tag = i->value("tag");
+        _textures.back()._url = i->value("url");
+    }
 }
 
 Config
 DetailTexture::getConfig() const
 {
+    optional<std::string> layername;
+
+    if ( _maskLayer.valid() && !_maskLayer->getName().empty() )
+        layername = _maskLayer->getName();
+
     Config conf("detail_texture");
-    conf.addIfSet( "start_lod", _startLOD );
-    conf.addIfSet( "intensity", _intensity );
-    conf.addIfSet( "image",     _imageURI );
+    conf.addIfSet( "start_lod",  _startLOD );
+    conf.addIfSet( "intensity",  _intensity );
+    conf.addIfSet( "scale",      _scale );
+    conf.addIfSet( "attenuation_distance", _attenuationDistance );
+    conf.addIfSet( "mask_layer", layername );
+    conf.addIfSet( "octaves",    _octaves );
+
+    if ( _textures.size() > 0 )
+    {
+        Config textures("textures");
+        for(std::vector<TextureSource>::const_iterator i = _textures.begin(); i != _textures.end(); ++i )
+        {
+            Config texture("texture");
+            texture.set("tag", i->_tag);
+            texture.set("url", i->_url);
+        }
+    }
 
     return conf;
 }
diff --git a/src/osgEarthUtil/EarthManipulator b/src/osgEarthUtil/EarthManipulator
index 8507e39..69ed8e4 100644
--- a/src/osgEarthUtil/EarthManipulator
+++ b/src/osgEarthUtil/EarthManipulator
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -352,6 +352,20 @@ namespace osgEarth { namespace Util
              * Gets the overall mouse sensitivity scale factor. Default = 1.0.
              */
             double getMouseSensitivity() const { return _mouse_sens; }
+            
+            /**
+             * Sets an overall touch sensitivity factor.
+             *
+             * @param value
+             *      A scale factor to apply to mouse readings.
+             *      0.005 = default; < 0.005 = less sensitive; > 0.005 = more sensitive.
+             */
+            void setTouchSensitivity( double value ) { _touch_sens = value; }
+
+            /**
+             * Gets the overall touch sensitivity scale factor. Default = 1.0.
+             */
+            double getTouchSensitivity() const { return _touch_sens; }
 
             /**
              * Sets the keyboard action sensitivity factor. This applies to navigation actions
@@ -520,6 +534,7 @@ namespace osgEarth { namespace Util
             double _mouse_sens;
             double _keyboard_sens;
             double _scroll_sens;
+            double _touch_sens;
             double _min_pitch;
             double _max_pitch;
 
@@ -561,7 +576,7 @@ namespace osgEarth { namespace Util
         /**
          * Gets the current camera position.
          */
-        Viewpoint getViewpoint() const;
+        Viewpoint getViewpoint() const;        
 
         /**
          * Sets the camera position, optionally moving it there over time.
@@ -571,7 +586,7 @@ namespace osgEarth { namespace Util
         /**
          * Cancels a viewpoint transition if one is in progress
          */
-        void cancelViewpointTransition() { _setting_viewpoint = false; }
+        void cancelViewpointTransition() { _setting_viewpoint = false; }        
 
         /**
          * Sets the viewpoint to activate when performing the ACTION_HOME action.
@@ -579,14 +594,21 @@ namespace osgEarth { namespace Util
         void setHomeViewpoint( const Viewpoint& vp, double duration_s = 0.0 );
 
         /**
+         * Whether the manipulator is performing a viewpoint transition
+         */
+        bool isSettingViewpoint() const { return _setting_viewpoint; }
+
+        /**
          * Locks the camera's focal point on the center of a node's bounding sphere.
          * While tethered, you can still call navigate or call setViewpoint() to move the
          * camera relative to the tether object. Pass NULL to deactivate the tether.
          *
          * @param node
          *      Node to which to tether the viewpoint.
+         * @param duration_s
+         *      The duration that the camera should transition when tethering to the node.
          */
-        void setTetherNode( osg::Node* node );
+        void setTetherNode( osg::Node* node, double duration_s =0.0  );
 
         /**
          * Gets the node to which the camera is tethered, or NULL if tethering is
@@ -708,7 +730,9 @@ namespace osgEarth { namespace Util
         virtual void getUsage(osg::ApplicationUsage& usage) const;
 
         virtual void computeHomePosition();
-
+        
+        // react to a tile-added event from the Terrain
+        virtual void handleTileAdded(const TileKey& key, osg::Node* tile, TerrainCallbackContext& context);
 
     protected:
 
@@ -716,6 +740,8 @@ namespace osgEarth { namespace Util
         
         bool intersect(const osg::Vec3d& start, const osg::Vec3d& end, osg::Vec3d& intersection) const;
 
+        bool intersectLookVector(osg::Vec3d& eye, osg::Vec3d& out_target, osg::Vec3d& up) const;
+
         // resets the mouse event stack and pushes the provided event.
         void resetMouse( osgGA::GUIActionAdapter& aa, bool flushEventStack=true);
 
@@ -725,9 +751,13 @@ namespace osgEarth { namespace Util
         // Add the current mouse osgGA::GUIEvent to internal stack.
         void addMouseEvent(const osgGA::GUIEventAdapter& ea);
 
-        // sets the camera position by doing a "look at" calculation. This is only used for
-        // the "home" function at the moment -- look into depcrecation -GW
-        void setByLookAt(const osg::Vec3d& eye, const osg::Vec3d& lv, const osg::Vec3d& up);
+        // sets the camera position by doing a "look at" calculation, converting the center
+        // point into a look vector and intersecting the terrain.
+        void setByLookAt(const osg::Vec3d& eye, const osg::Vec3d& center, const osg::Vec3d& up);
+
+        // sets the camera position by doing a "look at" calculation, but takes the target point
+        // "as-is" and does not try to find an intersection.
+        void setByLookAtRaw(const osg::Vec3d& eye, const osg::Vec3d& target, const osg::Vec3d& up);
 
         // checks to see whether the mouse is "moving".
         bool isMouseMoving();
@@ -764,8 +794,14 @@ namespace osgEarth { namespace Util
         //void recalculateLocalPitchAndAzimuth();
         void getLocalEulerAngles( double* out_azim, double* out_pitch =0L ) const;
 
+        /**
+         * Fire a ray from the current eyepoint along the current look vector,
+         * intersect the terrain at the closest point, and reset the matrix parameters
+         * based on that point.
+         */
+        bool recalculateCenterFromLookVector();
 
-        void recalculateCenter( const osg::CoordinateFrame& frame );
+        void recalculateCenter(const osg::CoordinateFrame& frame);
 
         osg::Matrixd getRotation(const osg::Vec3d& center) const;
         osg::Quat makeCenterRotation(const osg::Vec3d& center) const
@@ -838,6 +874,11 @@ namespace osgEarth { namespace Util
         virtual void handleMovementAction( const ActionType& type, double dx, double dy, osg::View* view );
         //virtual bool handleMultiTouchAction( const Action& action, const TouchEvent& te, osg::View* view );
 
+        /**
+         * Gets the viewpoint of the TetherNode.
+         */
+        Viewpoint getTetherNodeViewpoint() const;
+
     protected:
 
         // makeshift "stack" of the last 2 incoming events.
@@ -857,16 +898,11 @@ namespace osgEarth { namespace Util
         bool _is_geocentric;
         bool _srs_lookup_failed;
 
-        osg::observer_ptr<osg::Node> _tether_node;
-        osg::Transform*              _tether_xform;
-        osg::Vec3d                   _tether_local_center;
-        Viewpoint                    _pre_tether_vp;
+        osg::observer_ptr<osg::Node> _tether_node;        
 
         double                  _time_s_last_frame;
-        double                  _time_s_now;
-        osg::Timer_t            _now;
-        double                  _delta_t;
-        double                  _t_factor;
+        double                  _time_s_now;        
+        double                  _delta_t;        
         bool                    _thrown;
         double                  _throw_dx;
         double                  _throw_dy;
@@ -876,6 +912,7 @@ namespace osgEarth { namespace Util
         // The world coordinate of the Viewpoint focal point.
         osg::Vec3d              _center;
         GeoPoint                _centerMap;
+        double                  _centerHeight;
 
         // local2world matrix for the center point.
         osg::CoordinateFrame    _centerLocalToWorld;
@@ -894,8 +931,6 @@ namespace osgEarth { namespace Util
         osg::Vec3d              _previousUp;
         osg::ref_ptr<Task>      _task;
         osg::Timer_t            _time_last_frame;
-        //double                  _local_pitch;
-        //double                  _local_azim;
 
         bool                    _continuous;
         double                  _continuous_dx;
diff --git a/src/osgEarthUtil/EarthManipulator.cpp b/src/osgEarthUtil/EarthManipulator.cpp
index 1c26f18..44a3cba 100644
--- a/src/osgEarthUtil/EarthManipulator.cpp
+++ b/src/osgEarthUtil/EarthManipulator.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -19,6 +19,7 @@
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarth/MapNode>
 #include <osgEarth/NodeUtils>
+#include <osgEarth/GeoMath>
 #include <osg/Quat>
 #include <osg/Notify>
 #include <osg/MatrixTransform>
@@ -60,29 +61,6 @@ namespace
 }
 
 
-
-namespace
-{
-    // Callback that notifies the manipulator whenever the terrain changes
-    // around its center point.
-    struct ManipTerrainCallback : public TerrainCallback
-    {
-        ManipTerrainCallback(EarthManipulator* manip) : _manip(manip) { }
-
-        void onTileAdded( const TileKey& key, osg::Node* tile, TerrainCallbackContext& context )
-        {
-            const GeoPoint& centerMap = _manip->centerMap();
-            if ( _manip.valid() && key.getExtent().contains(centerMap.x(), centerMap.y()) )
-            {
-                _manip->recalculateCenter();
-            }
-        }
-
-        osg::observer_ptr<EarthManipulator> _manip;
-    };
-}
-
-
 //------------------------------------------------------------------------
 
 
@@ -192,10 +170,11 @@ Revisioned                      (),
 _single_axis_rotation           ( false ),
 _lock_azim_while_panning        ( true ),
 _mouse_sens                     ( 1.0 ),
+_touch_sens                     ( 0.005 ),
 _keyboard_sens                  ( 1.0 ),
 _scroll_sens                    ( 1.0 ),
 _min_pitch                      ( -89.9 ),
-_max_pitch                      ( -10.0 ),
+_max_pitch                      ( -4.0 ),
 _max_x_offset                   ( 0.0 ),
 _max_y_offset                   ( 0.0 ),
 _min_distance                   ( 0.001 ),
@@ -221,6 +200,7 @@ _bindings( rhs._bindings ),
 _single_axis_rotation( rhs._single_axis_rotation ),
 _lock_azim_while_panning( rhs._lock_azim_while_panning ),
 _mouse_sens( rhs._mouse_sens ),
+_touch_sens( rhs._touch_sens),
 _keyboard_sens( rhs._keyboard_sens ),
 _scroll_sens( rhs._scroll_sens ),
 _min_pitch( rhs._min_pitch ),
@@ -589,8 +569,7 @@ EarthManipulator::reinitialize()
     _last_action = ACTION_NULL;
     _srs_lookup_failed = false;
     _setting_viewpoint = false;
-    _delta_t = 0.0;
-    _t_factor = 1.0;
+    _delta_t = 0.0;    
     _has_pending_viewpoint = false;
     _lastPointOnEarth.set(0.0, 0.0, 0.0);
     _arc_height = 0.0;
@@ -609,17 +588,9 @@ EarthManipulator::established()
 
     if ( needToReestablish )
     {
-        osg::ref_ptr<osg::Node> safeNode = _node.get();
-        if ( !safeNode.valid() )
-            return false;
-
-        // find a map node.
-        MapNode* mapNode = MapNode::findMapNode( safeNode.get(), _findNodeTraversalMask );
-        if ( mapNode && !_settings->getDisableCollisionAvoidance() )
-        {            
-            _terrainCallback = new ManipTerrainCallback( this );
-            mapNode->getTerrain()->addTerrainCallback( _terrainCallback );
-        }   
+        osg::ref_ptr<osg::Node> safeNode;
+        if ( !_node.lock(safeNode) )
+            return false;      
 
         // find a CSN node - if there is one, we want to attach the manip to that
         _csn = findRelativeNodeOfType<osg::CoordinateSystemNode>( safeNode.get(), _findNodeTraversalMask );
@@ -670,13 +641,30 @@ EarthManipulator::established()
         _cached_srs = NULL;
         _srs_lookup_failed = false;
 
-        //OE_DEBUG << "[EarthManip] new CSN established." << std::endl;
+        OE_INFO << "[EarthManip] new CSN established." << std::endl;
     }
 
     return _csn.valid() && _node.valid();
 }
 
 
+void 
+EarthManipulator::handleTileAdded(const TileKey& key, osg::Node* tile, TerrainCallbackContext& context)
+{
+#if 0
+    // Only do collision avoidance if it's enabled, we're not tethering and we're not in the middle of setting a viewpoint.            
+    if (!getSettings()->getDisableCollisionAvoidance() &&
+        !getTetherNode() &&
+        !isSettingViewpoint() )
+    {                
+        const GeoPoint& pt = centerMap();
+        if ( key.getExtent().contains(pt.x(), pt.y()) )
+        {
+            recalculateCenterFromLookVector();
+        }
+    }
+#endif
+}
 
 bool
 EarthManipulator::createLocalCoordFrame( const osg::Vec3d& worldPos, osg::CoordinateFrame& out_frame ) const
@@ -700,6 +688,10 @@ EarthManipulator::setCenter( const osg::Vec3d& worldPos )
     {
         _centerMap.fromWorld( _cached_srs.get(), worldPos );
     }
+
+    // cache the "last known" focal point height so we can use it as a
+    // backup if necessary.
+    _centerHeight = _is_geocentric ? _center.length() : _center.z();
 }
 
 
@@ -960,7 +952,7 @@ EarthManipulator::setViewpoint( const Viewpoint& vp, double duration_s )
                     new_center.z(),
                     geocentric.x(), geocentric.y(), geocentric.z() );
 
-                new_center = geocentric;            
+                new_center = geocentric;
             }
         }
 
@@ -1075,48 +1067,14 @@ EarthManipulator::getViewpoint() const
 
 
 void
-EarthManipulator::setTetherNode( osg::Node* node )
+EarthManipulator::setTetherNode( osg::Node* node, double duration_s )
 {
     if (_tether_node != node)
     {
         _offset_x = 0.0;
         _offset_y = 0.0;
 
-        if ( node )
-        {
-            // pre-compute some tether properties. If the node is an MT, treat it
-            // a little differently.
-
-            // Find the deepest transform that has a single child. That is the one we
-            // will use to calculate the tether location.
-            _tether_xform = 0L;
-            for( osg::Group* c = node->asGroup(); c != 0L; )
-            {
-                osg::Transform* xform = dynamic_cast<osg::Transform*>(c);
-                if ( xform )
-                    _tether_xform = xform;
-                
-                c = c->getNumChildren() == 1 ? c->getChild(0)->asGroup() : 0L;
-            }
-
-            if ( _tether_xform )
-            {
-                osg::BoundingSphere bs;
-
-                for( unsigned i=0; i<_tether_xform->getNumChildren(); ++i )
-                {
-                    bs.expandBy( _tether_xform->getChild(i)->getBound() );
-                }
-
-                _tether_local_center = bs.center();
-            }
-            else
-            {
-                _tether_local_center.set( 0.0, 0.0, 0.0 );
-            }
-        }
-
-        else
+        if ( node == 0L )
         {
             // rekajigger the distance, center, and pitch to legal non-tethered values:
             double pitch;
@@ -1136,9 +1094,15 @@ EarthManipulator::setTetherNode( osg::Node* node )
             double newDistance = (eye-_center).length();
             setDistance( newDistance );
         }
-    }
+    }    
 
     _tether_node = node;
+
+    if (_tether_node.valid() && duration_s > 0.0)
+    {                
+        Viewpoint destVP = getTetherNodeViewpoint();
+        setViewpoint( destVP, duration_s );
+    }
 }
 
 
@@ -1174,6 +1138,89 @@ EarthManipulator::intersect(const osg::Vec3d& start, const osg::Vec3d& end, osg:
     return false;
 }
 
+bool
+EarthManipulator::intersectLookVector(osg::Vec3d& out_eye,
+                                      osg::Vec3d& out_target,
+                                      osg::Vec3d& out_up ) const
+{
+    bool success = false;
+
+    osg::ref_ptr<osg::Node> safeNode = _node.get();
+    if ( safeNode.valid() )
+    {
+        double R = _centerHeight; // = getSRS()->getEllipsoid()->getRadiusEquator();
+
+        getInverseMatrix().getLookAt(out_eye, out_target, out_up, 1.0);
+        osg::Vec3d look = out_target-out_eye;
+
+		osg::ref_ptr<osgUtil::LineSegmentIntersector> lsi =
+		    new osgEarth::DPLineSegmentIntersector(out_eye, out_eye+look*1e8);
+
+        lsi->setIntersectionLimit(lsi->LIMIT_NEAREST);
+
+        osgUtil::IntersectionVisitor iv(lsi.get());        
+        iv.setTraversalMask(_intersectTraversalMask);
+
+        safeNode->accept(iv);
+
+        if (lsi->containsIntersections())
+        {
+            out_target = lsi->getIntersections().begin()->getWorldIntersectPoint();
+            if ( !_is_geocentric || GeoMath::isPointVisible(out_eye, out_target, R) )
+            {
+                success = true;
+            }
+        }
+
+        if ( !success )
+        {
+            // backup plan: intersect spheroid (if geocentric) or base plane (if projected)
+            if ( _is_geocentric )
+            {
+                osg::Vec3d i0, i1;
+                unsigned hits = GeoMath::interesectLineWithSphere(out_eye, out_eye+look*1e8, R, i0, i1);
+                if ( hits > 0 )
+                {
+                    // Need to check not only for intersection, but also visibility
+                    // over the horizon.
+
+                    // one hit? look vec is tangent to sphere, or camera is underground
+                    if ( hits == 1 && GeoMath::isPointVisible(out_eye, i0, R) )
+                    {
+                        out_target = i0;
+                        success = true;
+                    }
+
+                    // two hits? look vec intersects sphere (in two places)
+                    else if ( hits == 2 )
+                    {
+                        // select the closest hit
+                        out_target = (out_eye-i0).length2() < (out_eye-i1).length2() ? i0 : i1;
+                        if ( GeoMath::isPointVisible(out_eye, out_target, R) )
+                        {
+                            success = true;
+                        }
+                    }
+                }
+            }
+
+            else // !_is_geocentric
+            {
+                osg::Vec3d i0;
+                osg::Plane zup(0, 0, 1, 0);
+                unsigned hits = GeoMath::intersectLineWithPlane(out_eye, out_eye+look, zup, i0);
+                if (hits > 0)
+                {
+                    out_target = i0;
+                    success = true;
+                }
+            }
+        }
+    }
+
+    return success;
+}
+
 void
 EarthManipulator::home(double unused)
 {
@@ -1297,7 +1344,14 @@ EarthManipulator::updateCamera( osg::Camera* eventCamera )
                     double py = 2.0*(((vp->height()/2)+p.y())/vp->height())-1.0;
 
                     osg::Matrix projMatrix;
-                    projMatrix.makePerspective(_vfov, vp->width()/vp->height(), 1.0f, 10000.0f);
+
+                    // if we're in ortho, switch. If we are already in perspective, just
+                    // grab the active matrix so we can apply offsets to it.
+                    if ( isOrtho ) 
+                        projMatrix.makePerspective(_vfov, vp->width()/vp->height(), 1.0f, 10000.0f);
+                    else
+                        projMatrix = proj;
+
                     projMatrix.postMult( osg::Matrix::translate(px, py, 0.0) );
 
                     _viewCamera->setProjectionMatrix( projMatrix );
@@ -1376,9 +1430,7 @@ EarthManipulator::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapt
         _time_s_last_frame = _time_s_now;
         _time_s_now = time_s_now;
         _delta_t = _time_s_now - _time_s_last_frame;
-        // this factor adjusts for the variation of frame rate relative to 60fps
-        _t_factor = _delta_t / 0.01666666666;
-
+        
         if ( _has_pending_viewpoint && _node.valid() )
         {
             _has_pending_viewpoint = false;
@@ -1451,7 +1503,7 @@ EarthManipulator::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapt
    
     // form the current Action based on the event type:
     Action action = ACTION_NULL;
-    _time_s_now = osg::Timer::instance()->time_s();
+    //_time_s_now = osg::Timer::instance()->time_s();
 
     // if tethering is active, check to see whether the incoming event 
     // will break the tether.
@@ -1601,7 +1653,7 @@ EarthManipulator::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapt
                         aa.requestRedraw();
 
                     if ( _continuous && !wasContinuous )
-                        _last_continuous_action_time = _time_s_now;
+                        _last_continuous_action_time = time_s_now; //_time_s_now;
 
                     aa.requestContinuousUpdate(_continuous);
                     _thrown = false;
@@ -1662,25 +1714,13 @@ EarthManipulator::postUpdate()
 void
 EarthManipulator::updateTether()
 {
-    osg::ref_ptr<osg::Node> tether_node;
-    if ( _tether_node.lock(tether_node) )
-    {
-        osg::Matrix localToWorld;
-
-        if ( _tether_xform )
-        {
-            osg::NodePathList nodePaths = _tether_xform->getParentalNodePaths();
-            if ( nodePaths.empty() )
-                return;
-
-            localToWorld = osg::computeLocalToWorld( nodePaths[0] );
-            if ( !localToWorld.valid() )
-                return;
+    if (!_setting_viewpoint)
+    {        
+        osg::ref_ptr<osg::Node> tether_node;
+        if ( _tether_node.lock(tether_node) )
+        {            
+            osg::Matrix localToWorld;
 
-            setCenter( _tether_local_center * localToWorld );
-        }
-        else
-        {
             osg::NodePathList nodePaths = tether_node->getParentalNodePaths();
             if ( nodePaths.empty() )
                 return;
@@ -1689,66 +1729,110 @@ EarthManipulator::updateTether()
             if ( !localToWorld.valid() )
                 return;
 
-            setCenter( localToWorld.getTrans() );
-        }
+            setCenter( osg::Vec3d(0,0,0) * localToWorld );
 
-        _previousUp = getUpVector( _centerLocalToWorld );
+            _previousUp = getUpVector( _centerLocalToWorld );
 
-        double sx = 1.0/sqrt(localToWorld(0,0)*localToWorld(0,0) + localToWorld(1,0)*localToWorld(1,0) + localToWorld(2,0)*localToWorld(2,0));
-        double sy = 1.0/sqrt(localToWorld(0,1)*localToWorld(0,1) + localToWorld(1,1)*localToWorld(1,1) + localToWorld(2,1)*localToWorld(2,1));
-        double sz = 1.0/sqrt(localToWorld(0,2)*localToWorld(0,2) + localToWorld(1,2)*localToWorld(1,2) + localToWorld(2,2)*localToWorld(2,2));
-        localToWorld = localToWorld*osg::Matrixd::scale(sx,sy,sz);
+            double sx = 1.0/sqrt(localToWorld(0,0)*localToWorld(0,0) + localToWorld(1,0)*localToWorld(1,0) + localToWorld(2,0)*localToWorld(2,0));
+            double sy = 1.0/sqrt(localToWorld(0,1)*localToWorld(0,1) + localToWorld(1,1)*localToWorld(1,1) + localToWorld(2,1)*localToWorld(2,1));
+            double sz = 1.0/sqrt(localToWorld(0,2)*localToWorld(0,2) + localToWorld(1,2)*localToWorld(1,2) + localToWorld(2,2)*localToWorld(2,2));
+            localToWorld = localToWorld*osg::Matrixd::scale(sx,sy,sz);
 
-        //Just track the center
-        if (_settings->getTetherMode() == TETHER_CENTER)
-        {
-            _centerRotation = _centerLocalToWorld.getRotate();
-        }
-        //Track all rotations
-        else if (_settings->getTetherMode() == TETHER_CENTER_AND_ROTATION)
-        {
-            _centerRotation = localToWorld.getRotate();
+            //Just track the center
+            if (_settings->getTetherMode() == TETHER_CENTER)
+            {
+                _centerRotation = _centerLocalToWorld.getRotate();
+            }
+            //Track all rotations
+            else if (_settings->getTetherMode() == TETHER_CENTER_AND_ROTATION)
+            {
+                _centerRotation = localToWorld.getRotate();
+            }
+            else if (_settings->getTetherMode() == TETHER_CENTER_AND_HEADING)
+            {
+                //Track just the heading
+                osg::Matrixd localToFrame(localToWorld*osg::Matrixd::inverse( _centerLocalToWorld ));
+                double azim = atan2(-localToFrame(0,1),localToFrame(0,0));
+                osg::Quat nodeRotationRelToFrame, rotationOfFrame;
+                nodeRotationRelToFrame.makeRotate(-azim,0.0,0.0,1.0);
+                rotationOfFrame = _centerLocalToWorld.getRotate();
+                _centerRotation = nodeRotationRelToFrame*rotationOfFrame;
+            }
         }
-        else if (_settings->getTetherMode() == TETHER_CENTER_AND_HEADING)
+    }
+    else
+    {
+        // Update the deltas since this is a moving node.
+        Viewpoint vp = getTetherNodeViewpoint();        
+        osg::Vec3d vpFocalPoint = vp.getFocalPoint();
+        if ( _cached_srs.valid() && vp.getSRS() && !_cached_srs->isEquivalentTo( vp.getSRS() ) )
         {
-            //Track just the heading
-            osg::Matrixd localToFrame(localToWorld*osg::Matrixd::inverse( _centerLocalToWorld ));
-            double azim = atan2(-localToFrame(0,1),localToFrame(0,0));
-            osg::Quat nodeRotationRelToFrame, rotationOfFrame;
-            nodeRotationRelToFrame.makeRotate(-azim,0.0,0.0,1.0);
-            rotationOfFrame = _centerLocalToWorld.getRotate();
-            _centerRotation = nodeRotationRelToFrame*rotationOfFrame;
+            vp.getSRS()->transform( vp.getFocalPoint(), _cached_srs.get(), vpFocalPoint );
         }
+        _delta_focal_point = vpFocalPoint - _start_viewpoint.getFocalPoint(); // TODO: adjust for lon=180 crossing
     }
 }
 
+Viewpoint EarthManipulator::getTetherNodeViewpoint() const
+{
+    osg::ref_ptr<osg::Node> tether_node;
+    if ( _tether_node.lock(tether_node) )
+    {
+        osg::Matrix localToWorld;
+
+        osg::NodePathList nodePaths = tether_node->getParentalNodePaths();
+        if ( nodePaths.empty() )
+            return Viewpoint();
+
+        localToWorld = osg::computeLocalToWorld( nodePaths[0] );
+        if ( !localToWorld.valid() )
+            return Viewpoint();
+
+        // For now we just care about the center point of the tethered node.
+        osg::Vec3d centerWorld = osg::Vec3d(0,0,0) * localToWorld;
+        GeoPoint centerMap;
+        centerMap.fromWorld( _cached_srs.get(), centerWorld );
+        Viewpoint vp = getViewpoint();
+        return Viewpoint( centerMap.vec3d(), vp.getHeading(), vp.getPitch(), vp.getRange(), vp.getSRS() );        
+    }    
+    return Viewpoint();
+}
+
 bool
 EarthManipulator::serviceTask()
 {
     if ( _task.valid() && _task->_type != TASK_NONE )
     {
         double dt = _time_s_now - _task->_time_last_service;
-
-        switch( _task->_type )
+        if ( dt > 0.0 )
         {
-            case TASK_PAN:
-                pan( dt * _task->_dx, dt * _task->_dy );
-                break;
-            case TASK_ROTATE:
-                rotate( dt * _task->_dx, dt * _task->_dy );
-                break;
-            case TASK_ZOOM:
-                zoom( dt * _task->_dx, dt * _task->_dy );
-                break;
-            default: break;
-        }
+            //OE_INFO << "serviceTask: fr="<<_frame_count<<", dt=" << dt << ", clamped = " << osg::clampBelow(dt,_task->_duration_s)
+            //    << std::endl;
 
-        _task->_duration_s -= dt;
-        _task->_time_last_service = _time_s_now;
+            // cap the DT so we don't exceed the expected delta.
+            dt = osg::clampBelow( dt, _task->_duration_s );
 
-        if ( _task->_duration_s <= 0.0 )
-        {
-            _task->_type = TASK_NONE;
+            switch( _task->_type )
+            {
+                case TASK_PAN:
+                    pan( dt * _task->_dx, dt * _task->_dy );
+                    break;
+                case TASK_ROTATE:
+                    rotate( dt * _task->_dx, dt * _task->_dy );
+                    break;
+                case TASK_ZOOM:
+                    zoom( dt * _task->_dx, dt * _task->_dy );
+                    break;
+                default: break;
+            }
+
+            _task->_duration_s -= dt;
+            _task->_time_last_service = _time_s_now;
+
+            if ( _task->_duration_s <= 0.0 )
+            {
+                _task->_type = TASK_NONE;
+            }
         }
     }
 
@@ -1831,9 +1915,9 @@ EarthManipulator::addTouchEvents(const osgGA::GUIEventAdapter& ea)
 
 bool
 EarthManipulator::parseTouchEvents( TouchEvents& output )
-{
-    const float sens = 0.005f;    
-        
+{    
+    double sens = this->getSettings()->getTouchSensitivity();
+
     if (_touchPointQueue.size() == 2 )
     {
         if (_touchPointQueue[0].size()   == 2 &&     // two fingers
@@ -1863,7 +1947,7 @@ EarthManipulator::parseTouchEvents( TouchEvents& output )
                 angle[1] = atan2(p1[0].y - p1[1].y, p1[0].x - p1[1].x);
                 float da = angle[0] - angle[1];
 
-                float dragThres = 2.0f;         
+                float dragThres = 2.0f * 0.0005 / sens;         
 
                 // now see if that corresponds to any touch events:
                 
@@ -1880,7 +1964,7 @@ EarthManipulator::parseTouchEvents( TouchEvents& output )
                 else
                 {                                 
                     // otherwise it's a pinch and/or a zoom.  You can do them together.
-                    if (fabs(deltaDistance) > 1.0)
+                    if (fabs(deltaDistance) > (1.0 * 0.0005 / sens ) )
                     {
                         // distance between the fingers changed: a pinch.
                         output.push_back(TouchEvent());
@@ -1889,14 +1973,14 @@ EarthManipulator::parseTouchEvents( TouchEvents& output )
                         ev._dx = 0.0, ev._dy = deltaDistance * -sens;
                     }
 
-                    if (fabs(da) > 0.01)
+                    if (fabs(da) > (0.01 * 0.0005 / sens) )
                     {
                         // angle between vectors changed: a twist.
                         output.push_back(TouchEvent());
                         TouchEvent& ev = output.back();
                         ev._eventType = EVENT_MULTI_TWIST;                    
                         ev._dx = da;
-                        //ev._dy = 0.5 * (dy[0]+dy[1]) * sens;
+                        //ev._dy = 0.5 * (dy[0]+dy[1]) * _touch_sens;
                         ev._dy = 0.0;
                     }
                 }             
@@ -2020,17 +2104,14 @@ EarthManipulator::getInverseMatrix() const
 void
 EarthManipulator::setByLookAt(const osg::Vec3d& eye,const osg::Vec3d& center,const osg::Vec3d& up)
 {
-    osg::ref_ptr<osg::Node> safeNode = _node.get();
-
-    if ( !safeNode.valid() ) return;
-
-    // compute rotation matrix
-    osg::Vec3d lv(center-eye);
-    setDistance( lv.length() );
-    setCenter( center );
-
-    if (_node.valid())
+    osg::ref_ptr<osg::Node> safeNode;
+    if ( _node.lock(safeNode) )
     {
+        // compute rotation matrix
+        osg::Vec3d lv(center-eye);
+        setDistance( lv.length() );
+        setCenter( center );
+
         bool hitFound = false;
 
         double distance = lv.length();
@@ -2041,7 +2122,7 @@ EarthManipulator::setByLookAt(const osg::Vec3d& eye,const osg::Vec3d& center,con
             !hitFound && i<2;
             ++i, endPoint = farPosition)
         {
-            // compute the intersection with the scene.s
+            // compute the intersection with the scene.
             
             osg::Vec3d ip;
             if (intersect(eye, endPoint, ip))
@@ -2066,19 +2147,50 @@ EarthManipulator::setByLookAt(const osg::Vec3d& eye,const osg::Vec3d& center,con
     recalculateRoll();
 }
 
-
 void
-EarthManipulator::recalculateCenter( const osg::CoordinateFrame& frame )
+EarthManipulator::setByLookAtRaw(const osg::Vec3d& eye,const osg::Vec3d& center,const osg::Vec3d& up)
 {
-    osg::ref_ptr<osg::Node> safeNode = _node.get();
-    if ( safeNode.valid() )
+    // compute rotation matrix
+    osg::Vec3d lv(center-eye);
+    setDistance( lv.length() );
+    setCenter( center );
+
+    osg::Matrixd rotation_matrix = osg::Matrixd::lookAt(eye,center,up);
+    _centerRotation = getRotation(_center).getRotate().inverse();
+    _rotation = rotation_matrix.getRotate().inverse() * _centerRotation.inverse();
+    _previousUp = getUpVector(_centerLocalToWorld);
+
+    recalculateRoll();
+}
+
+
+bool
+EarthManipulator::recalculateCenterFromLookVector()
+{    
+    // just re-applying the lookat parameters will calculate a new coordinate
+    // frame based on a look-at intersection.
+    osg::Vec3d eye, target, up;
+    bool intersected = intersectLookVector(eye, target, up);
+    if (intersected)
     {
-        bool hitFound = false;
+        setByLookAtRaw(eye, target, up);
+        //GeoPoint p;
+        //p.fromWorld(getSRS(), target);
+        //OE_INFO << "center = " << p.x() << ", " << p.y() << ", " << p.alt() << "\n";
+    }
+
+    return intersected;
+}
 
-        //osg::Vec3d eye = getMatrix().getTrans();
 
+void
+EarthManipulator::recalculateCenter( const osg::CoordinateFrame& frame )
+{
+    osg::ref_ptr<osg::Node> node;
+    if ( _node.lock(node) )
+    {
         // need to reintersect with the terrain
-        double ilen = safeNode->getBound().radius()*0.25f;
+        double ilen = node->getBound().radius()*0.25f;
 
         osg::Vec3d up = getUpVector(frame);
 
@@ -2092,35 +2204,15 @@ EarthManipulator::recalculateCenter( const osg::CoordinateFrame& frame )
             if (hit_ip2)
             {
                 setCenter( (_center-ip1).length2() < (_center-ip2).length2() ? ip1 : ip2 );
-                hitFound = true;
             }
             else
             {
                 setCenter( ip1 );
-                hitFound = true;
             }
         }
         else if (hit_ip2)
         {
             setCenter( ip2 );
-            hitFound = true;
-        }
-
-        if (hitFound)
-        {
-#if 0
-            // recalculate the distance based on the current eyepoint:
-            double oldDistance = _distance;
-            double newDistance = (eye-_center).length();
-            setDistance( newDistance );
-            OE_NOTICE << "OLD = " << oldDistance << ", NEW = " << newDistance << std::endl;
-#endif
-        }
-
-        else // if (!hitFound)
-        {
-            // ??
-            //OE_DEBUG<<"EarthManipulator unable to intersect with terrain."<<std::endl;
         }
     }
 }
@@ -2131,6 +2223,10 @@ EarthManipulator::pan( double dx, double dy )
 {
     if (!_tether_node.valid())
     {
+        // to pan, we need a focus point on the terrain:
+        if ( !recalculateCenterFromLookVector() )
+            return;
+
         double scale = -0.3f*_distance;
         double old_azim;
         getLocalEulerAngles( &old_azim );
@@ -2156,14 +2252,14 @@ EarthManipulator::pan( double dx, double dy )
         // save the previous CF so we can do azimuth locking:
         osg::CoordinateFrame oldCenterLocalToWorld = _centerLocalToWorld;
 
-        // move the cente rpoint:
+        // move the center point:
         setCenter( _center + dv );
 
         // need to recompute the intersection point along the look vector.
-        osg::ref_ptr<osg::Node> safeNode = _node.get();
-        if (safeNode.valid())
+        osg::ref_ptr<osg::Node> safeNode;
+        if ( _node.lock(safeNode) )
         {
-            recalculateCenter( oldCenterLocalToWorld );
+            //recalculateCenter( oldCenterLocalToWorld );
 
             osg::Vec3d new_localUp = getUpVector( _centerLocalToWorld );
 
@@ -2221,7 +2317,7 @@ EarthManipulator::rotate( double dx, double dy )
 
     bool tether = _tether_node.valid();
     double minp = osg::DegreesToRadians( osg::clampAbove(_settings->getMinPitch(), -89.9) );
-    double maxp = osg::DegreesToRadians( osg::clampBelow(_settings->getMaxPitch(), tether? 89.9 : -1.0) );
+    double maxp = osg::DegreesToRadians( osg::clampBelow(_settings->getMaxPitch(),  89.9) );//tether? 89.9 : -1.0) );
 
 #if 0
     OE_NOTICE << LC 
@@ -2265,9 +2361,13 @@ EarthManipulator::rotate( double dx, double dy )
 
 void
 EarthManipulator::zoom( double dx, double dy )
-{    
+{   
+    // in normal (non-tethered mode) we need a valid zoom point.
+    if ( !_tether_node.valid() )
+        recalculateCenterFromLookVector();
+
     double scale = 1.0f + dy;
-    setDistance( _distance * scale );    
+    setDistance( _distance * scale );
 }
 
 
@@ -2314,8 +2414,10 @@ EarthManipulator::screenToWorld(float x, float y, osg::View* theView, osg::Vec3d
     if ( !view || !view->getCamera() )
         return false;
 
-    osg::NodePath nodePath;
-    _csnObserverPath.getNodePath(nodePath);
+    osg::RefNodePath nodePath;
+    if ( !_csnObserverPath.getRefNodePath(nodePath) )
+        return false;
+
     if ( nodePath.empty() )
         return false;
 
diff --git a/src/osgEarthUtil/Ephemeris b/src/osgEarthUtil/Ephemeris
new file mode 100644
index 0000000..2d1d127
--- /dev/null
+++ b/src/osgEarthUtil/Ephemeris
@@ -0,0 +1,60 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTHUTIL_EPHEMERIS
+#define OSGEARTHUTIL_EPHEMERIS
+
+#include <osgEarthUtil/Common>
+#include <osgEarth/DateTime>
+#include <osg/Vec3d>
+
+namespace osgEarth { namespace Util 
+{
+    using namespace osgEarth;
+
+    /**
+     * An Ephemeris gives the positions of naturally occurring astronimical
+     * objects; in that case, the sun and the moon. Also includes some
+     * related utility functions.
+     */
+    class OSGEARTHUTIL_EXPORT Ephemeris : public osg::Referenced
+    {
+    public:
+        /**
+        * Gets the moon position in ECEF coordinates at the given time
+        */
+        virtual osg::Vec3d getMoonPositionECEF(const DateTime& dt) const;
+
+        /**
+        * Gets the sun position in ECEF coordinates at the given time
+        */
+        virtual osg::Vec3d getSunPositionECEF(const DateTime& dt) const;
+        
+        /**
+         * Gets an ECEF position from the right ascension, declination and range
+         *
+         * @param ra    Right ascension in radians
+         * @param decl  Declination in radians
+         * @param range Range in meters
+         */
+        osg::Vec3d getECEFfromRADecl(double ra, double decl, double range) const;
+    };
+    
+} } // namespace osgEarth::Util
+
+#endif // OSGEARTHUTIL_EPHEMERIS
diff --git a/src/osgEarthUtil/Ephemeris.cpp b/src/osgEarthUtil/Ephemeris.cpp
new file mode 100644
index 0000000..2347aa9
--- /dev/null
+++ b/src/osgEarthUtil/Ephemeris.cpp
@@ -0,0 +1,322 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include <osgEarthUtil/Ephemeris>
+#include <sstream>
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+//------------------------------------------------------------------------
+
+#undef  LC
+#define LC "[Ephemeris] "
+
+namespace
+{
+    // Astronomical Math
+    // http://www.stjarnhimlen.se/comp/ppcomp.html
+
+#define d2r(X) osg::DegreesToRadians(X)
+#define r2d(X) osg::RadiansToDegrees(X)
+#define nrad(X) { while( X > TWO_PI ) X -= TWO_PI; while( X < 0.0 ) X += TWO_PI; }
+#define nrad2(X) { while( X <= -osg::PI ) X += TWO_PI; while( X > osg::PI ) X -= TWO_PI; }
+
+    static const double TWO_PI = (2.0*osg::PI);
+    static const double JD2000 = 2451545.0;
+
+    struct CelestialPosition
+    {
+        osg::Vec3d _ecef;
+        double     _rightAscension;
+        double     _declination;
+        double     _localAzimuth;
+        double     _localElevation;
+        double     _localLatitude;
+        double     _localLongitude;
+    };
+    
+    osg::Vec3d getPositionFromRADecl(double ra, double decl, double range)
+    {
+        return osg::Vec3(0,range,0) * 
+            osg::Matrix::rotate( decl, 1, 0, 0 ) * 
+            osg::Matrix::rotate( ra - osg::PI_2, 0, 0, 1 );
+    }
+
+
+    double sgCalcEccAnom(double M, double e)
+    {
+        double eccAnom, E0, E1, diff;
+
+        double epsilon = osg::DegreesToRadians(0.001);
+        
+        eccAnom = M + e * sin(M) * (1.0 + e * cos (M));
+        // iterate to achieve a greater precision for larger eccentricities 
+        if (e > 0.05)
+        {
+            E0 = eccAnom;
+            do
+            {
+                 E1 = E0 - (E0 - e * sin(E0) - M) / (1 - e *cos(E0));
+                 diff = fabs(E0 - E1);
+                 E0 = E1;
+            } while (diff > epsilon );
+            return E0;
+        }
+        return eccAnom;
+    }
+
+    double getJulianDate( int year, int month, int date )
+    {
+        if ( month <= 2 )
+        {
+            month += 12;
+            year -= 1;
+        }
+
+        int A = int(year/100);
+        int B = 2-A+(A/4);
+        int C = int(365.25*(year+4716));
+        int D = int(30.6001*(month+1));
+        return B + C + D + date - 1524.5;
+    }
+
+    struct Sun
+    {
+        // https://www.cfa.harvard.edu/~wsoon/JuanRamirez09-d/Chang09-OptimalTiltAngleforSolarCollector.pdf
+        void getLatLonRaDecl(int year, int month, int date, double hoursUTC,
+                             double& out_lat,
+                             double& out_lon,
+                             double& out_ra,
+                             double& out_decl,
+                             double& out_almanacTime) const
+        {
+            double JD = getJulianDate(year, month, date);
+            double JD1 = (JD - JD2000);                         // julian time since JD2000 epoch
+            double JC = JD1/36525.0;                            // julian century
+
+            double mu = 282.937348 + 0.00004707624*JD1 + 0.0004569*(JC*JC);
+
+            double epsilon = 280.466457 + 0.985647358*JD1 + 0.000304*(JC*JC);
+
+            // orbit eccentricity:
+            double E = 0.01670862 - 0.00004204 * JC;
+
+            // mean anomaly of the perihelion
+            double M = epsilon - mu;
+
+            // perihelion anomaly:
+            double v =
+                M + 
+                360.0*E*sin(d2r(M))/osg::PI + 
+                900.0*(E*E)*sin(d2r(2*M))/4*osg::PI - 
+                180.0*(E*E*E)*sin(d2r(M))/4.0*osg::PI;
+
+            // longitude of the sun in ecliptic coordinates:
+            double sun_lon = d2r(v - 360.0 + mu); // lambda
+            nrad2(sun_lon);
+
+            // angle between the ecliptic plane and the equatorial plane
+            double zeta_deg = 23.4392;
+            double zeta = d2r(zeta_deg);
+
+            // latitude of the sun on the ecliptic plane:
+            double omega = d2r(0.0);
+
+            // latitude of the sun with respect to the equatorial plane (solar declination):
+            double sun_lat = asin( sin(sun_lon)*sin(zeta) );
+            nrad2(sun_lat);
+
+            // finally, adjust for the time of day (rotation of the earth)
+            double time_r = hoursUTC/24.0; // 0..1
+            nrad(sun_lon); // clamp to 0..TWO_PI
+            double sun_r = sun_lon/TWO_PI; // convert to 0..1
+
+            // rotational difference between UTC and current time
+            double diff_r = sun_r - time_r;
+            double diff_lon = TWO_PI * diff_r;
+
+            // apparent sun longitude.
+            double app_sun_lon = sun_lon - diff_lon + osg::PI;
+            nrad2(app_sun_lon);
+
+            out_lat = sun_lat;
+            out_lon = app_sun_lon;
+
+            // right ascension and declination.
+            double eclong = sun_lon;
+            double oblqec = d2r(zeta_deg - 0.0000004*JD1);
+            double num = cos(oblqec) * sin(eclong);
+            double den = cos(eclong);
+            out_ra = atan(num/den);
+            if ( den < 0.0 ) out_ra += osg::PI;
+            if ( den >= 0 && num < 0 ) out_ra += TWO_PI;
+            out_decl = asin(sin(oblqec)*sin(eclong));
+
+            // almanac time is the difference between the Julian Date and JD2000 epoch
+            out_almanacTime = JD1;
+        }
+
+        void getECEF(int year, int month, int date, double hoursUTC, osg::Vec3d& out_ecef)
+        {
+            double lat, applon, ra, decl, almanacTime;
+            getLatLonRaDecl(year, month, date, hoursUTC, lat, applon, ra, decl, almanacTime);
+            out_ecef.set(
+                cos(lat) * cos(-applon),
+                cos(lat) * sin(-applon),
+                sin(lat) );
+
+            out_ecef *= 149600000;
+        }
+
+        void getLocalAzEl(int year, int month, int date, double hoursUTC, double lat, double lon, double& out_az, double out_el)
+        {
+            // UNTESTED!
+            // http://stackoverflow.com/questions/257717/position-of-the-sun-given-time-of-day-and-lat-long 
+            double sunLat, sunAppLon, ra, decl, almanacTime;
+            getLatLonRaDecl(year, month, date, hoursUTC, sunLat, sunAppLon, ra, decl, almanacTime);
+            // UTC sidereal time:
+            double gmst = 6.697375 + .0657098242 * almanacTime + hoursUTC;
+            gmst = fmod(gmst, 24.0);
+            if ( gmst < 0.0 ) gmst += 24.0;
+            // Local mean sidereal time:
+            double lmst = gmst + r2d(lon)/15.0;
+            lmst = fmod(lmst, 24.0);
+            if ( lmst < 0.0 ) lmst += 24.0;
+            lmst = d2r(lmst*15.0);
+            // Hour angle:
+            double ha = lmst - ra;
+            nrad2(ha);
+            // Az/el:
+            out_el = asin(sin(decl)*sin(lat)+cos(decl)*cos(lat)*cos(ha));
+            out_az = asin(-cos(decl)*sin(ha)/cos(out_el));
+            double elc = asin(sin(decl)/sin(lat));
+            if ( out_el >= elc ) out_az = osg::PI - out_az;
+            if ( out_el <= elc && ha > 0.0 ) out_az += TWO_PI;
+        }
+    };
+
+    struct Moon
+    {
+        static std::string radiansToHoursMinutesSeconds(double ra)
+        {
+            while (ra < 0) ra += (osg::PI * 2.0);
+            //Get the total number of hours
+            double hours = (ra / (osg::PI * 2.0) ) * 24.0;
+            double minutes = hours - (int)hours;
+            hours -= minutes;
+            minutes *= 60.0;
+            double seconds = minutes - (int)minutes;
+            seconds *= 60.0;
+            std::stringstream buf;
+            buf << (int)hours << ":" << (int)minutes << ":" << (int)seconds;
+            return buf.str();
+        }
+
+        // From http://www.stjarnhimlen.se/comp/ppcomp.html
+        osg::Vec3d getPosition(int year, int month, int date, double hoursUTC ) const
+        {
+            //double julianDate = getJulianDate( year, month, date );
+            //julianDate += hoursUTC /24.0;
+            double d = 367*year - 7 * ( year + (month+9)/12 ) / 4 + 275*month/9 + date - 730530;
+            d += (hoursUTC / 24.0);                     
+
+            double ecl = osg::DegreesToRadians(23.4393 - 3.563E-7 * d);
+
+            double N = osg::DegreesToRadians(125.1228 - 0.0529538083 * d);
+            double i = osg::DegreesToRadians(5.1454);
+            double w = osg::DegreesToRadians(318.0634 + 0.1643573223 * d);
+            double a = 60.2666;//  (Earth radii)
+            double e = 0.054900;
+            double M = osg::DegreesToRadians(115.3654 + 13.0649929509 * d);
+
+            double E = M + e*(180.0/osg::PI) * sin(M) * ( 1.0 + e * cos(M) );
+            
+            double xv = a * ( cos(E) - e );
+            double yv = a * ( sqrt(1.0 - e*e) * sin(E) );
+
+            double v = atan2( yv, xv );
+            double r = sqrt( xv*xv + yv*yv );
+
+            //Compute the geocentric (Earth-centered) position of the moon in the ecliptic coordinate system
+            double xh = r * ( cos(N) * cos(v+w) - sin(N) * sin(v+w) * cos(i) );
+            double yh = r * ( sin(N) * cos(v+w) + cos(N) * sin(v+w) * cos(i) );
+            double zh = r * ( sin(v+w) * sin(i) );
+
+            // calculate the ecliptic latitude and longitude here
+            double lonEcl = atan2 (yh, xh);
+            double latEcl = atan2(zh, sqrt(xh*xh + yh*yh));
+
+            double xg = r * cos(lonEcl) * cos(latEcl);
+            double yg = r * sin(lonEcl) * cos(latEcl);
+            double zg = r * sin(latEcl);
+
+            double xe = xg;
+            double ye = yg * cos(ecl) -zg * sin(ecl);
+            double ze = yg * sin(ecl) +zg * cos(ecl);
+
+            double RA    = atan2(ye, xe);
+            double Dec = atan2(ze, sqrt(xe*xe + ye*ye));
+
+            //Just use the average distance from the earth            
+            double rg = 6378137.0 + 384400000.0;
+            
+            // finally, adjust for the time of day (rotation of the earth)
+            double time_r = hoursUTC/24.0; // 0..1            
+            double moon_r = RA/TWO_PI; // convert to 0..1
+
+            // rotational difference between UTC and current time
+            double diff_r = moon_r - time_r;
+            double diff_lon = TWO_PI * diff_r;
+
+            RA -= diff_lon;
+
+            nrad2(RA);
+
+            return getPositionFromRADecl( RA, Dec, rg );
+        }
+    };
+}
+
+
+//------------------------------------------------------------------------
+
+#undef  LC
+#define LC "[Ephemeris] "
+
+osg::Vec3d
+Ephemeris::getSunPositionECEF(const DateTime& date) const
+{
+    Sun sun;
+    osg::Vec3d ecef;
+    sun.getECEF( date.year(), date.month(), date.day(), date.hours(), ecef );
+    return ecef;
+}
+
+osg::Vec3d
+Ephemeris::getMoonPositionECEF(const DateTime& date) const
+{
+    Moon moon;
+    return moon.getPosition( date.year(), date.month(), date.day(), date.hours() );
+}
+
+osg::Vec3d
+Ephemeris::getECEFfromRADecl( double ra, double decl, double range ) const
+{
+    return getPositionFromRADecl(ra, decl, range);
+}
diff --git a/src/osgEarthUtil/ExampleResources b/src/osgEarthUtil/ExampleResources
index c15914d..8cb93ad 100644
--- a/src/osgEarthUtil/ExampleResources
+++ b/src/osgEarthUtil/ExampleResources
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -21,14 +21,22 @@
 
 #include <osgEarthUtil/Common>
 #include <osgEarthUtil/Controls>
-#include <osgEarthUtil/SkyNode>
 #include <osgEarthUtil/EarthManipulator>
-#include <osgEarthDrivers/ocean_surface/OceanSurface>
+#include <osgEarthUtil/Sky>
+#include <osgEarthUtil/Ocean>
 #include <osgEarth/Viewpoint>
 
-#include <osg/ArgumentParser>
-#include <osgViewer/View>
+//#include <osg/ArgumentParser>
 
+namespace osgEarth {
+    class MapNode;
+}
+namespace osg {
+    class ArgumentParser;
+}
+namespace osgViewer {
+    class View;
+};
 
 /**
  * This is a collection of resources used by the osgEarth example applications.
@@ -37,7 +45,6 @@ namespace osgEarth { namespace Util
 {
     using namespace osgEarth;
     using namespace osgEarth::Util::Controls;
-    using namespace osgEarth::Drivers;
 
     /**
      * Parses a set of built-in example arguments. Any Controls created by parsing
@@ -135,8 +142,7 @@ namespace osgEarth { namespace Util
     {
     public:
         Control* create( 
-            OceanSurfaceNode* ocean, 
-            osgViewer::View*  view ) const;
+            OceanNode* ocean ) const;
     };
 
 } } // namespace osgEarth::Util
diff --git a/src/osgEarthUtil/ExampleResources.cpp b/src/osgEarthUtil/ExampleResources.cpp
index 7ef71af..6628631 100644
--- a/src/osgEarthUtil/ExampleResources.cpp
+++ b/src/osgEarthUtil/ExampleResources.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -24,12 +24,18 @@
 #include <osgEarthUtil/MouseCoordsTool>
 #include <osgEarthUtil/AutoClipPlaneHandler>
 #include <osgEarthUtil/DataScanner>
+#include <osgEarthUtil/Sky>
+#include <osgEarthUtil/Ocean>
+#include <osgEarthUtil/Shadowing>
+#include <osgEarthUtil/ActivityMonitorTool>
+#include <osgEarthUtil/LogarithmicDepthBuffer>
 
 #include <osgEarthUtil/NormalMap>
 #include <osgEarthUtil/DetailTexture>
 #include <osgEarthUtil/LODBlending>
 #include <osgEarthUtil/VerticalScale>
 #include <osgEarthUtil/ContourMap>
+#include <osgEarthUtil/TextureSplatter>
 
 #include <osgEarthAnnotation/AnnotationData>
 #include <osgEarthAnnotation/AnnotationRegistry>
@@ -40,10 +46,12 @@
 
 #include <osgEarthDrivers/kml/KML>
 
-#include <osgGA/StateSetManipulator>
-#include <osgViewer/ViewerEventHandlers>
 #include <osgDB/FileNameUtils>
 #include <osgDB/WriteFile>
+#include <osgGA/StateSetManipulator>
+#include <osgGA/AnimationPathManipulator>
+#include <osgViewer/View>
+#include <osgViewer/ViewerEventHandlers>
 
 #define KML_PUSHPIN_URL "http://demo.pelicanmapping.com/icons/pushpin_yellow.png"
 
@@ -56,6 +64,7 @@ using namespace osgEarth::Util;
 using namespace osgEarth::Util::Controls;
 using namespace osgEarth::Symbology;
 using namespace osgEarth::Annotation;
+using namespace osgEarth::Drivers;
 
 //------------------------------------------------------------------------
 
@@ -212,20 +221,23 @@ MouseCoordsControlFactory::create(MapNode*         mapNode,
 
 namespace
 {
-    struct SkySliderHandler : public ControlEventHandler
+    struct SkyTimeSliderHandler : public ControlEventHandler
     {
-        SkySliderHandler(SkyNode* sky) : _sky(sky)  { }
+        SkyTimeSliderHandler(SkyNode* sky) : _sky(sky)  { }
 
         SkyNode* _sky;
 
         virtual void onValueChanged( class Control* control, float value )
         {
-            DateTime d;
-            _sky->getDateTime(d);
+            DateTime d = _sky->getDateTime();
             _sky->setDateTime(DateTime(d.year(), d.month(), d.day(), value));
         }
     };
 
+//#undef USE_AMBIENT_SLIDER
+#define USE_AMBIENT_SLIDER 1
+
+#ifdef USE_AMBIENT_SLIDER
     struct AmbientBrightnessHandler : public ControlEventHandler
     {
         AmbientBrightnessHandler(SkyNode* sky) : _sky(sky) { }
@@ -234,13 +246,55 @@ namespace
 
         virtual void onValueChanged( class Control* control, float value )
         {
-            _sky->setAmbientBrightness( value );
+            _sky->getSunLight()->setAmbient(osg::Vec4(value,value,value,1));
         }
     };
-}
+#endif
 
-//#undef USE_AMBIENT_SLIDER
-#define USE_AMBIENT_SLIDER 1
+    struct AnimateSkyUpdateCallback : public osg::NodeCallback
+    {    
+        /**
+        * Creates an AnimateSkyCallback.  
+        * @param rate    The time multipler from real time.  Default of 1440 means 1 minute real time will equal 1 day simulation time.
+        */
+        AnimateSkyUpdateCallback( double rate = 1440 ):
+    _rate( rate ),
+        _prevTime( -1 ),
+        _accumTime( 0.0 )
+    {
+    }
+
+    virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
+    {             
+        SkyNode* sky = dynamic_cast< SkyNode* >( node );
+        if (sky)
+        {            
+            double time = nv->getFrameStamp()->getSimulationTime();            
+            if (_prevTime > 0)
+            {                
+                TimeStamp t = sky->getDateTime().asTimeStamp();                  
+                double delta = ceil((time - _prevTime) * _rate);
+                _accumTime += delta;
+                // The time stamp only works in seconds so we wait until we've accumulated at least 1 second to change the date.
+                if (_accumTime > 1.0)
+                {
+                    double deltaS = floor(_accumTime );                    
+                    _accumTime -= deltaS;
+                    t += deltaS;
+                    sky->setDateTime( t );                        
+                }                
+            }            
+            _prevTime = time;
+        }
+        traverse( node, nv );
+    }
+
+    double _accumTime;
+    double _prevTime;    
+    double _rate;
+    };
+
+}
 
 Control*
 SkyControlFactory::create(SkyNode*         sky,
@@ -251,20 +305,19 @@ SkyControlFactory::create(SkyNode*         sky,
     grid->setChildSpacing( 10 );
     grid->setHorizFill( true );
 
-    grid->setControl( 0, 0, new LabelControl("Time: ", 16) );
+    grid->setControl( 0, 0, new LabelControl("Time (Hours UTC): ", 16) );
 
-    DateTime dt;
-    sky->getDateTime(dt);
+    DateTime dt = sky->getDateTime();
 
     HSliderControl* skySlider = grid->setControl(1, 0, new HSliderControl( 0.0f, 24.0f, dt.hours() ));
-    skySlider->setHorizFill( true, 200 );
-    skySlider->addEventHandler( new SkySliderHandler(sky) );
+    skySlider->setHorizFill( true, 300 );
+    skySlider->addEventHandler( new SkyTimeSliderHandler(sky) );
 
     grid->setControl(2, 0, new LabelControl(skySlider) );
 
 #ifdef USE_AMBIENT_SLIDER
-    grid->setControl(0, 1, new LabelControl("Ambient: ", 16) );
-    HSliderControl* ambient = grid->setControl(1, 1, new HSliderControl(0.0f, 1.0f, sky->getAmbientBrightness()));
+    grid->setControl(0, 1, new LabelControl("Min.Ambient: ", 16) );
+    HSliderControl* ambient = grid->setControl(1, 1, new HSliderControl(0.0f, 1.0f, sky->getSunLight()->getAmbient().r()));
     ambient->addEventHandler( new AmbientBrightnessHandler(sky) );
     grid->setControl(2, 1, new LabelControl(ambient) );
 #endif
@@ -278,47 +331,19 @@ namespace
 {
     struct ChangeSeaLevel : public ControlEventHandler
     {
-        ChangeSeaLevel( OceanSurfaceNode* ocean ) : _ocean(ocean) { }
+        ChangeSeaLevel( OceanNode* ocean ) : _ocean(ocean) { }
 
-        OceanSurfaceNode* _ocean;
+        OceanNode* _ocean;
 
         virtual void onValueChanged( class Control* control, float value )
         {
-            _ocean->options().seaLevel() = value;
-            _ocean->dirty();
-        }
-    };
-
-    struct ChangeLowFeather : public ControlEventHandler
-    {
-        ChangeLowFeather( OceanSurfaceNode* ocean ) : _ocean(ocean) { }
-
-        OceanSurfaceNode* _ocean;
-
-        virtual void onValueChanged( class Control* control, float value )
-        {
-            _ocean->options().lowFeatherOffset() = value;
-            _ocean->dirty();
-        }
-    };
-
-    struct ChangeHighFeather : public ControlEventHandler
-    {
-        ChangeHighFeather( OceanSurfaceNode* ocean ) : _ocean(ocean) { }
-
-        OceanSurfaceNode* _ocean;
-
-        virtual void onValueChanged( class Control* control, float value )
-        {
-            _ocean->options().highFeatherOffset() = value;
-            _ocean->dirty();
+            _ocean->setSeaLevel( value );
         }
     };
 }
 
 Control*
-OceanControlFactory::create(OceanSurfaceNode* ocean,
-                            osgViewer::View*  view   ) const
+OceanControlFactory::create(OceanNode* ocean) const
 {
     VBox* main = new VBox();
 
@@ -335,32 +360,6 @@ OceanControlFactory::create(OceanSurfaceNode* ocean,
     mslSlider->setHorizFill( true, 200 );
     mslSlider->addEventHandler( new ChangeSeaLevel(ocean) );
 
-    HBox* oceanBox2 = main->addControl(new HBox());
-    oceanBox2->setChildVertAlign( Control::ALIGN_CENTER );
-    oceanBox2->setChildSpacing( 10 );
-    oceanBox2->setHorizFill( true );
-
-    oceanBox2->addControl( new LabelControl("Low Feather: ", 16) );
-
-    HSliderControl* lfSlider = oceanBox2->addControl(new HSliderControl( -1000.0, 250.0f, -100.0f ));
-    lfSlider->setBackColor( Color::Gray );
-    lfSlider->setHeight( 12 );
-    lfSlider->setHorizFill( true, 200 );
-    lfSlider->addEventHandler( new ChangeLowFeather(ocean) );
-
-    HBox* oceanBox3 = main->addControl(new HBox());
-    oceanBox3->setChildVertAlign( Control::ALIGN_CENTER );
-    oceanBox3->setChildSpacing( 10 );
-    oceanBox3->setHorizFill( true );
-
-    oceanBox3->addControl( new LabelControl("High Feather: ", 16) );
-
-    HSliderControl* hfSlider = oceanBox3->addControl(new HSliderControl( -500.0f, 500.0f, -10.0f ));
-    hfSlider->setBackColor( Color::Gray );
-    hfSlider->setHeight( 12 );
-    hfSlider->setHorizFill( true, 200 );
-    hfSlider->addEventHandler( new ChangeHighFeather(ocean) );
-
     return main;
 }
 
@@ -454,17 +453,27 @@ MapNodeHelper::load(osg::ArgumentParser& args,
         }
     }
 
+    osg::ref_ptr<MapNode> mapNode;
     if ( !node )
     {
-        OE_WARN << LC << "No earth file." << std::endl;
-        return 0L;
+        if ( !args.find("--images") )
+        {
+            OE_WARN << LC << "No earth file." << std::endl;
+            return 0L;
+        }
+        else
+        {
+            mapNode = new MapNode();
+        }
     }
-
-    osg::ref_ptr<MapNode> mapNode = MapNode::get(node);
-    if ( !mapNode.valid() )
+    else
     {
-        OE_WARN << LC << "Loaded scene graph does not contain a MapNode - aborting" << std::endl;
-        return 0L;
+        mapNode = MapNode::get(node);
+        if ( !mapNode.valid() )
+        {
+            OE_WARN << LC << "Loaded scene graph does not contain a MapNode - aborting" << std::endl;
+            return 0L;
+        }
     }
 
     // warn about not having an earth manip
@@ -512,7 +521,6 @@ MapNodeHelper::parse(MapNode*             mapNode,
                      osg::Group*          root,
                      Control*             userControl ) const
 {
-    // this is a dubious move.
     if ( !root )
         root = mapNode;
 
@@ -528,6 +536,16 @@ MapNodeHelper::parse(MapNode*             mapNode,
     bool useCoords     = args.read("--coords") || useMGRS || useDMS || useDD;
     bool useOrtho      = args.read("--ortho");
     bool useAutoClip   = args.read("--autoclip");
+    bool useShadows    = args.read("--shadows");
+    bool animateSky    = args.read("--animate-sky");
+    bool showActivity  = args.read("--activity");
+    bool useLogDepth   = args.read("--logdepth");
+
+    if (args.read("--verbose"))
+        osgEarth::setNotifyLevel(osg::INFO);
+    
+    if (args.read("--quiet"))
+        osgEarth::setNotifyLevel(osg::FATAL);
 
     float ambientBrightness = 0.2f;
     args.read("--ambientBrightness", ambientBrightness);
@@ -540,9 +558,16 @@ MapNodeHelper::parse(MapNode*             mapNode,
 
     std::string imageExtensions;
     args.read("--image-extensions", imageExtensions);
+    
+    // animation path:
+    std::string animpath;
+    if ( args.read("--path", animpath) )
+    {
+        view->setCameraManipulator( new osgGA::AnimationPathManipulator(animpath) );
+    }
 
-    // install a canvas for any UI controls we plan to create:
-    ControlCanvas* canvas = ControlCanvas::get(view, false);
+    // Install a new Canvas for our UI controls, or use one that already exists.
+    ControlCanvas* canvas = ControlCanvas::getOrCreate( view );
 
     Container* mainContainer = canvas->addControl( new VBox() );
     mainContainer->setAbsorbEvents( true );
@@ -569,6 +594,7 @@ MapNodeHelper::parse(MapNode*             mapNode,
     const Config& lodBlendingConf = externals.child("lod_blending");
     const Config& vertScaleConf   = externals.child("vertical_scale");
     const Config& contourMapConf  = externals.child("contour_map");
+    const Config& texSplatConf    = externals.child("texture_splatter");
 
     // backwards-compatibility: read viewpoints at the top level:
     const ConfigSet& old_viewpoints = externals.children("viewpoint");
@@ -600,30 +626,75 @@ MapNodeHelper::parse(MapNode*             mapNode,
     // Adding a sky model:
     if ( useSky || !skyConf.empty() )
     {
-        double hours = skyConf.value( "hours", 12.0 );
-        SkyNode* sky = new SkyNode( mapNode->getMap() );
-        sky->setAmbientBrightness( ambientBrightness );
-        sky->setDateTime( DateTime(2011, 3, 6, hours) );
-        sky->attach( view );
-        root->addChild( sky );
-        Control* c = SkyControlFactory().create(sky, view);
-        if ( c )
-            mainContainer->addControl( c );
+        SkyOptions options(skyConf);
+        if ( options.getDriver().empty() )
+        {
+            if ( mapNode->getMapSRS()->isGeographic() )
+                options.setDriver("simple");
+            else
+                options.setDriver("gl");
+        }
+
+        SkyNode* sky = SkyNode::create(options, mapNode);
+        if ( sky )
+        {
+            sky->attach( view, 0 );
+            if ( mapNode->getNumParents() > 0 )
+            {
+                osgEarth::insertGroup(sky, mapNode->getParent(0));
+            }
+            else
+            {
+                sky->addChild( mapNode );
+                root = sky;
+            }
+                
+            Control* c = SkyControlFactory().create(sky, view);
+            if ( c )
+                mainContainer->addControl( c );
+
+            if (animateSky)
+            {
+                sky->setUpdateCallback( new AnimateSkyUpdateCallback() );
+            }
+
+        }
     }
 
     // Adding an ocean model:
     if ( useOcean || !oceanConf.empty() )
     {
-        OceanSurfaceNode* ocean = new OceanSurfaceNode( mapNode, oceanConf );
+        OceanNode* ocean = OceanNode::create(OceanOptions(oceanConf), mapNode);
         if ( ocean )
         {
-            root->addChild( ocean );
-            Control* c = OceanControlFactory().create(ocean, view);
+            // if there's a sky, we want to ocean under it
+            osg::Group* parent = osgEarth::findTopMostNodeOfType<SkyNode>(root);
+            if ( !parent ) parent = root;
+            parent->addChild( ocean );
+
+            Control* c = OceanControlFactory().create(ocean);
             if ( c )
                 mainContainer->addControl(c);
         }
     }
 
+    // Shadowing.
+    if ( useShadows )
+    {
+        ShadowCaster* caster = new ShadowCaster();
+        caster->setLight( view->getLight() );
+        caster->getShadowCastingGroup()->addChild( mapNode->getModelLayerGroup() );
+        if ( mapNode->getNumParents() > 0 )
+        {
+            insertGroup(caster, mapNode->getParent(0));
+        }
+        else
+        {
+            caster->addChild(mapNode);
+            root = caster;
+        }
+    }
+
     // Loading KML from the command line:
     if ( !kmlFile.empty() )
     {
@@ -696,12 +767,31 @@ MapNodeHelper::parse(MapNode*             mapNode,
         }
     }
 
+    // activity monitor (debugging)
+    if ( showActivity )
+    {
+        VBox* vbox = new VBox();
+        vbox->setBackColor( Color(Color::Black, 0.8) );
+        vbox->setHorizAlign( Control::ALIGN_RIGHT );
+        vbox->setVertAlign( Control::ALIGN_BOTTOM );
+        view->addEventHandler( new ActivityMonitorTool(vbox) );
+        canvas->addControl( vbox );
+    }
+
     // Install an auto clip plane clamper
     if ( useAutoClip )
     {
         mapNode->addCullCallback( new AutoClipPlaneCullCallback(mapNode) );
     }
 
+    // Install logarithmic depth buffer on main camera
+    if ( useLogDepth )
+    {
+        OE_INFO << LC << "Activating logarithmic depth buffer on main camera" << std::endl;
+        osgEarth::Util::LogarithmicDepthBuffer logDepth;
+        logDepth.install( view->getCamera() );
+    }
+
     // Scan for images if necessary.
     if ( !imageFolder.empty() )
     {
@@ -741,13 +831,20 @@ MapNodeHelper::parse(MapNode*             mapNode,
     // Install a detail texturer
     if ( !detailTexConf.empty() )
     {
-        osg::ref_ptr<DetailTexture> effect = new DetailTexture(detailTexConf);
-        if ( effect->getImage() )
+        osg::ref_ptr<DetailTexture> effect = new DetailTexture(detailTexConf, mapNode->getMap());
+        if ( true ) //effect->getImage() )
         {
             mapNode->getTerrainEngine()->addEffect( effect.get() );
         }
     }
 
+    // Install a texture splatter
+    if ( !texSplatConf.empty() )
+    {
+        osg::ref_ptr<TextureSplatter> effect = new TextureSplatter(texSplatConf, mapNode->getMap());
+        mapNode->getTerrainEngine()->addEffect( effect.get() );
+    }
+
     // Install elevation morphing
     if ( !lodBlendingConf.empty() )
     {
@@ -807,6 +904,7 @@ MapNodeHelper::configureView( osgViewer::View* view ) const
     view->addEventHandler(new osgViewer::ThreadingHandler());
     view->addEventHandler(new osgViewer::LODScaleHandler());
     view->addEventHandler(new osgGA::StateSetManipulator(view->getCamera()->getOrCreateStateSet()));
+    view->addEventHandler(new osgViewer::RecordCameraPathHandler());
 }
 
 
@@ -822,9 +920,11 @@ MapNodeHelper::usage() const
         << "  --dd                          : display decimal degrees coords under mouse\n"
         << "  --mgrs                        : show MGRS coords under mouse\n"
         << "  --ortho                       : use an orthographic camera\n"
+        << "  --logdepth                    : activates the logarithmic depth buffer\n"
         << "  --autoclip                    : installs an auto-clip plane callback\n"
         << "  --images [path]               : finds and loads image layers from folder [path]\n"
         << "  --image-extensions [ext,...]  : with --images, extensions to use\n"
         << "  --out-earth [file]            : write the loaded map to an earth file\n"
-        << "  --uniform [name] [min] [max]  : create a uniform controller with min/max values\n";
+        << "  --uniform [name] [min] [max]  : create a uniform controller with min/max values\n"
+        << "  --path [file]                 : load and playback an animation path\n";
 }
diff --git a/src/osgEarthUtil/Export b/src/osgEarthUtil/Export
index d8f6c60..812315f 100644
--- a/src/osgEarthUtil/Export
+++ b/src/osgEarthUtil/Export
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/FeatureManipTool b/src/osgEarthUtil/FeatureManipTool
index 7857b9a..0e27f3d 100644
--- a/src/osgEarthUtil/FeatureManipTool
+++ b/src/osgEarthUtil/FeatureManipTool
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/FeatureManipTool.cpp b/src/osgEarthUtil/FeatureManipTool.cpp
index e6a3698..dafcff1 100644
--- a/src/osgEarthUtil/FeatureManipTool.cpp
+++ b/src/osgEarthUtil/FeatureManipTool.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -23,6 +23,7 @@
 #include <osgEarth/ECEF>
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
+#include <osgEarth/GeoMath>
 #include <osgViewer/View>
 #include <osg/Depth>
 
diff --git a/src/osgEarthUtil/FeatureQueryTool b/src/osgEarthUtil/FeatureQueryTool
index cbb75ea..2a7beef 100644
--- a/src/osgEarthUtil/FeatureQueryTool
+++ b/src/osgEarthUtil/FeatureQueryTool
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/FeatureQueryTool.cpp b/src/osgEarthUtil/FeatureQueryTool.cpp
index ebe5d10..ce48f04 100644
--- a/src/osgEarthUtil/FeatureQueryTool.cpp
+++ b/src/osgEarthUtil/FeatureQueryTool.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -296,25 +296,22 @@ void
 FeatureReadoutCallback::onHit( FeatureSourceIndexNode* index, FeatureID fid, const EventArgs& args )
 {
     clear();
-    if ( index && index->getFeatureSource() )
+    const Feature* f = 0L;
+    if ( index && index->getFeature(fid, f) )
     {
-        Feature* f = index->getFeatureSource()->getFeature( fid );
-        if ( f )
-        {
-            unsigned r=0;
+        unsigned r=0;
 
-            _grid->setControl( 0, r, new LabelControl("FID", Color::Red) );
-            _grid->setControl( 1, r, new LabelControl(Stringify()<<fid, Color::White) );
-            ++r;
+        _grid->setControl( 0, r, new LabelControl("FID", Color::Red) );
+        _grid->setControl( 1, r, new LabelControl(Stringify()<<fid, Color::White) );
+        ++r;
 
-            const AttributeTable& attrs = f->getAttrs();
-            for( AttributeTable::const_iterator i = attrs.begin(); i != attrs.end(); ++i, ++r )
-            {
-                _grid->setControl( 0, r, new LabelControl(i->first, 14.0f, Color::Yellow) );
-                _grid->setControl( 1, r, new LabelControl(i->second.getString(), 14.0f, Color::White) );
-            }
-            _grid->setVisible( true );
+        const AttributeTable& attrs = f->getAttrs();
+        for( AttributeTable::const_iterator i = attrs.begin(); i != attrs.end(); ++i, ++r )
+        {
+            _grid->setControl( 0, r, new LabelControl(i->first, 14.0f, Color::Yellow) );
+            _grid->setControl( 1, r, new LabelControl(i->second.getString(), 14.0f, Color::White) );
         }
+        _grid->setVisible( true );
     }
     args._aa->requestRedraw();
 }
diff --git a/src/osgEarth/AlphaEffect b/src/osgEarthUtil/Fog
similarity index 50%
copy from src/osgEarth/AlphaEffect
copy to src/osgEarthUtil/Fog
index dba3b41..ff50930 100644
--- a/src/osgEarth/AlphaEffect
+++ b/src/osgEarthUtil/Fog
@@ -16,53 +16,57 @@
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
-#ifndef OSGEARTH_ALPHA_EFFECT_H
-#define OSGEARTH_ALPHA_EFFECT_H
+#ifndef OSGEARTHUTIL_FOG_H
+#define OSGEARTHUTIL_FOG_H
 
-#include <osgEarth/Common>
-#include <osg/StateSet>
+#include <osgEarthUtil/Common>
+#include <osgEarth/TerrainEffect>
 #include <osg/Uniform>
+#include <osg/Node>
 #include <osg/observer_ptr>
 
-namespace osgEarth
+
+namespace osgEarth { namespace Util
 {
-    /**
-     * Shader effect that lets you adjust the alpha channel.
+     /**
+     * Utility class for injecting fog capabilities into a VirtualProgram
      */
-    class OSGEARTH_EXPORT AlphaEffect : public osg::Referenced
+    class OSGEARTHUTIL_EXPORT FogEffect : public osg::Referenced
     {
     public:
-        /** constructs a new effect */
-        AlphaEffect();
+        /**
+         * Creates a new FogEffect
+         */         
+        FogEffect();
 
-        /** contructs a new effect and attaches it to a stateset. */
-        AlphaEffect(osg::StateSet* stateset);
+        /**
+         * Creates a new  FogEffect and attaches it to the stateset.
+         */
+        FogEffect(osg::StateSet* stateSet );
 
-    public:
-        /** The alpha channel value [0..1] */
-        void setAlpha(float value);
-        float getAlpha() const;
+        /**
+         * Attaches this FogEffect to the given StateSet
+         */
+        void attach(osg::StateSet* stateSet );
 
-    public:
-        /** attach this effect to a stateset. */
-        void attach(osg::StateSet* stateset);
+        /**
+         * Detatches this FogEffect from the given StateSet
+         */
+        void detach(osg::StateSet* stateSet );
 
-        /** detach this effect from any attached statesets. */
+        /**
+         * Detaches this FogEffect from all attached StateSets
+         */
         void detach();
-        /** detach this effect from a stateset. */
-        void detach(osg::StateSet* stateset);
 
     protected:
-        virtual ~AlphaEffect();
+        ~FogEffect();
 
         typedef std::list< osg::observer_ptr<osg::StateSet> > StateSetList;
-
         StateSetList _statesets;
-        osg::ref_ptr<osg::Uniform>       _alphaUniform;
 
-        void init();
     };
 
-} // namespace osgEarth::Util
+} } // namespace osgEarth::Util
 
-#endif // OSGEARTH_ALPHA_EFFECT_H
+#endif // OSGEARTHUTIL_FOG_H
diff --git a/src/osgEarthUtil/Fog.cpp b/src/osgEarthUtil/Fog.cpp
new file mode 100644
index 0000000..64bdb8f
--- /dev/null
+++ b/src/osgEarthUtil/Fog.cpp
@@ -0,0 +1,98 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2012 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include <osgEarthUtil/Fog>
+#include <osgEarth/Registry>
+#include <osgEarth/Capabilities>
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/TerrainEngineNode>
+
+#define LC "[Fog] "
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+
+namespace
+{
+    const char* vs =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+        "varying float fogFactor;\n"
+
+        "void oe_fog_vertex(inout vec4 VertexVIEW) \n"
+        "{ \n"        
+        "    float z = length( VertexVIEW.xyz );\n"
+        "    const float LOG2 = 1.442695;\n"        
+        "    fogFactor = exp2( -gl_Fog.density * gl_Fog.density * z * z * LOG2 );\n"
+        "    fogFactor = clamp(fogFactor, 0.0, 1.0);\n"
+        "} \n";
+
+    const char* fs =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+        "varying float fogFactor;\n"
+
+        "void oe_fog_frag(inout vec4 color) \n"
+        "{ \n"        
+        "    color.rgb = mix( gl_Fog.color.rgb, color.rgb, fogFactor);\n"
+        "} \n";
+}
+
+
+FogEffect::FogEffect()
+{
+}
+
+FogEffect::~FogEffect()
+{
+    detach();
+}
+
+void FogEffect::attach( osg::StateSet* stateSet )
+{
+    VirtualProgram* vp = VirtualProgram::getOrCreate( stateSet );
+    vp->setFunction( "oe_fog_vertex", vs, ShaderComp::LOCATION_VERTEX_VIEW );
+    vp->setFunction( "oe_fog_frag", fs, ShaderComp::LOCATION_FRAGMENT_LIGHTING );
+    _statesets.push_back(stateSet);
+}
+
+void FogEffect::detach( osg::StateSet* stateSet )
+{
+    VirtualProgram* vp = VirtualProgram::get(stateSet);
+    if ( vp )
+    {
+        vp->removeShader( "oe_fog_vertex" );
+        vp->removeShader( "oe_fog_frag" );
+    }
+}
+
+void FogEffect::detach()
+{
+    for (StateSetList::iterator it = _statesets.begin(); it != _statesets.end(); ++it)
+    {
+        osg::ref_ptr<osg::StateSet> stateset;
+        if ( (*it).lock(stateset) )
+        {
+            detach( stateset );
+            (*it) = 0L;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/osgEarthUtil/Formatter b/src/osgEarthUtil/Formatter
index dc26879..ef2e30e 100644
--- a/src/osgEarthUtil/Formatter
+++ b/src/osgEarthUtil/Formatter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/GLSLColorFilter b/src/osgEarthUtil/GLSLColorFilter
index 4093d24..64f9b32 100644
--- a/src/osgEarthUtil/GLSLColorFilter
+++ b/src/osgEarthUtil/GLSLColorFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -21,7 +21,7 @@
 
 #include <osgEarthUtil/Common>
 #include <osgEarth/ColorFilter>
-#include <osg/Uniform>
+#include <osg/Shader>
 
 namespace osgEarth { namespace Util
 {
@@ -35,18 +35,26 @@ namespace osgEarth { namespace Util
         GLSLColorFilter(const Config& conf);
         virtual ~GLSLColorFilter() { }
 
+        void setType(const osg::Shader::Type& type) { _type = type; }
+        const osg::Shader::Type& getType() const { return _type.get(); }
+
+        void setEntryPointFunctionName(const std::string& name) { _functionName = name; }
+
         void setCode(const std::string& code) { _code = code; }
         const std::string& getCode() const { return _code; }
 
     public: // ColorFilter
-        virtual std::string getEntryPointFunctionName(void) const;
+        virtual std::string getEntryPointFunctionName() const;
         virtual void install(osg::StateSet* stateSet) const;
         virtual Config getConfig() const;
 
     protected:
-        unsigned m_instanceId;
+        unsigned                    _instanceId;
+        optional<osg::Shader::Type> _type;
+        optional<std::string>       _functionName;
+        std::string                 _code;
+        
         void init();
-        std::string _code;
     };
 
 } } // namespace osgEarth::Util
diff --git a/src/osgEarthUtil/GLSLColorFilter.cpp b/src/osgEarthUtil/GLSLColorFilter.cpp
index 9d3bc21..4f79687 100644
--- a/src/osgEarthUtil/GLSLColorFilter.cpp
+++ b/src/osgEarthUtil/GLSLColorFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -55,30 +55,43 @@ GLSLColorFilter::GLSLColorFilter()
 void 
 GLSLColorFilter::init()
 {
-    m_instanceId = (++s_uniformNameGen) - 1;
+    _instanceId = (++s_uniformNameGen) - 1;
+    _type.init( osg::Shader::FRAGMENT );
+    _functionName.init("");
 }
 
 std::string
-GLSLColorFilter::getEntryPointFunctionName(void) const
+GLSLColorFilter::getEntryPointFunctionName() const
 {
-    return (osgEarth::Stringify() << FUNCTION_PREFIX << m_instanceId);
+    if ( _functionName.isSet() )
+        return _functionName.get();
+    else
+        return osgEarth::Stringify() << FUNCTION_PREFIX << _instanceId; // default
 }
 
 void 
 GLSLColorFilter::install(osg::StateSet* stateSet) const
 {
-    osgEarth::VirtualProgram* vp = dynamic_cast<osgEarth::VirtualProgram*>(stateSet->getAttribute(VirtualProgram::SA_TYPE));
+    osgEarth::VirtualProgram* vp = VirtualProgram::getOrCreate(stateSet);
     if (vp)
     {
-        // build the local shader (unique per instance). We will
-        // use a template with search and replace for this one.
-        std::string entryPoint = osgEarth::Stringify() << FUNCTION_PREFIX << m_instanceId;
-        std::string code = s_localShaderSource;
-        osgEarth::replaceIn(code, "__ENTRY_POINT__", entryPoint);
-        osgEarth::replaceIn(code, "__CODE__", _code);
-
-        osg::Shader* main = new osg::Shader(osg::Shader::FRAGMENT, code);
-        vp->setShader(entryPoint, main);
+        if (_functionName.isSet())
+        {
+            osg::Shader* shader = new osg::Shader(_type.value(), _code);
+            vp->setShader( getEntryPointFunctionName(), shader );
+        }
+        else
+        {
+            // build the local shader (unique per instance). We will
+            // use a template with search and replace for this one.
+            std::string entryPoint = getEntryPointFunctionName();
+            std::string code = s_localShaderSource;
+            osgEarth::replaceIn(code, "__ENTRY_POINT__", entryPoint);
+            osgEarth::replaceIn(code, "__CODE__", _code);
+
+            osg::Shader* main = new osg::Shader(_type.value(), code);
+            vp->setShader(entryPoint, main);
+        }
     }
 }
 
@@ -91,6 +104,12 @@ OSGEARTH_REGISTER_COLORFILTER( glsl, osgEarth::Util::GLSLColorFilter );
 GLSLColorFilter::GLSLColorFilter(const Config& conf)
 {
     init();
+    if ( conf.hasValue("function") )
+        _functionName = conf.value("function");
+    if ( conf.value("type").compare("vertex") == 0 )
+        _type = osg::Shader::VERTEX;
+    else if ( conf.value("type").compare("fragment") == 0 )
+        _type = osg::Shader::FRAGMENT;
     setCode( conf.value() );
 }
 
@@ -98,5 +117,8 @@ Config
 GLSLColorFilter::getConfig() const
 {
     Config conf("glsl", getCode());
+    conf.addIfSet( "function", _functionName );
+    conf.addIfSet( "type", "vertex",   _type, osg::Shader::VERTEX );
+    conf.addIfSet( "type", "fragment", _type, osg::Shader::FRAGMENT );
     return conf;
 }
diff --git a/src/osgEarthUtil/GammaColorFilter b/src/osgEarthUtil/GammaColorFilter
index 20d68a9..0c82523 100644
--- a/src/osgEarthUtil/GammaColorFilter
+++ b/src/osgEarthUtil/GammaColorFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/GammaColorFilter.cpp b/src/osgEarthUtil/GammaColorFilter.cpp
index e4362a6..66c7e92 100644
--- a/src/osgEarthUtil/GammaColorFilter.cpp
+++ b/src/osgEarthUtil/GammaColorFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/GeodeticGraticule b/src/osgEarthUtil/GeodeticGraticule
index 33e9eec..4c69eee 100644
--- a/src/osgEarthUtil/GeodeticGraticule
+++ b/src/osgEarthUtil/GeodeticGraticule
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/GeodeticGraticule.cpp b/src/osgEarthUtil/GeodeticGraticule.cpp
index c21c726..0498a67 100644
--- a/src/osgEarthUtil/GeodeticGraticule.cpp
+++ b/src/osgEarthUtil/GeodeticGraticule.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/HSLColorFilter b/src/osgEarthUtil/HSLColorFilter
index 39671c7..c4e4819 100644
--- a/src/osgEarthUtil/HSLColorFilter
+++ b/src/osgEarthUtil/HSLColorFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/HSLColorFilter.cpp b/src/osgEarthUtil/HSLColorFilter.cpp
index 7b5eff0..f11393a 100644
--- a/src/osgEarthUtil/HSLColorFilter.cpp
+++ b/src/osgEarthUtil/HSLColorFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/HTM b/src/osgEarthUtil/HTM
index 345efe0..f7b426d 100644
--- a/src/osgEarthUtil/HTM
+++ b/src/osgEarthUtil/HTM
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/HTM.cpp b/src/osgEarthUtil/HTM.cpp
index 780a38b..cf685d1 100644
--- a/src/osgEarthUtil/HTM.cpp
+++ b/src/osgEarthUtil/HTM.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/LODBlending b/src/osgEarthUtil/LODBlending
index d8fdd87..4fea4c2 100644
--- a/src/osgEarthUtil/LODBlending
+++ b/src/osgEarthUtil/LODBlending
@@ -56,6 +56,14 @@ namespace osgEarth { namespace Util
         void setVerticalScale( float value );
         float getVerticalScale() const { return _vscale.get(); }
 
+        /** Whether to blend imagery (default=true) */
+        void setBlendImagery( bool value );
+        bool getBlendImagery() const { return _blendImagery.get(); }
+
+        /** Whether to blend elevation (default=true) */
+        void setBlendElevation( bool value );
+        bool getBlendElevation() const { return _blendElevation.get(); }
+
     public: // TerrainEffect interface
 
         void onInstall(TerrainEngineNode* engine);
@@ -74,6 +82,8 @@ namespace osgEarth { namespace Util
         optional<float>              _delay;
         optional<float>              _duration;
         optional<float>              _vscale;
+        optional<bool>               _blendImagery;
+        optional<bool>               _blendElevation;
         osg::ref_ptr<osg::Uniform>   _delayUniform;
         osg::ref_ptr<osg::Uniform>   _durationUniform;
         osg::ref_ptr<osg::Uniform>   _vscaleUniform;
diff --git a/src/osgEarthUtil/LODBlending.cpp b/src/osgEarthUtil/LODBlending.cpp
index ded1b5b..5d01b97 100644
--- a/src/osgEarthUtil/LODBlending.cpp
+++ b/src/osgEarthUtil/LODBlending.cpp
@@ -46,7 +46,75 @@ namespace
     // a large PAGEDLOD cache will negate the blending effect when zooming out
     // and then back in. See MPGeometry.
 
-    const char* vs =
+    const char* vs_imagery =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+        "uniform float oe_min_tile_range_factor; \n"
+        "uniform vec4 oe_tile_key; \n"
+        "uniform float osg_FrameTime; \n"
+        "uniform float oe_tile_birthtime; \n"
+        "uniform float oe_lodblend_delay; \n"
+        "uniform float oe_lodblend_duration; \n"
+
+        "uniform mat4 oe_layer_parent_matrix; \n"
+        "varying vec4 oe_layer_texc; \n"
+        "varying vec4 oe_lodblend_texc; \n"
+        "varying float oe_lodblend_r; \n"
+
+        "void oe_lodblend_imagery_vertex(inout vec4 VertexVIEW) \n"
+        "{ \n"
+        "    float radius     = oe_tile_key.w; \n"
+        "    float near       = oe_min_tile_range_factor*radius; \n"
+        "    float far        = near + radius*2.0; \n"
+        "    float d          = length(VertexVIEW.xyz/VertexVIEW.w); \n"
+        "    float r_dist     = clamp((d-near)/(far-near), 0.0, 1.0); \n"
+
+        "    float r_time     = 1.0 - clamp(osg_FrameTime-(oe_tile_birthtime+oe_lodblend_delay), 0.0, oe_lodblend_duration)/oe_lodblend_duration; \n"
+        "    float r          = max(r_dist, r_time); \n"
+
+        "    oe_lodblend_texc = oe_layer_parent_matrix * oe_layer_texc; \n"
+        "    oe_lodblend_r    = oe_layer_parent_matrix[0][0] > 0.0 ? r : 0.0; \n" // obe?
+        "} \n";
+
+    const char* vs_elevation =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+        "attribute vec4 oe_terrain_attr; \n"
+        "attribute vec4 oe_terrain_attr2; \n"
+        "varying vec3 oe_Normal; \n"
+
+        "uniform float oe_min_tile_range_factor; \n"
+        "uniform vec4 oe_tile_key; \n"
+        "uniform float osg_FrameTime; \n"
+        "uniform float oe_tile_birthtime; \n"
+        "uniform float oe_lodblend_delay; \n"
+        "uniform float oe_lodblend_duration; \n"
+        "uniform float oe_lodblend_vscale; \n"
+
+        "void oe_lodblend_elevation_vertex(inout vec4 VertexMODEL) \n"
+        "{ \n"
+        "    float radius     = oe_tile_key.w; \n"
+        "    float near       = oe_min_tile_range_factor*radius; \n"
+        "    float far        = near + radius*2.0; \n"
+        "    vec4  VertexVIEW = gl_ModelViewMatrix * VertexMODEL; \n"
+        "    float d          = length(VertexVIEW.xyz/VertexVIEW.w); \n"
+        "    float r_dist     = clamp((d-near)/(far-near), 0.0, 1.0); \n"
+
+        "    float r_time     = 1.0 - clamp(osg_FrameTime-(oe_tile_birthtime+oe_lodblend_delay), 0.0, oe_lodblend_duration)/oe_lodblend_duration; \n"
+        "    float r          = max(r_dist, r_time); \n"
+
+        "    vec3  upVector   = oe_terrain_attr.xyz; \n"
+        "    float elev       = oe_terrain_attr.w; \n"
+        "    float elevOld    = oe_terrain_attr2.w; \n"
+
+        "    vec3  vscaleOffset = upVector * elev * (oe_lodblend_vscale-1.0); \n"
+        "    vec3  blendOffset  = upVector * r * oe_lodblend_vscale * (elevOld-elev); \n"
+        "    VertexMODEL       += vec4( (vscaleOffset + blendOffset)*VertexMODEL.w, 0.0 ); \n"
+        "} \n";
+
+    const char* vs_combined =
         "#version " GLSL_VERSION_STR "\n"
         GLSL_DEFAULT_PRECISION_FLOAT "\n"
 
@@ -67,7 +135,7 @@ namespace
         "varying vec4 oe_lodblend_texc; \n"
         "varying float oe_lodblend_r; \n"
 
-        "void oe_lodblend_vertex(inout vec4 VertexMODEL) \n"
+        "void oe_lodblend_combined_vertex(inout vec4 VertexMODEL) \n"
         "{ \n"
         "    float radius     = oe_tile_key.w; \n"
         "    float near       = oe_min_tile_range_factor*radius; \n"
@@ -87,13 +155,13 @@ namespace
         "    vec3  blendOffset  = upVector * r * oe_lodblend_vscale * (elevOld-elev); \n"
         "    VertexMODEL       += vec4( (vscaleOffset + blendOffset)*VertexMODEL.w, 0.0 ); \n"
 
-        "    oe_lodblend_texc    = oe_layer_parent_matrix * oe_layer_texc; \n"
-        "    oe_lodblend_r       = oe_layer_parent_matrix[0][0] > 0.0 ? r : 0.0; \n" // obe?
+        "    oe_lodblend_texc = oe_layer_parent_matrix * oe_layer_texc; \n"
+        "    oe_lodblend_r    = oe_layer_parent_matrix[0][0] > 0.0 ? r : 0.0; \n" // obe?
 
         "    oe_Normal = normalize(mix(normalize(oe_Normal), oe_terrain_attr2.xyz, r)); \n"
         "} \n";
 
-    const char* fs =
+    const char* fs_imagery =
         "#version " GLSL_VERSION_STR "\n"
         GLSL_DEFAULT_PRECISION_FLOAT "\n"
 
@@ -103,12 +171,12 @@ namespace
         "varying float oe_lodblend_r; \n"
         "uniform sampler2D oe_layer_tex_parent; \n"
 
-        "void oe_lodblend_fragment(inout vec4 color) \n"
+        "void oe_lodblend_imagery_fragment(inout vec4 color) \n"
         "{ \n"
         "    if ( oe_layer_uid >= 0 ) \n"
         "    { \n"
         "        vec4 texel = texture2D(oe_layer_tex_parent, oe_lodblend_texc.st); \n"
-        "        float enable = step(0.0001, texel.a); \n"          // did we get a parent texel?
+        "        float enable = step(0.09, texel.a); \n"          // did we get a parent texel?
         "        texel.rgb = mix(color.rgb, texel.rgb, enable); \n" // if not, use the incoming color for the blend
         "        texel.a = mix(0.0, color.a, enable); \n"           // ...and blend from alpha=0 for a fade-in effect.
         "        color = mix(color, texel, oe_lodblend_r); \n"
@@ -119,9 +187,11 @@ namespace
 
 LODBlending::LODBlending() :
 TerrainEffect(),
-_delay       ( 0.0f ),
-_duration    ( 0.25f ),
-_vscale      ( 1.0f )
+_delay         ( 0.0f ),
+_duration      ( 0.25f ),
+_vscale        ( 1.0f ),
+_blendImagery  ( true ),
+_blendElevation( true )
 {
     init();
 }
@@ -129,9 +199,11 @@ _vscale      ( 1.0f )
 
 LODBlending::LODBlending(const Config& conf) :
 TerrainEffect(),
-_delay       ( 0.0f ),
-_duration    ( 0.25f ),
-_vscale      ( 1.0f )
+_delay         ( 0.0f ),
+_duration      ( 0.25f ),
+_vscale        ( 1.0f ),
+_blendImagery  ( true ),
+_blendElevation( true )
 {
     mergeConfig(conf);
     init();
@@ -184,6 +256,23 @@ LODBlending::setVerticalScale(float vscale)
     }
 }
 
+void
+LODBlending::setBlendImagery(bool value)
+{
+   if ( value != _blendImagery.get() )
+   {
+      _blendImagery = value;
+   }
+}
+
+void
+LODBlending::setBlendElevation(bool value)
+{
+   if ( value != _blendElevation.get() )
+   {
+      _blendElevation = value;
+   }
+}
 
 void
 LODBlending::onInstall(TerrainEngineNode* engine)
@@ -198,8 +287,17 @@ LODBlending::onInstall(TerrainEngineNode* engine)
 
         VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
         vp->setName( "osgEarth::Util::LODBlending" );
-        vp->setFunction( "oe_lodblend_vertex",   vs, ShaderComp::LOCATION_VERTEX_MODEL );
-        vp->setFunction( "oe_lodblend_fragment", fs, ShaderComp::LOCATION_FRAGMENT_COLORING );
+
+        if ( _blendElevation == true )
+        {
+            vp->setFunction("oe_lodblend_elevation_vertex", vs_elevation, ShaderComp::LOCATION_VERTEX_MODEL );
+        }
+
+        if ( _blendImagery == true )
+        {
+            vp->setFunction("oe_lodblend_imagery_vertex", vs_imagery, ShaderComp::LOCATION_VERTEX_VIEW);
+            vp->setFunction("oe_lodblend_imagery_fragment", fs_imagery, ShaderComp::LOCATION_FRAGMENT_COLORING);
+        }
     }
 }
 
@@ -219,8 +317,9 @@ LODBlending::onUninstall(TerrainEngineNode* engine)
             VirtualProgram* vp = VirtualProgram::get(stateset);
             if ( vp )
             {
-                vp->removeShader( "oe_lodblend_vertex" );
-                vp->removeShader( "oe_lodblend_fragment" );
+                vp->removeShader( "oe_lodblend_imagery_vertex" );
+                vp->removeShader( "oe_lodblend_elevation_vertex" );
+                vp->removeShader( "oe_lodblend_imagery_fragment" );
             }
         }
     }
@@ -236,6 +335,8 @@ LODBlending::mergeConfig(const Config& conf)
     conf.getIfSet( "delay",    _delay );
     conf.getIfSet( "duration", _duration );
     conf.getIfSet( "vertical_scale", _vscale );
+    conf.getIfSet( "blend_imagery",  _blendImagery );
+    conf.getIfSet( "blend_elevation", _blendElevation );
 }
 
 Config
@@ -245,5 +346,7 @@ LODBlending::getConfig() const
     conf.addIfSet( "delay",    _delay );
     conf.addIfSet( "duration", _duration );
     conf.addIfSet( "vertical_scale", _vscale );
+    conf.addIfSet( "blend_imagery",  _blendImagery );
+    conf.addIfSet( "blend_elevation", _blendElevation );
     return conf;
 }
diff --git a/src/osgEarthUtil/LatLongFormatter b/src/osgEarthUtil/LatLongFormatter
index 3e0f4eb..dcfc682 100644
--- a/src/osgEarthUtil/LatLongFormatter
+++ b/src/osgEarthUtil/LatLongFormatter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/LatLongFormatter.cpp b/src/osgEarthUtil/LatLongFormatter.cpp
index b250ca3..587b775 100644
--- a/src/osgEarthUtil/LatLongFormatter.cpp
+++ b/src/osgEarthUtil/LatLongFormatter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -131,6 +131,8 @@ LatLongFormatter::format( const Angular& angle, int precision, const AngularForm
     return result;
 }
 
+#define SIGN(x) (x>=0.0?1.0:-1.0)
+
 bool
 LatLongFormatter::parseAngle( const std::string& input, Angular& out_value )
 {
@@ -145,7 +147,7 @@ LatLongFormatter::parseAngle( const std::string& input, Angular& out_value )
         sscanf(c, "%lfd %lfm %lfs",  &d, &m, &s) == 3 ||
         sscanf(c, "%lf %lf' %lf\"",  &d, &m, &s) == 3 )
     {
-        out_value.set( d + m/60.0 + s/3600.0, Units::DEGREES );
+        out_value.set( SIGN(d) * (fabs(d) + m/60.0 + s/3600.0), Units::DEGREES );
         return true;
     }
     else if (
@@ -157,7 +159,7 @@ LatLongFormatter::parseAngle( const std::string& input, Angular& out_value )
         sscanf(c, "%lfd%lf'",  &d, &m) == 2 ||
         sscanf(c, "%lf %lf'",  &d, &m) == 2 )
     {
-        out_value.set( d + m/60.0, Units::DEGREES );
+        out_value.set( SIGN(d) * (fabs(d) + m/60.0), Units::DEGREES );
         return true;
     }
     else if (
diff --git a/src/osgEarthUtil/LineOfSight b/src/osgEarthUtil/LineOfSight
index 7fc9659..279c1ea 100644
--- a/src/osgEarthUtil/LineOfSight
+++ b/src/osgEarthUtil/LineOfSight
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/LinearLineOfSight b/src/osgEarthUtil/LinearLineOfSight
index 4c7f9eb..ab119cc 100644
--- a/src/osgEarthUtil/LinearLineOfSight
+++ b/src/osgEarthUtil/LinearLineOfSight
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/LinearLineOfSight.cpp b/src/osgEarthUtil/LinearLineOfSight.cpp
index 09c13fc..af16214 100644
--- a/src/osgEarthUtil/LinearLineOfSight.cpp
+++ b/src/osgEarthUtil/LinearLineOfSight.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/LogarithmicDepthBuffer b/src/osgEarthUtil/LogarithmicDepthBuffer
new file mode 100644
index 0000000..8727efd
--- /dev/null
+++ b/src/osgEarthUtil/LogarithmicDepthBuffer
@@ -0,0 +1,62 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTH_UTIL_LOG_DEPTH_BUFFER_H
+#define OSGEARTH_UTIL_LOG_DEPTH_BUFFER_H  1
+
+#include <osgEarthUtil/Common>
+#include <osg/Camera>
+
+namespace osgEarth { namespace Util 
+{
+    /**
+     * Installs and controls a logarithmic depth buffer that improves
+     * rendering of close and far objects in the same view without
+     * z-fighting artifacts.
+     *
+     * Note: If you have any RTT cameras that deal with depth data,
+     * they need to use a log buffer as well! (e.g., ClampingTechnique)
+     *
+     * Another Note: For this to work properly, sufficient tessellation
+     * of objects very close to the camera is necessary. Huge polygons
+     * that intersect the near plane are likely to be clipped in their
+     * entirely. Increasing the tessellation can resolve that issue.
+     */
+    class OSGEARTHUTIL_EXPORT LogarithmicDepthBuffer
+    {
+    public:
+        /** Constructs a logarithmic depth buffer controller. */
+        LogarithmicDepthBuffer();
+
+        /** is it supported on this platform? */
+        bool supported() const { return _supported; }
+
+        /** Installs a logarithmic depth buffer on a camera. */
+        void install(osg::Camera* camera);
+
+        /** Uninstalls a logarithmic depth buffer from a camera. */
+        void uninstall(osg::Camera* camera);
+
+    protected:
+        osg::ref_ptr<osg::NodeCallback> _cullCallback;
+        bool _supported;
+    };
+
+} } // namespace osgEarth::Util
+
+#endif // OSGEARTH_UTIL_LOG_DEPTH_BUFFER_H
diff --git a/src/osgEarthUtil/LogarithmicDepthBuffer.cpp b/src/osgEarthUtil/LogarithmicDepthBuffer.cpp
new file mode 100644
index 0000000..3c67e61
--- /dev/null
+++ b/src/osgEarthUtil/LogarithmicDepthBuffer.cpp
@@ -0,0 +1,175 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include <osgEarthUtil/LogarithmicDepthBuffer>
+#include <osgEarth/CullingUtils>
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/Registry>
+#include <osgEarth/Capabilities>
+#include <osgUtil/CullVisitor>
+#include <osg/Uniform>
+#include <osg/buffered_value>
+
+#define LC "[LogarithmicDepthBuffer] "
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+//------------------------------------------------------------------------
+
+namespace
+{
+    struct LogDepthCullCallback : public osg::NodeCallback
+    {
+        void operator()(osg::Node* node, osg::NodeVisitor* nv)
+        {
+            osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
+            osg::Camera* camera = cv->getCurrentCamera();
+            if ( camera )
+            {
+                // find (or create) a stateset
+                osg::StateSet* stateset = 0L;
+                osg::ref_ptr<osg::StateSet> refStateSet;
+
+                osg::GraphicsContext* gc = camera->getGraphicsContext();
+                if ( gc )
+                {
+                    // faster method of re-using statesets when a GC is present
+                    unsigned id = gc->getState()->getContextID();
+                    refStateSet = _stateSets[id];
+                    if ( !refStateSet.valid() )
+                        refStateSet = new osg::StateSet();
+                    stateset = refStateSet.get();
+                }
+                else
+                {
+                    // no GC is present (e.g., RTT camera) so just make a fresh one
+                    refStateSet = new osg::StateSet();
+                    stateset = refStateSet.get();
+                }
+
+                // the uniform conveying the far clip plane:
+                osg::Uniform* u = stateset->getOrCreateUniform("oe_ldb_far", osg::Uniform::FLOAT);
+
+                // calculate the far plane based on the camera location:
+                osg::Vec3d E, C, U;
+                camera->getViewMatrixAsLookAt(E, C, U);                
+                double farplane = E.length() + 1e6;
+                const double nearplane = 1.0;
+                
+                // set for culling purposes:
+                double L, R, B, T, N, F;
+                camera->getProjectionMatrixAsFrustum(L, R, B, T, N, F);                
+                camera->setProjectionMatrixAsFrustum(L, R, B, T, N, farplane);
+
+                // communicate to the shader:
+                u->set( (float)farplane );
+
+                // and continue traversal of the camera's subgraph.
+                cv->pushStateSet( stateset );
+                traverse(node, nv);
+                cv->popStateSet();
+            }
+            else
+            {                    
+                traverse(node, nv);
+            }
+        }
+
+        // context-specific stateset collection
+        osg::buffered_value<osg::ref_ptr<osg::StateSet> > _stateSets;
+    };
+
+    const char* vertSource =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+        "uniform float oe_ldb_far; \n"
+        "varying float logz; \n"
+        "void oe_ldb_vert(inout vec4 clip) \n"
+        "{ \n"
+        "    const float C = 0.0005; \n"
+        "    float FC = 1.0/log2(oe_ldb_far*C + 1.0); \n"
+        "    logz = log2(clip.w*C + 1.0)*FC; \n"
+        "    clip.z = (2.0*logz - 1.0)*clip.w; \n"
+        "} \n";
+
+    const char* fragSource =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+        "varying float logz; \n"
+        "void oe_ldb_frag(inout vec4 clip) \n"
+        "{\n"
+        "    gl_FragDepth = logz; \n"
+        "}\n";
+}
+
+//------------------------------------------------------------------------
+
+LogarithmicDepthBuffer::LogarithmicDepthBuffer()
+{
+    _supported = Registry::capabilities().supportsGLSL();
+    if ( _supported )
+    {
+        _cullCallback = new LogDepthCullCallback();
+    }
+    else
+    {
+        OE_WARN << LC << "Not supported on this platform (no GLSL)" << std::endl;
+    }
+}
+
+void
+LogarithmicDepthBuffer::install(osg::Camera* camera)
+{
+    if ( camera && _supported )
+    {
+        // install the shader component:
+        osg::StateSet* stateset = camera->getOrCreateStateSet();
+        
+        VirtualProgram* vp = VirtualProgram::getOrCreate( stateset );
+        vp->setFunction( "oe_ldb_vert", vertSource, ShaderComp::LOCATION_VERTEX_CLIP, FLT_MAX );
+        vp->setFunction( "oe_ldb_frag", fragSource, ShaderComp::LOCATION_FRAGMENT_LIGHTING, FLT_MAX );
+
+        // configure the camera:
+        camera->setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR);
+
+        // install a cull callback to control the far plane:
+        camera->addCullCallback( _cullCallback.get() );
+    }
+}
+
+void
+LogarithmicDepthBuffer::uninstall(osg::Camera* camera)
+{
+    if ( camera && _supported )
+    {
+        camera->removeCullCallback( _cullCallback.get() );
+
+        osg::StateSet* stateset = camera->getStateSet();
+        if ( stateset )
+        {
+            VirtualProgram* vp = VirtualProgram::get( camera->getStateSet() );
+            if ( vp )
+            {
+                vp->removeShader( "oe_ldb_vert" );
+            }
+
+            stateset->removeUniform( "oe_ldb_far" );
+        }
+    }
+}
diff --git a/src/osgEarthUtil/MGRSFormatter b/src/osgEarthUtil/MGRSFormatter
index 9003f52..0e2c952 100644
--- a/src/osgEarthUtil/MGRSFormatter
+++ b/src/osgEarthUtil/MGRSFormatter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/MGRSFormatter.cpp b/src/osgEarthUtil/MGRSFormatter.cpp
index da25034..31c7510 100644
--- a/src/osgEarthUtil/MGRSFormatter.cpp
+++ b/src/osgEarthUtil/MGRSFormatter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/MGRSGraticule b/src/osgEarthUtil/MGRSGraticule
index ee2113d..d6b658f 100644
--- a/src/osgEarthUtil/MGRSGraticule
+++ b/src/osgEarthUtil/MGRSGraticule
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/MGRSGraticule.cpp b/src/osgEarthUtil/MGRSGraticule.cpp
index a184473..2115fbd 100644
--- a/src/osgEarthUtil/MGRSGraticule.cpp
+++ b/src/osgEarthUtil/MGRSGraticule.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -24,6 +24,7 @@
 
 #include <osgEarth/ECEF>
 #include <osgEarth/DepthOffset>
+#include <osgEarth/Registry>
 
 #include <osg/BlendFunc>
 #include <osg/PagedLOD>
@@ -124,24 +125,25 @@ MGRSGraticule::buildSQIDTiles( const std::string& gzd )
     char letter;
     sscanf( gzd.c_str(), "%u%c", &zone, &letter );
     
-    TextSymbol* textSym = _options->secondaryStyle()->get<TextSymbol>();
-    if ( !textSym )
-        textSym = _options->primaryStyle()->getOrCreate<TextSymbol>();
+    const TextSymbol* textSymFromOptions = _options->secondaryStyle()->get<TextSymbol>();
+    if ( !textSymFromOptions )
+        textSymFromOptions = _options->primaryStyle()->get<TextSymbol>();
+
+    // copy it since we intend to alter it
+    osg::ref_ptr<TextSymbol> textSym = 
+        textSymFromOptions ? new TextSymbol(*textSymFromOptions) :
+        new TextSymbol();
 
-    AltitudeSymbol* alt = _options->secondaryStyle()->get<AltitudeSymbol>();
     double h = 0.0;
 
     TextSymbolizer ts( textSym );
     MGRSFormatter mgrs(MGRSFormatter::PRECISION_100000M);
     osg::Geode* textGeode = new osg::Geode();
-    textGeode->getOrCreateStateSet()->setRenderBinDetails( 9999, "DepthSortedBin" );    
-    textGeode->getOrCreateStateSet()->setAttributeAndModes( _depthAttribute, 1 );
 
     const SpatialReference* ecefSRS = extent.getSRS()->getECEF();
     osg::Vec3d centerMap, centerECEF;
     extent.getCentroid(centerMap.x(), centerMap.y());
     extent.getSRS()->transform(centerMap, ecefSRS, centerECEF);
-    //extent.getSRS()->transformToECEF(centerMap, centerECEF);
 
     osg::Matrix local2world;
     ecefSRS->createLocalToWorld( centerECEF, local2world ); //= ECEF::createLocalToWorld(centerECEF);
@@ -250,7 +252,6 @@ MGRSGraticule::buildSQIDTiles( const std::string& gzd )
                 sqidTextMap.z() += 1000.0;
                 osg::Vec3d sqidTextECEF;
                 extent.getSRS()->transform(sqidTextMap, ecefSRS, sqidTextECEF);
-                //extent.getSRS()->transformToECEF(sqidTextMap, sqidTextECEF);
                 osg::Vec3d sqidLocal;
                 sqidLocal = sqidTextECEF * world2local;
 
@@ -321,7 +322,6 @@ MGRSGraticule::buildSQIDTiles( const std::string& gzd )
                         sqidTextMap.z() += 1000.0;
                         osg::Vec3d sqidTextECEF;
                         extent.getSRS()->transform(sqidTextMap, ecefSRS, sqidTextECEF);
-                        //extent.getSRS()->transformToECEF(sqidTextMap, sqidTextECEF);
                         osg::Vec3d sqidLocal = sqidTextECEF * world2local;
 
                         MGRSCoord mgrsCoord;
@@ -528,7 +528,6 @@ MGRSGraticule::buildSQIDTiles( const std::string& gzd )
 
     Style lineStyle;
     lineStyle.add( _options->secondaryStyle()->get<LineSymbol>() );
-    lineStyle.add( _options->secondaryStyle()->get<AltitudeSymbol>() );
 
     GeometryCompiler compiler;
     osg::ref_ptr<Session> session = new Session( getMapNode()->getMap() );
@@ -545,6 +544,8 @@ MGRSGraticule::buildSQIDTiles( const std::string& gzd )
     mt->addChild(textGeode);
     group->addChild( mt );
 
+    Registry::shaderGenerator().run(textGeode, Registry::stateSetCache());
+
     // prep for depth offset:
     //DepthOffsetUtils::prepareGraph( group );
     //group->getOrCreateStateSet()->addUniform( _minDepthOffset.get() );
diff --git a/src/osgEarthUtil/MeasureTool b/src/osgEarthUtil/MeasureTool
index dd0c54d..f030aa4 100644
--- a/src/osgEarthUtil/MeasureTool
+++ b/src/osgEarthUtil/MeasureTool
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/MeasureTool.cpp b/src/osgEarthUtil/MeasureTool.cpp
index 2a9c3b0..8d8ca4f 100644
--- a/src/osgEarthUtil/MeasureTool.cpp
+++ b/src/osgEarthUtil/MeasureTool.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/MouseCoordsTool b/src/osgEarthUtil/MouseCoordsTool
index 1a826d7..f0295e3 100644
--- a/src/osgEarthUtil/MouseCoordsTool
+++ b/src/osgEarthUtil/MouseCoordsTool
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/MouseCoordsTool.cpp b/src/osgEarthUtil/MouseCoordsTool.cpp
index 52ceb93..826bb65 100644
--- a/src/osgEarthUtil/MouseCoordsTool.cpp
+++ b/src/osgEarthUtil/MouseCoordsTool.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -76,12 +76,14 @@ MouseCoordsLabelCallback::MouseCoordsLabelCallback( LabelControl* label, Formatt
 _label    ( label ),
 _formatter( formatter )
 {
+#if 0
     if ( !formatter )
     {
         LatLongFormatter* formatter = new LatLongFormatter( LatLongFormatter::FORMAT_DECIMAL_DEGREES );
         formatter->setPrecision( 5 );
         _formatter = formatter;
     }
+#endif
 }
 
 void
@@ -89,9 +91,20 @@ MouseCoordsLabelCallback::set( const GeoPoint& mapCoords, osg::View* view, MapNo
 {
     if ( _label.valid() )
     {
-        _label->setText( Stringify()
-            <<  _formatter->format( mapCoords )
-            << ", " << mapCoords.z() );
+        if ( _formatter )
+        {
+            _label->setText( Stringify()
+                <<  _formatter->format( mapCoords )
+                << ", " << mapCoords.z() );
+        }
+        else
+        {
+            _label->setText( Stringify()
+                << std::fixed
+                << mapCoords.x()
+                << ", " << mapCoords.y()
+                << ", " << mapCoords.z() );
+        }
     }
 }
 
diff --git a/src/osgEarthUtil/Ocean b/src/osgEarthUtil/Ocean
new file mode 100644
index 0000000..8ea0d93
--- /dev/null
+++ b/src/osgEarthUtil/Ocean
@@ -0,0 +1,123 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTHUTIL_OCEAN
+#define OSGEARTHUTIL_OCEAN
+
+#include <osgEarthUtil/Common>
+#include <osgEarth/DateTime>
+#include <osgEarth/Config>
+#include <osgEarth/SpatialReference>
+#include <osg/Group>
+#include <osg/View>
+#include <osgDB/ReaderWriter>
+
+namespace osgEarth {
+    class MapNode;
+}
+namespace osgDB {
+    class Options;
+}
+
+namespace osgEarth { namespace Util 
+{
+    using namespace osgEarth;
+
+    /**
+     * Base Options structure for loading an Ocean node from a plugin.
+     */
+    class OSGEARTHUTIL_EXPORT OceanOptions : public DriverConfigOptions
+    {
+    public:
+        OceanOptions( const ConfigOptions& options =ConfigOptions() );
+        virtual ~OceanOptions() { }
+        virtual Config getConfig() const;
+
+        /** Maximum altitude at which the ocean is visible. */
+        optional<float>& maxAltitude() { return _maxAltitude; }
+        const optional<float>& maxAltitude() const { return _maxAltitude; }
+
+    protected:
+        virtual void mergeConfig( const Config& conf );
+        
+    private:
+        void fromConfig( const Config& conf );
+
+        optional<float> _maxAltitude;
+    };
+
+
+    /**
+    * Interface for a Node that renders an ocean surface.
+    */
+    class OSGEARTHUTIL_EXPORT OceanNode : public osg::Group
+    {
+    public:
+        static OceanNode* create(
+            MapNode* map);
+
+        static OceanNode* create(
+            const OceanOptions&  options,
+            MapNode*             map );
+
+    protected:
+        // CTOR (abstract base class)
+        OceanNode(const OceanOptions& options);
+
+        // SRS for this ocean. Impl class should call this.
+        void setSRS(const SpatialReference* srs) { _srs = srs; }
+        const SpatialReference* getSRS() { return _srs.get(); }
+
+        // protected DTOR (heap-only)
+        virtual ~OceanNode();
+
+    public:
+
+        /** Sets the sea level, as an offset from the ellipsoid (meter) */
+        void setSeaLevel(float offsetMeters);
+        float getSeaLevel() const { return _seaLevel; }
+
+    public: // osg::Group
+
+        virtual void traverse(osg::NodeVisitor&);
+
+    protected: // impl class can override these to detect changes
+
+        virtual void onSetSeaLevel() { }
+
+    private:
+
+        float _seaLevel;
+        osg::ref_ptr<const SpatialReference> _srs;
+        const OceanOptions _options;
+    };
+
+
+    /**
+     * Base class for an ocean driver plugin implementation.
+     */
+    class OSGEARTHUTIL_EXPORT OceanDriver : public osgDB::ReaderWriter
+    {
+    protected:
+        MapNode* getMapNode(const osgDB::Options* opt) const;
+        const OceanOptions& getOceanOptions(const osgDB::Options* opt) const;
+    };
+
+} } // namespace osgEarth::Util
+
+#endif //OSGEARTHUTIL_OCEAN
diff --git a/src/osgEarthUtil/Ocean.cpp b/src/osgEarthUtil/Ocean.cpp
new file mode 100644
index 0000000..83f30ce
--- /dev/null
+++ b/src/osgEarthUtil/Ocean.cpp
@@ -0,0 +1,196 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include <osgEarthUtil/Ocean>
+#include <osgEarth/Registry>
+#include <osgEarth/CullingUtils>
+#include <osgEarth/MapNode>
+#include <osgDB/ReadFile>
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+#undef  LC
+#define LC "[Ocean] "
+
+//------------------------------------------------------------------------
+
+OceanOptions::OceanOptions(const ConfigOptions& options) :
+DriverConfigOptions( options ),
+_maxAltitude       ( 250000.0 )
+{
+    fromConfig(_conf);
+}
+
+void
+OceanOptions::fromConfig( const Config& conf )
+{
+    conf.getIfSet( "max_altitude", _maxAltitude );
+}
+
+void
+OceanOptions::mergeConfig( const Config& conf )
+{
+    DriverConfigOptions::mergeConfig( conf );
+    fromConfig( conf );
+}
+
+Config
+OceanOptions::getConfig() const
+{
+    Config conf = DriverConfigOptions::getConfig();
+    conf.addIfSet( "max_altitude", _maxAltitude );
+    return conf;
+}
+
+//------------------------------------------------------------------------
+
+#undef  LC
+#define LC "[OceanNode] "
+
+OceanNode::OceanNode(const OceanOptions& options) :
+_options ( options ),
+_seaLevel( 0.0f )
+{
+    //NOP
+}
+
+OceanNode::~OceanNode()
+{
+    //nop
+}
+
+void
+OceanNode::setSeaLevel(float value)
+{
+    _seaLevel = value;
+    onSetSeaLevel();
+}
+
+void
+OceanNode::traverse(osg::NodeVisitor& nv)
+{
+    if ( nv.getVisitorType() == nv.CULL_VISITOR && _srs.valid() )
+    {
+        osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
+        if ( cv->getCurrentCamera() )
+        {
+            // find the current altitude:
+            osg::Vec3d eye = osg::Vec3d(0,0,0) * cv->getCurrentCamera()->getInverseViewMatrix();
+            osg::Vec3d local;
+            double altitude;
+            _srs->transformFromWorld(eye, local, &altitude);
+
+            // check against max altitude:
+            if ( _options.maxAltitude().isSet() && altitude > *_options.maxAltitude() )
+                return;
+            
+            // Set the near clip plane to account for an ocean sphere.
+            // First, adjust for the sea level offset:
+            altitude -= (double)getSeaLevel();
+
+            // clamp the absolute value so it will work above or below sea level
+            // and so we don't attempt to set the near clip below 1:
+            altitude = std::max( ::fabs(altitude), 1.0 );
+
+            // we don't want the ocean participating in the N/F calculation:
+            osg::CullSettings::ComputeNearFarMode mode = cv->getComputeNearFarMode();
+            cv->setComputeNearFarMode( osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR );
+
+            // visit the ocean:
+            osg::Group::traverse( nv );
+
+            cv->setComputeNearFarMode( mode );
+
+            // just use the height above (or below) the ocean as the near clip
+            // plane distance. Close enough and errs on the safe side.
+            double oldNear = cv->getCalculatedNearPlane();
+
+            double newNear = std::min( oldNear, altitude );
+            if ( newNear < oldNear )
+            {
+                cv->setCalculatedNearPlane( newNear );
+            }
+
+            return;
+        }
+    }
+    osg::Group::traverse( nv );
+}
+
+//------------------------------------------------------------------------
+
+#define MAPNODE_TAG "__osgEarth::MapNode"
+#define OPTIONS_TAG "__osgEarth::Util::OceanOptions"
+
+OceanNode*
+OceanNode::create(const OceanOptions& options,
+                  MapNode*            mapNode)
+{
+    OceanNode* result = 0L;
+
+    std::string driver = options.getDriver();
+    if ( driver.empty() )
+    {
+        OE_INFO << LC << "No driver in options; defaulting to \"simple\"." << std::endl;
+        OE_INFO << LC << options.getConfig().toJSON(true) << std::endl;
+        driver = "simple";
+    }
+
+    std::string driverExt = std::string(".osgearth_ocean_") + driver;
+
+    osg::ref_ptr<osgDB::Options> rwopts = Registry::instance()->cloneOrCreateOptions();
+    rwopts->setPluginData( MAPNODE_TAG, (void*)mapNode );
+    rwopts->setPluginData( OPTIONS_TAG, (void*)&options );
+
+    result = dynamic_cast<OceanNode*>( osgDB::readNodeFile( driverExt, rwopts.get() ) );
+    if ( result )
+    {
+        OE_INFO << LC << "Loaded ocean driver \"" << driver << "\" OK." << std::endl;
+    }
+    else
+    {
+        OE_WARN << LC << "FAIL, unable to load ocean driver \"" << driver << "\"" << std::endl;
+    }
+
+    return result;
+}
+
+OceanNode*
+OceanNode::create(MapNode* mapNode)
+{
+    OceanOptions options;
+    return create(options, mapNode);
+}
+
+//------------------------------------------------------------------------
+
+const OceanOptions&
+OceanDriver::getOceanOptions(const osgDB::Options* options) const
+{
+    return *static_cast<const OceanOptions*>( options->getPluginData(OPTIONS_TAG) );
+}
+
+
+MapNode*
+OceanDriver::getMapNode(const osgDB::Options* options) const
+{
+    return const_cast<MapNode*>(
+        static_cast<const MapNode*>(
+            options->getPluginData(MAPNODE_TAG) ) );
+}
diff --git a/src/osgEarthUtil/PolyhedralLineOfSight b/src/osgEarthUtil/PolyhedralLineOfSight
index 80d70ed..051cba5 100644
--- a/src/osgEarthUtil/PolyhedralLineOfSight
+++ b/src/osgEarthUtil/PolyhedralLineOfSight
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/PolyhedralLineOfSight.cpp b/src/osgEarthUtil/PolyhedralLineOfSight.cpp
index dd49de4..682f5f4 100644
--- a/src/osgEarthUtil/PolyhedralLineOfSight.cpp
+++ b/src/osgEarthUtil/PolyhedralLineOfSight.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/RGBColorFilter b/src/osgEarthUtil/RGBColorFilter
index c579485..0798577 100644
--- a/src/osgEarthUtil/RGBColorFilter
+++ b/src/osgEarthUtil/RGBColorFilter
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/RGBColorFilter.cpp b/src/osgEarthUtil/RGBColorFilter.cpp
index 14ae782..dbcc20e 100644
--- a/src/osgEarthUtil/RGBColorFilter.cpp
+++ b/src/osgEarthUtil/RGBColorFilter.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/RadialLineOfSight b/src/osgEarthUtil/RadialLineOfSight
index b370407..86a0409 100644
--- a/src/osgEarthUtil/RadialLineOfSight
+++ b/src/osgEarthUtil/RadialLineOfSight
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/RadialLineOfSight.cpp b/src/osgEarthUtil/RadialLineOfSight.cpp
index 1e1ebca..39388c3 100644
--- a/src/osgEarthUtil/RadialLineOfSight.cpp
+++ b/src/osgEarthUtil/RadialLineOfSight.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/ShadowUtils b/src/osgEarthUtil/ShadowUtils
deleted file mode 100644
index 84cb6b2..0000000
--- a/src/osgEarthUtil/ShadowUtils
+++ /dev/null
@@ -1,37 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-* GNU Lesser General Public License for more details.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#ifndef OSGEARTH_SHADOW_UTILS_H
-#define OSGEARTH_SHADOW_UTILS_H  1
-
-#include <osgEarthUtil/Common>
-#include <osg/Group>
-#include <osgShadow/ShadowedScene>
-
-namespace osgEarth { namespace Util 
-{
-    struct OSGEARTHUTIL_EXPORT ShadowUtils
-    {
-        static bool setUpShadows(
-            osgShadow::ShadowedScene* sscene,
-            osg::Group*               root);
-    };
-
-} } // namespace osgEarth::Util
-
-#endif // OSGEARTH_SHADOW_UTILS_H
diff --git a/src/osgEarthUtil/ShadowUtils.cpp b/src/osgEarthUtil/ShadowUtils.cpp
deleted file mode 100644
index 2110173..0000000
--- a/src/osgEarthUtil/ShadowUtils.cpp
+++ /dev/null
@@ -1,207 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-* GNU Lesser General Public License for more details.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#include <osgEarthUtil/ShadowUtils>
-
-#include <osgEarth/MapNode>
-#include <osgEarth/NodeUtils>
-#include <osgEarth/Notify>
-#include <osgEarth/VirtualProgram>
-#include <osgEarth/TerrainEngineNode>
-#include <osgEarth/TextureCompositor>
-#include <osgEarth/Registry>
-#include <osgEarth/Capabilities>
-
-#include <osg/StateSet>
-#include <osgShadow/StandardShadowMap>
-#include <osgShadow/ViewDependentShadowMap>
-
-#include <sstream>
-
-#define LC "[ShadowUtils] "
-
-using namespace osgEarth;
-using namespace osgEarth::Util;
-
-namespace
-{
-    bool setShadowUnit(osgShadow::ShadowedScene* sscene, int unit)
-    {
-        osgShadow::ShadowTechnique* st = sscene->getShadowTechnique();
-        if (st)
-        {
-            osgShadow::StandardShadowMap* ssm
-                = dynamic_cast<osgShadow::StandardShadowMap*>(st);
-            if (ssm)
-            {
-                ssm->setShadowTextureUnit( unit );
-                ssm->setShadowTextureCoordIndex( unit );
-                return true;
-            }
-            else
-            {
-                osgShadow::ViewDependentShadowMap* vdsm = dynamic_cast< osgShadow::ViewDependentShadowMap*>( st );
-                if (vdsm)
-                {
-                    sscene->getShadowSettings()
-                        ->setBaseShadowTextureUnit( unit );
-                    return true;
-                }
-            }
-        }
-
-        return false;
-    }
-}
-
-
-bool
-ShadowUtils::setUpShadows(osgShadow::ShadowedScene* sscene, osg::Group* root)
-{
-    osg::StateSet* ssStateSet = sscene->getOrCreateStateSet();
-
-    MapNode* mapNode = MapNode::findMapNode(root);
-    TerrainEngineNode* engine = mapNode->getTerrainEngine();
-    if (!engine)
-        return false;
-
-    TextureCompositor* compositor = engine->getTextureCompositor();
-    int su = -1;
-    if (!compositor->reserveTextureImageUnit(su))
-        return false;
-
-    OE_INFO << LC << "Reserved texture unit " << su << " for shadowing" << std::endl;
-
-    osgShadow::ViewDependentShadowMap* vdsm =  dynamic_cast< osgShadow::ViewDependentShadowMap*>(sscene->getShadowTechnique());
-    int su1 = -1;
-    if (vdsm && sscene->getShadowSettings()->getNumShadowMapsPerLight() == 2)
-    {
-        if (!compositor->reserveTextureImageUnit(su1) || su1 != su + 1)
-        {
-            OE_FATAL << LC << "couldn't get contiguous shadows for split vdsm\n";
-            sscene->getShadowSettings()->setNumShadowMapsPerLight(1);
-            if (su1 != -1)
-                compositor->releaseTextureImageUnit(su1);
-            su1 = -1;
-        }
-        else
-        {
-            OE_INFO << LC << "Reserved texture unit " << su1 << " for shadowing" << std::endl;
-        }
-    }
-
-    // create a virtual program to attach to the shadowed scene.
-    VirtualProgram* vp = new VirtualProgram();
-    vp->setName( "shadow:terrain" );
-    //vp->installDefaultColoringAndLightingShaders();
-
-    ssStateSet->setAttributeAndModes( vp, 1 );
-
-
-    std::stringstream buf;
-    buf << "#version " << GLSL_VERSION_STR << "\n";
-#ifdef OSG_GLES2_AVAILABLE
-    buf << "precision mediump float;\n";
-#endif
-    buf << "varying vec4 oe_shadow_ambient;\n";
-    buf << "varying vec4 oe_shadow_TexCoord0;\n";
-    if ( su1 >= 0 )
-        buf << "varying vec4 oe_shadow_TexCoord1;\n";
-
-
-    buf << "void oe_shadow_setupShadowCoords(inout vec4 VertexVIEW)\n";
-    buf << "{\n";
-    buf << "    vec4 position4 = VertexVIEW;\n";
-    buf << "    oe_shadow_TexCoord0.s = dot( position4, gl_EyePlaneS[" << su <<"]);\n";
-    buf << "    oe_shadow_TexCoord0.t = dot( position4, gl_EyePlaneT[" << su <<"]);\n";
-    buf << "    oe_shadow_TexCoord0.p = dot( position4, gl_EyePlaneR[" << su <<"]);\n";
-    buf << "    oe_shadow_TexCoord0.q = dot( position4, gl_EyePlaneQ[" << su <<"]);\n";
-    if (su1 >= 0)
-    {
-        buf << "    oe_shadow_TexCoord1.s = dot( position4, gl_EyePlaneS[" << su1 <<"]);\n";
-        buf << "    oe_shadow_TexCoord1.t = dot( position4, gl_EyePlaneT[" << su1 <<"]);\n";
-        buf << "    oe_shadow_TexCoord1.p = dot( position4, gl_EyePlaneR[" << su1 <<"]);\n";
-        buf << "    oe_shadow_TexCoord1.q = dot( position4, gl_EyePlaneQ[" << su1 <<"]);\n";
-    }
-
-    // the ambient lighting will control the intensity of the shadow.
-    buf << "    oe_shadow_ambient = gl_FrontLightProduct[0].ambient; \n"
-        << "}\n";
-
-    std::string setupShadowCoords;
-    setupShadowCoords = buf.str();
-
-    vp->setFunction(
-        "oe_shadow_setupShadowCoords", 
-        setupShadowCoords, 
-        ShaderComp::LOCATION_VERTEX_VIEW,
-        -1.0 );
-
-    std::stringstream buf2;
-    buf2 <<
-        "#version " << GLSL_VERSION_STR << "\n"
-#ifdef OSG_GLES2_AVAILABLE
-        "precision mediump float;\n"
-#endif
-        "uniform sampler2DShadow shadowTexture;\n"
-        "varying vec4 oe_shadow_TexCoord0;\n";
-
-    if (su1 >= 0)
-    {
-        // bound by vdsm
-        buf2 << "uniform sampler2DShadow shadowTexture1;\n";
-        buf2 << "varying vec4 oe_shadow_TexCoord1;\n";
-    }
-    buf2 <<
-        "varying vec4 oe_shadow_ambient;\n"
-
-        "void oe_shadow_applyLighting( inout vec4 color )\n"
-        "{\n"
-        "    float alpha = color.a;\n"
-        "    float shadowFac = shadow2DProj( shadowTexture, oe_shadow_TexCoord0).r;\n";
-    if (su1 > 0)
-    {
-        buf2 << "    shadowFac *= shadow2DProj( shadowTexture1, oe_shadow_TexCoord1).r;\n";
-    }
-
-    // calculate the shadowed color and mix if with the lit color based on the
-    // ambient lighting. The 0.5 is a multiplier that darkens the shadow in
-    // proportion to ambient light. It should probably be a uniform.
-    buf2 <<
-        "    vec4 colorInFullShadow = color * oe_shadow_ambient; \n"
-        "    color = mix(colorInFullShadow, color, shadowFac); \n"
-        "    color.a = alpha;\n"
-        "}\n";
-
-    std::string fragApplyLighting;
-    fragApplyLighting = buf2.str();
-
-    vp->setFunction(
-        "oe_shadow_applyLighting",
-        fragApplyLighting,
-        osgEarth::ShaderComp::LOCATION_FRAGMENT_LIGHTING );
-
-    setShadowUnit(sscene, su);
-
-    // VDSM uses a different sampler name, shadowTexture0.
-    ssStateSet
-        ->getOrCreateUniform("shadowTexture", osg::Uniform::SAMPLER_2D_SHADOW)
-        ->set(su);
-
-    return true;
-}
diff --git a/src/osgEarthUtil/Shadowing b/src/osgEarthUtil/Shadowing
new file mode 100644
index 0000000..88e051b
--- /dev/null
+++ b/src/osgEarthUtil/Shadowing
@@ -0,0 +1,129 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTH_UTIL_SHADOWING_H
+#define OSGEARTH_UTIL_SHADOWING_H  1
+
+#include <osgEarthUtil/Common>
+#include <osg/Camera>
+#include <osg/Texture2DArray>
+#include <osg/Matrix>
+#include <osg/Uniform>
+#include <osg/Light>
+
+
+namespace osgEarth { namespace Util 
+{
+    /**
+     * Group that casts shadows on its subgraph.
+     *
+     * NOTE!! This object is not multi-camera aware yet.
+     */
+    class OSGEARTHUTIL_EXPORT ShadowCaster : public osg::Group
+    {
+    public:
+        ShadowCaster();
+
+        /** Whether shadows are supported (requires GLSL) */
+        bool supported() const { return _supported; }
+
+        /**
+         * The light that will cast shadows.
+         * NOTE: Only works for point lights, like the sun.
+         */
+        void setLight(osg::Light* light) { _light = light; }
+        osg::Light* getLight() { return _light.get(); }
+
+        /**
+         * Group of geometry that should cast shadows on this node's children.
+         * You must add geometry here in order to cast shadows. The geometry
+         * you add here will only be used to generate shadows - it must still
+         * exist elsewhere in the scene graph for rendering.
+         */
+        osg::Group* getShadowCastingGroup() { return _castingGroup.get(); }
+
+        /**
+         * Slice ranges. Each slice (the space beteen each value in the list)
+         * represents a single shadow map in the Cascading Shadow Maps 
+         * implementation.
+         */
+        const std::vector<float>& getRanges() { return _ranges; }
+        void setRanges(const std::vector<float>& ranges);
+
+        /**
+         * The GPU texture image unit that will store the shadow map while
+         * rendering the subgraph.
+         */
+        int getTextureImageUnit() const { return _texImageUnit; }
+        void setTextureImageUnit(int unit);
+
+        /**
+         * The size (in both dimensions) of the shadow depth texture. Bigger
+         * is sharper. Default is 1024. The total texture size used will be
+         * size * # of range slices * 3 bytes.
+         */
+        unsigned getTextureSize() const { return _size; }
+        void setTextureSize(unsigned size);
+
+        /**
+         * The ambient color of the shadow. This is blended with the fragment
+         * color to achieve shadowing. Default is 0x7f7f7fff
+         */
+        void setShadowColor(const osg::Vec4f& value);
+        const osg::Vec4f& getShadowColor() const { return _color; }
+
+        /**
+         * The blurring factor to apply to shadow PCF sampling. Using a blurring
+         * factor will incur a GPU performance penalty. Default is 0.0.
+         * Decent values are [0.0 -> 0.001].
+         */
+        void setBlurFactor(float value);
+        float getBlurFactor() const { return _blurFactor; }
+
+
+    public: // osg::Node
+
+        virtual void traverse(osg::NodeVisitor& nv);
+
+    protected:
+        virtual ~ShadowCaster() { }
+
+        void reinitialize();
+
+        bool                                    _supported;
+        osg::ref_ptr<osg::Group>                _castingGroup;
+        unsigned                                _size;
+        float                                   _blurFactor;
+        osg::Vec4f                              _color;
+        osg::ref_ptr<osg::Light>                _light;
+        osg::ref_ptr<osg::Texture2DArray>       _shadowmap;
+        osg::ref_ptr<osg::StateSet>             _rttStateSet;
+        std::vector<float>                      _ranges;
+        std::vector<osg::ref_ptr<osg::Camera> > _rttCameras;
+        osg::Matrix                             _prevProjMatrix;
+
+        int                         _texImageUnit;
+        osg::ref_ptr<osg::StateSet> _renderStateSet;
+        osg::ref_ptr<osg::Uniform>  _shadowMapTexGenUniform;
+        osg::ref_ptr<osg::Uniform>  _shadowBlurUniform;
+        osg::ref_ptr<osg::Uniform>  _shadowColorUniform;
+    };
+
+} } // namespace osgEarth::Util
+
+#endif // OSGEARTH_UTIL_SHADOWING_H
diff --git a/src/osgEarthUtil/Shadowing.cpp b/src/osgEarthUtil/Shadowing.cpp
new file mode 100644
index 0000000..ae16549
--- /dev/null
+++ b/src/osgEarthUtil/Shadowing.cpp
@@ -0,0 +1,381 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include <osgEarthUtil/Shadowing>
+#include <osgEarth/CullingUtils>
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/Registry>
+#include <osgEarth/Capabilities>
+#include <osg/Texture2D>
+#include <osg/CullFace>
+#include <osgShadow/ConvexPolyhedron>
+
+#define LC "[ShadowCaster] "
+
+using namespace osgEarth::Util;
+
+
+ShadowCaster::ShadowCaster() :
+_size        ( 2048 ),
+_texImageUnit( 7 ),
+_blurFactor  ( 0.002f ),
+_color       ( osg::Vec4f(.4f, .4f, .4f, 1) )
+{
+    _castingGroup = new osg::Group();
+
+    _supported = Registry::capabilities().supportsGLSL();
+    if ( _supported )
+    {
+        // defaults to 4 slices.
+        _ranges.push_back(0.0f);
+        _ranges.push_back(100.0f);
+        _ranges.push_back(500.0f);
+        _ranges.push_back(1750.0f);
+        _ranges.push_back(5000.0f);
+
+        reinitialize();
+    }
+    else
+    {
+        OE_WARN << LC << "ShadowCaster not supported (no GLSL); disabled." << std::endl;
+    }
+}
+
+void
+ShadowCaster::setRanges(const std::vector<float>& ranges)
+{
+    _ranges = ranges;
+    reinitialize();
+}
+
+void
+ShadowCaster::setTextureImageUnit(int unit)
+{
+    _texImageUnit = unit;
+    reinitialize();
+}
+
+void
+ShadowCaster::setTextureSize(unsigned size)
+{
+    _size = size;
+    reinitialize();
+}
+
+void
+ShadowCaster::setBlurFactor(float value)
+{
+    _blurFactor = value;
+    if ( _shadowBlurUniform.valid() )
+        _shadowBlurUniform->set(value);
+}
+
+void
+ShadowCaster::setShadowColor(const osg::Vec4f& value)
+{
+    _color = value;
+    if ( _shadowColorUniform.valid() )
+        _shadowColorUniform->set(value);
+}
+
+void
+ShadowCaster::reinitialize()
+{
+    if ( !_supported )
+        return;
+
+    _shadowmap = 0L;
+    _rttCameras.clear();
+
+    int numSlices = (int)_ranges.size() - 1;
+    if ( numSlices < 1 )
+    {
+        OE_WARN << LC << "Illegal. Must have at least one range slice." << std::endl;
+        return ;
+    }
+
+    // create the projected texture:
+    _shadowmap = new osg::Texture2DArray();
+    _shadowmap->setTextureSize( _size, _size, numSlices );
+    _shadowmap->setInternalFormat( GL_DEPTH_COMPONENT );
+    _shadowmap->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR );
+    _shadowmap->setFilter( osg::Texture::MAG_FILTER, osg::Texture::LINEAR );
+    _shadowmap->setWrap( osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_BORDER );
+    _shadowmap->setWrap( osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_BORDER );
+    _shadowmap->setBorderColor(osg::Vec4(1,1,1,1));
+
+    // set up the RTT camera:
+    for(int i=0; i<numSlices; ++i)
+    {
+        osg::Camera* rtt = new osg::Camera();
+        rtt->setReferenceFrame( osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT );
+        rtt->setClearDepth( 1.0 );
+        rtt->setClearMask( GL_DEPTH_BUFFER_BIT );
+        rtt->setComputeNearFarMode( osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR );
+        rtt->setViewport( 0, 0, _size, _size );
+        rtt->setRenderOrder( osg::Camera::PRE_RENDER );
+        rtt->setRenderTargetImplementation( osg::Camera::FRAME_BUFFER_OBJECT );
+        rtt->setImplicitBufferAttachmentMask(0, 0);
+        rtt->attach( osg::Camera::DEPTH_BUFFER, _shadowmap.get(), 0, i );
+        rtt->addChild( _castingGroup.get() );
+        _rttCameras.push_back(rtt);
+    }
+
+    _rttStateSet = new osg::StateSet();
+
+    // only draw back faces to the shadow depth map
+    _rttStateSet->setAttributeAndModes( 
+        new osg::CullFace(osg::CullFace::FRONT),
+        osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
+
+    _renderStateSet = new osg::StateSet();
+    
+    std::string vertex = Stringify() << 
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+        "uniform mat4 oe_shadow_matrix[" << numSlices << "]; \n"
+        "varying vec4 oe_shadow_coord[" << numSlices << "]; \n"
+        "void oe_shadow_vertex(inout vec4 VertexVIEW) \n"
+        "{ \n"
+        "    for(int i=0; i<" << numSlices << "; ++i) \n"
+        "        oe_shadow_coord[i] = oe_shadow_matrix[i] * VertexVIEW;\n"
+        "} \n";
+
+    std::string fragment = Stringify() << 
+        "#version 120\n" //" GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+        "#extension GL_EXT_texture_array : enable \n"
+
+        "uniform sampler2DArray oe_shadow_map; \n"
+        "uniform vec4 oe_shadow_color; \n"
+        "uniform float oe_shadow_blur; \n"
+        "varying vec3 oe_Normal; \n"
+        "varying vec4 oe_shadow_coord[" << numSlices << "]; \n"
+
+        //TODO-run a generator and rplace
+        "#define OE_SHADOW_NUM_SAMPLES 16\n"
+        "const vec2 oe_shadow_samples[OE_SHADOW_NUM_SAMPLES] = vec2[]( vec2( -0.942016, -0.399062 ), vec2( 0.945586, -0.768907 ), vec2( -0.094184, -0.929389 ), vec2( 0.344959, 0.293878 ), vec2( -0.915886, 0.457714 ), vec2( -0.815442, -0.879125 ), vec2( -0.382775, 0.276768 ), vec2( 0.974844, 0.756484 ), vec2( 0.443233, -0.975116 ), vec2( 0.53743, -0.473734 ), vec2( -0.264969, -0.41893 ), vec2( 0.791975, 0.190909 ), vec2( -0.241888, 0.997065 ), vec2( -0.8141, 0.914376 ), vec2( 0.199841, 0. [...]
+
+        "float oe_shadow_rand(vec2 co){\n"
+        "   return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);\n"
+        "}\n"
+        
+        "vec2 oe_shadow_rot(vec2 p, float a) { \n"
+        "    vec2 sincos = vec2(sin(a), cos(a)); \n"
+        "    return vec2(dot(p, vec2(sincos.y, -sincos.x)), dot(p, sincos.xy)); \n"
+        "}\n"
+
+        // slow PCF sampling.
+        "float oe_shadow_multisample(in vec3 c, in float refvalue, in float blur) \n"
+        "{ \n"
+        "    float shadowed = 0.0; \n"
+        "    float a = 6.283185 * oe_shadow_rand(c.xy); \n"
+        "    vec4 b = vec4(oe_shadow_rot(vec2(1,0),a), oe_shadow_rot(vec2(0,1),a)); \n"
+        "    for(int i=0; i<OE_SHADOW_NUM_SAMPLES; ++i) { \n"
+        "        vec2 off = oe_shadow_samples[i];\n"
+        "        off = vec2(dot(off,b.xz), dot(off,b.yw)); \n"
+        "        vec3 pc = vec3(c.xy + off*blur, c.z); \n"
+        "        float depth = texture2DArray(oe_shadow_map, pc).r; \n"
+        "        if ( depth < 1.0 && depth < refvalue ) { \n"
+        "           shadowed += 1.0; \n"
+        "        } \n"
+        "    } \n"
+        "    return 1.0-(shadowed/OE_SHADOW_NUM_SAMPLES); \n"
+        "} \n"
+
+        "void oe_shadow_fragment( inout vec4 color )\n"
+        "{\n"
+        "    float alpha = color.a; \n"
+        "    float factor = 1.0; \n"
+
+        // pre-pixel biasing to reduce moire/acne
+        "    const float b0 = 0.001; \n"
+        "    const float b1 = 0.01; \n"
+        "    vec3 L = normalize(gl_LightSource[0].position.xyz); \n"
+        "    vec3 N = normalize(oe_Normal); \n"
+        "    float costheta = clamp(dot(L,N), 0.0, 1.0); \n"
+        "    float bias = b0*tan(acos(costheta)); \n"
+
+        // loop over the slices:
+        "    for(int i=0; i<" << numSlices << " && factor > 0.0; ++i) \n"
+        "    { \n"
+        "        vec4 c = oe_shadow_coord[i]; \n"
+        "        vec3 coord = vec3(c.x, c.y, float(i)); \n"
+
+        "        if ( oe_shadow_blur > 0.0 ) \n"
+        "        { \n"
+        "            factor = min(factor, oe_shadow_multisample(coord, c.z-bias, oe_shadow_blur)); \n"
+        "        } \n"
+        "        else \n"
+        "        { \n"
+        "            float depth = texture2DArray(oe_shadow_map, coord).r; \n"
+        "            if ( depth < 1.0 && depth < c.z-bias ) \n"
+        "                factor = 0.0; \n"
+        "        } \n"
+        "    } \n"
+
+        "    vec4 colorInFullShadow = color * oe_shadow_color; \n"
+        "    color = mix(colorInFullShadow, color, factor); \n"
+        "    color.a = alpha;\n"
+        "}\n";
+
+    VirtualProgram* vp = VirtualProgram::getOrCreate(_renderStateSet.get());
+
+    vp->setFunction(
+        "oe_shadow_vertex", 
+        vertex, 
+        ShaderComp::LOCATION_VERTEX_VIEW );
+
+    vp->setFunction(
+        "oe_shadow_fragment",
+        fragment,
+        ShaderComp::LOCATION_FRAGMENT_LIGHTING, 10.0f);
+
+    // the texture coord generator matrix array (from the caster):
+    _shadowMapTexGenUniform = _renderStateSet->getOrCreateUniform(
+        "oe_shadow_matrix",
+        osg::Uniform::FLOAT_MAT4,
+        numSlices );
+
+    // bind the shadow map texture itself:
+    _renderStateSet->setTextureAttribute(
+        _texImageUnit,
+        _shadowmap.get(),
+        osg::StateAttribute::ON );
+
+    _renderStateSet->addUniform( new osg::Uniform("oe_shadow_map", _texImageUnit) );
+
+    // blur factor:
+    _shadowBlurUniform = _renderStateSet->getOrCreateUniform(
+        "oe_shadow_blur",
+        osg::Uniform::FLOAT);
+
+    _shadowBlurUniform->set(_blurFactor);
+
+    // shadow color:
+    _shadowColorUniform = _renderStateSet->getOrCreateUniform(
+        "oe_shadow_color",
+        osg::Uniform::FLOAT_VEC4);
+
+    _shadowColorUniform->set(_color);
+}
+
+void
+ShadowCaster::traverse(osg::NodeVisitor& nv)
+{
+    if (_supported                             && 
+        nv.getVisitorType() == nv.CULL_VISITOR && 
+        _castingGroup->getNumChildren() > 0    && 
+        _shadowmap.valid() )
+    {
+        osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
+        osg::Camera* camera = cv->getCurrentCamera();
+        if ( camera )
+        {
+            osg::Matrix MV = *cv->getModelViewMatrix();
+            osg::Matrix inverseMV;
+            inverseMV.invert(MV);
+
+            osg::Vec3d camEye, camTo, camUp;
+            MV.getLookAt( camEye, camTo, camUp, 1.0 );
+
+            // position the light. We only really care about the directional vector.
+            osg::Vec4d lp4 = _light->getPosition();
+            osg::Vec3d lightVectorWorld( -lp4.x(), -lp4.y(), -lp4.z() );
+            lightVectorWorld.normalize();
+            osg::Vec3d lightPosWorld = osg::Vec3d(0,0,0) * inverseMV;
+
+            // construct the view matrix for the light. The up vector doesn't really
+            // matter so we'll just use the camera's.
+            osg::Matrix lightViewMat;
+            lightViewMat.makeLookAt(lightPosWorld, lightPosWorld+lightVectorWorld, camUp);
+            
+            //int i = nv.getFrameStamp()->getFrameNumber() % (_ranges.size()-1);
+            int i;
+            for(i=0; i < (int) _ranges.size()-1; ++i)
+            {
+                double n = _ranges[i];
+                double f = _ranges[i+1];
+
+                // take the camera's projection matrix and clamp it's near and far planes
+                // to our shadow map slice range.
+                osg::Matrix proj = _prevProjMatrix;
+                //cv->clampProjectionMatrix(proj, n, f);
+                double fovy,ar,zn,zf;
+                proj.getPerspective(fovy,ar,zn,zf);
+                proj.makePerspective(fovy,ar,std::max(n,zn),std::min(f,zf));
+                
+                // extract the corner points of the camera frustum in world space.
+                osg::Matrix MVP = MV * proj;
+                osg::Matrix inverseMVP;
+                inverseMVP.invert(MVP);
+                osgShadow::ConvexPolyhedron frustumPH;
+                frustumPH.setToUnitFrustum(true, true);
+                frustumPH.transform( inverseMVP, MVP );
+                std::vector<osg::Vec3d> verts;
+                frustumPH.getPoints( verts );
+
+                // project those on to the plane of the light camera and fit them
+                // to a bounding box. That box will form the extent of our orthographic camera.
+                osg::BoundingBoxd bbox;
+                for( std::vector<osg::Vec3d>::iterator v = verts.begin(); v != verts.end(); ++v )
+                    bbox.expandBy( (*v) * lightViewMat );
+
+                osg::Matrix lightProjMat;
+                n = -std::max(bbox.zMin(), bbox.zMax());
+                f = -std::min(bbox.zMin(), bbox.zMax());
+                lightProjMat.makeOrtho(bbox.xMin(), bbox.xMax(), bbox.yMin(), bbox.yMax(), n, f);
+
+                // configure the RTT camera for this slice:
+                _rttCameras[i]->setViewMatrix( lightViewMat );
+                _rttCameras[i]->setProjectionMatrix( lightProjMat );
+
+                // this xforms from clip [-1..1] to texture [0..1] space
+                static osg::Matrix s_scaleBiasMat = 
+                    osg::Matrix::translate(1.0,1.0,1.0) * 
+                    osg::Matrix::scale(0.5,0.5,0.5);
+                
+                // set the texture coordinate generation matrix that the shadow
+                // receiver will use to sample the shadow map. Doing this on the CPU
+                // prevents nasty precision issues!
+                osg::Matrix VPS = lightViewMat * lightProjMat * s_scaleBiasMat;
+                _shadowMapTexGenUniform->setElement(i, inverseMV * VPS);
+            }
+
+            // render the shadow maps.
+            cv->pushStateSet( _rttStateSet.get() );
+            for(i=0; i < (int) _rttCameras.size(); ++i)
+            {
+                _rttCameras[i]->accept( nv );
+            }
+            cv->popStateSet();
+            
+            // render the shadowed subgraph.
+            cv->pushStateSet( _renderStateSet.get() );
+            osg::Group::traverse( nv );
+            cv->popStateSet();
+
+            // save the projection matrix for the next frame.
+            _prevProjMatrix = *cv->getProjectionMatrix();
+
+            return;
+        }
+    }
+
+    osg::Group::traverse(nv);
+}
diff --git a/src/osgEarthUtil/SimplexNoise b/src/osgEarthUtil/SimplexNoise
new file mode 100644
index 0000000..711f88c
--- /dev/null
+++ b/src/osgEarthUtil/SimplexNoise
@@ -0,0 +1,159 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTH_UTIL_SIMPLEX_NOISE_H
+#define OSGEARTH_UTIL_SIMPLEX_NOISE_H 1
+
+#include <osgEarthUtil/Common>
+
+namespace osgEarth { namespace Util
+{
+    /**
+     * Simplex Noise Generator.
+     * Adapted from https://github.com/Taywee/Noise
+     */
+    class OSGEARTHUTIL_EXPORT SimplexNoise
+    {
+    public:
+        SimplexNoise();
+        virtual ~SimplexNoise() { }
+
+        static const double DefaultFrequency;
+        static const double DefaultPersistence;
+        static const double DefaultLacunarity;
+        static const double DefaultRangeLow;
+        static const double DefaultRangeHigh;
+        static const unsigned DefaultOctaves;
+
+        /**
+         * Frequency is how often the noise pattern resets to zero across
+         * the input domain. For example, if your input range from 
+         * -100 < x < 100, a frequency of 1/200 will cause the noise function to
+         * reset every 200 units, i.e., the extent of the input domain. (It will
+         * be zero at -100 and zero again at 100. It may cross zero in between,
+         * but that is not guaranteed.)
+         *
+         * In mapping terms, say your map is 100000m wide. A frequency of 1/100000
+         * will result in a pattern that resets (and wraps around) every 100000m.
+         *
+         * Default = 1.0.
+         */ 
+        void setFrequency(double freq) { _freq = freq; }
+        double getFrequency() const { return _freq; }
+
+        /**
+         * Persietence is the factor by which the noise function's amplitude
+         * decreases with each successive octave.
+         *   i.e.: Amp(Oct2) = Amp(Oct1) * Persistance.
+         * Default = 0.5.
+         * Effect: Higher persistence: noisiness persists as you get more detail;
+         *         Lower persistence: data smooths out faster as you get more detail.
+         */
+        void setPersistence(double pers) { _pers = pers; }
+        double getPersistence() const { return _pers; }
+
+        /**
+         * Lacunarity is the factor by which the noise function's frequency
+         * increases with each successive octave.
+         *   i.e.: Freq(Oct2) = Freq(Oct1) * Lacunarity.
+         * Default = 2.0.
+         * Effect: Higher lacunarity: more "plateaus" in the output;
+         *         Lower lacunarity:  more "chopiness" in the output.
+         */
+        void setLacunarity(double lac) { _lacunarity = lac; }
+        double getLacunarity() const { return _lacunarity; }
+
+        /**
+         * The output range for noise data.
+         * Default = [-1..1].
+         */
+        void setRange(double low, double high) { _low = low, _high = high; }
+        double getRangeLow() const { return _low; }
+        double getRangeHigh() const { return _high; }
+
+        /**
+         * Number of iterations over which to generate noise. The frequency starts
+         * where you set it, and over each successive iteration, it is scaled by
+         * the persistence. This lets you control the rate at which the "noisiness"
+         * decreases with each octave.
+         * Default = 1.
+         */
+        void setOctaves(unsigned octaves) { _octaves = octaves; }
+        unsigned getOctaves() const { return _octaves; }
+
+        /**
+         * Generates 2D simplex Noise
+         */
+        double getValue(double xin, double yin) const;
+
+        /**
+         * Generates 3D simplex Noise
+         */
+        double getValue(double xin, double yin, double zin) const;
+
+        /**
+         * Generates 4D simplex Noise
+         */
+        double getValue(double x, double y, double z, double w) const;
+
+    private:
+        // Inner class to speed up gradient computations
+        // (array access is a lot slower than member access)
+        struct Grad
+        {
+            Grad(double x, double y, double z);
+            Grad(double x, double y, double z, double w);
+            double x, y, z, w;
+        };
+
+        const static Grad grad3[12];
+        const static Grad grad4[32];
+        const static unsigned char perm[512];
+
+        // Skewing and unskewing factors for 2, 3, and 4 dimensions
+        static double const F2;
+        static double const G2;
+        static double const F3;
+        static double const G3;
+        static double const F4;
+        static double const G4;
+
+        unsigned char permMod12[512];
+        bool permMod12Computed;
+        void ComputePermMod12();
+
+        // This method is a *lot* faster than using (int)Math.floor(x)
+        inline static int FastFloor(double x);
+        static double Dot(Grad const & g, double x, double y);
+        static double Dot(Grad const & g, double x, double y, double z);
+        static double Dot(Grad const & g, double x, double y, double z, double w);
+
+        double Noise(double x, double y) const;
+        double Noise(double x, double y, double z) const;
+        double Noise(double x, double y, double z, double w) const;
+
+        double _freq;
+        double _pers;
+        double _lacunarity;
+        double _low, _high;
+        unsigned _octaves;
+    };
+
+} } // namespace osgEarth::Util
+
+#endif //OSGEARTH_UTIL_SIMPLEX_NOISE_H
diff --git a/src/osgEarthUtil/SimplexNoise.cpp b/src/osgEarthUtil/SimplexNoise.cpp
new file mode 100644
index 0000000..6a84aee
--- /dev/null
+++ b/src/osgEarthUtil/SimplexNoise.cpp
@@ -0,0 +1,556 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+#include <osgEarthUtil/SimplexNoise>
+#include <algorithm>
+
+#define POW2(x) ((double)(x==0 ? 1 : (2 << (x-1))))
+
+using namespace osgEarth::Util;
+
+const SimplexNoise::Grad SimplexNoise::grad3[12] = {
+    Grad(1, 1, 0), Grad(-1, 1, 0), Grad(1, -1, 0), Grad(-1, -1, 0),
+    Grad(1, 0, 1), Grad(-1, 0, 1), Grad(1, 0, -1), Grad(-1, 0, -1),
+    Grad(0, 1, 1), Grad(0, -1, 1), Grad(0, 1, -1), Grad(0, -1, -1)
+};
+
+const SimplexNoise::Grad SimplexNoise::grad4[32] =  {
+    Grad(0, 1, 1, 1), Grad(0, 1, 1, -1), Grad(0, 1, -1, 1), Grad(0, 1, -1, -1),
+    Grad(0, -1, 1, 1), Grad(0, -1, 1, -1), Grad(0, -1, -1, 1), Grad(0, -1, -1, -1),
+    Grad(1, 0, 1, 1), Grad(1, 0, 1, -1), Grad(1, 0, -1, 1), Grad(1, 0, -1, -1),
+    Grad(-1, 0, 1, 1), Grad(-1, 0, 1, -1), Grad(-1, 0, -1, 1), Grad(-1, 0, -1, -1),
+    Grad(1, 1, 0, 1), Grad(1, 1, 0, -1), Grad(1, -1, 0, 1), Grad(1, -1, 0, -1),
+    Grad(-1, 1, 0, 1), Grad(-1, 1, 0, -1), Grad(-1, -1, 0, 1), Grad(-1, -1, 0, -1),
+    Grad(1, 1, 1, 0), Grad(1, 1, -1, 0), Grad(1, -1, 1, 0), Grad(1, -1, -1, 0),
+    Grad(-1, 1, 1, 0), Grad(-1, 1, -1, 0), Grad(-1, -1, 1, 0), Grad(-1, -1, -1, 0)
+};
+
+const unsigned char SimplexNoise::perm[512] = {
+    151, 160, 137, 91, 90, 15,
+    131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23,
+    190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33,
+    88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166,
+    77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244,
+    102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196,
+    135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123,
+    5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42,
+    223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9,
+    129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228,
+    251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107,
+    49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254,
+    138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180,
+
+    151, 160, 137, 91, 90, 15,
+    131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23,
+    190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33,
+    88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166,
+    77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244,
+    102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196,
+    135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123,
+    5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42,
+    223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9,
+    129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228,
+    251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107,
+    49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254,
+    138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180
+};
+
+// Skewing and unskewing factors for 2, 3, and 4 dimensions
+double const SimplexNoise::F2 = 0.5*(sqrt(3.0)-1.0);
+double const SimplexNoise::G2 = (3.0-sqrt(3.0))/6.0;
+double const SimplexNoise::F3 = 1.0/3.0;
+double const SimplexNoise::G3 = 1.0/6.0;
+double const SimplexNoise::F4 = (sqrt(5.0)-1.0)/4.0;
+double const SimplexNoise::G4 = (5.0-sqrt(5.0))/20.0;
+
+SimplexNoise::Grad::Grad(double x, double y, double z)
+{
+    this->x = x;
+    this->y = y;
+    this->z = z;
+}
+
+SimplexNoise::Grad::Grad(double x, double y, double z, double w)
+{
+    this->x = x;
+    this->y = y;
+    this->z = z;
+    this->w = w;
+}
+
+// This method is a *lot* faster than using (int)Math.floor(x)
+int SimplexNoise::FastFloor(double x)
+{
+    int xi = (int)x;
+    return x<xi ? xi-1 : xi;
+}
+
+double SimplexNoise::Dot(Grad const & g, double x, double y)
+{
+    return g.x*x + g.y*y;
+}
+
+double SimplexNoise::Dot(Grad const & g, double x, double y, double z)
+{
+    return g.x*x + g.y*y + g.z*z;
+}
+
+double SimplexNoise::Dot(Grad const & g, double x, double y, double z, double w)
+{
+    return g.x*x + g.y*y + g.z*z + g.w*w;
+}
+
+
+const double   SimplexNoise::DefaultFrequency    =  1.0;
+const double   SimplexNoise::DefaultPersistence  =  0.5;
+const double   SimplexNoise::DefaultLacunarity   =  2.0;
+const double   SimplexNoise::DefaultRangeLow     = -1.0;
+const double   SimplexNoise::DefaultRangeHigh    =  1.0;
+const unsigned SimplexNoise::DefaultOctaves      =  10;
+
+
+SimplexNoise::SimplexNoise() :
+_octaves   ( DefaultOctaves ),
+_freq      ( DefaultFrequency ),
+_pers      ( DefaultPersistence ),
+_lacunarity( DefaultLacunarity ),
+_low       ( DefaultRangeLow ),
+_high      ( DefaultRangeHigh )
+{
+    for(unsigned int i=0; i<512; i++)
+    {
+        permMod12[i] = (unsigned char)(perm[i] % 12);
+    }
+}
+
+double SimplexNoise::getValue(double xin, double yin) const
+{
+    double freq = _freq;
+    double o = std::max(1u, _octaves);
+    double amp = 1.0;
+    double maxamp = 0.0;
+    double n = 0.0;
+
+    for(unsigned i=0; i<o; ++i)
+    {
+        n += Noise(xin*freq, yin*freq) * amp;
+        maxamp += amp;
+        amp *= _pers;
+        freq *= _lacunarity;
+    }
+    n /= maxamp;
+    n = n * (_high-_low)/2.0 + (_high+_low)/2.0;
+    return n;
+}
+
+double SimplexNoise::getValue(double xin, double yin, double zin) const
+{
+    double freq = _freq;
+    double o = std::max(1u, _octaves);
+    double amp = 1.0;
+    double maxamp = 0.0;
+    double n = 0.0;
+
+    for(unsigned i=0; i<o; ++i)
+    {
+        n += Noise(xin*freq, yin*freq, zin*freq) * amp;
+        maxamp += amp;
+        amp *= _pers;
+        freq *= _lacunarity;
+    }
+    n /= maxamp;
+    n = n * (_high-_low)/2.0 + (_high+_low)/2.0;
+    return n;
+}
+
+double SimplexNoise::getValue(double xin, double yin, double zin, double win) const
+{
+    double freq = _freq;
+    double o = std::max(1u, _octaves);
+    double amp = 1.0;
+    double maxamp = 0.0;
+    double n = 0.0;
+
+    for(unsigned i=0; i<o; ++i)
+    {
+        n += Noise(xin*freq, yin*freq, zin*freq, win*freq) * amp;
+        maxamp += amp;
+        amp *= _pers;
+        freq *= _lacunarity;
+    }
+    n /= maxamp;
+    n = n * (_high-_low)/2.0 + (_high+_low)/2.0;
+    return n;
+}
+
+
+// 2D simplex noise
+double SimplexNoise::Noise(double xin, double yin) const
+{
+    double n0, n1, n2; // Noise contributions from the three corners
+    // Skew the input space to determine which simplex cell we're in
+    double s = (xin+yin)*F2; // Hairy factor for 2D
+    int i = FastFloor(xin+s);
+    int j = FastFloor(yin+s);
+    double t = (i+j)*G2;
+    double X0 = i-t; // Unskew the cell origin back to (x,y) space
+    double Y0 = j-t;
+    double x0 = xin-X0; // The x,y distances from the cell origin
+    double y0 = yin-Y0;
+    // For the 2D case, the simplex shape is an equilateral triangle.
+    // Determine which simplex we are in.
+    int i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords
+    if(x0>y0)
+    {
+        i1=1;    // lower triangle, XY order: (0,0)->(1,0)->(1,1)
+        j1=0;
+    } else
+    {
+        i1=0;    // upper triangle, YX order: (0,0)->(0,1)->(1,1)
+        j1=1;
+    }
+    // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and
+    // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where
+    // c = (3-sqrt(3))/6
+    double x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords
+    double y1 = y0 - j1 + G2;
+    double x2 = x0 - 1.0 + 2.0 * G2; // Offsets for last corner in (x,y) unskewed coords
+    double y2 = y0 - 1.0 + 2.0 * G2;
+    // Work out the hashed gradient indices of the three simplex corners
+    int ii = i & 255;
+    int jj = j & 255;
+    int gi0 = permMod12[ii+perm[jj]];
+    int gi1 = permMod12[ii+i1+perm[jj+j1]];
+    int gi2 = permMod12[ii+1+perm[jj+1]];
+    // Calculate the contribution from the three corners
+    double t0 = 0.5 - x0*x0-y0*y0;
+    if(t0<0) n0 = 0.0;
+    else
+    {
+        t0 *= t0;
+        n0 = t0 * t0 * Dot(grad3[gi0], x0, y0);    // (x,y) of grad3 used for 2D gradient
+    }
+    double t1 = 0.5 - x1*x1-y1*y1;
+    if(t1<0) n1 = 0.0;
+    else
+    {
+        t1 *= t1;
+        n1 = t1 * t1 * Dot(grad3[gi1], x1, y1);
+    }
+    double t2 = 0.5 - x2*x2-y2*y2;
+    if(t2<0) n2 = 0.0;
+    else
+    {
+        t2 *= t2;
+        n2 = t2 * t2 * Dot(grad3[gi2], x2, y2);
+    }
+    // Add contributions from each corner to get the final noise value.
+    // The result is scaled to return values in the interval [-1,1].
+    return 70.0 * (n0 + n1 + n2);
+}
+
+
+// 3D simplex noise
+double SimplexNoise::Noise(double xin, double yin, double zin) const
+{
+    double n0, n1, n2, n3; // Noise contributions from the four corners
+    // Skew the input space to determine which simplex cell we're in
+    double s = (xin+yin+zin)*F3; // Very nice and simple skew factor for 3D
+    int i = FastFloor(xin+s);
+    int j = FastFloor(yin+s);
+    int k = FastFloor(zin+s);
+    double t = (i+j+k)*G3;
+    double X0 = i-t; // Unskew the cell origin back to (x,y,z) space
+    double Y0 = j-t;
+    double Z0 = k-t;
+    double x0 = xin-X0; // The x,y,z distances from the cell origin
+    double y0 = yin-Y0;
+    double z0 = zin-Z0;
+    // For the 3D case, the simplex shape is a slightly irregular tetrahedron.
+    // Determine which simplex we are in.
+    int i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords
+    int i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords
+    if(x0>=y0)
+    {
+        if(y0>=z0)
+        {
+            i1=1;    // X Y Z order
+            j1=0;
+            k1=0;
+            i2=1;
+            j2=1;
+            k2=0;
+        } else if(x0>=z0)
+        {
+            i1=1;    // X Z Y order
+            j1=0;
+            k1=0;
+            i2=1;
+            j2=0;
+            k2=1;
+        } else
+        {
+            i1=0;    // Z X Y order
+            j1=0;
+            k1=1;
+            i2=1;
+            j2=0;
+            k2=1;
+        }
+    } else     // x0<y0
+    {
+        if(y0<z0)
+        {
+            i1=0;    // Z Y X order
+            j1=0;
+            k1=1;
+            i2=0;
+            j2=1;
+            k2=1;
+        } else if(x0<z0)
+        {
+            i1=0;    // Y Z X order
+            j1=1;
+            k1=0;
+            i2=0;
+            j2=1;
+            k2=1;
+        } else
+        {
+            i1=0;    // Y X Z order
+            j1=1;
+            k1=0;
+            i2=1;
+            j2=1;
+            k2=0;
+        }
+    }
+    // A step of (1,0,0) in (i,j,k) means a step of (1-c,-c,-c) in (x,y,z),
+    // a step of (0,1,0) in (i,j,k) means a step of (-c,1-c,-c) in (x,y,z), and
+    // a step of (0,0,1) in (i,j,k) means a step of (-c,-c,1-c) in (x,y,z), where
+    // c = 1/6.
+    double x1 = x0 - i1 + G3; // Offsets for second corner in (x,y,z) coords
+    double y1 = y0 - j1 + G3;
+    double z1 = z0 - k1 + G3;
+    double x2 = x0 - i2 + 2.0*G3; // Offsets for third corner in (x,y,z) coords
+    double y2 = y0 - j2 + 2.0*G3;
+    double z2 = z0 - k2 + 2.0*G3;
+    double x3 = x0 - 1.0 + 3.0*G3; // Offsets for last corner in (x,y,z) coords
+    double y3 = y0 - 1.0 + 3.0*G3;
+    double z3 = z0 - 1.0 + 3.0*G3;
+    // Work out the hashed gradient indices of the four simplex corners
+    int ii = i & 255;
+    int jj = j & 255;
+    int kk = k & 255;
+    int gi0 = permMod12[ii+perm[jj+perm[kk]]];
+    int gi1 = permMod12[ii+i1+perm[jj+j1+perm[kk+k1]]];
+    int gi2 = permMod12[ii+i2+perm[jj+j2+perm[kk+k2]]];
+    int gi3 = permMod12[ii+1+perm[jj+1+perm[kk+1]]];
+    // Calculate the contribution from the four corners
+    double t0 = 0.6 - x0*x0 - y0*y0 - z0*z0;
+    if(t0<0) n0 = 0.0;
+    else
+    {
+        t0 *= t0;
+        n0 = t0 * t0 * Dot(grad3[gi0], x0, y0, z0);
+    }
+    double t1 = 0.6 - x1*x1 - y1*y1 - z1*z1;
+    if(t1<0) n1 = 0.0;
+    else
+    {
+        t1 *= t1;
+        n1 = t1 * t1 * Dot(grad3[gi1], x1, y1, z1);
+    }
+    double t2 = 0.6 - x2*x2 - y2*y2 - z2*z2;
+    if(t2<0) n2 = 0.0;
+    else
+    {
+        t2 *= t2;
+        n2 = t2 * t2 * Dot(grad3[gi2], x2, y2, z2);
+    }
+    double t3 = 0.6 - x3*x3 - y3*y3 - z3*z3;
+    if(t3<0) n3 = 0.0;
+    else
+    {
+        t3 *= t3;
+        n3 = t3 * t3 * Dot(grad3[gi3], x3, y3, z3);
+    }
+    // Add contributions from each corner to get the final noise value.
+    // The result is scaled to stay just inside [-1,1]
+    return 32.0*(n0 + n1 + n2 + n3);
+}
+
+double SimplexNoise::Noise(double x, double y, double z, double w) const
+{
+    double n0, n1, n2, n3, n4; // Noise contributions from the five corners
+    // Skew the (x,y,z,w) space to determine which cell of 24 simplices we're in
+    double s = (x + y + z + w) * F4; // Factor for 4D skewing
+    int i = FastFloor(x + s);
+    int j = FastFloor(y + s);
+    int k = FastFloor(z + s);
+    int l = FastFloor(w + s);
+    double t = (i + j + k + l) * G4; // Factor for 4D unskewing
+    double X0 = i - t; // Unskew the cell origin back to (x,y,z,w) space
+    double Y0 = j - t;
+    double Z0 = k - t;
+    double W0 = l - t;
+    double x0 = x - X0;    // The x,y,z,w distances from the cell origin
+    double y0 = y - Y0;
+    double z0 = z - Z0;
+    double w0 = w - W0;
+    // For the 4D case, the simplex is a 4D shape I won't even try to describe.
+    // To find out which of the 24 possible simplices we're in, we need to
+    // determine the magnitude ordering of x0, y0, z0 and w0.
+    // Six pair-wise comparisons are performed between each possible pair
+    // of the four coordinates, and the results are used to rank the numbers.
+    int rankx = 0;
+    int ranky = 0;
+    int rankz = 0;
+    int rankw = 0;
+    if(x0 > y0)
+    {
+        rankx++;
+    } else
+    {
+        ranky++;
+    }
+
+    if(x0 > z0)
+    {
+        rankx++;
+    } else
+    {
+        rankz++;
+    }
+
+    if(x0 > w0)
+    {
+        rankx++;
+    } else
+    {
+        rankw++;
+    }
+
+    if(y0 > z0)
+    {
+        ranky++;
+    } else
+    {
+        rankz++;
+    }
+
+    if(y0 > w0)
+    {
+        ranky++;
+    } else
+    {
+        rankw++;
+    }
+
+    if(z0 > w0)
+    {
+        rankz++;
+    } else
+    {
+        rankw++;
+    }
+
+    int i1, j1, k1, l1; // The integer offsets for the second simplex corner
+    int i2, j2, k2, l2; // The integer offsets for the third simplex corner
+    int i3, j3, k3, l3; // The integer offsets for the fourth simplex corner
+    // simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order.
+    // Many values of c will never occur, since e.g. x>y>z>w makes x<z, y<w and x<w
+    // impossible. Only the 24 indices which have non-zero entries make any sense.
+    // We use a thresholding to set the coordinates in turn from the largest magnitude.
+    // Rank 3 denotes the largest coordinate.
+    i1 = rankx >= 3 ? 1 : 0;
+    j1 = ranky >= 3 ? 1 : 0;
+    k1 = rankz >= 3 ? 1 : 0;
+    l1 = rankw >= 3 ? 1 : 0;
+    // Rank 2 denotes the second largest coordinate.
+    i2 = rankx >= 2 ? 1 : 0;
+    j2 = ranky >= 2 ? 1 : 0;
+    k2 = rankz >= 2 ? 1 : 0;
+    l2 = rankw >= 2 ? 1 : 0;
+    // Rank 1 denotes the second smallest coordinate.
+    i3 = rankx >= 1 ? 1 : 0;
+    j3 = ranky >= 1 ? 1 : 0;
+    k3 = rankz >= 1 ? 1 : 0;
+    l3 = rankw >= 1 ? 1 : 0;
+    // The fifth corner has all coordinate offsets = 1, so no need to compute that.
+    double x1 = x0 - i1 + G4; // Offsets for second corner in (x,y,z,w) coords
+    double y1 = y0 - j1 + G4;
+    double z1 = z0 - k1 + G4;
+    double w1 = w0 - l1 + G4;
+    double x2 = x0 - i2 + 2.0*G4; // Offsets for third corner in (x,y,z,w) coords
+    double y2 = y0 - j2 + 2.0*G4;
+    double z2 = z0 - k2 + 2.0*G4;
+    double w2 = w0 - l2 + 2.0*G4;
+    double x3 = x0 - i3 + 3.0*G4; // Offsets for fourth corner in (x,y,z,w) coords
+    double y3 = y0 - j3 + 3.0*G4;
+    double z3 = z0 - k3 + 3.0*G4;
+    double w3 = w0 - l3 + 3.0*G4;
+    double x4 = x0 - 1.0 + 4.0*G4; // Offsets for last corner in (x,y,z,w) coords
+    double y4 = y0 - 1.0 + 4.0*G4;
+    double z4 = z0 - 1.0 + 4.0*G4;
+    double w4 = w0 - 1.0 + 4.0*G4;
+    // Work out the hashed gradient indices of the five simplex corners
+    int ii = i & 255;
+    int jj = j & 255;
+    int kk = k & 255;
+    int ll = l & 255;
+    int gi0 = perm[ii+perm[jj+perm[kk+perm[ll]]]] % 32;
+    int gi1 = perm[ii+i1+perm[jj+j1+perm[kk+k1+perm[ll+l1]]]] % 32;
+    int gi2 = perm[ii+i2+perm[jj+j2+perm[kk+k2+perm[ll+l2]]]] % 32;
+    int gi3 = perm[ii+i3+perm[jj+j3+perm[kk+k3+perm[ll+l3]]]] % 32;
+    int gi4 = perm[ii+1+perm[jj+1+perm[kk+1+perm[ll+1]]]] % 32;
+    // Calculate the contribution from the five corners
+    double t0 = 0.6 - x0*x0 - y0*y0 - z0*z0 - w0*w0;
+    if(t0<0) n0 = 0.0;
+    else {
+        t0 *= t0;
+        n0 = t0 * t0 * Dot(grad4[gi0], x0, y0, z0, w0);
+    }
+    double t1 = 0.6 - x1*x1 - y1*y1 - z1*z1 - w1*w1;
+    if(t1<0) n1 = 0.0;
+    else {
+        t1 *= t1;
+        n1 = t1 * t1 * Dot(grad4[gi1], x1, y1, z1, w1);
+    }
+    double t2 = 0.6 - x2*x2 - y2*y2 - z2*z2 - w2*w2;
+    if(t2<0) n2 = 0.0;
+    else {
+        t2 *= t2;
+        n2 = t2 * t2 * Dot(grad4[gi2], x2, y2, z2, w2);
+    }
+    double t3 = 0.6 - x3*x3 - y3*y3 - z3*z3 - w3*w3;
+    if(t3<0) n3 = 0.0;
+    else {
+        t3 *= t3;
+        n3 = t3 * t3 * Dot(grad4[gi3], x3, y3, z3, w3);
+    }
+    double t4 = 0.6 - x4*x4 - y4*y4 - z4*z4 - w4*w4;
+    if(t4<0) n4 = 0.0;
+    else {
+        t4 *= t4;
+        n4 = t4 * t4 * Dot(grad4[gi4], x4, y4, z4, w4);
+    }
+    // Sum up and scale the result to cover the range [-1,1]
+    return 27.0 * (n0 + n1 + n2 + n3 + n4);
+}
diff --git a/src/osgEarthUtil/Sky b/src/osgEarthUtil/Sky
new file mode 100644
index 0000000..dcab1df
--- /dev/null
+++ b/src/osgEarthUtil/Sky
@@ -0,0 +1,220 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTHUTIL_SKY
+#define OSGEARTHUTIL_SKY
+
+#include <osgEarthUtil/Common>
+#include <osgEarthUtil/Ephemeris>
+#include <osgEarth/DateTime>
+#include <osgEarth/GeoData>
+#include <osgEarth/Config>
+#include <osg/Group>
+#include <osg/Uniform>
+#include <osg/View>
+#include <osgDB/ReaderWriter>
+
+namespace osgEarth {
+    class MapNode;
+    class UpdateLightingUniformsHelper;
+}
+namespace osgDB {
+    class Options;
+}
+
+namespace osgEarth { namespace Util 
+{
+    using namespace osgEarth;
+
+
+    /**
+     * Base Options structure for loading an environment node from
+     * a plugin.
+     */
+    class SkyOptions : public DriverConfigOptions
+    {
+    public:
+
+        /** Time of day - Hours [0..24] component of DateTime */
+        optional<float>& hours() { return _hours; }
+        const optional<float>& hours() const { return _hours; }
+
+        /** Ambient light level [0..1] */
+        optional<float>& ambient() { return _ambient; }
+        const optional<float>& ambient() const { return _ambient; }
+
+    public:
+        SkyOptions( const ConfigOptions& options =ConfigOptions() ) : DriverConfigOptions(options) {
+            fromConfig(_conf);
+        }
+        virtual ~SkyOptions() { }
+        virtual Config getConfig() const {
+            Config conf = DriverConfigOptions::getConfig();
+            conf.addIfSet("hours", _hours);
+            conf.addIfSet("ambient", _ambient);
+            return conf;
+        }
+
+    protected:
+        virtual void mergeConfig( const Config& conf ) {
+            DriverConfigOptions::mergeConfig( conf );
+            fromConfig( conf );
+        }
+        
+    private:
+        void fromConfig( const Config& conf ) {
+            conf.getIfSet("hours", _hours);
+            conf.getIfSet("ambient", _ambient);
+        }
+        
+        optional<float> _hours;
+        optional<float> _ambient;
+    };
+
+
+    /**
+    * Interface for classes that provide sky, lighting, and other 
+    * environmental effect.
+    */
+    class OSGEARTHUTIL_EXPORT SkyNode : public osg::Group
+    {
+    public:
+        /**
+         * Creates a new SkyNode with the default built-in sky driver
+         * and default options.
+         */
+        static SkyNode* create(
+            osgEarth::MapNode* mapNode );
+        
+        /**
+         * Creates a new SkyNode with custom options.
+         */
+        static SkyNode* create(
+            const SkyOptions&  options,
+            osgEarth::MapNode* mapNode );
+        
+        /**
+         * Creates a new SkyNode with a named driver.
+         */
+        static SkyNode* create(
+            const std::string& driver,
+            osgEarth::MapNode* mapNode );
+
+    protected:
+        // CTOR (abstract base class)
+        SkyNode();
+        SkyNode(const SkyOptions& options);
+
+        // protected DTOR (heap-only)
+        virtual ~SkyNode();
+
+    public:
+        /**
+         * Gets/Sets the Ephemeris used to position the sun and the moon
+         * based on date/time.
+         */
+        void setEphemeris(Ephemeris* ephemeris);
+        const Ephemeris* getEphemeris() const;
+
+        /**
+         * The ephemeris reference point for projected maps. Not applicable
+         * for geocentric maps.
+         */
+        void setReferencePoint(const GeoPoint& point);
+        const GeoPoint& getReferencePoint() const { return *_refpoint; }
+       
+        /**
+         * Whether the sky lights its subgraph.
+         */
+        void setLighting(osg::StateAttribute::OverrideValue value);
+        osg::StateAttribute::OverrideValue getLighting() const { return _lightingValue; }
+
+        /**
+         * Gets the date/time for which the enviroment is configured.
+         * Pass in an optional View to get the date/time specific to
+         * that View.
+         */
+        void setDateTime(const DateTime& dt);
+        const DateTime& getDateTime() const { return _dateTime; }
+
+        /** Whether the sun is visible */
+        void setSunVisible(bool value);
+        bool getSunVisible() const { return _sunVisible; }
+
+        /** Whether the moon is visible */
+        void setMoonVisible(bool value);
+        bool getMoonVisible() const { return _moonVisible; }
+
+        /** Whether the stars are visible */
+        void setStarsVisible(bool value);
+        bool getStarsVisible() const { return _starsVisible; }
+
+        /** Access the osg::Light representing the sun */
+        virtual osg::Light* getSunLight() = 0;
+
+    public:
+        
+        /** Attaches this sky node to a view (placing a sky light). Optional */
+        virtual void attach(osg::View* view, int lightNum) { }
+        void attach(osg::View* view) { attach(view, 0); }
+
+    public: // osg::Node
+
+        virtual void traverse(osg::NodeVisitor&);
+
+    protected:
+
+        // impl class can override these events.
+        virtual void onSetEphemeris() { }
+        virtual void onSetDateTime() { }
+        virtual void onSetReferencePoint() { }
+        virtual void onSetMoonVisible() { }
+        virtual void onSetStarsVisible() { }
+        virtual void onSetSunVisible() { }
+
+    private:
+
+        osg::ref_ptr<Ephemeris> _ephemeris;
+        DateTime                _dateTime;
+        bool                    _sunVisible;
+        bool                    _moonVisible;
+        bool                    _starsVisible;
+        optional<GeoPoint>      _refpoint;
+
+        osg::StateAttribute::OverrideValue _lightingValue;
+        osg::ref_ptr<osg::Uniform>         _lightingUniform;
+
+        osg::ref_ptr<UpdateLightingUniformsHelper> _lightingUniformsHelper;
+
+        void baseInit(const SkyOptions&);
+    };
+
+
+    /**
+     * Base class for an sky driver plugin implementation.
+     */
+    class OSGEARTHUTIL_EXPORT SkyDriver : public osgDB::ReaderWriter
+    {
+    protected:
+        MapNode* getMapNode(const osgDB::Options* opt) const;
+        const SkyOptions& getSkyOptions(const osgDB::Options* opt) const;
+    };
+
+} } // namespace osgEarth::Util
+
+#endif // OSGEARTHUTIL_SKY
diff --git a/src/osgEarthUtil/Sky.cpp b/src/osgEarthUtil/Sky.cpp
new file mode 100644
index 0000000..b790b73
--- /dev/null
+++ b/src/osgEarthUtil/Sky.cpp
@@ -0,0 +1,204 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include <osgEarthUtil/Sky>
+#include <osgEarthUtil/Ephemeris>
+#include <osgEarth/Registry>
+#include <osgEarth/ShaderFactory>
+#include <osgEarth/ShaderUtils>
+#include <osgDB/ReadFile>
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+#undef  LC
+#define LC "[SkyNode] "
+
+
+SkyNode::SkyNode()
+{
+    baseInit(SkyOptions());
+}
+
+SkyNode::SkyNode(const SkyOptions& options)
+{
+    baseInit(options);
+}
+
+SkyNode::~SkyNode()
+{
+    //nop
+}
+
+void
+SkyNode::baseInit(const SkyOptions& options)
+{
+    _ephemeris = new Ephemeris();
+    _sunVisible = true;
+    _moonVisible = true;
+    _starsVisible = true;
+
+    setLighting( osg::StateAttribute::ON );
+
+    if ( options.hours().isSet() )
+    {
+        float hours = osg::clampBetween(options.hours().get(), 0.0f, 24.0f);
+        _dateTime = DateTime(_dateTime.year(), _dateTime.month(), _dateTime.day(), (double)hours);
+        // (don't call setDateTime since we are called from the CTOR)
+    }
+}
+
+void
+SkyNode::setEphemeris(Ephemeris* ephemeris)
+{
+    // cannot be null.
+    _ephemeris = ephemeris ? ephemeris : new Ephemeris();
+    onSetEphemeris();
+}
+
+const Ephemeris*
+SkyNode::getEphemeris() const
+{
+    return _ephemeris.get();
+}
+
+void
+SkyNode::setDateTime(const DateTime& dt)
+{
+    _dateTime = dt;
+    //OE_INFO << LC << "Time = " << dt.asRFC1123() << std::endl;
+    onSetDateTime();
+}
+
+void
+SkyNode::setReferencePoint(const GeoPoint& value)
+{
+    _refpoint = value;
+    onSetReferencePoint();
+}
+
+void
+SkyNode::setLighting(osg::StateAttribute::OverrideValue value)
+{
+    _lightingValue = value;
+    _lightingUniform = Registry::shaderFactory()->createUniformForGLMode(
+        GL_LIGHTING, value );
+
+    this->getOrCreateStateSet()->addUniform( _lightingUniform.get() );
+}
+
+void
+SkyNode::setSunVisible(bool value)
+{
+    _sunVisible = value;
+    onSetSunVisible();
+}
+
+void
+SkyNode::setMoonVisible(bool value)
+{
+    _moonVisible = value;
+    onSetMoonVisible();
+}
+
+void
+SkyNode::setStarsVisible(bool value)
+{
+    _starsVisible = value;
+    onSetStarsVisible();
+}
+
+void
+SkyNode::traverse(osg::NodeVisitor& nv)
+{
+    if ( nv.getVisitorType() == nv.CULL_VISITOR )
+    {
+        // update the light model uniforms.
+        if ( _lightingUniformsHelper.valid() )
+        {
+            _lightingUniformsHelper->cullTraverse( this, &nv );
+        }
+    }
+    osg::Group::traverse(nv);
+}
+
+//------------------------------------------------------------------------
+
+#define MAPNODE_TAG     "__osgEarth::MapNode"
+#define SKY_OPTIONS_TAG "__osgEarth::Util::SkyOptions"
+
+SkyNode*
+SkyNode::create(const SkyOptions& options,
+                MapNode*          mapNode)
+{
+    SkyNode* result = 0L;
+
+    std::string driverName = options.getDriver();
+    if ( driverName.empty() )
+        driverName = "simple";
+
+    std::string driverExt = std::string(".osgearth_sky_") + driverName;
+
+    osg::ref_ptr<osgDB::Options> rwopts = Registry::instance()->cloneOrCreateOptions();
+    rwopts->setPluginData( MAPNODE_TAG, (void*)mapNode );
+    rwopts->setPluginData( SKY_OPTIONS_TAG, (void*)&options );
+
+    result = dynamic_cast<SkyNode*>( osgDB::readNodeFile( driverExt, rwopts.get() ) );
+    if ( result )
+    {
+        OE_INFO << LC << "Loaded sky driver \"" << driverName << "\" OK." << std::endl;
+    }
+    else
+    {
+        OE_WARN << LC << "FAIL, unable to load sky driver for \"" << driverName << "\"" << std::endl;
+    }
+
+    return result;
+}
+
+SkyNode*
+SkyNode::create(MapNode* mapNode)
+{
+    SkyOptions options;
+    return create(options, mapNode);
+}
+
+SkyNode*
+SkyNode::create(const std::string& driver, MapNode* mapNode)
+{
+    SkyOptions options;
+    options.setDriver( driver );
+    return create( options, mapNode );
+}
+
+
+//------------------------------------------------------------------------
+
+const SkyOptions&
+SkyDriver::getSkyOptions(const osgDB::Options* options) const
+{
+    return *static_cast<const SkyOptions*>( options->getPluginData(SKY_OPTIONS_TAG) );
+}
+
+
+MapNode*
+SkyDriver::getMapNode(const osgDB::Options* options) const
+{
+    return const_cast<MapNode*>(
+        static_cast<const MapNode*>( options->getPluginData(MAPNODE_TAG) ) );
+}
diff --git a/src/osgEarthUtil/SkyNode b/src/osgEarthUtil/SkyNode
deleted file mode 100644
index 472fa64..0000000
--- a/src/osgEarthUtil/SkyNode
+++ /dev/null
@@ -1,221 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-* GNU Lesser General Public License for more details.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#ifndef OSGEARTHUTIL_SKY_NODE
-#define OSGEARTHUTIL_SKY_NODE
-
-#include <osgEarthUtil/Common>
-#include <osgEarth/DateTime>
-#include <osgEarth/Map>
-#include <osg/MatrixTransform>
-#include <osg/Uniform>
-#include <osg/Group>
-#include <osg/View>
-
-namespace osgEarth { namespace Util 
-{
-    using namespace osgEarth;
-
-    /**
-    * Class that provides information about astronomical objects at a given time.
-    */
-    class OSGEARTHUTIL_EXPORT EphemerisProvider : public osg::Referenced
-    {
-    public:
-        /**
-        * Gets the moon position in geocentric coordinates at the given time
-        */
-        virtual osg::Vec3d getMoonPosition( const DateTime& dt ) = 0;
-
-        /**
-        * Gets the sun position in geocentric coordinates at the given time
-        */
-        virtual osg::Vec3d getSunPosition( const DateTime& dt ) = 0;
-    };
-
-
-    /**
-    * The default EphemerisProvider, provides positions based on freely available models
-    */
-    class OSGEARTHUTIL_EXPORT DefaultEphemerisProvider : public EphemerisProvider
-    {
-    public:
-        /**
-        * Gets the moon position in geocentric coordinates at the given time
-        */
-        virtual osg::Vec3d getMoonPosition( const DateTime& dt );
-
-        /**
-        * Gets the sun position in geocentric coordinates at the given time
-        */
-        virtual osg::Vec3d getSunPosition( const DateTime& dt );
-    };
-
-
-    /**
-     * A sky model.
-     */
-    class OSGEARTHUTIL_EXPORT SkyNode : public osg::Group
-    {
-    public:
-        /** Creates a new sky node based on the provided map. */
-        SkyNode( Map* map, const std::string& starFile="", float minStarMagnitude=-1.0f );
-        SkyNode( Map* map, float minStarMagnitude );
-
-        /** dtor */
-        virtual ~SkyNode() { }
-
-
-      
-    public:
-        /**
-         * Gets/Sets the EphemerisProvider
-         */
-        EphemerisProvider* getEphemerisProvider() const;
-        void setEphemerisProvider(EphemerisProvider* ephemerisProvider );
-
-        /**
-         * Gets a position from the right ascension, declination and range
-         * @param ra
-         *        Right ascension in radians
-         * @param decl
-         *        Declination in radians
-         * @param range
-         *        Range in meters
-         */
-        static osg::Vec3d getPositionFromRADecl( double ra, double decl, double range );
-
-    public:
-
-        /** Attached this sky node to a view (placing a sky light). */
-        void attach( osg::View* view, int lightNum =0 );
-       
-        /** Gets the date time for the sky position  */
-        void getDateTime(DateTime& output, osg::View* view =0L) const;
-
-        /** Sets the sky's position based on a julian date. */
-        void setDateTime(const DateTime& dt, osg::View* view =0L );
-
-        /** The minimum brightness for non-sunlit areas. */
-        void setAmbientBrightness( float value, osg::View* view =0L );
-        float getAmbientBrightness( osg::View* view =0L ) const;
-
-        /** Enables or disables automatic ambience */
-        void setAutoAmbience(bool value);
-        bool getAutoAmbience() const;
-
-        /** Whether the moon is visible */
-        void setMoonVisible( bool value, osg::View* view =0L );
-        bool getMoonVisible( osg::View* view =0L ) const;
-
-        /** Whether the stars are visible */
-        void setStarsVisible( bool value, osg::View* view =0L );
-        bool getStarsVisible( osg::View* view =0L ) const;
-
-    public: // deprecated
-
-        /** @deprecated Please use setDateTime(const DateTime&) above */
-        void setDateTime( int year, int month, int date, double hoursUTC, osg::View* view =0L );
-        /** @deprecated Please use getDateTime(DateTime&) above */
-        void getDateTime( int &year, int &month, int &date, double &hoursUTC, osg::View* view=0L );
-
-    public:
-        //override
-        virtual void traverse( osg::NodeVisitor& nv );
-
-        //override
-        virtual osg::BoundingSphere computeBound() const;
-
-    private:
-
-        /** Sets the sun's position as a unit vector. */
-        void setSunPosition( const osg::Vec3& pos, osg::View* view =0L );
-
-        /** Sets the moon position as a geocentric coordinate */
-        void setMoonPosition( const osg::Vec3d& pos, osg::View* view =0L );
-
-        /** Sets the sun's position as a latitude and longitude. */         
-        void setSunPosition( double lat_degrees, double lon_degrees, osg::View* view =0L );        
-
-
-        struct StarData
-        {
-            std::string name;
-            double right_ascension;
-            double declination;
-            double magnitude;
-            
-            StarData() { }
-            StarData( std::stringstream &ss );
-        };
-
-        struct PerViewData
-        {
-            osg::Vec3f                         _lightPos;
-            osg::ref_ptr<osg::Light>           _light;
-            osg::ref_ptr<osg::Uniform>         _lightPosUniform;
-            osg::Matrixd                       _sunMatrix;
-            osg::Matrixd                       _moonMatrix;
-            osg::Matrixd                       _starsMatrix;
-            bool                               _starsVisible;
-            bool                               _moonVisible;
-
-            // only available in per-view structures..not default
-            osg::ref_ptr<osg::Group>           _cullContainer;
-            osg::ref_ptr<osg::MatrixTransform> _sunXform;
-            osg::ref_ptr<osg::MatrixTransform> _moonXform;
-            osg::ref_ptr<osg::MatrixTransform> _starsXform;
-
-            DateTime _date;
-        };
-
-        PerViewData _defaultPerViewData;
-        typedef std::map<osg::View*, PerViewData> PerViewDataMap;
-        PerViewDataMap _perViewData;
-
-        float _innerRadius, _outerRadius, _sunDistance, _starRadius, _minStarMagnitude;
-        osg::ref_ptr<osg::Node> _sun, _stars, _atmosphere, _moon;
-        osg::ref_ptr<osg::Uniform> _starAlpha;
-        osg::ref_ptr<osg::Uniform> _starPointSize;
-
-        osg::Vec3d _moonPosition;
-        bool _autoAmbience;
-
-        osg::ref_ptr< const osg::EllipsoidModel > _ellipsoidModel;
-
-        void initialize( Map* map, const std::string& starFile="" );
-
-        void makeAtmosphere( const osg::EllipsoidModel* );
-        void makeSun();
-        void makeMoon();
-
-        void makeStars(const std::string& starFile);
-        osg::Node* buildStarGeometry(const std::vector<StarData>& stars);
-        void getDefaultStars(std::vector<StarData>& out_stars);
-        bool parseStarFile(const std::string& starFile, std::vector<StarData>& out_stars);
-
-        void setAmbientBrightness( PerViewData& data, float value );
-        void setSunPosition( PerViewData& data, const osg::Vec3& pos );
-        void setMoonPosition( PerViewData& data, const osg::Vec3d& pos );
-
-        osg::ref_ptr< EphemerisProvider > _ephemerisProvider;
-    };
-
-} } // namespace osgEarth::Util
-
-#endif //OSGEARTHUTIL_SKY_NODE
diff --git a/src/osgEarthUtil/SkyNode.cpp b/src/osgEarthUtil/SkyNode.cpp
deleted file mode 100644
index 4f44c82..0000000
--- a/src/osgEarthUtil/SkyNode.cpp
+++ /dev/null
@@ -1,1593 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-* GNU Lesser General Public License for more details.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#include <osgEarthUtil/SkyNode>
-#include <osgEarthUtil/StarData>
-#include <osgEarthUtil/LatLongFormatter>
-
-#include <osgEarth/VirtualProgram>
-#include <osgEarth/NodeUtils>
-#include <osgEarth/MapNode>
-#include <osgEarth/Utils>
-#include <osgEarth/Registry>
-#include <osgEarth/Capabilities>
-#include <osgEarth/CullingUtils>
-
-#include <osg/MatrixTransform>
-#include <osg/ShapeDrawable>
-#include <osg/PointSprite>
-#include <osg/BlendFunc>
-#include <osg/FrontFace>
-#include <osg/CullFace>
-#include <osg/Program>
-#include <osg/Camera>
-#include <osg/Point>
-#include <osg/Shape>
-#include <osg/Depth>
-#include <osg/Quat>
-
-#include <sstream>
-#include <time.h>
-
-#define LC "[SkyNode] "
-
-using namespace osgEarth;
-using namespace osgEarth::Util;
-
-//---------------------------------------------------------------------------
-
-#define BIN_STARS       -100003
-#define BIN_SUN         -100002
-#define BIN_MOON        -100001
-#define BIN_ATMOSPHERE  -100000
-
-//---------------------------------------------------------------------------
-
-namespace
-{
-    // constucts an ellipsoidal mesh that we will use to draw the atmosphere
-    osg::Geometry*
-    s_makeEllipsoidGeometry( const osg::EllipsoidModel* ellipsoid, double outerRadius, bool genTexCoords = false )
-    {
-        double hae = outerRadius - ellipsoid->getRadiusEquator();
-
-        osg::Geometry* geom = new osg::Geometry();
-        geom->setUseVertexBufferObjects(true);
-
-        int latSegments = 100;
-        int lonSegments = 2 * latSegments;
-
-        double segmentSize = 180.0/(double)latSegments; // degrees
-
-        osg::Vec3Array* verts = new osg::Vec3Array();
-        verts->reserve( latSegments * lonSegments );
-
-        osg::Vec2Array* texCoords = 0;
-        osg::Vec3Array* normals = 0;
-        if (genTexCoords)
-        {
-            texCoords = new osg::Vec2Array();
-            texCoords->reserve( latSegments * lonSegments );
-            geom->setTexCoordArray( 0, texCoords );
-
-            normals = new osg::Vec3Array();
-            normals->reserve( latSegments * lonSegments );
-            geom->setNormalArray( normals );
-            geom->setNormalBinding(osg::Geometry::BIND_PER_VERTEX );
-        }
-
-        osg::DrawElementsUShort* el = new osg::DrawElementsUShort( GL_TRIANGLES );
-        el->reserve( latSegments * lonSegments * 6 );
-
-        for( int y = 0; y <= latSegments; ++y )
-        {
-            double lat = -90.0 + segmentSize * (double)y;
-            for( int x = 0; x < lonSegments; ++x )
-            {
-                double lon = -180.0 + segmentSize * (double)x;
-                double gx, gy, gz;
-                ellipsoid->convertLatLongHeightToXYZ( osg::DegreesToRadians(lat), osg::DegreesToRadians(lon), hae, gx, gy, gz );
-                verts->push_back( osg::Vec3(gx, gy, gz) );
-
-                if (genTexCoords)
-                {
-                    double s = (lon + 180) / 360.0;
-                    double t = (lat + 90.0) / 180.0;
-                    texCoords->push_back( osg::Vec2(s, t ) );
-                }
-
-                if (normals)
-                {
-                    osg::Vec3 normal( gx, gy, gz);
-                    normal.normalize();
-                    normals->push_back( normal );
-                }
-
-
-                if ( y < latSegments )
-                {
-                    int x_plus_1 = x < lonSegments-1 ? x+1 : 0;
-                    int y_plus_1 = y+1;
-                    el->push_back( y*lonSegments + x );
-                    el->push_back( y_plus_1*lonSegments + x );
-                    el->push_back( y*lonSegments + x_plus_1 );
-                    el->push_back( y*lonSegments + x_plus_1 );
-                    el->push_back( y_plus_1*lonSegments + x );
-                    el->push_back( y_plus_1*lonSegments + x_plus_1 );
-                }
-            }
-        }
-
-        geom->setVertexArray( verts );
-        geom->addPrimitiveSet( el );
-
-//        OSG_ALWAYS << "s_makeEllipsoidGeometry Bounds: " << geom->computeBound().radius() << " outerRadius: " << outerRadius << std::endl;
-        
-        return geom;
-    }
-
-    // makes a disc geometry that we'll use to render the sun/moon
-    osg::Geometry*
-    s_makeDiscGeometry( double radius )
-    {
-        int segments = 48;
-        float deltaAngle = 360.0/(float)segments;
-
-        osg::Geometry* geom = new osg::Geometry();
-        geom->setUseVertexBufferObjects(true);
-
-        osg::Vec3Array* verts = new osg::Vec3Array();
-        verts->reserve( 1 + segments );
-        geom->setVertexArray( verts );
-
-        osg::DrawElementsUShort* el = new osg::DrawElementsUShort( GL_TRIANGLES );
-        el->reserve( 1 + 2*segments );
-        geom->addPrimitiveSet( el );
-
-        verts->push_back( osg::Vec3(0,0,0) ); // center point
-
-        for( int i=0; i<segments; ++i )
-        {
-            double angle = osg::DegreesToRadians( deltaAngle * (float)i );
-            double x = radius * cos( angle );
-            double y = radius * sin( angle );
-            verts->push_back( osg::Vec3(x, y, 0.0) );
-
-            int i_plus_1 = i < segments-1? i+1 : 0;
-            el->push_back( 0 );
-            el->push_back( 1 + i_plus_1 );
-            el->push_back( 1 + i );
-        }
-
-        return geom;
-    }
-}
-
-//---------------------------------------------------------------------------
-
-
-// Astronomical Math
-// http://www.stjarnhimlen.se/comp/ppcomp.html
-namespace
-{
-#define d2r(X) osg::DegreesToRadians(X)
-#define r2d(X) osg::RadiansToDegrees(X)
-#define nrad(X) { while( X > TWO_PI ) X -= TWO_PI; while( X < 0.0 ) X += TWO_PI; }
-#define nrad2(X) { while( X <= -osg::PI ) X += TWO_PI; while( X > osg::PI ) X -= TWO_PI; }
-
-    static const double TWO_PI = (2.0*osg::PI);
-    static const double JD2000 = 2451545.0;
-
-
-    double sgCalcEccAnom(double M, double e)
-    {
-        double eccAnom, E0, E1, diff;
-
-        double epsilon = osg::DegreesToRadians(0.001);
-        
-        eccAnom = M + e * sin(M) * (1.0 + e * cos (M));
-        // iterate to achieve a greater precision for larger eccentricities 
-        if (e > 0.05)
-        {
-            E0 = eccAnom;
-            do
-            {
-                 E1 = E0 - (E0 - e * sin(E0) - M) / (1 - e *cos(E0));
-                 diff = fabs(E0 - E1);
-                 E0 = E1;
-            } while (diff > epsilon );
-            return E0;
-        }
-        return eccAnom;
-    }
-
-    //double getTimeScale( int year, int month, int date, double hoursUT )
-    //{
-    //    int a = 367*year - 7 * ( year + (month+9)/12 ) / 4 + 275*month/9 + date - 730530;
-    //    return (double)a + hoursUT/24.0;
-    //}
-
-    double getJulianDate( int year, int month, int date )
-    {
-        if ( month <= 2 )
-        {
-            month += 12;
-            year -= 1;
-        }
-
-        int A = int(year/100);
-        int B = 2-A+(A/4);
-        int C = int(365.25*(year+4716));
-        int D = int(30.6001*(month+1));
-        return B + C + D + date - 1524.5;
-    }
-
-    struct Sun
-    {
-        Sun() { }
-
-        // https://www.cfa.harvard.edu/~wsoon/JuanRamirez09-d/Chang09-OptimalTiltAngleforSolarCollector.pdf
-        osg::Vec3d getPosition(int year, int month, int date, double hoursUTC ) const
-        {
-            double JD = getJulianDate(year, month, date);
-            double JD1 = (JD - JD2000);                         // julian time since JD2000 epoch
-            double JC = JD1/36525.0;                            // julian century
-
-            double mu = 282.937348 + 0.00004707624*JD1 + 0.0004569*(JC*JC);
-
-            double epsilon = 280.466457 + 0.985647358*JD1 + 0.000304*(JC*JC);
-
-            // orbit eccentricity:
-            double E = 0.01670862 - 0.00004204 * JC;
-
-            // mean anomaly of the perihelion
-            double M = epsilon - mu;
-
-            // perihelion anomaly:
-            double v =
-                M + 
-                360.0*E*sin(d2r(M))/osg::PI + 
-                900.0*(E*E)*sin(d2r(2*M))/4*osg::PI - 
-                180.0*(E*E*E)*sin(d2r(M))/4.0*osg::PI;
-
-            // longitude of the sun in ecliptic coordinates:
-            double sun_lon = d2r(v - 360.0 + mu); // lambda
-            nrad2(sun_lon);
-
-            // angle between the ecliptic plane and the equatorial plane
-            double zeta = d2r(23.4392); // zeta
-
-            // latitude of the sun on the ecliptic plane:
-            double omega = d2r(0.0);
-
-            // latitude of the sun with respect to the equatorial plane (solar declination):
-            double sun_lat = asin( sin(sun_lon)*sin(zeta) );
-            nrad2(sun_lat);
-
-            // finally, adjust for the time of day (rotation of the earth)
-            double time_r = hoursUTC/24.0; // 0..1
-            nrad(sun_lon); // clamp to 0..TWO_PI
-            double sun_r = sun_lon/TWO_PI; // convert to 0..1
-
-            // rotational difference between UTC and current time
-            double diff_r = sun_r - time_r;
-            double diff_lon = TWO_PI * diff_r;
-
-            // apparent sun longitude.
-            double app_sun_lon = sun_lon - diff_lon + osg::PI;
-            nrad2(app_sun_lon);
-
-#if 0
-            OE_INFO
-                << "sun lat = " << r2d(sun_lat) 
-                << ", sun lon = " << r2d(sun_lon)
-                << ", time delta_lon = " << r2d(diff_lon)
-                << ", app sun lon = " << r2d(app_sun_lon)
-                << std::endl;
-#endif
-
-            return osg::Vec3d(
-                cos(sun_lat) * cos(-app_sun_lon),
-                cos(sun_lat) * sin(-app_sun_lon),
-                sin(sun_lat) );
-        }
-    };
-
-    struct Moon
-    {
-        Moon() { }
-
-        static std::string radiansToHoursMinutesSeconds(double ra)
-        {
-            while (ra < 0) ra += (osg::PI * 2.0);
-            //Get the total number of hours
-            double hours = (ra / (osg::PI * 2.0) ) * 24.0;
-            double minutes = hours - (int)hours;
-            hours -= minutes;
-            minutes *= 60.0;
-            double seconds = minutes - (int)minutes;
-            seconds *= 60.0;
-            std::stringstream buf;
-            buf << (int)hours << ":" << (int)minutes << ":" << (int)seconds;
-            return buf.str();
-        }
-
-        // From http://www.stjarnhimlen.se/comp/ppcomp.html
-        osg::Vec3d getPosition(int year, int month, int date, double hoursUTC ) const
-        {
-            //double julianDate = getJulianDate( year, month, date );
-            //julianDate += hoursUTC /24.0;
-            double d = 367*year - 7 * ( year + (month+9)/12 ) / 4 + 275*month/9 + date - 730530;
-            d += (hoursUTC / 24.0);                     
-
-            double ecl = osg::DegreesToRadians(23.4393 - 3.563E-7 * d);
-
-            double N = osg::DegreesToRadians(125.1228 - 0.0529538083 * d);
-            double i = osg::DegreesToRadians(5.1454);
-            double w = osg::DegreesToRadians(318.0634 + 0.1643573223 * d);
-            double a = 60.2666;//  (Earth radii)
-            double e = 0.054900;
-            double M = osg::DegreesToRadians(115.3654 + 13.0649929509 * d);
-
-            double E = M + e*(180.0/osg::PI) * sin(M) * ( 1.0 + e * cos(M) );
-            
-            double xv = a * ( cos(E) - e );
-            double yv = a * ( sqrt(1.0 - e*e) * sin(E) );
-
-            double v = atan2( yv, xv );
-            double r = sqrt( xv*xv + yv*yv );
-
-            //Compute the geocentric (Earth-centered) position of the moon in the ecliptic coordinate system
-            double xh = r * ( cos(N) * cos(v+w) - sin(N) * sin(v+w) * cos(i) );
-            double yh = r * ( sin(N) * cos(v+w) + cos(N) * sin(v+w) * cos(i) );
-            double zh = r * ( sin(v+w) * sin(i) );
-
-            // calculate the ecliptic latitude and longitude here
-            double lonEcl = atan2 (yh, xh);
-            double latEcl = atan2(zh, sqrt(xh*xh + yh*yh));
-
-            double xg = r * cos(lonEcl) * cos(latEcl);
-            double yg = r * sin(lonEcl) * cos(latEcl);
-            double zg = r * sin(latEcl);
-
-            double xe = xg;
-            double ye = yg * cos(ecl) -zg * sin(ecl);
-            double ze = yg * sin(ecl) +zg * cos(ecl);
-
-            double RA    = atan2(ye, xe);
-            double Dec = atan2(ze, sqrt(xe*xe + ye*ye));
-
-            //Just use the average distance from the earth            
-            double rg = 6378137.0 + 384400000.0;
-            
-            // finally, adjust for the time of day (rotation of the earth)
-            double time_r = hoursUTC/24.0; // 0..1            
-            double moon_r = RA/TWO_PI; // convert to 0..1
-
-            // rotational difference between UTC and current time
-            double diff_r = moon_r - time_r;
-            double diff_lon = TWO_PI * diff_r;
-
-            RA -= diff_lon;
-
-            nrad2(RA);
-
-            return SkyNode::getPositionFromRADecl( RA, Dec, rg );
-        }
-    };
-}
-
-//---------------------------------------------------------------------------
-
-namespace
-{
-    // Atmospheric Scattering and Sun Shaders
-    // Adapted from code that is
-    // Copyright (c) 2004 Sean O'Neil
-
-    static char s_versionString[] =
-#ifdef OSG_GLES2_AVAILABLE
-        "#version 100 \n";
-#else
-        "#version 110 \n";
-#endif
-
-    static char s_mathUtils[] =
-        "float fastpow( in float x, in float y ) \n"
-        "{ \n"
-        "    return x/(x+y-y*x); \n"
-        "} \n";
-
-    static char s_atmosphereVertexDeclarations[] =
-        "uniform mat4 osg_ViewMatrixInverse;     // camera position \n"
-        "uniform vec3 atmos_v3LightPos;        // The direction vector to the light source \n"
-        "uniform vec3 atmos_v3InvWavelength;   // 1 / pow(wavelength,4) for the rgb channels \n"
-        "uniform float atmos_fOuterRadius;     // Outer atmosphere radius \n"
-        "uniform float atmos_fOuterRadius2;    // fOuterRadius^2 \n"		
-        "uniform float atmos_fInnerRadius;     // Inner planetary radius \n"
-        "uniform float atmos_fInnerRadius2;    // fInnerRadius^2 \n"
-        "uniform float atmos_fKrESun;          // Kr * ESun \n"	
-        "uniform float atmos_fKmESun;          // Km * ESun \n"		
-        "uniform float atmos_fKr4PI;           // Kr * 4 * PI \n"	
-        "uniform float atmos_fKm4PI;           // Km * 4 * PI \n"		
-        "uniform float atmos_fScale;           // 1 / (fOuterRadius - fInnerRadius) \n"	
-        "uniform float atmos_fScaleDepth;      // The scale depth \n"
-        "uniform float atmos_fScaleOverScaleDepth;     // fScale / fScaleDepth \n"	
-        "uniform int atmos_nSamples; \n"	
-        "uniform float atmos_fSamples; \n"				
-
-        "varying vec3 atmos_v3Direction; \n"
-        "varying vec3 atmos_mieColor; \n"
-        "varying vec3 atmos_rayleighColor; \n"
-
-        "vec3 vVec; \n"
-        "float atmos_fCameraHeight;    // The camera's current height \n"		
-        "float atmos_fCameraHeight2;   // fCameraHeight^2 \n";
-
-    static char s_atmosphereVertexShared[] =
-        "float atmos_scale(float fCos) \n"	
-        "{ \n"
-        "    float x = 1.0 - fCos; \n"
-        "    return atmos_fScaleDepth * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25)))); \n"
-        "} \n"
-
-        "void SkyFromSpace(void) \n"
-        "{ \n"
-        "    // Get the ray from the camera to the vertex and its length (which is the far point of the ray passing through the atmosphere) \n"
-        "    vec3 v3Pos = gl_Vertex.xyz; \n"
-        "    vec3 v3Ray = v3Pos - vVec; \n"
-        "    float fFar = length(v3Ray); \n"
-        "    v3Ray /= fFar; \n"
-
-        "    // Calculate the closest intersection of the ray with the outer atmosphere \n"
-        "    // (which is the near point of the ray passing through the atmosphere) \n"
-        "    float B = 2.0 * dot(vVec, v3Ray); \n"
-        "    float C = atmos_fCameraHeight2 - atmos_fOuterRadius2; \n"
-        "    float fDet = max(0.0, B*B - 4.0 * C); \n"	
-        "    float fNear = 0.5 * (-B - sqrt(fDet)); \n"		
-
-        "    // Calculate the ray's starting position, then calculate its atmos_ing offset \n"
-        "    vec3 v3Start = vVec + v3Ray * fNear; \n"			
-        "    fFar -= fNear; \n"	
-        "    float fStartAngle = dot(v3Ray, v3Start) / atmos_fOuterRadius; \n"			
-        "    float fStartDepth = exp(-1.0 / atmos_fScaleDepth); \n"
-        "    float fStartOffset = fStartDepth*atmos_scale(fStartAngle); \n"		
-
-        "    // Initialize the atmos_ing loop variables \n"	
-        "    float fSampleLength = fFar / atmos_fSamples; \n"		
-        "    float fScaledLength = fSampleLength * atmos_fScale; \n"					
-        "    vec3 v3SampleRay = v3Ray * fSampleLength; \n"	
-        "    vec3 v3SamplePoint = v3Start + v3SampleRay * 0.5; \n"	
-
-        "    // Now loop through the sample rays \n"
-        "    vec3 v3FrontColor = vec3(0.0, 0.0, 0.0); \n"
-        "    vec3 v3Attenuate; \n"  
-        "    for(int i=0; i<atmos_nSamples; i++) \n"		
-        "    { \n"
-        "        float fHeight = length(v3SamplePoint); \n"			
-        "        float fDepth = exp(atmos_fScaleOverScaleDepth * (atmos_fInnerRadius - fHeight)); \n"
-        "        float fLightAngle = dot(atmos_v3LightPos, v3SamplePoint) / fHeight; \n"		
-        "        float fCameraAngle = dot(v3Ray, v3SamplePoint) / fHeight; \n"			
-        "        float fscatter = (fStartOffset + fDepth*(atmos_scale(fLightAngle) - atmos_scale(fCameraAngle))); \n"	
-        "        v3Attenuate = exp(-fscatter * (atmos_v3InvWavelength * atmos_fKr4PI + atmos_fKm4PI)); \n"	
-        "        v3FrontColor += v3Attenuate * (fDepth * fScaledLength); \n"					
-        "        v3SamplePoint += v3SampleRay; \n"		
-        "    } \n"		
-
-        "    // Finally, scale the Mie and Rayleigh colors and set up the varying \n"			
-        "    // variables for the pixel shader \n"	
-        "    atmos_mieColor      = v3FrontColor * atmos_fKmESun; \n"				
-        "    atmos_rayleighColor = v3FrontColor * (atmos_v3InvWavelength * atmos_fKrESun); \n"						
-        "    atmos_v3Direction = vVec  - v3Pos; \n"			
-        "} \n"		
-
-        "void SkyFromAtmosphere(void) \n"		
-        "{ \n"
-        "  // Get the ray from the camera to the vertex, and its length (which is the far \n"
-        "  // point of the ray passing through the atmosphere) \n"		
-        "  vec3 v3Pos = gl_Vertex.xyz; \n"	
-        "  vec3 v3Ray = v3Pos - vVec; \n"			
-        "  float fFar = length(v3Ray); \n"					
-        "  v3Ray /= fFar; \n"				
-
-        "  // Calculate the ray's starting position, then calculate its atmos_ing offset \n"
-        "  vec3 v3Start = vVec; \n"
-        "  float fHeight = length(v3Start); \n"		
-        "  float fDepth = exp(atmos_fScaleOverScaleDepth * (atmos_fInnerRadius - atmos_fCameraHeight)); \n"
-        "  float fStartAngle = dot(v3Ray, v3Start) / fHeight; \n"	
-        "  float fStartOffset = fDepth*atmos_scale(fStartAngle); \n"
-
-        "  // Initialize the atmos_ing loop variables \n"		
-        "  float fSampleLength = fFar / atmos_fSamples; \n"			
-        "  float fScaledLength = fSampleLength * atmos_fScale; \n"				
-        "  vec3 v3SampleRay = v3Ray * fSampleLength; \n"		
-        "  vec3 v3SamplePoint = v3Start + v3SampleRay * 0.5; \n"
-
-        "  // Now loop through the sample rays \n"		
-        "  vec3 v3FrontColor = vec3(0.0, 0.0, 0.0); \n"		
-        "  vec3 v3Attenuate; \n"  
-        "  for(int i=0; i<atmos_nSamples; i++) \n"			
-        "  { \n"	
-        "    float fHeight = length(v3SamplePoint); \n"	
-        "    float fDepth = exp(atmos_fScaleOverScaleDepth * (atmos_fInnerRadius - fHeight)); \n"
-        "    float fLightAngle = dot(atmos_v3LightPos, v3SamplePoint) / fHeight; \n"
-        "    float fCameraAngle = dot(v3Ray, v3SamplePoint) / fHeight; \n"	
-        "    float fscatter = (fStartOffset + fDepth*(atmos_scale(fLightAngle) - atmos_scale(fCameraAngle))); \n"	
-        "    v3Attenuate = exp(-fscatter * (atmos_v3InvWavelength * atmos_fKr4PI + atmos_fKm4PI)); \n"	
-        "    v3FrontColor += v3Attenuate * (fDepth * fScaledLength); \n"		
-        "    v3SamplePoint += v3SampleRay; \n"		
-        "  } \n"
-
-        "  // Finally, scale the Mie and Rayleigh colors and set up the varying \n"
-        "  // variables for the pixel shader \n"					
-        "  atmos_mieColor      = v3FrontColor * atmos_fKmESun; \n"			
-        "  atmos_rayleighColor = v3FrontColor * (atmos_v3InvWavelength * atmos_fKrESun); \n"				
-        "  atmos_v3Direction = vVec - v3Pos; \n"				
-        "} \n";
-
-
-    static char s_atmosphereVertexMain[] =
-        "void main(void) \n"
-        "{ \n"
-        "  // Get camera position and height \n"
-        "  vVec = osg_ViewMatrixInverse[3].xyz; \n"
-        "  atmos_fCameraHeight = length(vVec); \n"
-        "  atmos_fCameraHeight2 = atmos_fCameraHeight*atmos_fCameraHeight; \n"
-        "  gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; \n"
-        "  if(atmos_fCameraHeight >= atmos_fOuterRadius) { \n"
-        "      SkyFromSpace(); \n"
-        "  } \n"
-        "  else { \n"
-        "      SkyFromAtmosphere(); \n"
-        "  } \n"
-        "} \n";
-
-    static char s_atmosphereFragmentDeclarations[] =
-        "uniform vec3 atmos_v3LightPos; \n"							
-        "uniform float atmos_g; \n"				
-        "uniform float atmos_g2; \n"
-        "uniform float atmos_fWeather; \n"
-
-        "varying vec3 atmos_v3Direction; \n"	
-        "varying vec3 atmos_mieColor; \n"
-        "varying vec3 atmos_rayleighColor; \n"
-
-        "const float fExposure = 4.0; \n";
-        
-    static char s_atmosphereFragmentMain[] =
-        "void main(void) \n"			
-        "{ \n"				
-        "    float fCos = dot(atmos_v3LightPos, atmos_v3Direction) / length(atmos_v3Direction); \n"
-        "    float fRayleighPhase = 1.0; \n" // 0.75 * (1.0 + fCos*fCos); \n"
-        "    float fMiePhase = 1.5 * ((1.0 - atmos_g2) / (2.0 + atmos_g2)) * (1.0 + fCos*fCos) / fastpow(1.0 + atmos_g2 - 2.0*atmos_g*fCos, 1.5); \n"
-        "    vec3 f4Color = fRayleighPhase * atmos_rayleighColor + fMiePhase * atmos_mieColor; \n"
-        "    vec3 color = 1.0 - exp(f4Color * -fExposure); \n"
-        "    gl_FragColor.rgb = color.rgb*atmos_fWeather; \n"
-        "    gl_FragColor.a = (color.r+color.g+color.b) * 2.0; \n"
-        "} \n";
-
-    static char s_sunVertexSource[] = 
-        "varying vec3 atmos_v3Direction; \n"
-
-        "void main() \n"
-        "{ \n"
-        "    vec3 v3Pos = gl_Vertex.xyz; \n"
-        "    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; \n"
-        "    atmos_v3Direction = vec3(0.0,0.0,1.0) - v3Pos; \n"
-        "    atmos_v3Direction = atmos_v3Direction/length(atmos_v3Direction); \n"
-        "} \n";
-
-    static char s_sunFragmentSource[] =
-        "uniform float sunAlpha; \n"
-        "varying vec3 atmos_v3Direction; \n"
-
-        "void main( void ) \n"
-        "{ \n"
-        "   float fCos = -atmos_v3Direction[2]; \n"         
-        "   float fMiePhase = 0.050387596899224826 * (1.0 + fCos*fCos) / fastpow(1.9024999999999999 - -1.8999999999999999*fCos, 1.5); \n"
-        "   gl_FragColor.rgb = fMiePhase*vec3(.3,.3,.2); \n"
-        "   gl_FragColor.a = sunAlpha*gl_FragColor.r; \n"
-        "} \n";
-
-    static char s_moonVertexSource[] = 
-        "uniform mat4 osg_ModelViewProjectionMatrix;"
-        "varying vec4 moon_TexCoord;\n"
-        "void main() \n"
-        "{ \n"
-        "    moon_TexCoord = gl_MultiTexCoord0; \n"
-        "    gl_Position = osg_ModelViewProjectionMatrix * gl_Vertex; \n"
-        "} \n";
-
-    static char s_moonFragmentSource[] =
-        "varying vec4 moon_TexCoord;\n"
-        "uniform sampler2D moonTex;\n"
-        "void main( void ) \n"
-        "{ \n"
-        "   gl_FragColor = texture2D(moonTex, moon_TexCoord.st);\n"
-        "} \n";
-}
-
-//---------------------------------------------------------------------------
-
-namespace
-{
-    static std::string s_createStarVertexSource()
-    {
-        float glslVersion = Registry::instance()->getCapabilities().getGLSLVersion();
-
-        return Stringify()
-            << "#version " << (glslVersion < 1.2f ? GLSL_VERSION_STR : "120") << "\n"
-
-            << "float remap( float val, float vmin, float vmax, float r0, float r1 ) \n"
-            << "{ \n"
-            << "    float vr = (clamp(val, vmin, vmax)-vmin)/(vmax-vmin); \n"
-            << "    return r0 + vr * (r1-r0); \n"
-            << "} \n"
-
-            << "uniform vec3 atmos_v3LightPos; \n"
-            << "uniform mat4 osg_ViewMatrixInverse; \n"
-            << "varying float visibility; \n"
-            << "varying vec4 osg_FrontColor; \n"
-            << "void main() \n"
-            << "{ \n"
-            << "    osg_FrontColor = gl_Color; \n"
-            << "    gl_PointSize = gl_Color.r * " << (glslVersion < 1.2f ? "2.0" : "14.0") << ";\n"
-            << "    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; \n"
-
-            << "    vec3 eye = osg_ViewMatrixInverse[3].xyz; \n"
-            << "    float hae = length(eye) - 6378137.0; \n"
-            // "highness": visibility increases with altitude
-            << "    float highness = remap( hae, 25000.0, 150000.0, 0.0, 1.0 ); \n"
-            << "    eye = normalize(eye); \n"
-            // "darkness": visibility increase as the sun goes around the other side of the earth
-            << "    float darkness = 1.0-remap(dot(eye,atmos_v3LightPos), -0.25, 0.0, 0.0, 1.0); \n"
-            << "    visibility = clamp(highness + darkness, 0.0, 1.0); \n"
-            << "} \n";
-    }
-
-    static std::string s_createStarFragmentSource()
-    {
-        float glslVersion = Registry::instance()->getCapabilities().getGLSLVersion();
-
-        if ( glslVersion < 1.2f )
-        {
-            return Stringify()
-                << "#version " << GLSL_VERSION_STR << "\n"
-#ifdef OSG_GLES2_AVAILABLE
-                << "precision highp float;\n"
-#endif  
-                << "varying float visibility; \n"
-                << "varying vec4 osg_FrontColor; \n"
-                << "void main( void ) \n"
-                << "{ \n"
-                << "    gl_FragColor = osg_FrontColor * visibility; \n"
-                << "} \n";
-        }
-        else
-        {
-            return Stringify()
-                << "#version 120 \n"
-#ifdef OSG_GLES2_AVAILABLE
-                << "precision highp float;\n"
-#endif
-                << "varying float visibility; \n"
-                << "varying vec4 osg_FrontColor; \n"
-                << "void main( void ) \n"
-                << "{ \n"
-                << "    float b1 = 1.0-(2.0*abs(gl_PointCoord.s-0.5)); \n"
-                << "    float b2 = 1.0-(2.0*abs(gl_PointCoord.t-0.5)); \n"
-                << "    float i = b1*b1 * b2*b2; \n"
-                << "    gl_FragColor = osg_FrontColor * i * visibility; \n"
-                << "} \n";
-        }
-    }
-}
-
-
-//---------------------------------------------------------------------------
-
-osg::Vec3d
-DefaultEphemerisProvider::getSunPosition(const DateTime& date)
-{
-    Sun sun;
-    return sun.getPosition( date.year(), date.month(), date.day(), date.hours() );
-}
-
-osg::Vec3d
-DefaultEphemerisProvider::getMoonPosition(const DateTime& date)
-{
-    Moon moon;
-    return moon.getPosition( date.year(), date.month(), date.day(), date.hours() );
-}
-
-//---------------------------------------------------------------------------
-
-SkyNode::SkyNode( Map* map, const std::string& starFile, float minStarMagnitude ) : 
-_minStarMagnitude(minStarMagnitude)
-{
-    initialize(map, starFile);
-}
-
-SkyNode::SkyNode( Map *map, float minStarMagnitude) : 
-_minStarMagnitude(minStarMagnitude)
-{
-    initialize(map);
-}
-
-void
-SkyNode::initialize( Map *map, const std::string& starFile )
-{
-    _ephemerisProvider = new DefaultEphemerisProvider();
-
-    // intialize the default settings:
-    _defaultPerViewData._lightPos.set( osg::Vec3f(0.0f, 1.0f, 0.0f) );
-    _defaultPerViewData._light = new osg::Light( 0 );  
-    _defaultPerViewData._light->setPosition( osg::Vec4( _defaultPerViewData._lightPos, 0 ) );
-    _defaultPerViewData._light->setAmbient( osg::Vec4(0.2f, 0.2f, 0.2f, 2.0) );
-    _defaultPerViewData._light->setDiffuse( osg::Vec4(1,1,1,1) );
-    _defaultPerViewData._light->setSpecular( osg::Vec4(0,0,0,1) );
-    _defaultPerViewData._starsVisible = true;
-    _defaultPerViewData._moonVisible = true;
-    
-    // set up the uniform that conveys the normalized light position in world space
-    _defaultPerViewData._lightPosUniform = new osg::Uniform( osg::Uniform::FLOAT_VEC3, "atmos_v3LightPos" );
-    _defaultPerViewData._lightPosUniform->set( _defaultPerViewData._lightPos / _defaultPerViewData._lightPos.length() );
-    
-    // set up the astronomical parameters:
-    _ellipsoidModel = map->getProfile()->getSRS()->getGeographicSRS()->getEllipsoid();
-    _innerRadius = _ellipsoidModel->getRadiusPolar();
-    _outerRadius = _innerRadius * 1.025f;
-    _sunDistance = _innerRadius * 12000.0f;
-
-    // make the sky elements (don't change the order here)
-    makeAtmosphere( _ellipsoidModel.get() );
-
-    makeSun();
-
-    makeMoon();
-
-    if (_minStarMagnitude < 0)
-    {
-      const char* magEnv = ::getenv("OSGEARTH_MIN_STAR_MAGNITUDE");
-      if (magEnv)
-        _minStarMagnitude = as<float>(std::string(magEnv), -1.0f);
-    }
-
-    makeStars(starFile);
-
-    // automatically compute ambient lighting based on the eyepoint
-    _autoAmbience = false;
-
-    //Set a default time
-    setDateTime( DateTime(2011, 3, 6, 18.) );
-}
-
-osg::BoundingSphere
-SkyNode::computeBound() const
-{
-    return osg::BoundingSphere();
-}
-
-void
-SkyNode::traverse( osg::NodeVisitor& nv )
-{    
-    osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
-    if ( cv )
-    {
-
-        // If there's a custom projection matrix clamper installed, remove it temporarily.
-        // We dont' want it mucking with our sky elements.
-        osg::ref_ptr<osg::CullSettings::ClampProjectionMatrixCallback> cb = cv->getClampProjectionMatrixCallback();
-        cv->setClampProjectionMatrixCallback( 0L );
-
-        osg::View* view = cv->getCurrentCamera()->getView();
-
-                
-        //Try to find the per view data for camera's view if there is one.
-        PerViewDataMap::iterator itr = _perViewData.find( view );
-        
-        if ( itr == _perViewData.end() )
-        {
-            // If we don't find any per view data, just use the first one that is stored.
-            // This needs to be reworked to be per camera and also to automatically create a 
-            // new data structure on demand since camera's can be added/removed on the fly.
-            itr = _perViewData.begin();
-        }
-        
-
-        if ( _autoAmbience )
-        {
-            const float minAmb = 0.2f;
-            const float maxAmb = 0.92f;
-            const float minDev = -0.2f;
-            const float maxDev = 0.75f;
-            osg::Vec3 eye = cv->getViewPoint(); eye.normalize();
-            osg::Vec3 sun = itr->second._lightPos; sun.normalize();
-            float dev = osg::clampBetween(eye*sun, minDev, maxDev);
-            float r   = (dev-minDev)/(maxDev-minDev);
-            float amb = minAmb + r*(maxAmb-minAmb);
-            itr->second._light->setAmbient( osg::Vec4(amb,amb,amb,1.0) );
-            //OE_INFO << "dev=" << dev << ", amb=" << amb << std::endl;
-        }
-
-        itr->second._cullContainer->accept( nv );
-
-        // restore a custom clamper.
-        if ( cb.valid() ) cv->setClampProjectionMatrixCallback( cb.get() );
-    }
-
-    else
-    {
-        osg::Group::traverse( nv );
-    }
-}
-
-EphemerisProvider*
-SkyNode::getEphemerisProvider() const
-{
-    return _ephemerisProvider;
-}
-
-void
-SkyNode::setEphemerisProvider(EphemerisProvider* ephemerisProvider )
-{
-    if (_ephemerisProvider != ephemerisProvider)
-    {
-        _ephemerisProvider = ephemerisProvider;
-
-        //Update the positions of the planets
-        for( PerViewDataMap::iterator i = _perViewData.begin(); i != _perViewData.end(); ++i )
-        {
-            setDateTime(i->second._date, i->first);
-        }
-    }
-}
-
-void
-SkyNode::attach( osg::View* view, int lightNum )
-{
-    if ( !view ) return;
-
-    // creates the new per-view if it does not already exist
-    PerViewData& data = _perViewData[view];
-
-    data._light = osg::clone( _defaultPerViewData._light.get() );
-    data._light->setLightNum( lightNum );
-    data._light->setAmbient( _defaultPerViewData._light->getAmbient() );
-    data._lightPos = _defaultPerViewData._lightPos;
-
-    // the cull callback has to be on a parent group-- won't work on the xforms themselves.
-    data._cullContainer = new osg::Group();
-
-    data._sunXform = new osg::MatrixTransform();
-    data._sunMatrix = osg::Matrixd::translate(
-        _sunDistance * data._lightPos.x(),
-        _sunDistance * data._lightPos.y(),
-        _sunDistance * data._lightPos.z() );
-    data._sunXform->setMatrix( data._sunMatrix );
-    data._sunXform->addChild( _sun.get() );
-    data._cullContainer->addChild( data._sunXform.get() );
-
-    data._moonXform = new osg::MatrixTransform();
-    data._moonMatrix = _defaultPerViewData._moonMatrix;
-    data._moonXform->setMatrix( data._moonMatrix );
-    data._moonXform->addChild( _moon.get() );
-    data._cullContainer->addChild( data._moonXform.get() );
-    data._moonVisible = _defaultPerViewData._moonVisible;
-    data._moonXform->setNodeMask( data._moonVisible ? ~0 : 0 );
-    
-    data._starsXform = new osg::MatrixTransform();
-    data._starsMatrix = _defaultPerViewData._starsMatrix;
-    data._starsXform->setMatrix( _defaultPerViewData._starsMatrix );
-    data._starsXform->addChild( _stars.get() );
-    data._cullContainer->addChild( data._starsXform.get() );
-    data._starsVisible = _defaultPerViewData._starsVisible;
-    data._starsXform->setNodeMask( data._starsVisible ? ~0 : 0 );
-
-    data._cullContainer->addChild( _atmosphere.get() );
-    data._lightPosUniform = osg::clone( _defaultPerViewData._lightPosUniform.get() );
-    data._cullContainer->getOrCreateStateSet()->addUniform( data._lightPosUniform.get() );
-
-    // node to traverse the child nodes
-    data._cullContainer->addChild( new TraverseNode<osg::Group>(this) );
-
-    view->setLightingMode( osg::View::SKY_LIGHT );
-    view->setLight( data._light.get() );
-    view->getCamera()->setClearColor( osg::Vec4(0,0,0,1) );
-
-    data._date = _defaultPerViewData._date;
-}
-
-void
-SkyNode::setAmbientBrightness( float value, osg::View* view )
-{
-    if ( !view )
-    {
-        setAmbientBrightness( _defaultPerViewData, value );
-
-        for( PerViewDataMap::iterator i = _perViewData.begin(); i != _perViewData.end(); ++i )
-            setAmbientBrightness( i->second, value );
-    }
-    else if ( _perViewData.find(view) != _perViewData.end() )
-    {
-        setAmbientBrightness( _perViewData[view], value );
-    }
-}
-
-float
-SkyNode::getAmbientBrightness( osg::View* view ) const
-{
-    if ( view )
-    {
-        PerViewDataMap::const_iterator i = _perViewData.find(view);
-        if ( i != _perViewData.end() )
-            return i->second._light->getAmbient().r();
-    }
-    return _defaultPerViewData._light->getAmbient().r();
-}
-
-void
-SkyNode::setAutoAmbience( bool value )
-{
-    _autoAmbience = value;
-}
-
-bool
-SkyNode::getAutoAmbience() const
-{
-    return _autoAmbience;
-}
-
-void 
-SkyNode::setAmbientBrightness( PerViewData& data, float value )
-{
-    value = osg::clampBetween( value, 0.0f, 1.0f );
-    data._light->setAmbient( osg::Vec4f(value, value, value, 1.0f) );
-    _autoAmbience = false;
-}
-
-void
-SkyNode::setSunPosition( const osg::Vec3& pos, osg::View* view )
-{
-    if ( !view )
-    {
-        setSunPosition( _defaultPerViewData, pos );
-        for( PerViewDataMap::iterator i = _perViewData.begin(); i != _perViewData.end(); ++i )
-            setSunPosition( i->second, pos );
-    }
-    else if ( _perViewData.find(view) != _perViewData.end() )
-    {
-        setSunPosition( _perViewData[view], pos );
-    }
-}
-
-void
-SkyNode::setMoonPosition( const osg::Vec3d& pos, osg::View* view )
-{
-    _moonPosition = pos;
-    if ( !view )
-    {
-        setMoonPosition( _defaultPerViewData, pos );
-        for( PerViewDataMap::iterator i = _perViewData.begin(); i != _perViewData.end(); ++i )
-            setMoonPosition( i->second, pos );
-    }
-    else if ( _perViewData.find(view) != _perViewData.end() )
-    {
-        setMoonPosition( _perViewData[view], pos );
-    }
-}
-
-void
-SkyNode::setSunPosition( PerViewData& data, const osg::Vec3& pos )
-{
-    data._lightPos = pos;
-
-    if ( data._light.valid() )
-        data._light->setPosition( osg::Vec4( data._lightPos, 0 ) );
-
-    if ( data._lightPosUniform.valid() )
-        data._lightPosUniform->set( data._lightPos / data._lightPos.length() );
-
-    if ( data._sunXform.valid() )
-    {
-        data._sunXform->setMatrix( osg::Matrix::translate( 
-            _sunDistance * data._lightPos.x(), 
-            _sunDistance * data._lightPos.y(),
-            _sunDistance * data._lightPos.z() ) );
-    }
-}
-
-void
-SkyNode::setSunPosition( double lat_degrees, double long_degrees, osg::View* view )
-{
-    if (_ellipsoidModel.valid())
-    {
-        double x, y, z;
-        _ellipsoidModel->convertLatLongHeightToXYZ(
-            osg::RadiansToDegrees(lat_degrees),
-            osg::RadiansToDegrees(long_degrees),
-            0, 
-            x, y, z);
-        osg::Vec3d up  = _ellipsoidModel->computeLocalUpVector(x, y, z);
-        setSunPosition( up, view );
-    }
-}
-
-void
-SkyNode::setMoonPosition( PerViewData& data, const osg::Vec3d& pos )
-{
-    if ( data._moonXform.valid() )
-    {
-        data._moonMatrix = osg::Matrixd::translate( pos.x(), pos.y(), pos.z() );
-        data._moonXform->setMatrix( data._moonMatrix );
-    }
-}
-
-
-void
-SkyNode::getDateTime( DateTime& out, osg::View* view ) const
-{    
-    if ( view )
-    {
-        PerViewDataMap::const_iterator i = _perViewData.find(view);
-        if (i != _perViewData.end() )
-        {
-            out = i->second._date;
-            return;
-        }
-    }
-    out = _defaultPerViewData._date;
-}
-
-void
-SkyNode::getDateTime(int& year, int& month, int& date, double& hoursUTC, osg::View* view)
-{
-    DateTime temp;
-    getDateTime(temp, view);
-
-    year = temp.year();
-    month = temp.month();
-    date = temp.day();
-    hoursUTC = temp.hours();
-
-    OE_WARN << LC <<
-        "The method getDateTime(int&,int&,int&,double&,View*) is deprecated; "
-        "please use getDateTime(DateTime&, View*) instead" << std::endl;
-}
-
-
-void
-SkyNode::setDateTime(const DateTime& dt, osg::View* view)
-{    
-    if ( _ellipsoidModel.valid() )
-    {
-        osg::Vec3d sunPosition;
-        osg::Vec3d moonPosition;
-
-        if (_ephemerisProvider)
-        {
-            sunPosition = _ephemerisProvider->getSunPosition( dt );
-            moonPosition = _ephemerisProvider->getMoonPosition( dt );
-        }
-        else
-        {
-            OE_NOTICE << "You must provide an EphemerisProvider" << std::endl;
-        }
-
-        sunPosition.normalize();
-        setSunPosition( sunPosition, view );
-        setMoonPosition( moonPosition, view );
-
-        // position the stars:
-        double time_r = dt.hours()/24.0; // 0..1
-        double rot_z = -osg::PI + TWO_PI*time_r;
-
-        osg::Matrixd starsMatrix = osg::Matrixd::rotate( -rot_z, 0, 0, 1 );
-        if ( !view )
-        {
-            _defaultPerViewData._starsMatrix = starsMatrix;
-            _defaultPerViewData._date = dt;
-
-            for( PerViewDataMap::iterator i = _perViewData.begin(); i != _perViewData.end(); ++i )
-            {
-                i->second._starsMatrix = starsMatrix;
-                i->second._starsXform->setMatrix( starsMatrix );
-                i->second._date = dt;
-            }
-        }
-        else if ( _perViewData.find(view) != _perViewData.end() )
-        {
-            PerViewData& data = _perViewData[view];
-            data._starsMatrix = starsMatrix;
-            data._starsXform->setMatrix( starsMatrix );
-            data._date = dt;
-        }
-    }
-}
-
-
-void
-SkyNode::setDateTime( int year, int month, int date, double hoursUTC, osg::View* view )
-{
-    // backwards compatibility
-    setDateTime( DateTime(year, month, date, hoursUTC), view );
-
-    OE_WARN << LC << 
-        "The method setDateTime(int,int,int,double,View*) is deprecated; "
-        "please use setDateTime(const DateTime&, View*) instead"
-        << std::endl;
-}
-
-
-void
-SkyNode::setStarsVisible( bool value, osg::View* view )
-{
-    if ( !view )
-    {
-        _defaultPerViewData._starsVisible = value;
-        _defaultPerViewData._starsXform->setNodeMask( value ? ~0 : 0 );
-        for( PerViewDataMap::iterator i = _perViewData.begin(); i != _perViewData.end(); ++i )
-        {
-            i->second._starsVisible = value;
-            i->second._starsXform->setNodeMask( value ? ~0 : 0 );
-        }
-    }
-    else if ( _perViewData.find(view) != _perViewData.end() )
-    {
-        _perViewData[view]._starsVisible = value;
-        _perViewData[view]._starsXform->setNodeMask( value ? ~0 : 0 );
-    }
-}
-
-void
-SkyNode::setMoonVisible( bool value, osg::View* view )
-{
-    if ( !view )
-    {
-        _defaultPerViewData._moonVisible = value;
-        _defaultPerViewData._moonXform->setNodeMask( value ? ~0 : 0 );
-        for( PerViewDataMap::iterator i = _perViewData.begin(); i != _perViewData.end(); ++i )
-        {
-            i->second._moonVisible = value;
-            i->second._moonXform->setNodeMask( value ? ~0 : 0 );
-        }
-    }
-    else if ( _perViewData.find(view) != _perViewData.end() )
-    {
-        _perViewData[view]._moonVisible = value;
-        _perViewData[view]._moonXform->setNodeMask( value ? ~0 : 0 );
-    }
-}
-
-bool
-SkyNode::getStarsVisible( osg::View* view ) const
-{
-    PerViewDataMap::const_iterator i = _perViewData.find(view);
-
-    if ( !view || i == _perViewData.end() )
-    {
-        return _defaultPerViewData._starsVisible;
-    }
-    else
-    {
-        return i->second._starsVisible;
-    }
-}
-
-bool
-SkyNode::getMoonVisible( osg::View* view ) const
-{
-    PerViewDataMap::const_iterator i = _perViewData.find(view);
-
-    if ( !view || i == _perViewData.end() )
-    {
-        return _defaultPerViewData._moonVisible;
-    }
-    else
-    {
-        return i->second._moonVisible;
-    }
-}
-
-void
-SkyNode::makeAtmosphere( const osg::EllipsoidModel* em )
-{
-    // create some skeleton geometry to shade:
-    osg::Geometry* drawable = s_makeEllipsoidGeometry( em, _outerRadius );
-    
-    osg::Geode* geode = new osg::Geode();
-    geode->addDrawable( drawable );
-
-    osg::StateSet* set = geode->getOrCreateStateSet();
-
-    // configure the state set:
-    set->setMode( GL_LIGHTING, osg::StateAttribute::OFF );
-    set->setAttributeAndModes( new osg::CullFace(osg::CullFace::BACK), osg::StateAttribute::ON );
-    //set->setRenderingHint( osg::StateSet::TRANSPARENT_BIN );
-    set->setAttributeAndModes( new osg::Depth( osg::Depth::LESS, 0, 1, false ) ); // no depth write
-    set->setAttributeAndModes( new osg::Depth(osg::Depth::ALWAYS, 0, 1, false) ); // no zbuffer
-    set->setAttributeAndModes( new osg::BlendFunc( GL_ONE, GL_ONE ), osg::StateAttribute::ON );
-
-    if ( Registry::capabilities().supportsGLSL() )
-    {
-        // next, create and add the shaders:
-        osg::Program* program = new osg::Program();
-        osg::Shader* vs = new osg::Shader( osg::Shader::VERTEX, Stringify()
-            << s_versionString
-            << s_mathUtils
-            << s_atmosphereVertexDeclarations
-            << s_atmosphereVertexShared
-            << s_atmosphereVertexMain );
-        program->addShader( vs );
-
-        osg::Shader* fs = new osg::Shader( osg::Shader::FRAGMENT, Stringify()
-            << s_versionString
-#ifdef OSG_GLES2_AVAILABLE
-            << "precision highp float;\n"
-#endif
-            << s_mathUtils
-            << s_atmosphereFragmentDeclarations
-            //<< s_atmosphereFragmentShared
-            << s_atmosphereFragmentMain );
-        program->addShader( fs );
-
-        set->setAttributeAndModes( program, osg::StateAttribute::ON );
-
-        // apply the uniforms:
-        float r_wl   = ::powf( .65f, 4.0f );
-        float g_wl = ::powf( .57f, 4.0f );
-        float b_wl  = ::powf( .475f, 4.0f );
-        osg::Vec3 RGB_wl( 1.0f/r_wl, 1.0f/g_wl, 1.0f/b_wl );
-        float Kr = 0.0025f;
-        float Kr4PI = Kr * 4.0f * osg::PI;
-        float Km = 0.0015f;
-        float Km4PI = Km * 4.0f * osg::PI;
-        float ESun = 15.0f;
-        float MPhase = -.095f;
-        float RayleighScaleDepth = 0.25f;
-        int   Samples = 2;
-        float Weather = 1.0f;
-
-        float Scale = 1.0f / (_outerRadius - _innerRadius);
-
-        // TODO: replace this with a UBO.
-
-        set->getOrCreateUniform( "atmos_v3InvWavelength", osg::Uniform::FLOAT_VEC3 )->set( RGB_wl );
-        set->getOrCreateUniform( "atmos_fInnerRadius",    osg::Uniform::FLOAT )->set( _innerRadius );
-        set->getOrCreateUniform( "atmos_fInnerRadius2",   osg::Uniform::FLOAT )->set( _innerRadius * _innerRadius );
-        set->getOrCreateUniform( "atmos_fOuterRadius",    osg::Uniform::FLOAT )->set( _outerRadius );
-        set->getOrCreateUniform( "atmos_fOuterRadius2",   osg::Uniform::FLOAT )->set( _outerRadius * _outerRadius );
-        set->getOrCreateUniform( "atmos_fKrESun",         osg::Uniform::FLOAT )->set( Kr * ESun );
-        set->getOrCreateUniform( "atmos_fKmESun",         osg::Uniform::FLOAT )->set( Km * ESun );
-        set->getOrCreateUniform( "atmos_fKr4PI",          osg::Uniform::FLOAT )->set( Kr4PI );
-        set->getOrCreateUniform( "atmos_fKm4PI",          osg::Uniform::FLOAT )->set( Km4PI );
-        set->getOrCreateUniform( "atmos_fScale",          osg::Uniform::FLOAT )->set( Scale );
-        set->getOrCreateUniform( "atmos_fScaleDepth",     osg::Uniform::FLOAT )->set( RayleighScaleDepth );
-        set->getOrCreateUniform( "atmos_fScaleOverScaleDepth", osg::Uniform::FLOAT )->set( Scale / RayleighScaleDepth );
-        set->getOrCreateUniform( "atmos_g",               osg::Uniform::FLOAT )->set( MPhase );
-        set->getOrCreateUniform( "atmos_g2",              osg::Uniform::FLOAT )->set( MPhase * MPhase );
-        set->getOrCreateUniform( "atmos_nSamples",        osg::Uniform::INT )->set( Samples );
-        set->getOrCreateUniform( "atmos_fSamples",        osg::Uniform::FLOAT )->set( (float)Samples );
-        set->getOrCreateUniform( "atmos_fWeather",        osg::Uniform::FLOAT )->set( Weather );
-    }
-    
-    // A nested camera isolates the projection matrix calculations so the node won't 
-    // affect the clip planes in the rest of the scene.
-    osg::Camera* cam = new osg::Camera();
-    cam->getOrCreateStateSet()->setRenderBinDetails( BIN_ATMOSPHERE, "RenderBin" );
-    cam->setRenderOrder( osg::Camera::NESTED_RENDER );
-    cam->setComputeNearFarMode( osg::CullSettings::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES );
-    cam->addChild( geode );
-
-    _atmosphere = cam;
-}
-
-void
-SkyNode::makeSun()
-{
-    osg::Billboard* sun = new osg::Billboard();
-    sun->setMode( osg::Billboard::POINT_ROT_EYE );
-    sun->setNormal( osg::Vec3(0, 0, 1) );
-
-    float sunRadius = _innerRadius * 100.0f;
-
-    sun->addDrawable( s_makeDiscGeometry( sunRadius*80.0f ) ); 
-
-    osg::StateSet* set = sun->getOrCreateStateSet();
-    set->setMode( GL_BLEND, 1 );
-
-    set->getOrCreateUniform( "sunAlpha", osg::Uniform::FLOAT )->set( 1.0f );
-
-    // configure the stateset
-    set->setMode( GL_LIGHTING, osg::StateAttribute::OFF );
-    set->setMode( GL_CULL_FACE, osg::StateAttribute::OFF );
-    set->setAttributeAndModes( new osg::Depth(osg::Depth::ALWAYS, 0, 1, false), osg::StateAttribute::ON );
-   // set->setAttributeAndModes( new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA), osg::StateAttribute::ON );
-
-    // create shaders
-    if ( Registry::capabilities().supportsGLSL() )
-    {
-        osg::Program* program = new osg::Program();
-        osg::Shader* vs = new osg::Shader( osg::Shader::VERTEX, Stringify()
-            << s_versionString
-            << s_sunVertexSource );
-        program->addShader( vs );
-        osg::Shader* fs = new osg::Shader( osg::Shader::FRAGMENT, Stringify()
-            << s_versionString
-#ifdef OSG_GLES2_AVAILABLE
-            << "precision highp float;\n"
-#endif
-            << s_mathUtils
-            << s_sunFragmentSource );
-        program->addShader( fs );
-        set->setAttributeAndModes( program, osg::StateAttribute::ON );
-    }
-
-    // make the sun's transform:
-    // todo: move this?
-    _defaultPerViewData._sunXform = new osg::MatrixTransform();
-    _defaultPerViewData._sunXform->setMatrix( osg::Matrix::translate( 
-        _sunDistance * _defaultPerViewData._lightPos.x(), 
-        _sunDistance * _defaultPerViewData._lightPos.y(), 
-        _sunDistance * _defaultPerViewData._lightPos.z() ) );
-    _defaultPerViewData._sunXform->addChild( sun );
-
-    // A nested camera isolates the projection matrix calculations so the node won't 
-    // affect the clip planes in the rest of the scene.
-    osg::Camera* cam = new osg::Camera();
-    cam->getOrCreateStateSet()->setRenderBinDetails( BIN_SUN, "RenderBin" );
-    cam->setRenderOrder( osg::Camera::NESTED_RENDER );
-    cam->setComputeNearFarMode( osg::CullSettings::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES );
-    cam->addChild( sun );
-
-    _sun = cam;
-}
-
-void
-SkyNode::makeMoon()
-{
-    osg::ref_ptr< osg::EllipsoidModel > em = new osg::EllipsoidModel( 1738140.0, 1735970.0 );   
-    osg::Geode* moon = new osg::Geode;
-    moon->getOrCreateStateSet()->setAttributeAndModes( new osg::Program(), osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED );
-    osg::Geometry* geom = s_makeEllipsoidGeometry( em.get(), em->getRadiusEquator(), true );    
-    //TODO:  Embed this texture in code or provide a way to have a default resource directory for osgEarth.
-    //       Right now just need to have this file somewhere in your OSG_FILE_PATH
-    osg::Image* image = osgDB::readImageFile( "moon_1024x512.jpg" );
-    osg::Texture2D * texture = new osg::Texture2D( image );
-    texture->setFilter(osg::Texture::MIN_FILTER,osg::Texture::LINEAR);
-    texture->setFilter(osg::Texture::MAG_FILTER,osg::Texture::LINEAR);
-    texture->setResizeNonPowerOfTwoHint(false);
-    geom->getOrCreateStateSet()->setTextureAttributeAndModes( 0, texture, osg::StateAttribute::ON | osg::StateAttribute::PROTECTED);
-    
-    osg::Vec4Array* colors = new osg::Vec4Array(1);    
-    geom->setColorArray( colors );
-    geom->setColorBinding(osg::Geometry::BIND_OVERALL);
-    (*colors)[0] = osg::Vec4(1, 1, 1, 1 );
-    moon->addDrawable( geom  ); 
-    
-    osg::StateSet* set = moon->getOrCreateStateSet();
-    // configure the stateset
-    set->setMode( GL_LIGHTING, osg::StateAttribute::ON );
-    set->setAttributeAndModes( new osg::CullFace( osg::CullFace::BACK ), osg::StateAttribute::ON);
-    set->setRenderBinDetails( BIN_MOON, "RenderBin" );
-    set->setAttributeAndModes( new osg::Depth(osg::Depth::ALWAYS, 0, 1, false), osg::StateAttribute::ON );
-    set->setAttributeAndModes( new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA), osg::StateAttribute::ON );
-
-#ifdef OSG_GLES2_AVAILABLE
-    
-    if ( Registry::capabilities().supportsGLSL() )
-    {
-        set->addUniform(new osg::Uniform("moonTex", 0));
-        
-        // create shaders
-        osg::Program* program = new osg::Program();
-        osg::Shader* vs = new osg::Shader( osg::Shader::VERTEX, Stringify()
-                                          << s_versionString
-                                          << "precision highp float;\n"
-                                          << s_moonVertexSource );
-        program->addShader( vs );
-        osg::Shader* fs = new osg::Shader( osg::Shader::FRAGMENT, Stringify()
-                                          << s_versionString
-                                          << "precision highp float;\n"
-                                          << s_moonFragmentSource );
-        program->addShader( fs );
-        set->setAttributeAndModes( program, osg::StateAttribute::ON | osg::StateAttribute::PROTECTED );
-    }
-#endif
-    
-    // make the moon's transform:
-    // todo: move this?
-    _defaultPerViewData._moonXform = new osg::MatrixTransform();    
-
-    Moon moonModel;
-    //Get some default value of the moon
-    osg::Vec3d pos = moonModel.getPosition( 2011, 2, 1, 0 );            
-    _defaultPerViewData._moonXform->setMatrix( osg::Matrix::translate( pos ) ); 
-    _defaultPerViewData._moonXform->addChild( moon );
-
-    //If we couldn't load the moon texture, turn the moon off
-    if (!image)
-    {
-        OE_INFO << LC << "Couldn't load moon texture, add osgEarth's data directory your OSG_FILE_PATH" << std::endl;
-        _defaultPerViewData._moonXform->setNodeMask( 0 );
-        _defaultPerViewData._moonVisible = false;
-    }
-
-    // A nested camera isolates the projection matrix calculations so the node won't 
-    // affect the clip planes in the rest of the scene.
-    osg::Camera* cam = new osg::Camera();
-    cam->getOrCreateStateSet()->setRenderBinDetails( BIN_MOON, "RenderBin" );
-    cam->setRenderOrder( osg::Camera::NESTED_RENDER );
-    cam->setComputeNearFarMode( osg::CullSettings::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES );
-    cam->addChild( moon );
-
-    _moon = cam;
-}
-
-SkyNode::StarData::StarData(std::stringstream &ss)
-{
-  std::getline( ss, name, ',' );
-  std::string buff;
-  std::getline( ss, buff, ',' );
-  std::stringstream(buff) >> right_ascension;
-  std::getline( ss, buff, ',' );
-  std::stringstream(buff) >> declination;
-  std::getline( ss, buff, '\n' );
-  std::stringstream(buff) >> magnitude;
-}
-
-void
-SkyNode::makeStars(const std::string& starFile)
-{
-  _starRadius = 20000.0 * (_sunDistance > 0.0 ? _sunDistance : _outerRadius);
-
-  std::vector<StarData> stars;
-
-  if( starFile.empty() || parseStarFile(starFile, stars) == false )
-  {
-    if( !starFile.empty() )
-      OE_WARN << "Warning: Unable to use star field defined in file \"" << starFile << "\", using default star data." << std::endl;
-
-    getDefaultStars(stars);
-  }
-
-  osg::Node* starNode = buildStarGeometry(stars);
-
-  _stars = starNode;
-}
-
-osg::Node*
-SkyNode::buildStarGeometry(const std::vector<StarData>& stars)
-{
-  double minMag = DBL_MAX, maxMag = DBL_MIN;
-
-  osg::Vec3Array* coords = new osg::Vec3Array();
-  std::vector<StarData>::const_iterator p;
-  for( p = stars.begin(); p != stars.end(); p++ )
-  {
-    
-    osg::Vec3d v = getPositionFromRADecl( p->right_ascension, p->declination, _starRadius );
-    coords->push_back( v );
-
-    if ( p->magnitude < minMag ) minMag = p->magnitude;
-    if ( p->magnitude > maxMag ) maxMag = p->magnitude;
-  }
-
-  osg::Vec4Array* colors = new osg::Vec4Array();
-  for( p = stars.begin(); p != stars.end(); p++ )
-  {
-      float c = ( (p->magnitude-minMag) / (maxMag-minMag) );
-      colors->push_back( osg::Vec4(c,c,c,1.0f) );
-  }
-
-  osg::Geometry* geometry = new osg::Geometry;
-  geometry->setUseVertexBufferObjects(true);
-
-  geometry->setVertexArray( coords );
-  geometry->setColorArray( colors );
-  geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
-  geometry->addPrimitiveSet( new osg::DrawArrays(osg::PrimitiveSet::POINTS, 0, coords->size()));
-
-  osg::StateSet* sset = geometry->getOrCreateStateSet();
-
-  if ( Registry::capabilities().supportsGLSL() )
-  {
-    sset->setTextureAttributeAndModes( 0, new osg::PointSprite(), osg::StateAttribute::ON );
-    sset->setMode( GL_VERTEX_PROGRAM_POINT_SIZE, osg::StateAttribute::ON );
-
-    osg::Program* program = new osg::Program;
-    program->addShader( new osg::Shader(osg::Shader::VERTEX, s_createStarVertexSource()) );
-    program->addShader( new osg::Shader(osg::Shader::FRAGMENT, s_createStarFragmentSource()) );
-    sset->setAttributeAndModes( program, osg::StateAttribute::ON );
-  }
-
-  sset->setRenderBinDetails( BIN_STARS, "RenderBin");
-  sset->setAttributeAndModes( new osg::Depth(osg::Depth::ALWAYS, 0, 1, false), osg::StateAttribute::ON );
-  sset->setMode(GL_BLEND, 1);
-
-  osg::Geode* starGeode = new osg::Geode;
-  starGeode->addDrawable( geometry );
-
-  // A separate camera isolates the projection matrix calculations.
-  osg::Camera* cam = new osg::Camera();
-  cam->getOrCreateStateSet()->setRenderBinDetails( BIN_STARS, "RenderBin" );
-  cam->setRenderOrder( osg::Camera::NESTED_RENDER );
-  cam->setComputeNearFarMode( osg::CullSettings::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES );
-  cam->addChild( starGeode );
-
-  return cam;
-  //return starGeode;
-}
-
-void
-SkyNode::getDefaultStars(std::vector<StarData>& out_stars)
-{
-  out_stars.clear();
-
-  for(const char **sptr = s_defaultStarData; *sptr; sptr++)
-  {
-    std::stringstream ss(*sptr);
-    out_stars.push_back(StarData(ss));
-
-    if (out_stars[out_stars.size() - 1].magnitude < _minStarMagnitude)
-      out_stars.pop_back();
-  }
-}
-
-bool
-SkyNode::parseStarFile(const std::string& starFile, std::vector<StarData>& out_stars)
-{
-  out_stars.clear();
-
-  std::fstream in(starFile.c_str());
-  if (!in)
-  {
-    OE_WARN <<  "Warning: Unable to open file star file \"" << starFile << "\"" << std::endl;
-    return false ;
-  }
-
-  while (!in.eof())
-  {
-    std::string line;
-
-    std::getline(in, line);
-    if (in.eof())
-      break;
-
-    if (line.empty() || line[0] == '#') 
-      continue;
-
-    std::stringstream ss(line);
-    out_stars.push_back(StarData(ss));
-
-    if (out_stars[out_stars.size() - 1].magnitude < _minStarMagnitude)
-      out_stars.pop_back();
-  }
-
-  in.close();
-
-  return true;
-}
-
-osg::Vec3d
-SkyNode::getPositionFromRADecl( double ra, double decl, double range )
-{
-    return osg::Vec3(0,range,0) * 
-           osg::Matrix::rotate( decl, 1, 0, 0 ) * 
-           osg::Matrix::rotate( ra - osg::PI_2, 0, 0, 1 );
-}
diff --git a/src/osgEarthUtil/SpatialData b/src/osgEarthUtil/SpatialData
index e2091e1..7239d02 100644
--- a/src/osgEarthUtil/SpatialData
+++ b/src/osgEarthUtil/SpatialData
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/SpatialData.cpp b/src/osgEarthUtil/SpatialData.cpp
index 4df7518..854a06e 100644
--- a/src/osgEarthUtil/SpatialData.cpp
+++ b/src/osgEarthUtil/SpatialData.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/StarData b/src/osgEarthUtil/StarData
index bbc37f7..ab46e13 100644
--- a/src/osgEarthUtil/StarData
+++ b/src/osgEarthUtil/StarData
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/TFS b/src/osgEarthUtil/TFS
index 4564cdc..787262b 100644
--- a/src/osgEarthUtil/TFS
+++ b/src/osgEarthUtil/TFS
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -28,9 +28,7 @@
 
 #include <osgDB/ReaderWriter>
 #include <osg/Version>
-#if OSG_MIN_VERSION_REQUIRED(2,9,5)
 #include <osgDB/Options>
-#endif
 
 
 #include <string>
diff --git a/src/osgEarthUtil/TFS.cpp b/src/osgEarthUtil/TFS.cpp
index c70288e..bc1a10d 100644
--- a/src/osgEarthUtil/TFS.cpp
+++ b/src/osgEarthUtil/TFS.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/TFSPackager b/src/osgEarthUtil/TFSPackager
index 16501be..3036132 100644
--- a/src/osgEarthUtil/TFSPackager
+++ b/src/osgEarthUtil/TFSPackager
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/TFSPackager.cpp b/src/osgEarthUtil/TFSPackager.cpp
index 44025f9..d7343bf 100644
--- a/src/osgEarthUtil/TFSPackager.cpp
+++ b/src/osgEarthUtil/TFSPackager.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -21,6 +21,7 @@
 #include <osgEarth/Registry>
 #include <osgDB/FileNameUtils>
 #include <osgDB/FileUtils>
+#include <osgEarth/FileUtils>
 
 #define LC "[TFSPackager] "
 
@@ -262,7 +263,7 @@ public:
               //OE_NOTICE << "Writing " << features.size() << " features to " << filename << std::endl;
 
               if ( !osgDB::fileExists( osgDB::getFilePath(filename) ) )
-                  osgDB::makeDirectoryForFile( filename );
+                  osgEarth::makeDirectoryForFile( filename );
 
 
               std::fstream output( filename.c_str(), std::ios_base::out );
diff --git a/src/osgEarthUtil/TMS b/src/osgEarthUtil/TMS
index 914b4a2..7fbd0aa 100644
--- a/src/osgEarthUtil/TMS
+++ b/src/osgEarthUtil/TMS
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -38,9 +38,7 @@
 #include <osgDB/ReaderWriter>
 
 #include <osg/Version>
-#if OSG_MIN_VERSION_REQUIRED(2,9,5)
 #include <osgDB/Options>
-#endif
 
 namespace osgEarth 
 {   
diff --git a/src/osgEarthUtil/TMS.cpp b/src/osgEarthUtil/TMS.cpp
index 2285a0e..f68b348 100644
--- a/src/osgEarthUtil/TMS.cpp
+++ b/src/osgEarthUtil/TMS.cpp
@@ -1,757 +1,794 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#include <osgEarthUtil/TMS>
-#include <osgEarth/Common>
-#include <osgEarth/GeoData>
-#include <osgEarth/XmlUtils>
-#include <osgEarth/TileKey>
-#include <osgEarth/TileSource>
-#include <osgEarth/Registry>
-#include <osgEarth/StringUtils>
-#include <osgEarth/Profile>
-
-#include <osg/Notify>
-#include <osgDB/FileUtils>
-#include <osgDB/FileNameUtils>
-
-#include <limits.h>
-#include <iomanip>
-
-using namespace osgEarth;
-using namespace osgEarth::Util::TMS;
-
-#define LC "[TMS] "
-
-static std::string toString(double value, int precision = 25)
-{
-    std::stringstream out;
-    out << std::fixed << std::setprecision(precision) << value;
-	std::string outStr;
-	outStr = out.str();
-    return outStr;
-}
-
-TileFormat::TileFormat():
-_width(0),
-_height(0)
-{
-}
-
-TileSet::TileSet():
-_unitsPerPixel(0.0),
-_order(0)
-{
-}
-
-TileMap::TileMap():
-_originX(0),
-_originY(0),
-_minX(0.0),
-_minY(0.0),
-_maxX(0.0),
-_maxY(0.0),
-_minLevel(0),
-_maxLevel(0),
-_numTilesHigh(-1),
-_numTilesWide(-1),
-_timestamp(0)
-{   
-}
-
-void TileMap::setOrigin(double x, double y)
-{
-    _originX = x;
-    _originY = y;
-}
-
-void TileMap::getExtents( double &minX, double &minY, double &maxX, double &maxY) const
-{
-    minX = _minX;
-    minY = _minY;
-    maxX = _maxX;
-    maxY = _maxY;
-}
-
-void TileMap::setExtents( double minX, double minY, double maxX, double maxY)
-{
-    _minX = minX;
-    _minY = minY;
-    _maxX = maxX;
-    _maxY = maxY;
-}
-
-
-
-
-#define ELEM_TILEMAP "tilemap"
-#define ELEM_TITLE "title"
-#define ELEM_ABSTRACT "abstract"
-#define ELEM_SRS "srs"
-#define ELEM_VERTICAL_SRS "vsrs"
-#define ELEM_BOUNDINGBOX "boundingbox"
-#define ELEM_ORIGIN "origin"
-#define ELEM_TILE_FORMAT "tileformat"
-#define ELEM_TILESETS "tilesets"
-#define ELEM_TILESET "tileset"
-#define ELEM_DATA_EXTENTS "dataextents"
-#define ELEM_DATA_EXTENT "dataextent"
-
-#define ATTR_VERSION "version"
-#define ATTR_TILEMAPSERVICE "tilemapservice"
-
-#define ATTR_MINX "minx"
-#define ATTR_MINY "miny"
-#define ATTR_MAXX "maxx"
-#define ATTR_MAXY "maxy"
-#define ATTR_X "x"
-#define ATTR_Y "y"
-#define ATTR_MIN_LEVEL "minlevel"
-#define ATTR_MAX_LEVEL "maxlevel"
-
-#define ATTR_WIDTH "width"
-#define ATTR_HEIGHT "height"
-#define ATTR_MIME_TYPE "mime-type"
-#define ATTR_EXTENSION "extension"
-
-#define ATTR_PROFILE "profile"
-
-#define ATTR_HREF "href"
-#define ATTR_ORDER "order"
-#define ATTR_UNITSPERPIXEL "units-per-pixel"
-
-bool intersects(const double &minXa, const double &minYa, const double &maxXa, const double &maxYa,
-                const double &minXb, const double &minYb, const double &maxXb, const double &maxYb)
-{
-    return  osg::maximum(minXa, minXb) <= osg::minimum(maxXa,maxXb) &&
-            osg::maximum(minYa, minYb) <= osg::minimum(maxYa, maxYb);
-}
-
-
-
-void TileMap::computeMinMaxLevel()
-{
-    _minLevel = INT_MAX;
-    _maxLevel = 0;
-    for (TileSetList::iterator itr = _tileSets.begin(); itr != _tileSets.end(); ++itr)
-    { 
-        if (itr->getOrder() < _minLevel) _minLevel = itr->getOrder();
-        if (itr->getOrder() > _maxLevel) _maxLevel = itr->getOrder();
-    }
-}
-
-void TileMap::computeNumTiles()
-{
-    _numTilesWide = -1;
-    _numTilesHigh = -1;
-
-    if (_tileSets.size() > 0)
-    {
-        unsigned int level = _tileSets[0].getOrder();
-        double res = _tileSets[0].getUnitsPerPixel();
-
-        _numTilesWide = (int)((_maxX - _minX) / (res * _format.getWidth()));
-        _numTilesHigh = (int)((_maxY - _minY) / (res * _format.getWidth()));
-
-        //In case the first level specified isn't level 0, compute the number of tiles at level 0
-        for (unsigned int i = 0; i < level; i++)
-        {
-            _numTilesWide /= 2;
-            _numTilesHigh /= 2;
-        }
-
-        OE_DEBUG << LC << "TMS has " << _numTilesWide << ", " << _numTilesHigh << " tiles at level 0 " <<  std::endl;
-    }
-}
-
-const Profile*
-TileMap::createProfile() const
-{
-    osg::ref_ptr< SpatialReference > spatialReference =  osgEarth::SpatialReference::create(_srs);
-
-    if (getProfileType() == Profile::TYPE_GEODETIC)
-    {
-        return osgEarth::Registry::instance()->getGlobalGeodeticProfile();
-    }
-    else if (getProfileType() == Profile::TYPE_MERCATOR)
-    {
-        return osgEarth::Registry::instance()->getSphericalMercatorProfile();
-    }    
-    else if (spatialReference->isSphericalMercator())
-    {
-        //HACK:  Some TMS sources, most notably TileCache, use a global mercator extent that is very slightly different than
-        //       the automatically computed mercator bounds which can cause rendering issues due to the some texture coordinates
-        //       crossing the dateline.  If the incoming bounds are nearly the same as our definion of global mercator, just use our definition.
-        double eps = 0.01;
-        osg::ref_ptr< const Profile > merc = osgEarth::Registry::instance()->getSphericalMercatorProfile();
-        if (_numTilesWide == 1 && _numTilesHigh == 1 &&
-            osg::equivalent(merc->getExtent().xMin(), _minX, eps) && 
-            osg::equivalent(merc->getExtent().yMin(), _minY, eps) &&
-            osg::equivalent(merc->getExtent().xMax(), _maxX, eps) &&
-            osg::equivalent(merc->getExtent().yMax(), _maxY, eps))
-        {            
-            return osgEarth::Registry::instance()->getSphericalMercatorProfile();
-        }
-    }
-
-    else if ( 
-        spatialReference->isGeographic()  && 
-        !spatialReference->isPlateCarre() &&
-        osg::equivalent(_minX, -180.) &&
-        osg::equivalent(_maxX,  180.) &&
-        osg::equivalent(_minY,  -90.) &&
-        osg::equivalent(_maxY,   90.) )
-    {
-        return osgEarth::Registry::instance()->getGlobalGeodeticProfile();
-    }
-    else if ( _profile_type == Profile::TYPE_MERCATOR )
-    {
-        return osgEarth::Registry::instance()->getSphericalMercatorProfile();
-    }    
-
-    // everything else is a "LOCAL" profile.
-    return Profile::create(
-        _srs,
-        _minX, _minY, _maxX, _maxY,
-        _vsrs,
-        osg::maximum(_numTilesWide, (unsigned int)1),
-        osg::maximum(_numTilesHigh, (unsigned int)1) );
-}
-
-
-std::string
-TileMap::getURL(const osgEarth::TileKey& tilekey, bool invertY)
-{
-    if (!intersectsKey(tilekey))
-    {
-        //OE_NOTICE << LC << "No key intersection for tile key " << tilekey.str() << std::endl;
-        return "";
-    }
-
-    unsigned int zoom = tilekey.getLevelOfDetail();
-
-    unsigned int x, y;
-    tilekey.getTileXY(x, y);
-
-    //Some TMS like services swap the Y coordinate so 0,0 is the upper left rather than the lower left.  The normal TMS
-    //specification has 0,0 at the bottom left, so inverting Y will make 0,0 in the upper left.
-    //http://code.google.com/apis/maps/documentation/overlays.html#Google_Maps_Coordinates
-    if (!invertY)
-    {
-        unsigned int numRows, numCols;
-        tilekey.getProfile()->getNumTiles(tilekey.getLevelOfDetail(), numCols, numRows);
-        y  = numRows - y - 1;
-    }
-
-    //OE_NOTICE << LC << "KEY: " << tilekey.str() << " level " << zoom << " ( " << x << ", " << y << ")" << std::endl;
-
-    //Select the correct TileSet
-    if ( _tileSets.size() > 0 )
-    {
-        for (TileSetList::iterator itr = _tileSets.begin(); itr != _tileSets.end(); ++itr)
-        { 
-            if (itr->getOrder() == zoom)
-            {                
-                std::stringstream ss; 
-                std::string basePath = osgDB::getFilePath(_filename);                
-                if (!basePath.empty())
-                {
-                    ss << basePath << "/";
-                }
-                ss << zoom << "/" << x << "/" << y << "." << _format.getExtension();                
-                std::string ssStr;
-				ssStr = ss.str();
-				return ssStr;
-            }
-        }
-    }
-    else // Just go with it. No way of knowing the max level.
-    {
-        std::stringstream ss; 
-        std::string basePath = osgDB::getFilePath(_filename);                
-        if (!basePath.empty())
-        {
-            ss << basePath << "/";
-        }
-        ss << zoom << "/" << x << "/" << y << "." << _format.getExtension();                
-        std::string ssStr;
-        ssStr = ss.str();
-        return ssStr;
-    }
-
-    return "";
-}
-
-bool
-TileMap::intersectsKey(const TileKey& tilekey)
-{
-    osg::Vec3d keyMin, keyMax;
-
-    //double keyMinX, keyMinY, keyMaxX, keyMaxY;
-
-    //Check to see if the key overlaps the bounding box using lat/lon.  This is necessary to check even in 
-    //Mercator situations in case the BoundingBox is described using lat/lon coordinates such as those produced by GDAL2Tiles
-    //This should be considered a bug on the TMS production side, but we can work around it for now...
-    tilekey.getExtent().getBounds(keyMin.x(), keyMin.y(), keyMax.x(), keyMax.y());
-    //tilekey.getExtent().getBounds(keyMinX, keyMinY, keyMaxX, keyMaxY);
-
-    bool inter = intersects(_minX, _minY, _maxX, _maxY, keyMin.x(), keyMin.y(), keyMax.x(), keyMax.y() ); //keyMinX, keyMinY, keyMaxX, keyMaxY);
-
-    if (!inter && tilekey.getProfile()->getSRS()->isSphericalMercator())
-    {
-        tilekey.getProfile()->getSRS()->transform(keyMin, tilekey.getProfile()->getSRS()->getGeographicSRS(), keyMin );
-        tilekey.getProfile()->getSRS()->transform(keyMax, tilekey.getProfile()->getSRS()->getGeographicSRS(), keyMax );
-        inter = intersects(_minX, _minY, _maxX, _maxY, keyMin.x(), keyMin.y(), keyMax.x(), keyMax.y() );
-        //tilekey.getProfile()->getSRS()->transform2D(keyMinX, keyMinY, tilekey.getProfile()->getSRS()->getGeographicSRS(), keyMinX, keyMinY);
-        //tilekey.getProfile()->getSRS()->transform2D(keyMaxX, keyMaxY, tilekey.getProfile()->getSRS()->getGeographicSRS(), keyMaxX, keyMaxY);
-        //inter = intersects(_minX, _minY, _maxX, _maxY, keyMinX, keyMinY, keyMaxX, keyMaxY);
-    }
-
-    return inter;
-}
-
-void
-TileMap::generateTileSets(unsigned int numLevels)
-{
-    osg::ref_ptr<const Profile> profile = createProfile();
-
-    _tileSets.clear();
-
-    double width = (_maxX - _minX);
-//    double height = (_maxY - _minY);
-
-    for (unsigned int i = 0; i < numLevels; ++i)
-    {
-        unsigned int numCols, numRows;
-        profile->getNumTiles(i, numCols, numRows);
-        double res = (width / (double)numCols) / (double)_format.getWidth();
-
-        TileSet ts;
-        ts.setUnitsPerPixel(res);
-        ts.setOrder(i);
-        _tileSets.push_back(ts);
-    }
-}
-
-std::string getHorizSRSString(const osgEarth::SpatialReference* srs)
-{
-    if (srs->isSphericalMercator())
-    {
-        return "EPSG:900913";
-    }
-    else if (srs->isGeographic())
-    {
-        return "EPSG:4326";
-    }
-    else
-    {
-        return srs->getHorizInitString(); //srs();
-    }	
-}
-
-
-TileMap*
-TileMap::create(const std::string& url,
-                const Profile* profile,
-                const std::string& format,
-                int tile_width,
-                int tile_height)
-{
-    //Profile profile(type);
-
-    const GeoExtent& ex = profile->getExtent();
-
-    TileMap* tileMap = new TileMap();
-    tileMap->setProfileType(profile->getProfileType()); //type);
-    tileMap->setExtents(ex.xMin(), ex.yMin(), ex.xMax(), ex.yMax());
-    tileMap->setOrigin(ex.xMin(), ex.yMin());
-    tileMap->_filename = url;
-    tileMap->_srs = getHorizSRSString(profile->getSRS());
-    tileMap->_vsrs = profile->getSRS()->getVertInitString();
-    //tileMap->_vsrs = profile->getVerticalSRS() ? profile->getVerticalSRS()->getInitString() : "";
-    tileMap->_format.setWidth( tile_width );
-    tileMap->_format.setHeight( tile_height );
-    tileMap->_format.setExtension( format );
-    profile->getNumTiles( 0, tileMap->_numTilesWide, tileMap->_numTilesHigh );
-
-    tileMap->generateTileSets();
-    tileMap->computeMinMaxLevel();
-
-    return tileMap;
-}
-
-TileMap* TileMap::create(const TileSource* tileSource, const Profile* profile)
-{
-    TileMap* tileMap = new TileMap();
-
-    tileMap->setTitle( tileSource->getName() );
-    tileMap->setProfileType( profile->getProfileType() );
-
-    const GeoExtent& ex = profile->getExtent();
-    
-    tileMap->_srs = getHorizSRSString(profile->getSRS()); //srs();
-    tileMap->_vsrs = profile->getSRS()->getVertInitString(); //profile->getVerticalSRS() ? profile->getVerticalSRS()->getInitString() : 0L;
-    tileMap->_originX = ex.xMin();
-    tileMap->_originY = ex.yMin();
-    tileMap->_minX = ex.xMin();
-    tileMap->_minY = ex.yMin();
-    tileMap->_maxX = ex.xMax();
-    tileMap->_maxY = ex.yMax();
-    profile->getNumTiles( 0, tileMap->_numTilesWide, tileMap->_numTilesHigh );
-
-    tileMap->_format.setWidth( tileSource->getPixelsPerTile() );
-    tileMap->_format.setHeight( tileSource->getPixelsPerTile() );
-    tileMap->_format.setExtension( tileSource->getExtension() );
-
-    tileMap->generateTileSets();
-
-    return tileMap;
-}
-
-
-
-//----------------------------------------------------------------------------
-
-
-TileMap* 
-TileMapReaderWriter::read( const std::string& location, const osgDB::ReaderWriter::Options* options )
-{
-    TileMap* tileMap = NULL;
-
-    ReadResult r = URI(location).readString();
-    if ( r.failed() )
-    {
-        OE_WARN << LC << "Failed to read TMS tile map file from " << location << std::endl;
-        return 0L;
-    }
-    
-    // Read tile map into a Config:
-    Config conf;
-    std::stringstream buf( r.getString() );
-    conf.fromXML( buf );
-
-    // parse that into a tile map:        
-    tileMap = TileMapReaderWriter::read( conf );
-
-    if (tileMap)
-    {
-        tileMap->setFilename( location );
-
-        // record the timestamp (if there is one) in the tilemap. It's not a persistent field
-        // but will help with things like per-session caching.
-        tileMap->setTimeStamp( r.lastModifiedTime() );
-    }
-
-    return tileMap;
-}
-
-TileMap*
-TileMapReaderWriter::read( const Config& conf )
-{
-    const Config* tileMapConf = conf.find( ELEM_TILEMAP );
-    if ( !tileMapConf )
-    {
-        OE_WARN << LC << "Could not find root TileMap element " << std::endl;
-        return 0L;
-    }
-
-    TileMap* tileMap = new TileMap();
-
-    tileMap->setVersion       ( tileMapConf->value(ATTR_VERSION) );
-    tileMap->setTileMapService( tileMapConf->value(ATTR_TILEMAPSERVICE) ); 
-    tileMap->setTitle         ( tileMapConf->value(ELEM_TITLE) );
-    tileMap->setAbstract      ( tileMapConf->value(ELEM_ABSTRACT) );
-    tileMap->setSRS           ( tileMapConf->value(ELEM_SRS) );
-    tileMap->setVerticalSRS   ( tileMapConf->value(ELEM_VERTICAL_SRS) );
-
-    const Config* bboxConf = tileMapConf->find( ELEM_BOUNDINGBOX );
-    if ( bboxConf )
-    {
-        double minX = bboxConf->value<double>( ATTR_MINX, 0.0 );
-        double minY = bboxConf->value<double>( ATTR_MINY, 0.0 );
-        double maxX = bboxConf->value<double>( ATTR_MAXX, 0.0 );
-        double maxY = bboxConf->value<double>( ATTR_MAXY, 0.0 );
-        tileMap->setExtents( minX, minY, maxX, maxY);
-    }
-
-    //Read the origin
-    const Config* originConf = tileMapConf->find(ELEM_ORIGIN);
-    if ( originConf )
-    {
-        tileMap->setOriginX( originConf->value<double>( ATTR_X, 0.0) );
-        tileMap->setOriginY( originConf->value<double>( ATTR_Y, 0.0) );
-    }
-
-    //Read the tile format
-    const Config* formatConf = tileMapConf->find( ELEM_TILE_FORMAT );
-    if ( formatConf )
-    {
-        tileMap->getFormat().setExtension( formatConf->value(ATTR_EXTENSION) );
-        tileMap->getFormat().setMimeType ( formatConf->value(ATTR_MIME_TYPE) );
-        tileMap->getFormat().setWidth    ( formatConf->value<unsigned>(ATTR_WIDTH,  256) );
-        tileMap->getFormat().setHeight   ( formatConf->value<unsigned>(ATTR_HEIGHT, 256) );
-    }
-
-    //Read the tilesets
-    const Config* tileSetsConf = tileMapConf->find(ELEM_TILESETS);
-    if ( tileSetsConf )
-    {
-        //Read the profile
-        std::string profile = tileSetsConf->value(ATTR_PROFILE);
-        if (profile == "global-geodetic") tileMap->setProfileType( Profile::TYPE_GEODETIC );
-        else if (profile == "global-mercator") tileMap->setProfileType( Profile::TYPE_MERCATOR );
-        else if (profile == "local") tileMap->setProfileType( Profile::TYPE_LOCAL );
-        else tileMap->setProfileType( Profile::TYPE_UNKNOWN );
-
-        //Read each TileSet
-        const ConfigSet& setConfs = tileSetsConf->children(ELEM_TILESET);
-        for( ConfigSet::const_iterator i = setConfs.begin(); i != setConfs.end(); ++i )
-        {
-            const Config& conf = *i;
-            TileSet tileset;
-            tileset.setHref( conf.value(ATTR_HREF) );
-            tileset.setOrder( conf.value<unsigned>(ATTR_ORDER, ~0) );
-            tileset.setUnitsPerPixel( conf.value<double>(ATTR_UNITSPERPIXEL, 0.0) );
-            tileMap->getTileSets().push_back(tileset);
-        }
-    }
-
-    //Try to compute the profile based on the SRS if there was no PROFILE tag given
-    if (tileMap->getProfileType() == Profile::TYPE_UNKNOWN && !tileMap->getSRS().empty())
-    {
-        tileMap->setProfileType( Profile::getProfileTypeFromSRS(tileMap->getSRS()) );
-    }
-
-    tileMap->computeMinMaxLevel();
-    tileMap->computeNumTiles();
-
-    //Read the data areas
-    const Config* extentsConf = tileMapConf->find(ELEM_DATA_EXTENTS);
-    if ( extentsConf )
-    {
-        osg::ref_ptr< const osgEarth::Profile > profile = tileMap->createProfile();
-        OE_DEBUG << LC << "Found DataExtents " << std::endl;
-        const ConfigSet& children = extentsConf->children(ELEM_DATA_EXTENT);
-        for( ConfigSet::const_iterator i = children.begin(); i != children.end(); ++i )
-        {
-            const Config& conf = *i;
-            double minX = conf.value<double>(ATTR_MINX, 0.0);
-            double minY = conf.value<double>(ATTR_MINY, 0.0);
-            double maxX = conf.value<double>(ATTR_MAXX, 0.0);
-            double maxY = conf.value<double>(ATTR_MAXY, 0.0);
-
-            unsigned int maxLevel = conf.value<unsigned>(ATTR_MAX_LEVEL, 0);
-
-            //OE_DEBUG << LC << "Read area " << minX << ", " << minY << ", " << maxX << ", " << maxY << ", minlevel=" << minLevel << " maxlevel=" << maxLevel << std::endl;
-
-            if ( maxLevel > 0 )
-                tileMap->getDataExtents().push_back( DataExtent(GeoExtent(profile->getSRS(), minX, minY, maxX, maxY), 0, maxLevel));
-            else
-                tileMap->getDataExtents().push_back( DataExtent(GeoExtent(profile->getSRS(), minX, minY, maxX, maxY), 0) );
-        }
-    }
-
-
-    return tileMap;
-}
-
-static XmlDocument*
-tileMapToXmlDocument(const TileMap* tileMap)
-{
-    //Create the root XML document
-    osg::ref_ptr<XmlDocument> doc = new XmlDocument();
-    doc->setName( ELEM_TILEMAP );
-    
-    //Create the root node
-    //osg::ref_ptr<XmlElement> e_tile_map = new XmlElement( ELEM_TILEMAP );
-    //doc->getChildren().push_back( e_tile_map.get() );
-
-    doc->getAttrs()[ ATTR_VERSION ] = tileMap->getVersion();
-    doc->getAttrs()[ ATTR_TILEMAPSERVICE ] = tileMap->getTileMapService();
-  
-    doc->addSubElement( ELEM_TITLE, tileMap->getTitle() );
-    doc->addSubElement( ELEM_ABSTRACT, tileMap->getAbstract() );
-    doc->addSubElement( ELEM_SRS, tileMap->getSRS() );
-    doc->addSubElement( ELEM_VERTICAL_SRS, tileMap->getVerticalSRS() );
-
-    osg::ref_ptr<XmlElement> e_bounding_box = new XmlElement( ELEM_BOUNDINGBOX );
-    double minX, minY, maxX, maxY;
-    tileMap->getExtents( minX, minY, maxX, maxY );
-    e_bounding_box->getAttrs()[ATTR_MINX] = toString(minX);
-    e_bounding_box->getAttrs()[ATTR_MINY] = toString(minY);
-    e_bounding_box->getAttrs()[ATTR_MAXX] = toString(maxX);
-    e_bounding_box->getAttrs()[ATTR_MAXY] = toString(maxY);
-    doc->getChildren().push_back(e_bounding_box.get() );
-
-    osg::ref_ptr<XmlElement> e_origin = new XmlElement( ELEM_ORIGIN );
-    e_origin->getAttrs()[ATTR_X] = toString(tileMap->getOriginX());
-    e_origin->getAttrs()[ATTR_Y] = toString(tileMap->getOriginY());
-    doc->getChildren().push_back(e_origin.get());
-
-    osg::ref_ptr<XmlElement> e_tile_format = new XmlElement( ELEM_TILE_FORMAT );
-    e_tile_format->getAttrs()[ ATTR_EXTENSION ] = tileMap->getFormat().getExtension();
-    e_tile_format->getAttrs()[ ATTR_MIME_TYPE ] = tileMap->getFormat().getMimeType();
-    e_tile_format->getAttrs()[ ATTR_WIDTH ] = toString<unsigned int>(tileMap->getFormat().getWidth());
-    e_tile_format->getAttrs()[ ATTR_HEIGHT ] = toString<unsigned int>(tileMap->getFormat().getHeight());
-    doc->getChildren().push_back(e_tile_format.get());
-
-    osg::ref_ptr< const osgEarth::Profile > profile = tileMap->createProfile();
-
-    osg::ref_ptr<XmlElement> e_tile_sets = new XmlElement ( ELEM_TILESETS );
-    std::string profileString = "none";
-    if (profile->isEquivalentTo(osgEarth::Registry::instance()->getGlobalGeodeticProfile()))
-    {
-        profileString = "global-geodetic";
-    }
-    else if (profile->isEquivalentTo(osgEarth::Registry::instance()->getGlobalMercatorProfile()))
-    {
-        profileString = "global-mercator";
-    }
-    else
-    {
-        profileString = "local";
-    }
-    e_tile_sets->getAttrs()[ ATTR_PROFILE ] = profileString;
-
-
-    for (TileMap::TileSetList::const_iterator itr = tileMap->getTileSets().begin(); itr != tileMap->getTileSets().end(); ++itr)
-    {
-        osg::ref_ptr<XmlElement> e_tile_set = new XmlElement( ELEM_TILESET );
-        e_tile_set->getAttrs()[ATTR_HREF] = itr->getHref();
-        e_tile_set->getAttrs()[ATTR_ORDER] = toString<unsigned int>(itr->getOrder());
-        e_tile_set->getAttrs()[ATTR_UNITSPERPIXEL] = toString(itr->getUnitsPerPixel());
-        e_tile_sets->getChildren().push_back( e_tile_set.get() );
-    }
-    doc->getChildren().push_back(e_tile_sets.get());
-
-    //Write out the data areas
-    if (tileMap->getDataExtents().size() > 0)
-    {
-        osg::ref_ptr<XmlElement> e_data_extents = new XmlElement( ELEM_DATA_EXTENTS );
-        for (DataExtentList::const_iterator itr = tileMap->getDataExtents().begin(); itr != tileMap->getDataExtents().end(); ++itr)
-        {
-            osg::ref_ptr<XmlElement> e_data_extent = new XmlElement( ELEM_DATA_EXTENT );
-            e_data_extent->getAttrs()[ATTR_MINX] = toString(itr->xMin());
-            e_data_extent->getAttrs()[ATTR_MINY] = toString(itr->yMin());
-            e_data_extent->getAttrs()[ATTR_MAXX] = toString(itr->xMax());
-            e_data_extent->getAttrs()[ATTR_MAXY] = toString(itr->yMax());
-            if ( itr->minLevel().isSet() )
-                e_data_extent->getAttrs()[ATTR_MIN_LEVEL] = toString<unsigned int>(*itr->minLevel());
-            if ( itr->maxLevel().isSet() )
-                e_data_extent->getAttrs()[ATTR_MAX_LEVEL] = toString<unsigned int>(*itr->maxLevel());
-            e_data_extents->getChildren().push_back( e_data_extent );
-        }
-        doc->getChildren().push_back( e_data_extents.get() );
-    }
-    return doc.release();
-}
-
-void
-TileMapReaderWriter::write(const TileMap* tileMap, const std::string &location)
-{
-    std::string path = osgDB::getFilePath(location);
-    if (!osgDB::fileExists(path) && !osgDB::makeDirectory(path))
-    {
-        OE_WARN << LC << "Couldn't create path " << std::endl;
-    }
-    std::ofstream out(location.c_str());
-    write(tileMap, out);
-}
-
-void
-TileMapReaderWriter::write(const TileMap* tileMap, std::ostream &output)
-{
-    osg::ref_ptr<XmlDocument> doc = tileMapToXmlDocument(tileMap);    
-    doc->store(output);
-}
-
-
-//----------------------------------------------------------------------------
-
-TileMapEntry::TileMapEntry( const std::string& _title, const std::string& _href, const std::string& _srs, const std::string& _profile ):
-title( _title ),
-href( _href ),
-srs( _srs ),
-profile( _profile )
-{
-}
-
-//----------------------------------------------------------------------------
-
-TileMapServiceReader::TileMapServiceReader()
-{
-}
-
-TileMapServiceReader::TileMapServiceReader(const TileMapServiceReader& rhs)
-{
-}
-
-bool
-TileMapServiceReader::read( const std::string &location, const osgDB::ReaderWriter::Options* options, TileMapEntryList& tileMaps )
-{     
-    ReadResult r = URI(location).readString();
-    if ( r.failed() )
-    {
-        OE_WARN << LC << "Failed to read TileMapServices from " << location << std::endl;
-        return 0L;
-    }    
-    
-    // Read tile map into a Config:
-    Config conf;
-    std::stringstream buf( r.getString() );
-    conf.fromXML( buf );    
-
-    // parse that into a tile map:        
-    return read( conf, tileMaps );    
-}
-
-bool
-TileMapServiceReader::read( const Config& conf, TileMapEntryList& tileMaps)
-{    
-    const Config* TileMapServiceConf = conf.find("tilemapservice");
-
-    if (!TileMapServiceConf)
-    {
-        OE_NOTICE << "Couldn't find root TileMapService element" << std::endl;
-    }
-
-    const Config* TileMapsConf = TileMapServiceConf->find("tilemaps");
-    if (TileMapsConf)
-    {
-        const ConfigSet& TileMaps = TileMapsConf->children("tilemap");
-        if (TileMaps.size() == 0)
-        {            
-            return false;
-        }
-        
-        for (ConfigSet::const_iterator itr = TileMaps.begin(); itr != TileMaps.end(); ++itr)
-        {
-            std::string href = itr->value("href");
-            std::string title = itr->value("title");
-            std::string profile = itr->value("profile");
-            std::string srs = itr->value("srs");            
-
-            tileMaps.push_back( TileMapEntry( title, href, srs, profile ) );
-        }        
-
-        return true;
-    }    
-    return false;
-}
-
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+#include <osgEarthUtil/TMS>
+#include <osgEarth/Common>
+#include <osgEarth/GeoData>
+#include <osgEarth/XmlUtils>
+#include <osgEarth/TileKey>
+#include <osgEarth/TileSource>
+#include <osgEarth/Registry>
+#include <osgEarth/StringUtils>
+#include <osgEarth/Profile>
+
+#include <osg/Notify>
+#include <osgDB/FileUtils>
+#include <osgDB/FileNameUtils>
+
+#include <limits.h>
+#include <iomanip>
+
+using namespace osgEarth;
+using namespace osgEarth::Util::TMS;
+
+#define LC "[TMS] "
+
+//-----------------------------------------------------------------
+
+namespace
+{
+    std::string toString(double value, int precision = 7)
+    {
+        std::stringstream out;
+        out << std::fixed << std::setprecision(precision) << value;
+	    std::string outStr;
+	    outStr = out.str();
+        return outStr;
+    }
+}
+
+//-----------------------------------------------------------------
+
+TileFormat::TileFormat():
+_width(0),
+_height(0)
+{
+}
+
+TileSet::TileSet():
+_unitsPerPixel(0.0),
+_order(0)
+{
+}
+
+TileMap::TileMap():
+_originX(0),
+_originY(0),
+_minX(0.0),
+_minY(0.0),
+_maxX(0.0),
+_maxY(0.0),
+_minLevel(0),
+_maxLevel(0),
+_numTilesHigh(-1),
+_numTilesWide(-1),
+_timestamp(0),
+_version("1.0"),
+_tileMapService("http://tms.osgeo.org/1.0.0")
+{   
+}
+
+void TileMap::setOrigin(double x, double y)
+{
+    _originX = x;
+    _originY = y;
+}
+
+void TileMap::getExtents( double &minX, double &minY, double &maxX, double &maxY) const
+{
+    minX = _minX;
+    minY = _minY;
+    maxX = _maxX;
+    maxY = _maxY;
+}
+
+void TileMap::setExtents( double minX, double minY, double maxX, double maxY)
+{
+    _minX = minX;
+    _minY = minY;
+    _maxX = maxX;
+    _maxY = maxY;
+}
+
+
+
+
+#define ELEM_TILEMAP "tilemap"
+#define ELEM_TITLE "title"
+#define ELEM_ABSTRACT "abstract"
+#define ELEM_SRS "srs"
+#define ELEM_VERTICAL_SRS "vsrs"
+#define ELEM_VERTICAL_DATUM "vdatum"
+#define ELEM_BOUNDINGBOX "boundingbox"
+#define ELEM_ORIGIN "origin"
+#define ELEM_TILE_FORMAT "tileformat"
+#define ELEM_TILESETS "tilesets"
+#define ELEM_TILESET "tileset"
+#define ELEM_DATA_EXTENTS "dataextents"
+#define ELEM_DATA_EXTENT "dataextent"
+
+#define ATTR_VERSION "version"
+#define ATTR_TILEMAPSERVICE "tilemapservice"
+
+#define ATTR_MINX "minx"
+#define ATTR_MINY "miny"
+#define ATTR_MAXX "maxx"
+#define ATTR_MAXY "maxy"
+#define ATTR_X "x"
+#define ATTR_Y "y"
+#define ATTR_MIN_LEVEL "minlevel"
+#define ATTR_MAX_LEVEL "maxlevel"
+
+#define ATTR_WIDTH "width"
+#define ATTR_HEIGHT "height"
+#define ATTR_MIME_TYPE "mime-type"
+#define ATTR_EXTENSION "extension"
+
+#define ATTR_PROFILE "profile"
+
+#define ATTR_HREF "href"
+#define ATTR_ORDER "order"
+#define ATTR_UNITSPERPIXEL "units-per-pixel"
+
+bool intersects(const double &minXa, const double &minYa, const double &maxXa, const double &maxYa,
+                const double &minXb, const double &minYb, const double &maxXb, const double &maxYb)
+{
+    return  osg::maximum(minXa, minXb) <= osg::minimum(maxXa,maxXb) &&
+            osg::maximum(minYa, minYb) <= osg::minimum(maxYa, maxYb);
+}
+
+
+
+void TileMap::computeMinMaxLevel()
+{
+    _minLevel = INT_MAX;
+    _maxLevel = 0;
+    for (TileSetList::iterator itr = _tileSets.begin(); itr != _tileSets.end(); ++itr)
+    { 
+        if (itr->getOrder() < _minLevel) _minLevel = itr->getOrder();
+        if (itr->getOrder() > _maxLevel) _maxLevel = itr->getOrder();
+    }
+}
+
+void TileMap::computeNumTiles()
+{
+    _numTilesWide = -1;
+    _numTilesHigh = -1;
+
+    if (_tileSets.size() > 0)
+    {
+        unsigned int level = _tileSets[0].getOrder();
+        double res = _tileSets[0].getUnitsPerPixel();
+
+        _numTilesWide = (int)((_maxX - _minX) / (res * _format.getWidth()));
+        _numTilesHigh = (int)((_maxY - _minY) / (res * _format.getWidth()));
+
+        //In case the first level specified isn't level 0, compute the number of tiles at level 0
+        for (unsigned int i = 0; i < level; i++)
+        {
+            _numTilesWide /= 2;
+            _numTilesHigh /= 2;
+        }
+
+        OE_DEBUG << LC << "TMS has " << _numTilesWide << ", " << _numTilesHigh << " tiles at level 0 " <<  std::endl;
+    }
+}
+
+const Profile*
+TileMap::createProfile() const
+{
+    osg::ref_ptr<const Profile> profile = 0L;
+    osg::ref_ptr< SpatialReference > spatialReference =  osgEarth::SpatialReference::create(_srs, _vsrs);
+
+    if (getProfileType() == Profile::TYPE_GEODETIC)
+    {
+        profile = osgEarth::Registry::instance()->getGlobalGeodeticProfile();
+    }
+    else if (getProfileType() == Profile::TYPE_MERCATOR)
+    {
+        profile = osgEarth::Registry::instance()->getSphericalMercatorProfile();
+    }    
+    else if (spatialReference->isSphericalMercator())
+    {
+        //HACK:  Some TMS sources, most notably TileCache, use a global mercator extent that is very slightly different than
+        //       the automatically computed mercator bounds which can cause rendering issues due to the some texture coordinates
+        //       crossing the dateline.  If the incoming bounds are nearly the same as our definion of global mercator, just use our definition.
+        double eps = 0.01;
+        osg::ref_ptr< const Profile > merc = osgEarth::Registry::instance()->getSphericalMercatorProfile();
+        if (_numTilesWide == 1 && _numTilesHigh == 1 &&
+            osg::equivalent(merc->getExtent().xMin(), _minX, eps) && 
+            osg::equivalent(merc->getExtent().yMin(), _minY, eps) &&
+            osg::equivalent(merc->getExtent().xMax(), _maxX, eps) &&
+            osg::equivalent(merc->getExtent().yMax(), _maxY, eps))
+        {            
+            profile = osgEarth::Registry::instance()->getSphericalMercatorProfile();
+        }
+    }
+
+    else if ( 
+        spatialReference->isGeographic()  && 
+        !spatialReference->isPlateCarre() &&
+        osg::equivalent(_minX, -180.) &&
+        osg::equivalent(_maxX,  180.) &&
+        osg::equivalent(_minY,  -90.) &&
+        osg::equivalent(_maxY,   90.) )
+    {
+        profile = osgEarth::Registry::instance()->getGlobalGeodeticProfile();
+    }
+    else if ( _profile_type == Profile::TYPE_MERCATOR )
+    {
+        profile = osgEarth::Registry::instance()->getSphericalMercatorProfile();
+    }
+
+    if ( !profile )
+    {
+        // everything else is a "LOCAL" profile.
+        profile = Profile::create(
+            _srs,
+            _minX, _minY, _maxX, _maxY,
+            _vsrs,
+            osg::maximum(_numTilesWide, (unsigned int)1),
+            osg::maximum(_numTilesHigh, (unsigned int)1) );
+    }
+    else if ( !_vsrs.empty() )
+    {
+        // vdatum override?
+        ProfileOptions options(profile->toProfileOptions());
+        options.vsrsString() = _vsrs;
+        profile = Profile::create(options);
+    }
+    
+
+    return profile.release();
+}
+
+
+std::string
+TileMap::getURL(const osgEarth::TileKey& tilekey, bool invertY)
+{
+    if (!intersectsKey(tilekey))
+    {
+        //OE_NOTICE << LC << "No key intersection for tile key " << tilekey.str() << std::endl;
+        return "";
+    }
+
+    unsigned int zoom = tilekey.getLevelOfDetail();
+
+    unsigned int x, y;
+    tilekey.getTileXY(x, y);
+
+    //Some TMS like services swap the Y coordinate so 0,0 is the upper left rather than the lower left.  The normal TMS
+    //specification has 0,0 at the bottom left, so inverting Y will make 0,0 in the upper left.
+    //http://code.google.com/apis/maps/documentation/overlays.html#Google_Maps_Coordinates
+    if (!invertY)
+    {
+        unsigned int numRows, numCols;
+        tilekey.getProfile()->getNumTiles(tilekey.getLevelOfDetail(), numCols, numRows);
+        y  = numRows - y - 1;
+    }    
+
+    //OE_NOTICE << LC << "KEY: " << tilekey.str() << " level " << zoom << " ( " << x << ", " << y << ")" << std::endl;
+
+    //Select the correct TileSet
+    if ( _tileSets.size() > 0 )
+    {
+        for (TileSetList::iterator itr = _tileSets.begin(); itr != _tileSets.end(); ++itr)
+        { 
+            if (itr->getOrder() == zoom)
+            {                
+                std::stringstream ss; 
+                std::string basePath = osgDB::getFilePath(_filename);                
+                if (!basePath.empty())
+                {
+                    ss << basePath << "/";
+                }
+                ss << zoom << "/" << x << "/" << y << "." << _format.getExtension();                
+                std::string ssStr;
+				ssStr = ss.str();
+				return ssStr;
+            }
+        }
+    }
+    else // Just go with it. No way of knowing the max level.
+    {
+        std::stringstream ss; 
+        std::string basePath = osgDB::getFilePath(_filename);                
+        if (!basePath.empty())
+        {
+            ss << basePath << "/";
+        }
+        ss << zoom << "/" << x << "/" << y << "." << _format.getExtension();                
+        std::string ssStr;
+        ssStr = ss.str();
+        return ssStr;
+    }
+
+    return "";
+}
+
+bool
+TileMap::intersectsKey(const TileKey& tilekey)
+{
+    osg::Vec3d keyMin, keyMax;
+
+    //double keyMinX, keyMinY, keyMaxX, keyMaxY;
+
+    //Check to see if the key overlaps the bounding box using lat/lon.  This is necessary to check even in 
+    //Mercator situations in case the BoundingBox is described using lat/lon coordinates such as those produced by GDAL2Tiles
+    //This should be considered a bug on the TMS production side, but we can work around it for now...
+    tilekey.getExtent().getBounds(keyMin.x(), keyMin.y(), keyMax.x(), keyMax.y());
+    //tilekey.getExtent().getBounds(keyMinX, keyMinY, keyMaxX, keyMaxY);
+
+    bool inter = intersects(_minX, _minY, _maxX, _maxY, keyMin.x(), keyMin.y(), keyMax.x(), keyMax.y() ); //keyMinX, keyMinY, keyMaxX, keyMaxY);
+
+    if (!inter && tilekey.getProfile()->getSRS()->isSphericalMercator())
+    {
+        tilekey.getProfile()->getSRS()->transform(keyMin, tilekey.getProfile()->getSRS()->getGeographicSRS(), keyMin );
+        tilekey.getProfile()->getSRS()->transform(keyMax, tilekey.getProfile()->getSRS()->getGeographicSRS(), keyMax );
+        inter = intersects(_minX, _minY, _maxX, _maxY, keyMin.x(), keyMin.y(), keyMax.x(), keyMax.y() );
+        //tilekey.getProfile()->getSRS()->transform2D(keyMinX, keyMinY, tilekey.getProfile()->getSRS()->getGeographicSRS(), keyMinX, keyMinY);
+        //tilekey.getProfile()->getSRS()->transform2D(keyMaxX, keyMaxY, tilekey.getProfile()->getSRS()->getGeographicSRS(), keyMaxX, keyMaxY);
+        //inter = intersects(_minX, _minY, _maxX, _maxY, keyMinX, keyMinY, keyMaxX, keyMaxY);
+    }
+
+    return inter;
+}
+
+void
+TileMap::generateTileSets(unsigned int numLevels)
+{
+    osg::ref_ptr<const Profile> profile = createProfile();
+
+    _tileSets.clear();
+
+    double width = (_maxX - _minX);
+//    double height = (_maxY - _minY);
+
+    for (unsigned int i = 0; i < numLevels; ++i)
+    {
+        unsigned int numCols, numRows;
+        profile->getNumTiles(i, numCols, numRows);
+        double res = (width / (double)numCols) / (double)_format.getWidth();
+
+        TileSet ts;
+        ts.setUnitsPerPixel(res);
+        ts.setOrder(i);
+        _tileSets.push_back(ts);
+    }
+}
+
+std::string getHorizSRSString(const osgEarth::SpatialReference* srs)
+{
+    if (srs->isSphericalMercator())
+    {
+        return "EPSG:900913";
+    }
+    else if (srs->isGeographic())
+    {
+        return "EPSG:4326";
+    }
+    else
+    {
+        return srs->getHorizInitString(); //srs();
+    }	
+}
+
+
+TileMap*
+TileMap::create(const std::string& url,
+                const Profile*     profile,
+                const std::string& format,
+                int                tile_width,
+                int                tile_height)
+{
+    const GeoExtent& ex = profile->getExtent();
+
+    TileMap* tileMap = new TileMap();
+    tileMap->setProfileType(profile->getProfileType());
+    tileMap->setExtents(ex.xMin(), ex.yMin(), ex.xMax(), ex.yMax());
+    tileMap->setOrigin(ex.xMin(), ex.yMin());
+    tileMap->_filename = url;
+    tileMap->_srs = getHorizSRSString(profile->getSRS());
+    tileMap->_vsrs = profile->getSRS()->getVertInitString();
+    tileMap->_format.setWidth( tile_width );
+    tileMap->_format.setHeight( tile_height );
+    profile->getNumTiles( 0, tileMap->_numTilesWide, tileMap->_numTilesHigh );
+
+    // format can be a mime-type or an extension:
+    std::string::size_type p = format.find('/');
+    if ( p == std::string::npos )
+    {
+        tileMap->_format.setExtension(format);
+        tileMap->_format.setMimeType( Registry::instance()->getMimeTypeForExtension(format) );
+    }
+    else
+    {
+        tileMap->_format.setMimeType(format);
+        tileMap->_format.setExtension( Registry::instance()->getExtensionForMimeType(format) );
+    }
+
+    tileMap->generateTileSets();
+    tileMap->computeMinMaxLevel();
+
+    return tileMap;
+}
+
+TileMap* TileMap::create(const TileSource* tileSource, const Profile* profile)
+{
+    TileMap* tileMap = new TileMap();
+
+    tileMap->setTitle( tileSource->getName() );
+    tileMap->setProfileType( profile->getProfileType() );
+
+    const GeoExtent& ex = profile->getExtent();
+    
+    tileMap->_srs = getHorizSRSString(profile->getSRS()); //srs();
+    tileMap->_vsrs = profile->getSRS()->getVertInitString(); //profile->getVerticalSRS() ? profile->getVerticalSRS()->getInitString() : 0L;
+    tileMap->_originX = ex.xMin();
+    tileMap->_originY = ex.yMin();
+    tileMap->_minX = ex.xMin();
+    tileMap->_minY = ex.yMin();
+    tileMap->_maxX = ex.xMax();
+    tileMap->_maxY = ex.yMax();
+    profile->getNumTiles( 0, tileMap->_numTilesWide, tileMap->_numTilesHigh );
+
+    tileMap->_format.setWidth( tileSource->getPixelsPerTile() );
+    tileMap->_format.setHeight( tileSource->getPixelsPerTile() );
+    tileMap->_format.setExtension( tileSource->getExtension() );
+
+    tileMap->generateTileSets();
+
+    return tileMap;
+}
+
+
+
+//----------------------------------------------------------------------------
+
+
+TileMap* 
+TileMapReaderWriter::read( const std::string& location, const osgDB::ReaderWriter::Options* options )
+{
+    TileMap* tileMap = NULL;
+
+    ReadResult r = URI(location).readString();
+    if ( r.failed() )
+    {
+        OE_WARN << LC << "Failed to read TMS tile map file from " << location << std::endl;
+        return 0L;
+    }
+    
+    // Read tile map into a Config:
+    Config conf;
+    std::stringstream buf( r.getString() );
+    conf.fromXML( buf );
+
+    // parse that into a tile map:        
+    tileMap = TileMapReaderWriter::read( conf );
+
+    if (tileMap)
+    {
+        tileMap->setFilename( location );
+
+        // record the timestamp (if there is one) in the tilemap. It's not a persistent field
+        // but will help with things like per-session caching.
+        tileMap->setTimeStamp( r.lastModifiedTime() );
+    }
+
+    return tileMap;
+}
+
+TileMap*
+TileMapReaderWriter::read( const Config& conf )
+{
+    const Config* tileMapConf = conf.find( ELEM_TILEMAP );
+    if ( !tileMapConf )
+    {
+        OE_WARN << LC << "Could not find root TileMap element " << std::endl;
+        return 0L;
+    }
+
+    TileMap* tileMap = new TileMap();
+
+    tileMap->setVersion       ( tileMapConf->value(ATTR_VERSION) );
+    tileMap->setTileMapService( tileMapConf->value(ATTR_TILEMAPSERVICE) ); 
+    tileMap->setTitle         ( tileMapConf->value(ELEM_TITLE) );
+    tileMap->setAbstract      ( tileMapConf->value(ELEM_ABSTRACT) );
+    tileMap->setSRS           ( tileMapConf->value(ELEM_SRS) );
+
+    if (tileMapConf->hasValue(ELEM_VERTICAL_SRS))
+        tileMap->setVerticalSRS( tileMapConf->value(ELEM_VERTICAL_SRS) );
+    if (tileMapConf->hasValue(ELEM_VERTICAL_DATUM))
+        tileMap->setVerticalSRS( tileMapConf->value(ELEM_VERTICAL_DATUM) );
+
+    const Config* bboxConf = tileMapConf->find( ELEM_BOUNDINGBOX );
+    if ( bboxConf )
+    {
+        double minX = bboxConf->value<double>( ATTR_MINX, 0.0 );
+        double minY = bboxConf->value<double>( ATTR_MINY, 0.0 );
+        double maxX = bboxConf->value<double>( ATTR_MAXX, 0.0 );
+        double maxY = bboxConf->value<double>( ATTR_MAXY, 0.0 );
+        tileMap->setExtents( minX, minY, maxX, maxY);
+    }
+
+    //Read the origin
+    const Config* originConf = tileMapConf->find(ELEM_ORIGIN);
+    if ( originConf )
+    {
+        tileMap->setOriginX( originConf->value<double>( ATTR_X, 0.0) );
+        tileMap->setOriginY( originConf->value<double>( ATTR_Y, 0.0) );
+    }
+
+    //Read the tile format
+    const Config* formatConf = tileMapConf->find( ELEM_TILE_FORMAT );
+    if ( formatConf )
+    {
+        OE_DEBUG << LC << "Read TileFormat " << formatConf->value(ATTR_EXTENSION) << std::endl;
+        tileMap->getFormat().setExtension( formatConf->value(ATTR_EXTENSION) );
+        tileMap->getFormat().setMimeType ( formatConf->value(ATTR_MIME_TYPE) );
+        tileMap->getFormat().setWidth    ( formatConf->value<unsigned>(ATTR_WIDTH,  256) );
+        tileMap->getFormat().setHeight   ( formatConf->value<unsigned>(ATTR_HEIGHT, 256) );
+    }
+    else
+    {
+        OE_WARN << LC << "No TileFormat in TileMap!" << std::endl;
+    }
+
+    //Read the tilesets
+    const Config* tileSetsConf = tileMapConf->find(ELEM_TILESETS);
+    if ( tileSetsConf )
+    {
+        //Read the profile
+        std::string profile = tileSetsConf->value(ATTR_PROFILE);
+        if (profile == "global-geodetic") tileMap->setProfileType( Profile::TYPE_GEODETIC );
+        else if (profile == "global-mercator") tileMap->setProfileType( Profile::TYPE_MERCATOR );
+        else if (profile == "local") tileMap->setProfileType( Profile::TYPE_LOCAL );
+        else tileMap->setProfileType( Profile::TYPE_UNKNOWN );
+
+        //Read each TileSet
+        const ConfigSet& setConfs = tileSetsConf->children(ELEM_TILESET);
+        for( ConfigSet::const_iterator i = setConfs.begin(); i != setConfs.end(); ++i )
+        {
+            const Config& conf = *i;
+            TileSet tileset;
+            tileset.setHref( conf.value(ATTR_HREF) );
+            tileset.setOrder( conf.value<unsigned>(ATTR_ORDER, ~0) );
+            tileset.setUnitsPerPixel( conf.value<double>(ATTR_UNITSPERPIXEL, 0.0) );
+            tileMap->getTileSets().push_back(tileset);
+        }
+    }
+
+    //Try to compute the profile based on the SRS if there was no PROFILE tag given
+    if (tileMap->getProfileType() == Profile::TYPE_UNKNOWN && !tileMap->getSRS().empty())
+    {
+        tileMap->setProfileType( Profile::getProfileTypeFromSRS(tileMap->getSRS()) );
+    }
+
+    tileMap->computeMinMaxLevel();
+    tileMap->computeNumTiles();
+
+    //Read the data areas
+    const Config* extentsConf = tileMapConf->find(ELEM_DATA_EXTENTS);
+    if ( extentsConf )
+    {
+        osg::ref_ptr< const osgEarth::Profile > profile = tileMap->createProfile();
+        OE_DEBUG << LC << "Found DataExtents " << std::endl;
+        const ConfigSet& children = extentsConf->children(ELEM_DATA_EXTENT);
+        for( ConfigSet::const_iterator i = children.begin(); i != children.end(); ++i )
+        {
+            const Config& conf = *i;
+            double minX = conf.value<double>(ATTR_MINX, 0.0);
+            double minY = conf.value<double>(ATTR_MINY, 0.0);
+            double maxX = conf.value<double>(ATTR_MAXX, 0.0);
+            double maxY = conf.value<double>(ATTR_MAXY, 0.0);
+
+            unsigned int maxLevel = conf.value<unsigned>(ATTR_MAX_LEVEL, 0);
+
+            //OE_DEBUG << LC << "Read area " << minX << ", " << minY << ", " << maxX << ", " << maxY << ", minlevel=" << minLevel << " maxlevel=" << maxLevel << std::endl;
+
+            if ( maxLevel > 0 )
+                tileMap->getDataExtents().push_back( DataExtent(GeoExtent(profile->getSRS(), minX, minY, maxX, maxY), 0, maxLevel));
+            else
+                tileMap->getDataExtents().push_back( DataExtent(GeoExtent(profile->getSRS(), minX, minY, maxX, maxY), 0) );
+        }
+    }
+
+
+    return tileMap;
+}
+
+static XmlDocument*
+tileMapToXmlDocument(const TileMap* tileMap)
+{
+    //Create the root XML document
+    osg::ref_ptr<XmlDocument> doc = new XmlDocument();
+    doc->setName( ELEM_TILEMAP );
+    doc->getAttrs()[ ATTR_VERSION ] = tileMap->getVersion();
+    doc->getAttrs()[ ATTR_TILEMAPSERVICE ] = tileMap->getTileMapService();
+  
+    doc->addSubElement( ELEM_TITLE, tileMap->getTitle() );
+    doc->addSubElement( ELEM_ABSTRACT, tileMap->getAbstract() );
+    doc->addSubElement( ELEM_SRS, tileMap->getSRS() );
+    doc->addSubElement( ELEM_VERTICAL_SRS, tileMap->getVerticalSRS() );
+
+    osg::ref_ptr<XmlElement> e_bounding_box = new XmlElement( ELEM_BOUNDINGBOX );
+    double minX, minY, maxX, maxY;
+    tileMap->getExtents( minX, minY, maxX, maxY );
+    e_bounding_box->getAttrs()[ATTR_MINX] = toString(minX);
+    e_bounding_box->getAttrs()[ATTR_MINY] = toString(minY);
+    e_bounding_box->getAttrs()[ATTR_MAXX] = toString(maxX);
+    e_bounding_box->getAttrs()[ATTR_MAXY] = toString(maxY);
+    doc->getChildren().push_back(e_bounding_box.get() );
+
+    osg::ref_ptr<XmlElement> e_origin = new XmlElement( ELEM_ORIGIN );
+    e_origin->getAttrs()[ATTR_X] = toString(tileMap->getOriginX());
+    e_origin->getAttrs()[ATTR_Y] = toString(tileMap->getOriginY());
+    doc->getChildren().push_back(e_origin.get());
+
+    osg::ref_ptr<XmlElement> e_tile_format = new XmlElement( ELEM_TILE_FORMAT );
+    e_tile_format->getAttrs()[ ATTR_EXTENSION ] = tileMap->getFormat().getExtension();
+    e_tile_format->getAttrs()[ ATTR_MIME_TYPE ] = tileMap->getFormat().getMimeType();
+    e_tile_format->getAttrs()[ ATTR_WIDTH ] = toString<unsigned int>(tileMap->getFormat().getWidth());
+    e_tile_format->getAttrs()[ ATTR_HEIGHT ] = toString<unsigned int>(tileMap->getFormat().getHeight());
+    doc->getChildren().push_back(e_tile_format.get());
+
+    osg::ref_ptr< const osgEarth::Profile > profile = tileMap->createProfile();
+
+    osg::ref_ptr<XmlElement> e_tile_sets = new XmlElement ( ELEM_TILESETS );
+    std::string profileString = "none";
+    if (profile->isEquivalentTo(osgEarth::Registry::instance()->getGlobalGeodeticProfile()))
+    {
+        profileString = "global-geodetic";
+    }
+    else if (profile->isEquivalentTo(osgEarth::Registry::instance()->getGlobalMercatorProfile()))
+    {
+        profileString = "global-mercator";
+    }
+    else
+    {
+        profileString = "local";
+    }
+    e_tile_sets->getAttrs()[ ATTR_PROFILE ] = profileString;
+
+
+    for (TileMap::TileSetList::const_iterator itr = tileMap->getTileSets().begin(); itr != tileMap->getTileSets().end(); ++itr)
+    {
+        osg::ref_ptr<XmlElement> e_tile_set = new XmlElement( ELEM_TILESET );
+        e_tile_set->getAttrs()[ATTR_HREF] = itr->getHref();
+        e_tile_set->getAttrs()[ATTR_ORDER] = toString<unsigned int>(itr->getOrder());
+        e_tile_set->getAttrs()[ATTR_UNITSPERPIXEL] = toString(itr->getUnitsPerPixel());
+        e_tile_sets->getChildren().push_back( e_tile_set.get() );
+    }
+    doc->getChildren().push_back(e_tile_sets.get());
+
+    //Write out the data areas
+    if (tileMap->getDataExtents().size() > 0)
+    {
+        osg::ref_ptr<XmlElement> e_data_extents = new XmlElement( ELEM_DATA_EXTENTS );
+        for (DataExtentList::const_iterator itr = tileMap->getDataExtents().begin(); itr != tileMap->getDataExtents().end(); ++itr)
+        {
+            osg::ref_ptr<XmlElement> e_data_extent = new XmlElement( ELEM_DATA_EXTENT );
+            e_data_extent->getAttrs()[ATTR_MINX] = toString(itr->xMin());
+            e_data_extent->getAttrs()[ATTR_MINY] = toString(itr->yMin());
+            e_data_extent->getAttrs()[ATTR_MAXX] = toString(itr->xMax());
+            e_data_extent->getAttrs()[ATTR_MAXY] = toString(itr->yMax());
+            if ( itr->minLevel().isSet() )
+                e_data_extent->getAttrs()[ATTR_MIN_LEVEL] = toString<unsigned int>(*itr->minLevel());
+            if ( itr->maxLevel().isSet() )
+                e_data_extent->getAttrs()[ATTR_MAX_LEVEL] = toString<unsigned int>(*itr->maxLevel());
+            e_data_extents->getChildren().push_back( e_data_extent );
+        }
+        doc->getChildren().push_back( e_data_extents.get() );
+    }
+    return doc.release();
+}
+
+void
+TileMapReaderWriter::write(const TileMap* tileMap, const std::string &location)
+{
+    std::string path = osgDB::getFilePath(location);
+    if (!osgDB::fileExists(path) && !osgDB::makeDirectory(path))
+    {
+        OE_WARN << LC << "Couldn't create path " << std::endl;
+    }
+    std::ofstream out(location.c_str());
+    write(tileMap, out);
+}
+
+void
+TileMapReaderWriter::write(const TileMap* tileMap, std::ostream &output)
+{
+    osg::ref_ptr<XmlDocument> doc = tileMapToXmlDocument(tileMap);    
+    doc->store(output);
+}
+
+
+//----------------------------------------------------------------------------
+
+TileMapEntry::TileMapEntry( const std::string& _title, const std::string& _href, const std::string& _srs, const std::string& _profile ):
+title( _title ),
+href( _href ),
+srs( _srs ),
+profile( _profile )
+{
+}
+
+//----------------------------------------------------------------------------
+
+TileMapServiceReader::TileMapServiceReader()
+{
+}
+
+TileMapServiceReader::TileMapServiceReader(const TileMapServiceReader& rhs)
+{
+}
+
+bool
+TileMapServiceReader::read( const std::string &location, const osgDB::ReaderWriter::Options* options, TileMapEntryList& tileMaps )
+{     
+    ReadResult r = URI(location).readString();
+    if ( r.failed() )
+    {
+        OE_WARN << LC << "Failed to read TileMapServices from " << location << std::endl;
+        return 0L;
+    }    
+    
+    // Read tile map into a Config:
+    Config conf;
+    std::stringstream buf( r.getString() );
+    conf.fromXML( buf );    
+
+    // parse that into a tile map:        
+    return read( conf, tileMaps );    
+}
+
+bool
+TileMapServiceReader::read( const Config& conf, TileMapEntryList& tileMaps)
+{    
+    const Config* TileMapServiceConf = conf.find("tilemapservice");
+
+    if (!TileMapServiceConf)
+    {
+        OE_NOTICE << "Couldn't find root TileMapService element" << std::endl;
+    }
+
+    const Config* TileMapsConf = TileMapServiceConf->find("tilemaps");
+    if (TileMapsConf)
+    {
+        const ConfigSet& TileMaps = TileMapsConf->children("tilemap");
+        if (TileMaps.size() == 0)
+        {            
+            return false;
+        }
+        
+        for (ConfigSet::const_iterator itr = TileMaps.begin(); itr != TileMaps.end(); ++itr)
+        {
+            std::string href = itr->value("href");
+            std::string title = itr->value("title");
+            std::string profile = itr->value("profile");
+            std::string srs = itr->value("srs");            
+
+            tileMaps.push_back( TileMapEntry( title, href, srs, profile ) );
+        }        
+
+        return true;
+    }    
+    return false;
+}
+
diff --git a/src/osgEarthUtil/TMSBackFiller b/src/osgEarthUtil/TMSBackFiller
index 5d7c08b..e48751b 100644
--- a/src/osgEarthUtil/TMSBackFiller
+++ b/src/osgEarthUtil/TMSBackFiller
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/TMSBackFiller.cpp b/src/osgEarthUtil/TMSBackFiller.cpp
index b3fbece..99c8dba 100644
--- a/src/osgEarthUtil/TMSBackFiller.cpp
+++ b/src/osgEarthUtil/TMSBackFiller.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -135,6 +135,7 @@ void TMSBackFiller::writeTile( const TileKey& key, osg::Image* image )
 {
     std::string filename = getFilename( key );
     if ( !osgDB::fileExists( osgDB::getFilePath(filename) ) )
-        osgDB::makeDirectoryForFile( filename );
+        osgEarth::makeDirectoryForFile( filename );
     osgDB::writeImageFile( *image, filename, _options.get() );        
-}     
\ No newline at end of file
+}
+     
diff --git a/src/osgEarthUtil/TMSPackager b/src/osgEarthUtil/TMSPackager
index 1a17056..e9ed51f 100644
--- a/src/osgEarthUtil/TMSPackager
+++ b/src/osgEarthUtil/TMSPackager
@@ -1,171 +1,179 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#ifndef OSGEARTHUTIL_TMS_PACKAGER_H
-#define OSGEARTHUTIL_TMS_PACKAGER_H
-
-#include <osgEarthUtil/Common>
-#include <osgEarth/ImageLayer>
-#include <osgEarth/ElevationLayer>
-#include <osgEarth/Profile>
-#include <osgEarth/TaskService>
-
-namespace osgEarth { namespace Util
-{
-    /**
-     * Utility that reads tiles from an ImageLayer or ElevationLayer and stores
-     * the resulting data in a disk-based TMS (Tile Map Service) repository.
-     *
-     * See: http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification
-     */
-    class OSGEARTHUTIL_EXPORT TMSPackager
-    {
-    public:
-        /**
-         * Constructs a new packager.
-         * @param profile    Profile of packaged tile data (required)
-         */
-        TMSPackager( const Profile* outProfile, osgDB::Options* imageWriteOptions);
-
-        /** dtor */
-        virtual ~TMSPackager() { }
-
-        /**
-         * Whether to dump out progress messages 
-         * default = false
-         */
-        void setVerbose( bool value ) { _verbose = value; }
-        bool getVerbose() const { return _verbose; }
-
-        /**
-         * Whether to abort if a tile writing error is encountered
-         * default = true
-         */
-        void setAbortOnError( bool value ) { _abortOnError = value; }
-        bool getAbortOnError() const { return _abortOnError; }
-
-        /**
-         * Maximum level of detail of tiles to package
-         */
-        void setMaxLevel( unsigned value ) { _maxLevel = value; }
-        unsigned getMaxLevel() const { return _maxLevel; }
-
-        /**
-         * Whether to overwrite files that already exist in the repo
-         * default = false
-         */
-        void setOverwrite( bool value ) { _overwrite = value; }
-        bool getOverwrite() const { return _overwrite; }
-
-        /**
-         * Whether to package empty image tiles. An empty tile is a tile
-         * that is fully transparent. By default, the packager discards
-         * them and does not subdivide past them.
-         * default = false
-         */
-        void setKeepEmptyImageTiles( bool value ) { _keepEmptyImageTiles = value; }
-        bool getKeepEmptyImageTiles() const { return _keepEmptyImageTiles; }
-
-        /**
-         * Whether to subdivide single color image tiles. An single color tile is a tile
-         * that is filled with a single color. By default, the packager does not subdivide past them.
-         * default = false
-         */
-        void setSubdivideSingleColorImageTiles( bool value ) { _subdivideSingleColorImageTiles = value; }
-        bool getSubdivideSingleColorImageTiles() const { return _subdivideSingleColorImageTiles; }
-
-        /**
-         * Bounding box to package
-         */
-        void addExtent( const GeoExtent& value );
-
-        /**
-         * Result structure for method calls
-         */
-        struct Result {
-            Result(int tasks=0) : ok(true), taskCount(tasks) { }
-            Result(const std::string& m) : message(m), ok(false), taskCount(0) { }
-            operator bool() const { return ok; }
-            bool ok;
-            std::string message;
-            int taskCount;
-        };
-
-        /**
-         * Packages an image layer as a TMS repository.
-         * @param layer          Image layer to export
-         * @param rootFolder     Root output folder of TMS repo
-         * @param imageExtension (optional) Force an image type extension (e.g., "jpg")
-         */
-        Result package(
-            ImageLayer*        layer,
-            const std::string& rootFolder,
-            osgEarth::ProgressCallback* progress=0L,
-            const std::string& imageExtension="png" );
-
-        /**
-         * Packages an elevation layer as a TMS repository.
-         * @param layer          Image layer to 
-         * @param rootFolder     Root output folder of TMS repo
-         */
-        Result package( 
-            ElevationLayer*    layer,
-            const std::string& rootFolder,
-            osgEarth::ProgressCallback* progress=0L );
-
-    protected:
-
-        int packageImageTile(
-            ImageLayer*          layer,
-            const TileKey&       key,
-            const std::string&   rootDir,
-            const std::string&   extension,
-            osgEarth::TaskRequestVector& tasks,
-            Threading::MultiEvent* semaphore,
-            osgEarth::ProgressCallback* progress,
-            unsigned&            out_maxLevel );
-
-        int packageElevationTile(
-            ElevationLayer*      layer,
-            const TileKey&       key,
-            const std::string&   rootDir,
-            const std::string&   extension,
-            osgEarth::TaskRequestVector& tasks,
-            Threading::MultiEvent* semaphore,
-            osgEarth::ProgressCallback* progress,
-            unsigned&            out_maxLevel );
-
-        bool shouldPackageKey( 
-            const TileKey&     key ) const;
-
-    protected:
-
-        bool                        _verbose;
-        bool                        _abortOnError;
-        bool                        _overwrite;
-        bool                        _keepEmptyImageTiles;
-        bool                        _subdivideSingleColorImageTiles;
-        unsigned                    _maxLevel;
-        std::vector<GeoExtent>      _extents;
-        osg::ref_ptr<const Profile> _outProfile;
-        osg::ref_ptr<osgDB::Options>    _imageWriteOptions;
-    };
-
-} } // namespace osgEarth::Util
-
-#endif // OSGEARTHUTIL_TMS_PACKAGER_H
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTHUTIL_TMS_PACKAGER_H
+#define OSGEARTHUTIL_TMS_PACKAGER_H
+
+#include <osgEarthUtil/Common>
+#include <osgEarth/Profile>
+#include <osgEarth/Map>
+#include <osgEarth/TileHandler>
+#include <osgEarth/TileVisitor>
+
+namespace osgEarth { namespace Util
+{
+    class TMSPackager;
+
+    /**
+    * A TileHandler that writes out a tile from a layer in a TMS structure. packages a tile in a TMS structure
+    */
+    class OSGEARTHUTIL_EXPORT WriteTMSTileHandler : public TileHandler
+    {
+    public:
+        WriteTMSTileHandler(TerrainLayer* layer, Map* map, TMSPackager* packager);
+
+        TerrainLayer* getLayer();
+
+        virtual bool handleTile( const TileKey& key, const TileVisitor& tv );
+        virtual bool hasData( const TileKey& key ) const;
+        virtual std::string getProcessString() const;
+
+    protected:
+        
+        std::string getPathForTile( const TileKey &key );
+
+    protected:
+        osg::ref_ptr< TerrainLayer > _layer;
+        osg::ref_ptr< Map > _map;
+        TMSPackager* _packager;
+    };
+
+    /**
+    * Utility that reads tiles from an ImageLayer or ElevationLayer and stores
+    * the resulting data in a disk-based TMS (Tile Map Service) repository.
+    *
+    * See: http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification
+    */
+    class OSGEARTHUTIL_EXPORT TMSPackager
+    {
+    public:
+        TMSPackager();      
+
+        /**
+         * Gets the destination directory
+         */
+        const std::string& getDestination() const;
+
+        /**
+         * Sets the destination directory
+         */
+        void setDestination( const std::string& destination);
+
+        /**
+         * Gets the extension to write the data with.
+         */
+        const std::string& getExtension() const;
+
+        /**
+         * Sets the extension to write the data with.
+         */
+        void setExtension( const std::string& extension);
+
+        /**
+         * Gets the elevation pixel depth, either 16 or 32.
+         */
+        unsigned getElevationPixelDepth() const;
+        
+        /**
+         * Sets the elevation pixel depth, either 16 or 32.
+         */
+        void setElevationPixelDepth(unsigned value);
+        
+
+        /**
+         * Gets whether to overwrite existing tiles or not.
+         */
+        bool getOverwrite() const;
+
+        /**
+         * Sets whether to overwrite existing tiles or not.
+         */
+        void setOverwrite(bool overwrite);
+
+        /**
+         * Gets whether to keep completely transparent images or not.
+         */
+        bool getKeepEmpties() const;
+
+        /**
+         * Sets whether to keep completely transparent or not.
+         */
+        void setKeepEmpties(bool keepEmpties);
+
+        /**
+         * Gets the image write options.
+         */
+        osgDB::Options* getOptions() const;
+
+        /**
+         * Sets the image write options.
+         */
+        void setWriteOptions( osgDB::Options* options );        
+
+        /**
+         * Gets the layer name to use in the TMS XML.
+         */
+        const std::string& getLayerName() const;
+
+        /**
+         * Sets the layer name to use in the TMS XML.
+         */
+        void setLayerName( const std::string& name);
+
+        /**
+         * Gets the TileVisitor used to traverse the tiles.
+         */
+        TileVisitor* getTileVisitor() const;
+
+        /**
+         * Sets the TileVisitor used to traverse the tiles.
+         */
+        void setVisitor(TileVisitor* visitor);
+
+        /**
+         * Build the tiles for the given layer and map.
+         */
+        void run( TerrainLayer* layer, Map* map );
+
+        /**
+         * Write out the TMS XML for the given layer and map.
+         */
+        void writeXML( TerrainLayer* layer, Map* map);
+
+    protected:
+
+        std::string _destination;
+        std::string _extension;
+        unsigned int _elevationPixelDepth;
+        std::string _layerName;
+        bool _overwrite;
+        osg::ref_ptr<osgDB::Options>    _writeOptions;
+
+        unsigned int _width;
+        unsigned int _height;
+
+        bool _keepEmpties;
+
+        osg::ref_ptr< TileVisitor > _visitor;
+        osg::ref_ptr< WriteTMSTileHandler > _handler;
+
+    };
+
+} } // namespace osgEarth::Util
+
+#endif // OSGEARTHUTIL_TMS_PACKAGER_H
diff --git a/src/osgEarthUtil/TMSPackager.cpp b/src/osgEarthUtil/TMSPackager.cpp
index f528f33..0547f1b 100644
--- a/src/osgEarthUtil/TMSPackager.cpp
+++ b/src/osgEarthUtil/TMSPackager.cpp
@@ -1,623 +1,420 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#include <osgEarthUtil/TMSPackager>
-#include <osgEarthUtil/TMS>
-#include <osgEarth/ImageUtils>
-#include <osgEarth/ImageToHeightFieldConverter>
-#include <osgEarth/TaskService>
-#include <osgDB/FileUtils>
-#include <osgDB/FileNameUtils>
-#include <osgDB/WriteFile>
-
-#define LC "[TMSPackager] "
-
-using namespace osgEarth::Util;
-using namespace osgEarth;
-
-
-namespace
-{
-    struct CreateImageTileTask
-    {
-        void init(ImageLayer* layer, const TileKey& key, const std::string& path, const std::string& extension, osgDB::Options* imageWriteOptions, bool keepEmpties, bool verbose)
-        {
-          _layer = layer;
-          _key = key;
-          _path = path;
-          _extension = extension;
-          _imageWriteOptions = imageWriteOptions;
-          _keepEmptyImageTiles = keepEmpties;
-          _verbose = verbose;
-        }
-
-        void execute()
-        {
-            //bool isSingleColor = false;
-            bool tileOK = false;
-
-            GeoImage image = _layer->createImage( _key );
-            if ( image.valid() )
-            {
-                // Check for single color
-                //if ( !_subdivideSingleColorImageTiles )
-                //{
-                //    isSingleColor = ImageUtils::isSingleColorImage(image.getImage());
-                //    if ( isSingleColor && _verbose )
-                //    {
-                //        OE_NOTICE << LC << "Not subdividing single color tile " << key.str() << std::endl;
-                //    }
-                //}
-
-                // check for empty:
-                if ( !_keepEmptyImageTiles && ImageUtils::isEmptyImage(image.getImage()) )
-                {
-                    if (  _verbose )
-                    {
-                        OE_NOTICE << LC << "Skipping empty tile " << _key.str() << std::endl;
-                    }
-                }
-                else
-                {
-                    // convert to RGB if necessary
-                    osg::ref_ptr<osg::Image> final = image.getImage();
-                    if ( _extension == "jpg" && final->getPixelFormat() != GL_RGB )
-                        final = ImageUtils::convertToRGB8( image.getImage() );
-
-                    // dump it to disk
-                    osgDB::makeDirectoryForFile( _path );
-                    tileOK = osgDB::writeImageFile( *final.get(), _path, _imageWriteOptions);
-
-                    if ( _verbose )
-                    {
-                        if ( tileOK ) {
-                            OE_NOTICE << LC << "Wrote tile " << _key.str() << " (" << _key.getExtent().toString() << ")" << std::endl;
-                        }
-                        else {
-                            OE_NOTICE << LC << "Error write tile " << _key.str() << std::endl;
-                        }
-                    }
-
-                    //if ( _abortOnError && !tileOK )
-                    //{
-                    //    return Result( Stringify() << "Aborting, write failed for tile " << key.str() );
-                    //}
-                }
-            }
-        }
-
-    private:
-        osg::ref_ptr<ImageLayer> _layer;
-        TileKey _key;
-        std::string _path;
-        std::string _extension;
-        osg::ref_ptr<osgDB::Options> _imageWriteOptions;
-        bool _keepEmptyImageTiles;
-        bool _verbose;
-    };
-
-
-    struct CreateElevationTileTask
-    {
-        void init(ElevationLayer* layer, const TileKey& key, const std::string& path, bool verbose)
-        {
-          _layer = layer;
-          _key = key;
-          _path = path;
-          _verbose = verbose;
-        }
-
-        void execute()
-        {
-            bool tileOK = false;
-
-            GeoHeightField hf = _layer->createHeightField( _key );
-            if ( hf.valid() )
-            {
-                // convert the HF to an image
-                ImageToHeightFieldConverter conv;
-                osg::ref_ptr<osg::Image> image = conv.convert( hf.getHeightField() );
-
-                // dump it to disk
-                osgDB::makeDirectoryForFile( _path );
-                tileOK = osgDB::writeImageFile( *image.get(), _path );
-
-                if ( _verbose )
-                {
-                    if ( tileOK ) {
-                        OE_NOTICE << LC << "Wrote tile " << _key.str() << " (" << _key.getExtent().toString() << ")" << std::endl;
-                    }
-                    else {
-                        OE_NOTICE << LC << "Error write tile " << _key.str() << std::endl;
-                    }
-                }
-
-                //if ( _abortOnError && !tileOK )
-                //{
-                //    return Result( Stringify() << "Aborting, write failed for tile " << key.str() );
-                //}
-            }
-        }
-
-    private:
-        osg::ref_ptr<ElevationLayer> _layer;
-        TileKey _key;
-        std::string _path;
-        bool _verbose;
-    };
-
-
-    class PackageTileProgressCallback : public osgEarth::ProgressCallback
-    {
-    public:
-      PackageTileProgressCallback(osgEarth::ProgressCallback* proxyProgress)
-        : _progress(proxyProgress), _total(0), _completed(0)
-      {
-      }
-
-      virtual ~PackageTileProgressCallback() { }
-
-      void setTotalTasks(int total) { _total = total; }
-
-      bool reportProgress(double current, double total, unsigned currentStage, unsigned totalStages, const std::string& msg)
-      {
-        return false;
-      }
-
-      void onCompleted()
-      {
-        if (_completed >= _total)
-          return;
-
-        _completed++;
-        if (_progress.valid())
-          _progress->reportProgress(_completed, _total);
-
-        if (_completed >= _total)
-          _progress->onCompleted();
-      }
-
-    private:
-      osg::ref_ptr<osgEarth::ProgressCallback> _progress;
-      int _total;
-      int _completed;
-    };
-}
-
-
-TMSPackager::TMSPackager(const Profile* outProfile, osgDB::Options* imageWriteOptions) :
-_outProfile         ( outProfile ),
-_maxLevel           ( 99 ),
-_verbose            ( false ),
-_overwrite          ( false ),
-_keepEmptyImageTiles( false ),
-_subdivideSingleColorImageTiles ( false ),
-_abortOnError       ( true ),
-_imageWriteOptions  (imageWriteOptions)
-{
-    //nop
-}
-
-
-void
-TMSPackager::addExtent( const GeoExtent& extent )
-{
-    _extents.push_back(extent);
-}
-
-
-bool
-TMSPackager::shouldPackageKey( const TileKey& key ) const
-{
-    // if there are no extent filters, or we're at a sufficiently low level, 
-    // always package the key.
-    if ( _extents.size() == 0 || key.getLevelOfDetail() <= 1 )
-        return true;
-
-    // check for intersection with one of the filter extents.
-    for( std::vector<GeoExtent>::const_iterator i = _extents.begin(); i != _extents.end(); ++i )
-    {
-        if ( i->intersects( key.getExtent() ) )
-            return true;
-    }
-
-    return false;
-}
-
-
-int
-TMSPackager::packageImageTile(ImageLayer*                  layer,
-                              const TileKey&               key,
-                              const std::string&           rootDir,
-                              const std::string&           extension,
-                              TaskRequestVector&           tasks,
-                              Threading::MultiEvent*       semaphore,
-                              osgEarth::ProgressCallback*  progress,
-                              unsigned&                    out_maxLevel )
-{
-    unsigned minLevel = layer->getImageLayerOptions().minLevel().isSet() ?
-        *layer->getImageLayerOptions().minLevel() : 0;
-    
-    int taskCount = 0;
-
-    bool hasData = layer->getTileSource()->hasData( key );
-
-    if ( shouldPackageKey(key) && key.getLevelOfDetail() >= minLevel && hasData )
-    {        
-        OE_DEBUG << "Packaging key " << key.str() << std::endl;
-        unsigned w, h;
-        key.getProfile()->getNumTiles( key.getLevelOfDetail(), w, h );
-
-        std::string path = Stringify() 
-            << rootDir 
-            << "/" << key.getLevelOfDetail() 
-            << "/" << key.getTileX() 
-            << "/" << h - key.getTileY() - 1
-            << "." << extension;
-
-        bool isSingleColor = false;
-        bool tileOK = osgDB::fileExists(path) && !_overwrite;
-        if ( !tileOK )
-        {
-            ParallelTask<CreateImageTileTask>* task = new ParallelTask<CreateImageTileTask>( semaphore );
-            task->init(layer, key, path, extension, _imageWriteOptions, _keepEmptyImageTiles, _verbose);
-            task->setProgressCallback(progress);
-            tasks.push_back(task);            
-            taskCount++;
-
-            tileOK = true;
-        }
-        else
-        {
-            if ( _verbose )
-            {
-                OE_NOTICE << LC << "Tile " << key.str() << " already exists" << std::endl;
-            }
-        }
-
-        // increment the maximum detected tile level:
-        if ( tileOK && key.getLevelOfDetail() > out_maxLevel )
-        {
-            out_maxLevel = key.getLevelOfDetail();
-        }
-
-        // see if subdivision should continue.
-        unsigned lod = key.getLevelOfDetail();
-        const ImageLayerOptions& options = layer->getImageLayerOptions();
-
-        unsigned layerMaxLevel = (options.maxLevel().isSet()? *options.maxLevel() : 99);
-        unsigned maxLevel = std::min(_maxLevel, layerMaxLevel);
-        bool subdivide =
-            (options.minLevel().isSet() && lod < *options.minLevel()) ||
-            (tileOK && lod+1 < maxLevel);
-
-        // subdivide if necessary:
-        if ( (subdivide == true) && (isSingleColor == false) )
-        {
-            for( unsigned q=0; q<4; ++q )
-            {                
-                TileKey childKey = key.createChildKey(q);
-
-                taskCount += packageImageTile( layer, childKey, rootDir, extension, tasks, semaphore, progress, out_maxLevel );                
-            }
-        }
-    }
-
-    return taskCount;
-}
-
-
-int
-TMSPackager::packageElevationTile(ElevationLayer*               layer,
-                                  const TileKey&                key,
-                                  const std::string&            rootDir,
-                                  const std::string&            extension,
-                                  osgEarth::TaskRequestVector&  tasks,
-                                  Threading::MultiEvent*        semaphore,
-                                  osgEarth::ProgressCallback*   progress,
-                                  unsigned&                     out_maxLevel)
-{
-    unsigned minLevel = layer->getElevationLayerOptions().minLevel().isSet() ?
-        *layer->getElevationLayerOptions().minLevel() : 0;
-
-    int taskCount = 0;
-
-    bool hasData = layer->getTileSource()->hasData( key );
-
-    if ( shouldPackageKey(key) && key.getLevelOfDetail() >= minLevel && hasData )
-    {
-        unsigned w, h;
-        key.getProfile()->getNumTiles( key.getLevelOfDetail(), w, h );
-
-        std::string path = Stringify() 
-            << rootDir 
-            << "/" << key.getLevelOfDetail() 
-            << "/" << key.getTileX() 
-            << "/" << h - key.getTileY() - 1
-            << "." << extension;
-
-        bool tileOK = osgDB::fileExists(path) && !_overwrite;
-        if ( !tileOK )
-        {
-            ParallelTask<CreateElevationTileTask>* task = new ParallelTask<CreateElevationTileTask>( semaphore );
-            task->init(layer, key, path, _verbose);
-            task->setProgressCallback(progress);
-            tasks.push_back(task);
-            taskCount++;
-
-            tileOK = true;
-        }
-        else
-        {
-            if ( _verbose )
-            {
-                OE_NOTICE << LC << "Tile " << key.str() << " already exists" << std::endl;
-            }
-        }
-
-        // increment the maximum detected tile level:
-        if ( tileOK && key.getLevelOfDetail() > out_maxLevel )
-        {
-            out_maxLevel = key.getLevelOfDetail();
-        }
-
-        // see if subdivision should continue.
-        unsigned lod = key.getLevelOfDetail();
-        const ElevationLayerOptions& options = layer->getElevationLayerOptions();
-
-        unsigned layerMaxLevel = (options.maxLevel().isSet()? *options.maxLevel() : 99);
-        unsigned maxLevel = std::min(_maxLevel, layerMaxLevel);
-        bool subdivide =
-            (options.minLevel().isSet() && lod < *options.minLevel()) ||
-            (tileOK && lod+1 < maxLevel);
-
-        // subdivide if necessary:
-        if ( subdivide )
-        {
-            for( unsigned q=0; q<4; ++q )
-            {
-                TileKey childKey = key.createChildKey(q);                
-                taskCount += packageElevationTile( layer, childKey, rootDir, extension, tasks, semaphore, progress, out_maxLevel );
-            }
-        }
-    }
-
-    return taskCount;
-}
-
-
-TMSPackager::Result
-TMSPackager::package(ImageLayer*        layer,
-                     const std::string& rootFolder,
-                     osgEarth::ProgressCallback* progress,
-                     const std::string& overrideExtension )
-{
-  osg::Timer* timer = osg::Timer::instance();
-  osg::Timer_t start_t = timer->tick();
-
-    if ( !layer || !_outProfile.valid() )
-        return Result( "Illegal null layer or profile" );
-
-    // attempt to create the output folder:
-    osgDB::makeDirectory( rootFolder );
-    if ( !osgDB::fileExists( rootFolder ) )
-        return Result( "Unable to create output folder" );
-
-    // collect the root tile keys in preparation for packaging:
-    std::vector<TileKey> rootKeys;
-    _outProfile->getRootKeys( rootKeys );
-
-    if ( rootKeys.size() == 0 )
-        return Result( "Unable to calculate root key set" );
-
-    // fetch one tile to see what the image size should be
-    
-    GeoImage testImage;
-    for( std::vector<TileKey>::iterator i = rootKeys.begin(); i != rootKeys.end() && !testImage.valid(); ++i )
-    {
-        testImage = layer->createImage( *i );
-    }
-    if ( !testImage.valid() )
-        return Result( "Unable to get a test image!" );
-
-    // try to determine the image extension:
-    std::string extension = overrideExtension;
-
-    if ( extension.empty() && testImage.valid() )
-    {
-        extension = toLower( osgDB::getFileExtension( testImage.getImage()->getFileName() ) );
-        if ( extension.empty() )
-        {
-            if ( ImageUtils::hasAlphaChannel(testImage.getImage()) )
-            {
-                extension = "png";
-            }
-            else
-            {
-                extension = "jpg";
-            }
-        }
-    }
-
-    // compute a mime type
-    std::string mimeType;
-    if ( extension == "png" )
-        mimeType = "image/png";
-    else if ( extension == "jpg" || extension == "jpeg" )
-        mimeType = "image/jpeg";
-    else if ( extension == "tif" || extension == "tiff" )
-        mimeType = "image/tiff";
-    else {
-        OE_WARN << LC << "Unable to determine mime-type for extension \"" << extension << "\"" << std::endl;
-    }
-
-    if ( _verbose )
-    {
-        OE_NOTICE << LC << "MIME-TYPE = " << mimeType << ", Extension = " << extension << std::endl;
-    }
-
-
-    // semaphore and tasks collection for multithreading
-    osgEarth::Threading::MultiEvent semaphore;
-    osgEarth::TaskRequestVector tasks;
-    int taskCount = 0;
-    
-    PackageTileProgressCallback* tileProgress = 0L;
-    if (progress)
-      tileProgress = new PackageTileProgressCallback(progress);
-
-    // package the tile hierarchy
-    unsigned maxLevel = 0;
-    for( std::vector<TileKey>::const_iterator i = rootKeys.begin(); i != rootKeys.end(); ++i )
-    {
-        taskCount += packageImageTile( layer, *i, rootFolder, extension, tasks, &semaphore, tileProgress, maxLevel );
-    }
-
-    // Run all the tasks in parallel
-    OE_DEBUG << LC << "Packaging image layer \"" << layer->getName() << "\", total number of tiles: " << taskCount << std::endl;
-
-    semaphore.reset( taskCount );
-    if (tileProgress) tileProgress->setTotalTasks(taskCount);
-
-    unsigned num = 2 * OpenThreads::GetNumberOfProcessors();
-    osg::ref_ptr<osgEarth::TaskService> taskService = new osgEarth::TaskService("TMS Packager", num);
-
-    for( TaskRequestVector::iterator i = tasks.begin(); i != tasks.end(); ++i )
-          taskService->add( i->get() );
-
-    // Wait for them to complete
-    semaphore.wait();
-
-    osg::Timer_t end_t = timer->tick();
-    double elapsed = (end_t - start_t) * timer->getSecondsPerTick();
-    OE_DEBUG << LC << "Packaging image layer\"" << layer->getName() << "\" complete. Seconds elapsed: " << elapsed << std::endl;
-
-
-
-    // create the tile map metadata:
-    osg::ref_ptr<TMS::TileMap> tileMap = TMS::TileMap::create(
-        "",
-        _outProfile.get(),
-        extension,
-        testImage.getImage()->s(),
-        testImage.getImage()->t() );
-
-    tileMap->setTitle( layer->getName() );
-    tileMap->setVersion( "1.0.0" );
-    tileMap->getFormat().setMimeType( mimeType );
-    tileMap->generateTileSets( std::min(23u, maxLevel+1) );
-
-    // write out the tilemap catalog:
-    std::string tileMapFilename = osgDB::concatPaths(rootFolder, "tms.xml");
-    TMS::TileMapReaderWriter::write( tileMap.get(), tileMapFilename );
-
-    return Result();
-}
-
-
-TMSPackager::Result
-TMSPackager::package(ElevationLayer*    layer,
-                     const std::string& rootFolder,
-                     osgEarth::ProgressCallback* progress )
-{
-    osg::Timer* timer = osg::Timer::instance();
-    osg::Timer_t start_t = timer->tick();
-
-    if ( !layer || !_outProfile.valid() )
-        return Result( "Illegal null layer or profile" );
-
-    // attempt to create the output folder:
-    osgDB::makeDirectory( rootFolder );
-    if ( !osgDB::fileExists( rootFolder ) )
-        return Result( "Unable to create output folder" );
-
-    // collect the root tile keys in preparation for packaging:
-    std::vector<TileKey> rootKeys;
-    _outProfile->getRootKeys( rootKeys );
-
-    if ( rootKeys.size() == 0 )
-        return Result( "Unable to calculate root key set" );
-
-    std::string extension = "tif", mimeType = "image/tiff";
-    if ( _verbose )
-    {
-        OE_NOTICE << LC << "MIME-TYPE = " << mimeType << ", Extension = " << extension << std::endl;
-    }
-
-    // fetch one tile to see what the tile size will be
-    GeoHeightField testHF;
-    for( std::vector<TileKey>::iterator i = rootKeys.begin(); i != rootKeys.end() && !testHF.valid(); ++i )
-    {
-        testHF = layer->createHeightField( *i );
-    }
-    if ( !testHF.valid() )
-        return Result( "Unable to determine heightfield size" );
-
-    osgEarth::Threading::MultiEvent semaphore;
-    osgEarth::TaskRequestVector tasks;
-    int taskCount = 0;
-
-    PackageTileProgressCallback* tileProgress = 0L;
-    if (progress)
-      tileProgress = new PackageTileProgressCallback(progress);
-
-    // package the tile hierarchy
-    unsigned maxLevel = 0;
-    for( std::vector<TileKey>::const_iterator i = rootKeys.begin(); i != rootKeys.end(); ++i )
-    {
-        taskCount += packageElevationTile( layer, *i, rootFolder, extension, tasks, &semaphore, tileProgress, maxLevel );
-    }
-
-    // run all the tasks in parallel
-    OE_DEBUG << LC << "Packaging elevation layer \"" << layer->getName() << "\", total number of tiles: " << taskCount << std::endl;
-
-    semaphore.reset( taskCount );
-    if (tileProgress) tileProgress->setTotalTasks(taskCount);
-
-    unsigned num = 2 * OpenThreads::GetNumberOfProcessors();
-    osg::ref_ptr<osgEarth::TaskService> taskService = new osgEarth::TaskService("TMS Elevation Packager", num);
-
-    for (TaskRequestVector::iterator i = tasks.begin(); i != tasks.end(); ++i)
-        taskService->add( i->get() );
-
-    semaphore.wait();
-
-
-    // create the tile map metadata:
-    osg::ref_ptr<TMS::TileMap> tileMap = TMS::TileMap::create(
-        "",
-        _outProfile.get(),
-        extension,
-        testHF.getHeightField()->getNumColumns(),
-        testHF.getHeightField()->getNumRows() );
-
-    tileMap->setTitle( layer->getName() );
-    tileMap->setVersion( "1.0.0" );
-    tileMap->getFormat().setMimeType( mimeType );
-    tileMap->generateTileSets( std::min(23u, maxLevel+1) );
-
-    // write out the tilemap catalog:
-    std::string tileMapFilename = osgDB::concatPaths(rootFolder, "tms.xml");
-    TMS::TileMapReaderWriter::write( tileMap.get(), tileMapFilename );
-
-    osg::Timer_t end_t = timer->tick();
-    double elapsed = (end_t - start_t) * timer->getSecondsPerTick();
-    OE_DEBUG << LC << "Packaging elevation layer \"" << layer->getName() << "\" complete. Seconds elapsed: " << elapsed << std::endl;
-
-    return Result();
-}
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osgEarthUtil/TMSPackager>
+#include <osgEarthUtil/TMS>
+#include <osgEarth/ImageUtils>
+#include <osgEarth/ImageToHeightFieldConverter>
+#include <osgEarth/TaskService>
+#include <osgEarth/FileUtils>
+#include <osgEarth/CacheEstimator>
+#include <osgDB/FileUtils>
+#include <osgDB/FileNameUtils>
+#include <osgDB/WriteFile>
+
+
+#define LC "[TMSPackager] "
+
+using namespace osgEarth::Util;
+using namespace osgEarth;
+
+WriteTMSTileHandler::WriteTMSTileHandler(TerrainLayer* layer,  Map* map, TMSPackager* packager):
+    _layer( layer ),
+    _map(map),
+    _packager(packager)
+{
+}
+
+std::string WriteTMSTileHandler::getPathForTile( const TileKey &key )
+{
+    std::string layerFolder = toLegalFileName( _packager->getLayerName() );         
+    unsigned w, h;
+    key.getProfile()->getNumTiles( key.getLevelOfDetail(), w, h );         
+
+    return Stringify() 
+        << _packager->getDestination()
+        << "/" << layerFolder
+        << "/" << key.getLevelOfDetail() 
+        << "/" << key.getTileX() 
+        << "/" << h - key.getTileY() - 1
+        << "." << _packager->getExtension();
+}
+
+
+bool WriteTMSTileHandler::handleTile(const TileKey& key, const TileVisitor& tv)
+{    
+    ImageLayer* imageLayer = dynamic_cast< ImageLayer* >( _layer.get() );
+    ElevationLayer* elevationLayer = dynamic_cast< ElevationLayer* >( _layer.get() );
+
+    // Get the path to write to
+    std::string path = getPathForTile( key );
+
+    // Don't write out a new file if we're not overwriting
+    if (osgDB::fileExists(path) && !_packager->getOverwrite())
+        return true;
+
+    // attempt to create the output folder:        
+    osgEarth::makeDirectoryForFile( path );       
+
+
+    if (imageLayer)
+    {                        
+        GeoImage geoImage = imageLayer->createImage( key );
+
+        if (geoImage.valid())
+        {                             
+            if (!_packager->getKeepEmpties() && ImageUtils::isEmptyImage(geoImage.getImage()))
+            {
+                OE_INFO << "Not writing completely transparent image for key " << key.str() << std::endl;
+                return false;
+            }
+
+            // mask out areas not included in the request:
+            for(std::vector<GeoExtent>::const_iterator g = tv.getExtents().begin();
+                g != tv.getExtents().end();
+                ++g)
+            {
+                geoImage.applyAlphaMask( *g );
+            }
+
+            // OE_NOTICE << "Created image for " << key.str() << std::endl;
+            osg::ref_ptr< const osg::Image > final = geoImage.getImage();                        
+
+            // convert to RGB if necessary            
+            if ( _packager->getExtension() == "jpg" && final->getPixelFormat() != GL_RGB )
+            {
+                final = ImageUtils::convertToRGB8( final );
+            }            
+            return osgDB::writeImageFile(*final, path, _packager->getOptions());
+        }            
+    }
+    else if (elevationLayer )
+    {
+        GeoHeightField hf = elevationLayer->createHeightField( key );
+        if (hf.valid())
+        {
+            // convert the HF to an image
+            ImageToHeightFieldConverter conv;
+            osg::ref_ptr< osg::Image > image = conv.convert( hf.getHeightField(), _packager->getElevationPixelDepth() );				            
+            return osgDB::writeImageFile(*image.get(), path, _packager->getOptions());
+        }            
+    }
+    return false;        
+} 
+
+bool WriteTMSTileHandler::hasData( const TileKey& key ) const
+{
+    TileSource* ts = _layer->getTileSource();
+    if (ts)
+    {
+        return ts->hasData(key);
+    }
+    return true;
+}
+
+std::string WriteTMSTileHandler::getProcessString() const
+{
+    ImageLayer* imageLayer = dynamic_cast< ImageLayer* >( _layer.get() );
+    ElevationLayer* elevationLayer = dynamic_cast< ElevationLayer* >( _layer.get() );    
+
+    std::stringstream buf;
+    buf << "osgearth_package --tms ";
+    if (imageLayer)
+    {        
+        for (unsigned int i = 0; i < _map->getNumImageLayers(); i++)
+        {
+            if (imageLayer == _map->getImageLayerAt(i))
+            {
+                buf << " --image " << i << " ";
+                break;
+            }
+        }
+    }
+    else if (elevationLayer)
+    {
+        for (unsigned int i = 0; i < _map->getNumElevationLayers(); i++)
+        {
+            if (elevationLayer == _map->getElevationLayerAt(i))
+            {
+                buf << " --elevation " << i << " ";
+                break;
+            }
+        }
+    }
+
+    // Options
+    buf << " --out " << _packager->getDestination() << " ";
+    buf << " --ext " << _packager->getExtension() << " ";
+    buf << " --elevation-pixel-depth " << _packager->getElevationPixelDepth() << " ";
+    if (_packager->getOptions())
+    {
+        buf << " --db-options " << _packager->getOptions()->getOptionString() << " ";    
+    }
+    if (_packager->getOverwrite())
+    {
+        buf << " --overwrite ";
+    }            
+    return buf.str();
+}
+
+
+/*****************************************************************************************************/
+
+TMSPackager::TMSPackager():
+_visitor(new TileVisitor()),
+    _extension(""),
+    _destination("out"),
+    _elevationPixelDepth(32),
+    _width(0),
+    _height(0),
+    _overwrite(false),
+    _keepEmpties(false)
+{
+}
+
+const std::string& TMSPackager::getDestination() const
+{
+    return _destination;
+}
+
+void TMSPackager::setDestination( const std::string& destination)
+{
+    _destination = destination;
+}
+
+const std::string& TMSPackager::getExtension() const
+{
+    return _extension;
+}
+
+void TMSPackager::setExtension( const std::string& extension)
+{
+    _extension = extension;
+}
+
+ void TMSPackager::setElevationPixelDepth(unsigned value)
+ {
+     _elevationPixelDepth = value;
+ }
+
+ unsigned TMSPackager::getElevationPixelDepth() const
+ {
+     return _elevationPixelDepth;
+ }
+
+osgDB::Options* TMSPackager::getOptions() const
+{
+    return _writeOptions.get();
+}
+
+void TMSPackager::setWriteOptions( osgDB::Options* options )
+{
+    _writeOptions = options;
+}
+
+const std::string& TMSPackager::getLayerName() const
+{
+    return _layerName;
+}
+
+void TMSPackager::setLayerName( const std::string& name)
+{
+    _layerName = name;
+}
+
+bool TMSPackager::getOverwrite() const
+{
+    return _overwrite;
+}
+
+void TMSPackager::setOverwrite(bool overwrite)
+{
+    _overwrite = overwrite;
+}
+
+bool TMSPackager::getKeepEmpties() const
+{
+    return _keepEmpties;
+}
+
+void TMSPackager::setKeepEmpties(bool keepEmpties)
+{
+    _keepEmpties = keepEmpties;
+}
+
+TileVisitor* TMSPackager::getTileVisitor() const
+{
+    return _visitor;
+}
+
+void TMSPackager::setVisitor(TileVisitor* visitor)
+{
+    _visitor = visitor;
+}    
+
+void TMSPackager::run( TerrainLayer* layer,  Map* map  )
+{    
+    // Get a test image from the root keys
+
+    // collect the root tile keys in preparation for packaging:
+    std::vector<TileKey> rootKeys;
+    map->getProfile()->getRootKeys( rootKeys );
+
+    // fetch one tile to see what the image size should be
+    ImageLayer* imageLayer = dynamic_cast<ImageLayer*>(layer);
+    ElevationLayer* elevationLayer = dynamic_cast<ElevationLayer*>(layer);
+
+    // Come up with a default name for the layer if it doesn't already have one.
+    if (layer->getName().empty())
+    {
+        std::stringstream layerName;
+
+        unsigned int index = 0;
+        if (imageLayer)
+        {            
+            layerName << "image";
+            // Get the index of the layer
+            for (unsigned int i = 0; i < map->getNumImageLayers(); i++)
+            {
+                if (map->getImageLayerAt(i) == imageLayer)
+                {
+                    index = i;
+                    break;
+                }
+            }            
+        }
+        else if (elevationLayer)
+        {
+            layerName << "elevation";
+            // Get the index of the layer
+            for (unsigned int i = 0; i < map->getNumElevationLayers(); i++)
+            {
+                if (map->getElevationLayerAt(i) == elevationLayer)
+                {
+                    index = i;
+                    break;
+                }
+            }
+        }
+        layerName << index+1;
+        OE_NOTICE << "Setting layer name to " << layerName.str() << std::endl;
+        setLayerName(layerName.str());
+    }
+    else
+    {
+        setLayerName(layer->getName());
+    }
+
+
+
+    if (imageLayer)
+    {
+        GeoImage testImage;
+        for( std::vector<TileKey>::iterator i = rootKeys.begin(); i != rootKeys.end() && !testImage.valid(); ++i )
+        {
+            testImage = imageLayer->createImage( *i );
+        }
+        if (testImage.valid())
+        {
+            _width = testImage.getImage()->s();
+            _height = testImage.getImage()->t();
+
+            bool alphaChannelRequired =
+                ImageUtils::hasAlphaChannel(testImage.getImage()) ||
+                _visitor->getExtents().size() > 0;
+
+            // Figure out the extension if we haven't already assigned one.
+            if (_extension.empty())
+            {
+                if (alphaChannelRequired)
+                {
+                    _extension = "png";
+                }
+                else
+                {
+                    _extension = "jpg";
+                }
+            }
+            else if (_extension == "jpg" && alphaChannelRequired)
+            {
+                _extension = "png";
+                OE_NOTICE << LC << "Extension changed to PNG since output requires an alpha channel" << std::endl;
+            }
+
+            OE_INFO << LC << "Output extension: " << _extension << std::endl;
+        }
+    }
+    else if (elevationLayer)
+    {
+        // We must use tif no matter what with elevation layers.  It's the only format that currently can read/write single band imagery.
+        _extension = "tif";
+        GeoHeightField testHF;
+        for( std::vector<TileKey>::iterator i = rootKeys.begin(); i != rootKeys.end() && !testHF.valid(); ++i )
+        {
+            testHF = elevationLayer->createHeightField( *i );
+        }
+
+        if (testHF.valid())
+        {
+            _width = testHF.getHeightField()->getNumColumns();
+            _height = testHF.getHeightField()->getNumRows();
+        }
+    }
+
+
+    _handler = new WriteTMSTileHandler(layer, map, this);    
+    _visitor->setTileHandler( _handler );    
+    _visitor->run( map->getProfile() );    
+}
+
+void TMSPackager::writeXML( TerrainLayer* layer, Map* map)
+{
+     // create the tile map metadata:
+    osg::ref_ptr<TMS::TileMap> tileMap = TMS::TileMap::create(
+        "",
+        map->getProfile(),        
+        _extension,
+        _width,
+        _height
+        );
+
+    std::string mimeType;
+    if ( _extension == "png" )
+        mimeType = "image/png";
+    else if ( _extension == "jpg" || _extension == "jpeg" )
+        mimeType = "image/jpeg";
+    else if ( _extension == "tif" || _extension == "tiff" )
+        mimeType = "image/tiff";
+    else {
+        OE_WARN << LC << "Unable to determine mime-type for extension \"" << _extension << "\"" << std::endl;
+    }
+
+
+    //TODO:  Fix
+    unsigned int maxLevel = 23;
+    tileMap->setTitle( _layerName );
+    tileMap->setVersion( "1.0.0" );
+    tileMap->getFormat().setMimeType( mimeType );
+    tileMap->generateTileSets( std::min(23u, maxLevel+1) );
+    
+
+    // write out the tilemap catalog:
+    std::string tileMapFilename = osgDB::concatPaths( osgDB::concatPaths(_destination, toLegalFileName( _layerName )), "tms.xml");
+    OE_NOTICE << "Layer name " << _layerName << std::endl;
+    TMS::TileMapReaderWriter::write( tileMap.get(), tileMapFilename );
+}
\ No newline at end of file
diff --git a/src/osgEarthUtil/TerrainProfile b/src/osgEarthUtil/TerrainProfile
index 419a4fe..6fddd40 100644
--- a/src/osgEarthUtil/TerrainProfile
+++ b/src/osgEarthUtil/TerrainProfile
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -141,6 +141,11 @@ namespace osgEarth { namespace Util {
 
         /** dtor */
         virtual ~TerrainProfileCalculator();
+
+        /**
+         * Set the MapNode to compute the terrain profile against
+         */
+        void setMapNode( osgEarth::MapNode* mapNode );
     
         /**
          * Add a ChangedCallback
@@ -163,11 +168,21 @@ namespace osgEarth { namespace Util {
         const GeoPoint& getStart() const;
 
         /**
+         * Gets the start point of the terrain profile
+         */
+        GeoPoint getStart(AltitudeMode altMode) const;
+
+        /**
          * Gets the end point of the terrain profile
          */
         const GeoPoint& getEnd() const;
 
         /**
+         * Gets the end point of the terrain profile
+         */
+        GeoPoint getEnd(AltitudeMode altMode) const;
+
+        /**
          * Sets the start and end points of the terrain profile
          */
         void setStartEnd(const osgEarth::GeoPoint& start, const osgEarth::GeoPoint& end);
@@ -192,7 +207,7 @@ namespace osgEarth { namespace Util {
          * @param profile
          *        The resulting TerrainProfile
          */
-        static void computeTerrainProfile( osgEarth::MapNode* mapNode, const osgEarth::GeoPoint& start, const osgEarth::GeoPoint& end, unsigned int numSamples, TerrainProfile& profile);
+        static void computeTerrainProfile( osgEarth::MapNode* mapNode, const osgEarth::GeoPoint& start, const osgEarth::GeoPoint& end, TerrainProfile& profile);
 
 
 
diff --git a/src/osgEarthUtil/TerrainProfile.cpp b/src/osgEarthUtil/TerrainProfile.cpp
index 48f4458..6fa98c2 100644
--- a/src/osgEarthUtil/TerrainProfile.cpp
+++ b/src/osgEarthUtil/TerrainProfile.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -108,6 +108,19 @@ TerrainProfileCalculator::~TerrainProfileCalculator()
     _mapNode->getTerrain()->removeTerrainCallback( this );
 }
 
+void TerrainProfileCalculator::setMapNode( osgEarth::MapNode* mapNode )
+{
+  if (_mapNode.valid())
+      _mapNode->getTerrain()->removeTerrainCallback( this );
+
+  _mapNode = mapNode;
+  if (_mapNode.valid())
+  {
+      _mapNode->getTerrain()->addTerrainCallback( this );
+      recompute();
+  }
+}
+
 void TerrainProfileCalculator::addChangedCallback( ChangedCallback* callback )
 {
     _changedCallbacks.push_back( callback );
@@ -132,11 +145,33 @@ const GeoPoint& TerrainProfileCalculator::getStart() const
     return _start;
 }
 
+GeoPoint TerrainProfileCalculator::getStart(AltitudeMode altMode) const
+{
+    if( _start.altitudeMode() != altMode )
+    {
+        double newAlt = 0.0;
+        _start.transformZ(altMode, _mapNode->getTerrain(), newAlt);
+        return GeoPoint(_start.getSRS(), _start.x(), _start.y(), newAlt);
+    }
+    return _start;
+}
+
 const GeoPoint& TerrainProfileCalculator::getEnd() const
 {
     return _end;
 }
 
+GeoPoint TerrainProfileCalculator::getEnd(AltitudeMode altMode) const
+{
+    if( _end.altitudeMode() != altMode )
+    {
+        double newAlt = 0.0;
+        _end.transformZ(altMode, _mapNode->getTerrain(), newAlt);
+        return GeoPoint(_end.getSRS(), _end.x(), _end.y(), newAlt);
+    }
+    return _end;
+}
+
 void TerrainProfileCalculator::setStartEnd(const GeoPoint& start, const GeoPoint& end)
 {
     if (_start != start || _end != end)
@@ -166,20 +201,7 @@ void TerrainProfileCalculator::recompute()
 {
     if (_start.isValid() && _end.isValid())
     {
-        //computeTerrainProfile( _mapNode.get(), _start, _end, _numSamples, _profile);
-        osg::Vec3d start, end;
-        _start.toWorld( start, _mapNode->getTerrain() );
-        _end.toWorld( end, _mapNode->getTerrain() );
-        osgSim::ElevationSlice slice;
-        slice.setStartPoint( start );
-        slice.setEndPoint( end );
-        slice.setDatabaseCacheReadCallback( 0 );
-        slice.computeIntersections( _mapNode->getTerrainEngine());
-        _profile.clear();
-        for (unsigned int i = 0; i < slice.getDistanceHeightIntersections().size(); i++)
-        {
-            _profile.addElevation( slice.getDistanceHeightIntersections()[i].first, slice.getDistanceHeightIntersections()[i].second);
-        }
+        computeTerrainProfile( _mapNode.get(), _start, _end, _profile);
 
         for( ChangedCallbackList::iterator i = _changedCallbacks.begin(); i != _changedCallbacks.end(); i++ )
         {
@@ -187,34 +209,26 @@ void TerrainProfileCalculator::recompute()
                 i->get()->onChanged(this);
         }
     }
+    else
+    {
+        _profile.clear();
+    }
 }
 
-void TerrainProfileCalculator::computeTerrainProfile( osgEarth::MapNode* mapNode, const GeoPoint& start, const GeoPoint& end, unsigned int numSamples, TerrainProfile& profile)
+void TerrainProfileCalculator::computeTerrainProfile( osgEarth::MapNode* mapNode, const GeoPoint& start, const GeoPoint& end, TerrainProfile& profile)
 {
-    GeoPoint geoStart(start);
-    geoStart.makeGeographic();
-
-    GeoPoint geoEnd(end);
-    geoEnd.makeGeographic();
+    osg::Vec3d startvec, endvec;
+    start.toWorld( startvec, mapNode->getTerrain() );
+    end.toWorld( endvec, mapNode->getTerrain() );
+    osgSim::ElevationSlice slice;
+    slice.setStartPoint( startvec );
+    slice.setEndPoint( endvec );
+    slice.setDatabaseCacheReadCallback( 0 );
+    slice.computeIntersections( mapNode->getTerrainEngine());
 
-    double startXRad = osg::DegreesToRadians( geoStart.x() );
-    double startYRad = osg::DegreesToRadians( geoStart.y() );
-    double endXRad = osg::DegreesToRadians( geoEnd.x() );
-    double endYRad = osg::DegreesToRadians( geoEnd.y() );
-
-    double distance = osgEarth::GeoMath::distance(startYRad, startXRad, endYRad, endXRad );
-
-    double spacing = distance / ((double)numSamples - 1.0);
-    
     profile.clear();
-
-    for (unsigned int i = 0; i < numSamples; i++)
+    for (unsigned int i = 0; i < slice.getDistanceHeightIntersections().size(); i++)
     {
-        double t = (double)i / (double)numSamples;
-        double lat, lon;
-        GeoMath::interpolate( startYRad, startXRad, endYRad, endXRad, t, lat, lon );
-        double hamsl;
-        mapNode->getTerrain()->getHeight( geoStart.getSRS(), osg::RadiansToDegrees(lon), osg::RadiansToDegrees(lat), &hamsl );
-        profile.addElevation( spacing * (double)i, hamsl );
+        profile.addElevation( slice.getDistanceHeightIntersections()[i].first, slice.getDistanceHeightIntersections()[i].second);
     }
 }
diff --git a/src/osgEarthUtil/TextureSplatter b/src/osgEarthUtil/TextureSplatter
new file mode 100644
index 0000000..05a4127
--- /dev/null
+++ b/src/osgEarthUtil/TextureSplatter
@@ -0,0 +1,121 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2012 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTHUTIL_TEXTURE_SPLATTER_H
+#define OSGEARTHUTIL_TEXTURE_SPLATTER_H
+
+#include <osgEarthUtil/Common>
+#include <osgEarth/TerrainEffect>
+#include <osgEarth/ImageLayer>
+#include <osgEarth/URI>
+#include <osg/Image>
+#include <osg/Uniform>
+#include <osg/Texture2DArray>
+
+using namespace osgEarth;
+
+namespace osgEarth {
+class Map;
+
+namespace Util
+{
+    /**
+     * Controller that splats texture on the terrain according to a
+     * color mask.
+     */
+    class OSGEARTHUTIL_EXPORT TextureSplatter : public TerrainEffect
+    {
+    public:
+        /** construct a new splatting controller */
+        TextureSplatter();
+
+        /**
+         * The image layer that generates the splat mask. You must call
+         * this prior to installing the effect. The layer must be installed
+         * in the Map with shared=true.
+         */            
+        void setMaskLayer(ImageLayer* layer) { _maskLayer = layer; }
+        ImageLayer* getMaskLayer() { return _maskLayer.get(); }
+
+        /** Sets the LOD at which to start detailing */
+        void setStartLOD( unsigned lod );
+        unsigned getStartLOD() const { return _startLOD.get(); }
+
+        /** Sets the intensity (0=none, 1=full) */
+        void setIntensity( float value );
+        float getIntensity() const { return _intensity.get(); }
+
+        /** Sets the attenuation distance, i.e. the distance from the camera
+            at which the detail texture will fade out completely. */
+        void setAttenuationDistance( float value );
+        float getAttenuationDistance() const { return _attenuationDistance.get(); }
+
+        /** Sets the scale factor for the texture (1..) */
+        void setScale( float value );
+        float getScale() const { return _scale.get(); }
+
+        /** Whether to use the inverse of the mask */
+        void setInvert( bool value );
+        bool getInvert() const { return _invert.get(); }
+
+    public: // TerrainEffect interface
+
+        void onInstall(TerrainEngineNode* engine);
+        void onUninstall(TerrainEngineNode* engine);
+
+    public: // serialization
+
+        TextureSplatter(const Config& conf, const Map* map);
+        void mergeConfig(const Config& conf);
+        virtual Config getConfig() const;
+
+    protected:
+        virtual ~TextureSplatter() { }
+        void init();
+
+        optional<float>          _intensity;
+        optional<unsigned>       _startLOD;
+        optional<std::string>    _maskLayerName;
+        optional<float>          _scale;
+        optional<float>          _attenuationDistance;
+        optional<bool>           _invert;
+
+        struct TextureSource {
+            std::string _tag;
+            std::string _url;
+        };
+        std::vector<TextureSource> _textures;
+
+        osg::ref_ptr<osg::Uniform>         _intensityUniform;
+        osg::ref_ptr<osg::Uniform>         _startLODUniform;
+        osg::ref_ptr<osg::Uniform>         _scaleUniform;
+        osg::ref_ptr<osg::Uniform>         _attenuationDistanceUniform;
+        osg::ref_ptr<osg::Uniform>         _samplerUniform;
+        osg::ref_ptr<osg::Uniform>         _maskUniform;
+        osg::ref_ptr<osg::Uniform>         _brightnessUniform;
+        osg::ref_ptr<osg::Texture2DArray>  _texture;
+        int                                _unit;
+        osg::observer_ptr<ImageLayer>      _maskLayer;
+        osg::ref_ptr<osgDB::Options>       _dbOptions;
+
+        std::string genFragShader();
+    };
+
+} } // namespace osgEarth::Util
+
+#endif // OSGEARTHUTIL_TEXTURE_SPLATTER_H
diff --git a/src/osgEarthUtil/TextureSplatter.cpp b/src/osgEarthUtil/TextureSplatter.cpp
new file mode 100644
index 0000000..1bf3a12
--- /dev/null
+++ b/src/osgEarthUtil/TextureSplatter.cpp
@@ -0,0 +1,362 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2012 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include <osgEarthUtil/TextureSplatter>
+#include <osgEarth/Registry>
+#include <osgEarth/Capabilities>
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/TerrainEngineNode>
+#include <osgEarth/ImageUtils>
+#include <osgEarth/URI>
+
+#define LC "[TextureSplatter] "
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+namespace
+{
+    const char* vs =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+        "uniform vec4 oe_tile_key; \n"
+        "uniform float oe_splat_L0; \n"
+        "uniform float oe_splat_scale; \n"
+        "uniform float oe_splat_attenuation_distance; \n"
+
+        "varying vec4 oe_layer_tilec; \n"
+        "varying vec2 oe_splat_tc; \n"
+        "varying float oe_splat_atten_factor; \n"
+
+        "int oe_splat_ipow(in int x, in int y) { \n"
+        "   int r = 1; \n"
+        "   while( y > 0 ) { \n"
+        "       r *= x; \n"
+        "       --y; \n"
+        "   } \n"
+        "   return r; \n"
+        "}\n"
+
+        "void oe_splat_vertex(inout vec4 VertexVIEW) \n"
+        "{ \n"
+        "    float dL = oe_tile_key.z - oe_splat_L0; \n"
+        "    float twoPowDeltaL = float(oe_splat_ipow(2, int(abs(dL)))); \n"
+        "    float factor = dL >= 0.0 ? twoPowDeltaL : 1.0/twoPowDeltaL; \n"
+
+        "    vec2 a = floor(oe_tile_key.xy / factor); \n"
+        "    vec2 b = a * factor; \n"
+        "    vec2 c = (a+1.0) * factor; \n"
+        "    vec2 offset = (oe_tile_key.xy-b)/(c-b); \n"
+        "    vec2 scale = vec2(1.0/factor); \n"
+
+        "    float tscale = pow(2.0, oe_splat_scale-1.0); \n"
+        "    oe_splat_tc = tscale * ((oe_layer_tilec.st * scale) + offset); \n"
+        
+        "    float r = 1.0-((-VertexVIEW.z/VertexVIEW.w)/oe_splat_attenuation_distance);\n"
+        "    oe_splat_atten_factor = clamp(r, 0.0, 1.0); \n"
+        "} \n";
+
+}
+
+
+TextureSplatter::TextureSplatter() :
+TerrainEffect(),
+_startLOD    ( 10 ),
+_intensity   ( 1.0f ),
+_scale       ( 1.0f ),
+_attenuationDistance( FLT_MAX )
+{
+    init();
+}
+
+TextureSplatter::TextureSplatter(const Config& conf, const Map* map) :
+TerrainEffect(),
+_startLOD    ( 10 ),
+_intensity   ( 1.0f ),
+_scale       ( 1.0f ),
+_attenuationDistance( FLT_MAX )
+{
+    mergeConfig(conf);
+
+    if ( map )
+    {
+        if ( _maskLayerName.isSet() )
+        {
+            _maskLayer = map->getImageLayerByName(*_maskLayerName);
+        }
+
+        _dbOptions = Registry::instance()->cloneOrCreateOptions(map->getDBOptions());        
+    }
+
+    init();
+}
+
+
+void
+TextureSplatter::init()
+{
+    // negative means unset:
+    _unit = -1;
+
+    _startLODUniform   = new osg::Uniform(osg::Uniform::FLOAT, "oe_splat_L0");
+    _startLODUniform->set( (float)_startLOD.get() );
+
+    _intensityUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_splat_intensity");
+    _intensityUniform->set( _intensity.get() );
+
+    _scaleUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_splat_scale");
+    _scaleUniform->set( _scale.get() );
+
+    _attenuationDistanceUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_splat_attenuation_distance");
+    _attenuationDistanceUniform->set( _attenuationDistance.get() );
+
+    _brightnessUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_splat_brightness");
+    _brightnessUniform->set( 1.0f );
+}
+
+
+void
+TextureSplatter::setStartLOD(unsigned lod)
+{
+    if ( lod != _startLOD.get() )
+    {
+        _startLOD = lod;
+        _startLODUniform->set( (float)_startLOD.get() );
+    }
+}
+
+
+void
+TextureSplatter::setIntensity(float intensity)
+{
+    _intensity = osg::clampBetween( intensity, 0.0f, 1.0f );
+    _intensityUniform->set( _intensity.get() );
+}
+
+
+void
+TextureSplatter::setScale(float scale)
+{
+    _scale = osg::clampAbove( scale, 1.0f );
+    _scaleUniform->set( _scale.get() );
+}
+
+
+void
+TextureSplatter::setAttenuationDistance(float value)
+{
+    _attenuationDistance = osg::clampAbove(value, 1.0f);
+    _attenuationDistanceUniform->set( _attenuationDistance.get() );
+}
+
+
+void
+TextureSplatter::onInstall(TerrainEngineNode* engine)
+{
+    if ( engine )
+    {
+        if ( !_texture.valid() )
+        {
+            _texture = new osg::Texture2DArray();
+            _texture->setTextureSize(1024, 1024, _textures.size());
+            _texture->setWrap( osg::Texture::WRAP_S, osg::Texture::REPEAT );
+            _texture->setWrap( osg::Texture::WRAP_T, osg::Texture::REPEAT );
+            _texture->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR );
+            _texture->setFilter( osg::Texture::MAG_FILTER, osg::Texture::LINEAR );
+            _texture->setResizeNonPowerOfTwoHint( false );
+
+            for(unsigned i=0; i<_textures.size(); ++i)
+            {
+                const TextureSource& ts = _textures[i];
+                osg::ref_ptr<osg::Image> image = URI(ts._url).getImage(_dbOptions.get());
+                if ( image->s() != 1024 || image->t() != 1024 )
+                {
+                    osg::ref_ptr<osg::Image> imageResized;
+                    ImageUtils::resizeImage( image.get(), 1024, 1024, imageResized );
+                    _texture->setImage( i, imageResized.get() );
+                }
+                else
+                {
+                    _texture->setImage( i, image.get() );
+                }
+            }
+
+            OE_INFO << LC << "Loaded " << _textures.size() << " splat textures" << std::endl;
+        }
+
+        osg::StateSet* stateset = engine->getOrCreateStateSet();
+
+        if ( engine->getTextureCompositor()->reserveTextureImageUnit(_unit) )
+        {
+            _samplerUniform = stateset->getOrCreateUniform( "oe_splat_tex", osg::Uniform::SAMPLER_2D_ARRAY );
+            _samplerUniform->set( _unit );
+            stateset->setTextureAttribute( _unit, _texture.get(), osg::StateAttribute::ON ); // don't use "..andModes"
+        }
+
+        if ( _maskLayer.valid() )
+        {
+            int unit = *_maskLayer->shareImageUnit();
+            _maskUniform = stateset->getOrCreateUniform("oe_splat_mask", osg::Uniform::SAMPLER_2D);
+            _maskUniform->set(unit);
+            OE_NOTICE << LC << "Installed layer " << _maskLayer->getName() << " as texture mask on unit " << unit << std::endl;
+        }
+        else
+        {
+            exit(-1);
+        }
+
+        stateset->addUniform( _startLODUniform.get() );
+        stateset->addUniform( _intensityUniform.get() );
+        stateset->addUniform( _scaleUniform.get() );
+        stateset->addUniform( _attenuationDistanceUniform.get() );
+        stateset->addUniform( _brightnessUniform.get() );
+
+        std::string fs = genFragShader();
+        VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
+        vp->setFunction( "oe_splat_vertex",   vs, ShaderComp::LOCATION_VERTEX_VIEW );
+        vp->setFunction( "oe_splat_fragment", fs, ShaderComp::LOCATION_FRAGMENT_COLORING );
+    }
+}
+
+
+void
+TextureSplatter::onUninstall(TerrainEngineNode* engine)
+{
+    osg::StateSet* stateset = engine->getStateSet();
+    if ( stateset )
+    {
+        stateset->removeUniform( _startLODUniform.get() );
+        stateset->removeUniform( _intensityUniform.get() );
+        stateset->removeUniform( _scaleUniform.get() );
+        stateset->removeUniform( _attenuationDistanceUniform.get() );
+        stateset->removeUniform( _brightnessUniform.get() );
+
+        if ( _samplerUniform.valid() )
+        {
+            int unit;
+            _samplerUniform->get(unit);
+            stateset->removeUniform( _samplerUniform.get() );
+            stateset->removeTextureAttribute(unit, osg::StateAttribute::TEXTURE);
+        }
+
+        VirtualProgram* vp = VirtualProgram::get(stateset);
+        if ( vp )
+        {
+            vp->removeShader( "oe_splat_vertex" );
+            vp->removeShader( "oe_splat_fragment" );
+        }
+    }
+
+    if ( _unit >= 0 )
+    {
+        engine->getTextureCompositor()->releaseTextureImageUnit( _unit );
+        _unit = -1;
+    }
+}
+
+std::string
+TextureSplatter::genFragShader()
+{
+    std::stringstream buf;
+
+    buf <<
+        "#version " GLSL_VERSION_STR "\n"
+        "#extension GL_EXT_texture_array : enable\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+        "uniform vec4 oe_tile_key; \n"
+        "uniform float oe_splat_L0; \n"
+        "uniform sampler2D oe_splat_mask; \n"
+        "uniform sampler2DArray oe_splat_tex; \n"
+        "uniform float oe_splat_intensity; \n"
+        "uniform float oe_splat_brightness; \n"
+        "varying vec2 oe_splat_tc; \n"
+        "varying vec4 oe_layer_tilec; \n"
+        "varying float oe_splat_atten_factor; \n"
+
+        "void oe_splat_fragment(inout vec4 color) \n"
+        "{ \n"
+        "    if ( oe_tile_key.z >= oe_splat_L0 ) \n"
+        "    { \n"
+        "        vec4 m = texture2D(oe_splat_mask, oe_layer_tilec.st); \n"
+        "        vec4 texel; \n";
+
+    //TODO: cycle through the textures and splat splat spalt.
+    buf <<
+        "        texel = texture2DArray(oe_splat_tex, vec3(oe_splat_tc, 0.0));\n"
+        "        texel.rgb *= oe_splat_brightness;\n"
+        "        color.rgb = mix(color.rgb, texel.rgb, m.a * oe_splat_intensity * oe_splat_atten_factor);\n";
+
+    buf <<
+        "    } \n"
+        "} \n";
+
+    std::string r;
+    r = buf.str();
+    return r;
+}
+
+//-------------------------------------------------------------
+
+void
+TextureSplatter::mergeConfig(const Config& conf)
+{
+    conf.getIfSet( "start_lod",  _startLOD );
+    conf.getIfSet( "intensity",  _intensity );
+    conf.getIfSet( "scale",      _scale );
+    conf.getIfSet( "attenuation_distance", _attenuationDistance );
+    conf.getIfSet( "mask_layer", _maskLayerName );
+
+    ConfigSet textures = conf.child("textures").children("texture");
+    for(ConfigSet::iterator i = textures.begin(); i != textures.end(); ++i)
+    {
+        _textures.push_back(TextureSource());
+        _textures.back()._tag = i->value("tag");
+        _textures.back()._url = i->value("url");
+    }
+}
+
+Config
+TextureSplatter::getConfig() const
+{
+    Config conf("texture_splatter");
+
+    optional<std::string> layername;
+    if ( _maskLayer.valid() && !_maskLayer->getName().empty() )
+        layername = _maskLayer->getName();
+
+    conf.addIfSet( "start_lod",  _startLOD );
+    conf.addIfSet( "intensity",  _intensity );
+    conf.addIfSet( "scale",      _scale );
+    conf.addIfSet( "attenuation_distance", _attenuationDistance );
+    conf.addIfSet( "mask_layer", layername );
+
+    if ( _textures.size() > 0 )
+    {
+        Config textures("textures");
+        for(std::vector<TextureSource>::const_iterator i = _textures.begin(); i != _textures.end(); ++i )
+        {
+            Config texture("texture");
+            texture.set("tag", i->_tag);
+            texture.set("url", i->_url);
+        }
+    }
+
+    return conf;
+}
diff --git a/src/osgEarthUtil/TileIndex b/src/osgEarthUtil/TileIndex
index 7715794..3d8cc6a 100644
--- a/src/osgEarthUtil/TileIndex
+++ b/src/osgEarthUtil/TileIndex
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/TileIndex.cpp b/src/osgEarthUtil/TileIndex.cpp
index 591e091..ca35d93 100644
--- a/src/osgEarthUtil/TileIndex.cpp
+++ b/src/osgEarthUtil/TileIndex.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/TileIndexBuilder b/src/osgEarthUtil/TileIndexBuilder
index 35fc4f4..0ff168e 100644
--- a/src/osgEarthUtil/TileIndexBuilder
+++ b/src/osgEarthUtil/TileIndexBuilder
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/TileIndexBuilder.cpp b/src/osgEarthUtil/TileIndexBuilder.cpp
index e83d8f2..c399960 100644
--- a/src/osgEarthUtil/TileIndexBuilder.cpp
+++ b/src/osgEarthUtil/TileIndexBuilder.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -123,4 +123,5 @@ void TileIndexBuilder::expandFilenames()
             _expandedFilenames.push_back( filename );
         }
     }
-}
\ No newline at end of file
+}
+
diff --git a/src/osgEarthUtil/UTMGraticule b/src/osgEarthUtil/UTMGraticule
index ba81c3b..a729506 100644
--- a/src/osgEarthUtil/UTMGraticule
+++ b/src/osgEarthUtil/UTMGraticule
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -24,6 +24,7 @@
 #include <osgEarth/MapNodeObserver>
 #include <osgEarthSymbology/Style>
 #include <osgEarthFeatures/Feature>
+#include <osg/ClipPlane>
 #include <vector>
 
 namespace osgEarth { namespace Util
@@ -102,6 +103,16 @@ namespace osgEarth { namespace Util
          */
         const UTMGraticuleOptions& getOptions() const { return _options.value(); }
 
+        /**
+         * Sets the clip plane to use. If you don't set one, the object will
+         * create both a ClipNode and ClipPlane for geocentric horizon clipping.
+         * If you do set a clip plane, this object will update it automatically,
+         * and we expect that you have the plane registered with an osg::ClipNode
+         * elsewhere in the scene graph.
+         */
+        void setClipPlane(osg::ClipPlane* clipPlane);
+        osg::ClipPlane* getClipPlane() const { return _clipPlane.get(); }
+
         
     public: // MapNodeObserver
 
@@ -123,7 +134,7 @@ namespace osgEarth { namespace Util
         typedef std::map<std::string, GeoExtent> SectorTable;
         SectorTable _gzd;
 
-        osg::StateAttribute* _depthAttribute;
+        osg::ref_ptr<osg::ClipPlane> _clipPlane;
 
     protected:
         unsigned int getID() const { return _id; }
diff --git a/src/osgEarthUtil/UTMGraticule.cpp b/src/osgEarthUtil/UTMGraticule.cpp
index 2b35836..bc37c88 100644
--- a/src/osgEarthUtil/UTMGraticule.cpp
+++ b/src/osgEarthUtil/UTMGraticule.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
@@ -39,6 +39,8 @@
 #include <osg/PagedLOD>
 #include <osg/Depth>
 #include <osg/Program>
+#include <osg/ClipNode>
+#include <osg/ClipPlane>
 #include <osgDB/FileNameUtils>
 
 #define LC "[UTMGraticule] "
@@ -105,13 +107,24 @@ UTMGraticule::init()
     }
 
     // make the shared depth attr:
-    _depthAttribute = new osg::Depth(osg::Depth::LEQUAL,0,1,false);
+    //_depthAttribute = new osg::Depth(osg::Depth::LEQUAL,0,1,false);
+    this->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, 0);
+
+    // install the range callback for clip plane activation
+    this->addCullCallback( new RangeUniformCullCallback() );
 
     // this will intialize the graph.
     rebuild();
 }
 
 void
+UTMGraticule::setClipPlane(osg::ClipPlane* value)
+{
+    _clipPlane = value;
+    rebuild();
+}
+
+void
 UTMGraticule::setMapNode( MapNode* mapNode )
 {
     _mapNode = mapNode;
@@ -171,8 +184,6 @@ UTMGraticule::rebuild()
         line->stroke()->width() = 1.0;
         line->tessellation() = 20;
 
-        AltitudeSymbol* alt = _options->primaryStyle()->getOrCreate<AltitudeSymbol>();
-
         TextSymbol* text = _options->primaryStyle()->getOrCreate<TextSymbol>();
         text->fill()->color() = Color(Color::White, 0.3f);
         text->halo()->color() = Color(Color::Black, 0.2f);
@@ -181,7 +192,24 @@ UTMGraticule::rebuild()
 
 
     // rebuild the graph:
-    _root = new DrapeableNode( getMapNode(), false );
+
+    // Horizon clipping plane.
+    osg::ClipPlane* cp = _clipPlane.get();
+    if ( cp )
+    {
+        _root = this;
+    }
+    else
+    {
+        osg::ClipNode* clipNode = new osg::ClipNode();
+        osgEarth::Registry::shaderGenerator().run( clipNode );
+        cp = new osg::ClipPlane( 0 );
+        clipNode->addClipPlane( cp );
+        _root = clipNode;
+    }
+    _root->addCullCallback( new ClipToGeocentricHorizon(_profile->getSRS(), cp) );
+
+
     this->addChild( _root );
 
     // build the base Grid Zone Designator (GZD) loolup table. This is a table
@@ -233,8 +261,6 @@ UTMGraticule::rebuild()
         if ( tile )
             _root->addChild( tile );
     }
-
-    //DepthOffsetUtils::prepareGraph( _root );
 }
 
 
@@ -297,29 +323,24 @@ UTMGraticule::buildGZDTile( const std::string& name, const GeoExtent& extent )
     
     osg::Vec3d centerECEF;
     extent.getSRS()->transform( tileCenter, ecefSRS, centerECEF );
-    //extent.getSRS()->transformToECEF( tileCenter, centerECEF );
 
     if ( hasText )
     {
         osg::Vec3d west, east;
         extent.getSRS()->transform( osg::Vec3d(extent.xMin(),tileCenter.y(),0), ecefSRS, west );
         extent.getSRS()->transform( osg::Vec3d(extent.xMax(),tileCenter.y(),0), ecefSRS, east );
-        //extent.getSRS()->transformToECEF(osg::Vec3d(extent.xMin(),tileCenter.y(),0), west );
-        //extent.getSRS()->transformToECEF(osg::Vec3d(extent.xMax(),tileCenter.y(),0), east );
 
         TextSymbol* textSym = _options->primaryStyle()->getOrCreate<TextSymbol>();
         textSym->size() = (west-east).length() / 3.0;
 
         TextSymbolizer ts( textSym );
         
-        osg::Geode* textGeode = new osg::Geode();
-        textGeode->getOrCreateStateSet()->setRenderBinDetails( 9998, "DepthSortedBin" );   
-        textGeode->getOrCreateStateSet()->setAttributeAndModes( _depthAttribute, 1 );
-        
+        osg::Geode* textGeode = new osg::Geode();        
         osg::Drawable* d = ts.create(name);
         d->getOrCreateStateSet()->setRenderBinToInherit();
-
         textGeode->addDrawable(d);
+        Registry::shaderGenerator().run(textGeode, Registry::stateSetCache());
+
         osg::Matrixd centerL2W;
         ecefSRS->createLocalToWorld( centerECEF, centerL2W );
         osg::MatrixTransform* mt = new osg::MatrixTransform(centerL2W);
diff --git a/src/osgEarthUtil/WFS b/src/osgEarthUtil/WFS
index 8e628f0..da80abc 100644
--- a/src/osgEarthUtil/WFS
+++ b/src/osgEarthUtil/WFS
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -28,9 +28,7 @@
 
 #include <osgDB/ReaderWriter>
 #include <osg/Version>
-#if OSG_MIN_VERSION_REQUIRED(2,9,5)
 #include <osgDB/Options>
-#endif
 
 
 #include <string>
diff --git a/src/osgEarthUtil/WFS.cpp b/src/osgEarthUtil/WFS.cpp
index 1d3f453..0e2af0d 100644
--- a/src/osgEarthUtil/WFS.cpp
+++ b/src/osgEarthUtil/WFS.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/src/osgEarthUtil/WMS b/src/osgEarthUtil/WMS
index ffa166d..927a604 100644
--- a/src/osgEarthUtil/WMS
+++ b/src/osgEarthUtil/WMS
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
+* Copyright 2008-2014 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -27,9 +27,7 @@
 
 #include <osgDB/ReaderWriter>
 #include <osg/Version>
-#if OSG_MIN_VERSION_REQUIRED(2,9,5)
 #include <osgDB/Options>
-#endif
 
 
 #include <string>
diff --git a/src/osgEarthUtil/WMS.cpp b/src/osgEarthUtil/WMS.cpp
index 5d7bbbd..fa90f04 100644
--- a/src/osgEarthUtil/WMS.cpp
+++ b/src/osgEarthUtil/WMS.cpp
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2013 Pelican Mapping
+ * Copyright 2008-2014 Pelican Mapping
  * http://osgearth.org
  *
  * osgEarth is free software; you can redistribute it and/or modify
diff --git a/tests/annotation.earth b/tests/annotation.earth
index bb89532..c17a17f 100644
--- a/tests/annotation.earth
+++ b/tests/annotation.earth
@@ -91,11 +91,12 @@ osgEarth Sample - Annotations
                     LINESTRING(-120.37 34.039, -120.09 33.96, -119.75 34, -118.43 33.37, -118.48 32.88)
                 </geometry>
                 <style type="text/css">
-                    fill:             #ff00ff7f;
-                    stroke:           #ffff00;
-                    stroke-width:     3;
-                    extrusion-height: 30000;
-                    render-lighting:  false;
+                    fill:                #ff00ff7f;
+                    stroke:              #ffff00;
+                    stroke-width:        3;
+					stroke-crease-angle: 45.0;
+                    extrusion-height:    30000;
+                    render-lighting:     false;
                 </style>
             </feature>
 
@@ -149,6 +150,21 @@ osgEarth Sample - Annotations
                     render-lighting: false;
                 </style>
             </circle>
+
+            <feature name="Long Line">
+                <srs>wgs84</srs>
+                <geometry>
+                    LINESTRING(0 0, 0 65)
+                </geometry>
+                <style type="text/css">
+                    stroke:              #ffff00;
+                    stroke-width:        3;
+                    stroke-tessellation: 90;
+                    altitude-clamping:   relative;
+					altitude-technique:  scene;
+                    render-lighting:     false;
+                </style>
+            </feature>
             
         </annotations>
         
diff --git a/tests/bing.earth b/tests/bing.earth
index f8270c6..070b04d 100644
--- a/tests/bing.earth
+++ b/tests/bing.earth
@@ -10,15 +10,9 @@ Mircosoft Bing Maps example.
 Supported imagery sets are Aerial, AerialWithLabels, and Road.
 -->
 
-<map>
-    <options>
-        <elevation_tile_size>5</elevation_tile_size>        
-        <terrain lighting="false" first_lod="1"/>
-    </options>
-    
-    <image driver="bing">
+<map>    
+    <image name="bing" driver="bing">
         <key>your-api-key-here</key>
         <imagery_set>AerialWithLabels</imagery_set>
-    </image>
-    
+    </image>    
 </map>
diff --git a/tests/boston.earth b/tests/boston.earth
index 80ffe5e..571544b 100644
--- a/tests/boston.earth
+++ b/tests/boston.earth
@@ -7,40 +7,24 @@ to extruded buildings.
 
 <map name="Boston Demo" type="geocentric" version="2">
     
-    <options elevation_tile_size="15">
-        <terrain lighting="true"/>
-    </options>    
-    
-    <image name="mapquest_open_aerial" driver="xyz" enabled="false">
-        <url>http://oatile[1234].mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg</url>
-        <profile>spherical-mercator</profile>
-        <cache_policy usage="no_cache"/>
-        <nodata_image>http://oatile3.mqcdn.com/tiles/1.0.0/sat/13/636/6210.jpg</nodata_image>
-    </image>
-    
     <image name="readymap_imagery" driver="tms">
         <url>http://readymap.org/readymap/tiles/1.0.0/22/</url>
-        <color_filters>
-            <gamma rgb="1.3"/>
-        </color_filters>
     </image>
     
-        
     <elevation name="readymap_elevation" driver="tms">
         <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
     </elevation>
-    
       
     <model name="buildings" driver="feature_geom">
              
         <features name="buildings" driver="ogr">
             <url>../data/boston_buildings_utm19.shp</url>
             <build_spatial_index>true</build_spatial_index>
+			<resample min_length="2.5"/>
         </features>
         
-		<!--
-        <feature_indexing/>
-		-->
+		<!-- Uncomment this for the featuremanip or featurequery demo. -->
+        <!-- <feature_indexing/> -->
         
         <!--
            The "layout" element activates tiling and paging of the feature set. If you
@@ -53,10 +37,7 @@ to extruded buildings.
               
            The "tile size factor" controls how a feature is tiled. The higher this factor,
               the smaller the tile size, and the more tiles will be used to render a given
-              level of detail. The default is 15. (The renderer multiplies a tile's radius
-              by the tile_size_factor to calculate the maximum suitable camera range for
-              a given level of subdivision. It compares this to the feature level's max
-              range to decide how many tiles to create.)
+              level of detail. The default is 15. tile size = max range / tile size factor.
         -->
         
         <layout tile_size_factor="52">
@@ -73,34 +54,33 @@ to extruded buildings.
                     extrusion-height:        3.5 * max([story_ht_], 1);
                     extrusion-flatten:       true;
                     extrusion-wall-style:    building-wall;
-                    extrusion-wall-gradient: 0.8;
+                    extrusion-wall-gradient: 0.5;
                     extrusion-roof-style:    building-rooftop;
                     altitude-clamping:       terrain;
                     altitude-technique:      map;
                     altitude-binding:        vertex;
+					altitude-resolution:     0;
                 }            
                 building-wall {
                     skin-library:     us_resources;
                     skin-tags:        building;
                     skin-random-seed: 1;
-                    fill:             #ffffff;
                 }
                 building-rooftop {
                     skin-library:     us_resources;
                     skin-tags:        rooftop;
                     skin-tiled:       true;
                     skin-random-seed: 1;
-                    fill:             #ffffff;
                 }
             </style>
         </styles>   
     </model>
     
     
-    <model name="Streets" driver="feature_geom" enabled="false">
+    <model name="Streets" driver="feature_geom" enabled="true">
         <features name="streets" driver="ogr" build_spatial_index="true">
             <url>../data/boston-scl-utm19n-meters.shp</url>
-            <resample max_length="25"/>
+			<resample min_length="25" max_length="25"/>
         </features>
         
         <layout crop_features="true" tile_size_factor="7.5">
@@ -110,38 +90,68 @@ to extruded buildings.
         <styles>
             <style type="text/css">
                 streets {
-                    stroke:                       #ffff007f;
+                    stroke:                       #ffff7f7f;
                     stroke-width:                 7.5m;
                     altitude-clamping:            terrain;
-                    altitude-technique:           gpu;
-                    render-depth-offset-min-bias: 10;
+                    render-depth-offset-min-bias: 3.6;
                 }
             </style>
         </styles>        
     </model>
+	
+	
+	<model name="streetlamps" driver="feature_geom">
+
+        <features name="street centerlines" driver="ogr" build_spatial_index="true">
+            <url>../data/boston-scl-utm19n-meters.shp</url>
+			<resample min_length="25" max_length="25"/>
+		</features>
+
+        <layout tile_size_factor="5" crop_features="true">
+            <level max_range="1000" style="default"/>
+        </layout>
+        
+        <instancing>true</instancing>
+		<cluster_culling>false</cluster_culling>
+        
+        <styles>
+            <style type="text/css">
+                default {
+                    model:             "../data/streetlight.osgb";
+                    model-script:      positionAlongSegments();
+					model-heading:     feature.properties.heading;
+                    altitude-clamping: terrain;
+                }
+            </style>
+            <script language="javascript">
+				<url>../data/scripts/createLineOffsetPoints.js</url>
+			</script>
+        </styles>   
+    </model>
     
     
-    <model name="Parks" driver="feature_geom" enabled="false">
+    <model name="Parks" driver="feature_geom" enabled="true">
         <features name="parks" driver="ogr" build_spatial_index="true">
             <url>../data/boston-parks.shp</url>
         </features>
         
-        <layout tile_size_factor="3.5">
-            <level max_range="3500"/>
+        <layout tile_size_factor="3">
+            <level max_range="2000"/>
         </layout>
         
         <instancing>true</instancing>
+		<cluster_culling>false</cluster_culling>
         
         <styles>
             <style type="text/css">
                 parks {
-                   model:                  "../data/tree.ive";
+                   model:                  "../data/loopix/tree4.osgb";
+				   model-scale:            0.15 + 0.1*Math.random();
                    model-placement:        random;
-                   model-density:          12000;
-                   model-scale:            1.0;
+                   model-density:          3000;
+				   model-heading:          Math.random() * 360.0;
                    altitude-clamping:      terrain;
-                   altitude-technique:     map;
-                   altitude-resolution:    0.001;
+				   render-min-alpha:       0.15;
                 }
             </style>
         </styles>        
@@ -153,9 +163,9 @@ to extruded buildings.
             <viewpoint name="Boston Overview" heading="24.261" height="0" lat="42.34425" long="-71.076262" pitch="-21.6" range="3450"/>
             <viewpoint name="Boston Downtown 1" heading="117" lat="42.3568" long="-71.0585" height="0" pitch="-20.4" range="1500" />
             <viewpoint name="Boston Downtown 2" heading="-128.5" lat="42.3582" long="-71.0546" height="0" pitch="-19" range="1620" />
-            <viewpoint name="Boston Street Level" heading="-145.85" lat="42.36460" long="-71.053612" pitch="-10.1" range="85.034"/>
+            <viewpoint name="Boston Street Level" heading="-145.64081" lat="42.364015" long="-71.054149" pitch="-9.701" range="144.95"/>
         </viewpoints>
-        <sky hours="14.0"/>
+        <sky driver="simple" hours="14.0"/>
     </external>
   
 </map>
diff --git a/tests/boston.earth b/tests/boston_projected.earth
similarity index 63%
copy from tests/boston.earth
copy to tests/boston_projected.earth
index 80ffe5e..c097c44 100644
--- a/tests/boston.earth
+++ b/tests/boston_projected.earth
@@ -5,42 +5,35 @@ Demonstrates the use of a Resource Library in order to apply "typical" textures
 to extruded buildings.
 -->
 
-<map name="Boston Demo" type="geocentric" version="2">
-    
-    <options elevation_tile_size="15">
-        <terrain lighting="true"/>
-    </options>    
-    
-    <image name="mapquest_open_aerial" driver="xyz" enabled="false">
-        <url>http://oatile[1234].mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg</url>
-        <profile>spherical-mercator</profile>
-        <cache_policy usage="no_cache"/>
-        <nodata_image>http://oatile3.mqcdn.com/tiles/1.0.0/sat/13/636/6210.jpg</nodata_image>
-    </image>
+<map name="Boston Demo" type="projected" version="2">
+
+    <options elevation_tile_size="8">
+		<profile srs="+proj=utm +zone=19 +ellps=GRS80 +units=m +no_defs"
+		         xmin="291343" xmax="367343"
+				 ymin="4653876" ymax="4722276"
+                 num_tiles_wide_at_lod_0="1"
+				 num_tiles_high_at_lod_0="1"/>
+		<overlay_blending>false</overlay_blending>
+    </options>
     
     <image name="readymap_imagery" driver="tms">
         <url>http://readymap.org/readymap/tiles/1.0.0/22/</url>
-        <color_filters>
-            <gamma rgb="1.3"/>
-        </color_filters>
     </image>
     
-        
     <elevation name="readymap_elevation" driver="tms">
         <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
     </elevation>
-    
       
     <model name="buildings" driver="feature_geom">
              
         <features name="buildings" driver="ogr">
             <url>../data/boston_buildings_utm19.shp</url>
             <build_spatial_index>true</build_spatial_index>
+			<resample min_length="2.5"/>
         </features>
         
-		<!--
-        <feature_indexing/>
-		-->
+		<!-- Uncomment this for the featuremanip or featurequery demo. -->
+        <!-- <feature_indexing/> -->
         
         <!--
            The "layout" element activates tiling and paging of the feature set. If you
@@ -53,10 +46,7 @@ to extruded buildings.
               
            The "tile size factor" controls how a feature is tiled. The higher this factor,
               the smaller the tile size, and the more tiles will be used to render a given
-              level of detail. The default is 15. (The renderer multiplies a tile's radius
-              by the tile_size_factor to calculate the maximum suitable camera range for
-              a given level of subdivision. It compares this to the feature level's max
-              range to decide how many tiles to create.)
+              level of detail. The default is 15. tile size = max range / tile size factor.
         -->
         
         <layout tile_size_factor="52">
@@ -73,34 +63,33 @@ to extruded buildings.
                     extrusion-height:        3.5 * max([story_ht_], 1);
                     extrusion-flatten:       true;
                     extrusion-wall-style:    building-wall;
-                    extrusion-wall-gradient: 0.8;
+                    extrusion-wall-gradient: 0.5;
                     extrusion-roof-style:    building-rooftop;
                     altitude-clamping:       terrain;
                     altitude-technique:      map;
                     altitude-binding:        vertex;
+					altitude-resolution:     0;
                 }            
                 building-wall {
                     skin-library:     us_resources;
                     skin-tags:        building;
                     skin-random-seed: 1;
-                    fill:             #ffffff;
                 }
                 building-rooftop {
                     skin-library:     us_resources;
                     skin-tags:        rooftop;
                     skin-tiled:       true;
                     skin-random-seed: 1;
-                    fill:             #ffffff;
                 }
             </style>
         </styles>   
     </model>
     
     
-    <model name="Streets" driver="feature_geom" enabled="false">
+    <model name="Streets" driver="feature_geom" enabled="true">
         <features name="streets" driver="ogr" build_spatial_index="true">
             <url>../data/boston-scl-utm19n-meters.shp</url>
-            <resample max_length="25"/>
+			<resample min_length="25" max_length="25"/>
         </features>
         
         <layout crop_features="true" tile_size_factor="7.5">
@@ -110,38 +99,68 @@ to extruded buildings.
         <styles>
             <style type="text/css">
                 streets {
-                    stroke:                       #ffff007f;
+                    stroke:                       #ffff7f7f;
                     stroke-width:                 7.5m;
                     altitude-clamping:            terrain;
-                    altitude-technique:           gpu;
-                    render-depth-offset-min-bias: 10;
+                    render-depth-offset-min-bias: 3.6;
                 }
             </style>
         </styles>        
     </model>
+	
+	
+	<model name="streetlamps" driver="feature_geom">
+
+        <features name="street centerlines" driver="ogr" build_spatial_index="true">
+            <url>../data/boston-scl-utm19n-meters.shp</url>
+			<resample min_length="25" max_length="25"/>
+		</features>
+
+        <layout tile_size_factor="5" crop_features="true">
+            <level max_range="1000" style="default"/>
+        </layout>
+        
+        <instancing>true</instancing>
+		<cluster_culling>false</cluster_culling>
+        
+        <styles>
+            <style type="text/css">
+                default {
+                    model:             "../data/streetlight.osgb";
+                    model-script:      positionAlongSegments();
+					model-heading:     feature.properties.heading;
+                    altitude-clamping: terrain;
+                }
+            </style>
+            <script language="javascript">
+				<url>../data/scripts/createLineOffsetPoints.js</url>
+			</script>
+        </styles>   
+    </model>
     
     
-    <model name="Parks" driver="feature_geom" enabled="false">
+    <model name="Parks" driver="feature_geom" enabled="true">
         <features name="parks" driver="ogr" build_spatial_index="true">
             <url>../data/boston-parks.shp</url>
         </features>
         
-        <layout tile_size_factor="3.5">
-            <level max_range="3500"/>
+        <layout tile_size_factor="3">
+            <level max_range="2000"/>
         </layout>
         
         <instancing>true</instancing>
+		<cluster_culling>false</cluster_culling>
         
         <styles>
             <style type="text/css">
                 parks {
-                   model:                  "../data/tree.ive";
+                   model:                  "../data/loopix/tree4.osgb";
+				   model-scale:            0.15 + 0.1*Math.random();
                    model-placement:        random;
-                   model-density:          12000;
-                   model-scale:            1.0;
+                   model-density:          3000;
+				   model-heading:          Math.random() * 360.0;
                    altitude-clamping:      terrain;
-                   altitude-technique:     map;
-                   altitude-resolution:    0.001;
+				   render-min-alpha:       0.15;
                 }
             </style>
         </styles>        
@@ -153,9 +172,9 @@ to extruded buildings.
             <viewpoint name="Boston Overview" heading="24.261" height="0" lat="42.34425" long="-71.076262" pitch="-21.6" range="3450"/>
             <viewpoint name="Boston Downtown 1" heading="117" lat="42.3568" long="-71.0585" height="0" pitch="-20.4" range="1500" />
             <viewpoint name="Boston Downtown 2" heading="-128.5" lat="42.3582" long="-71.0546" height="0" pitch="-19" range="1620" />
-            <viewpoint name="Boston Street Level" heading="-145.85" lat="42.36460" long="-71.053612" pitch="-10.1" range="85.034"/>
+            <viewpoint name="Boston Street Level" heading="-145.64081" lat="42.364015" long="-71.054149" pitch="-9.701" range="144.95"/>
         </viewpoints>
-        <sky hours="14.0"/>
+        <sky driver="simple" hours="14.0"/>
     </external>
   
 </map>
diff --git a/tests/colorramp.earth b/tests/colorramp.earth
new file mode 100644
index 0000000..0b14519
--- /dev/null
+++ b/tests/colorramp.earth
@@ -0,0 +1,20 @@
+<!--
+osgEarth Sample - Color ramping
+
+Demonstrates how to apply a color ramp to an elevation ( or any other type of single band ) datasource.
+-->
+<map name="readymap.org" type="geocentric">
+
+    <!-- Elevation as a true elevation source -->       
+    <elevation name="readymap_elevation" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
+    </elevation>
+
+    <!--Displays the same elevation using an elevaton contour-->
+    <image name="color ramp" driver="colorramp">
+    	<elevation name="readymap_elevation" driver="tms">
+            <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
+        </elevation>
+        <ramp>..\data\colorramps\elevation.clr</ramp>
+    </image>
+</map>
diff --git a/tests/datum_override.earth b/tests/datum_override.earth
index 892072c..3fb1235 100644
--- a/tests/datum_override.earth
+++ b/tests/datum_override.earth
@@ -18,8 +18,8 @@ http://spatialreference.org
         <lighting>false</lighting>
         <profile srs="+proj=longlat +ellps=clrk66 +datum=NAD27 +no_defs"/>
     </options> 
-    
-    <image name="pelican nasa blue marble" driver="tms">
-        <url>http://demo.pelicanmapping.com/rmweb/data/bluemarble-tms/tms.xml</url>
+        
+    <image name="readymap_imagery" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
     </image>
 </map>
diff --git a/tests/feature_custom_filters.earth b/tests/feature_custom_filters.earth
index cc2f4f3..ce19220 100644
--- a/tests/feature_custom_filters.earth
+++ b/tests/feature_custom_filters.earth
@@ -1,6 +1,10 @@
 <!--
 osgEarth Sample
-This example shows how you can use a custom FeatureFilter, even one that is defined in an Earth File!  Run this example with the osgearth_feature_filter example
+Run this example with:
+
+  osgearth_featurefilter feature_custom_features.earth
+
+Shows how you can use a custom FeatureFilter, even one that is defined in an Earth File!  
 -->
 
 <map name="Feature Custom Filter Demo" type="geocentric" version="2">
diff --git a/tests/feature_draped_lines.earth b/tests/feature_draped_lines.earth
index 09daf6b..f8d392d 100644
--- a/tests/feature_draped_lines.earth
+++ b/tests/feature_draped_lines.earth
@@ -1,7 +1,8 @@
 <!--
 osgEarth Sample
 
-Demonstrate the Altitude Symbol and GPU-based vertex clamping.
+Demonstrates feature draping using projective texturing, 
+i.e. "altitude-clamping: terrain-drape".
 -->
 
 <map name="Geometry Rasterizer Demo" type="round" version="2">
@@ -17,8 +18,7 @@ Demonstrate the Altitude Symbol and GPU-based vertex clamping.
     </image>
     
     <model name="world_boundaries" driver="feature_geom">
-
-        <!-- Configure the OGR feature driver to read the shapefile. -->             
+            
         <features name="world" driver="ogr">
             <url>../data/world.shp</url>
             <build_spatial_index>true</build_spatial_index>
diff --git a/tests/feature_draped_polygons.earth b/tests/feature_draped_polygons.earth
index c9b1ab0..df1ecc6 100644
--- a/tests/feature_draped_polygons.earth
+++ b/tests/feature_draped_polygons.earth
@@ -1,8 +1,11 @@
 <!--
 osgEarth Sample
 
-This one demonstrates how to read feature data from a shapefile and "drape" it
+This one demonstrates how to read polygon data from a shapefile and "drape" it
 on the map using the overlay technique.
+
+It also shows the "selector" technique of using SQL queries to selectively style
+the feature data.
 -->
 
 <map name="Feature Overlay Demo" type="geocentric" version="2">
@@ -22,26 +25,31 @@ on the map using the overlay technique.
         <features name="states" driver="ogr">
             <url>../data/world.shp</url>
             <buffer distance="-0.05"/>
+			<resample max_length="5.0"/>
         </features>
         
-        <styles>
-        
+        <styles>        
             <style type="text/css">
                 p1 {
                    fill:               #ffff8066;
-                   altitude-clamping:  terrain-drape;
+				   render-depth-test:  false;
+				   altitude-clamping:  terrain-drape;
                 }       
                 p2 {
                    fill:               #80ffff66;
+				   render-depth-test:  false;
                 }   
                 p3 {
                    fill:               #ff80ff66;
+				   render-depth-test:  false;
                 }       
                 p4 {
                    fill:               #ff808066;
+				   render-depth-test:  false;
                 }     
                 p5 {
                    fill:               #80ff8066;
+				   render-depth-test:  false;
                 }                                      
             </style>
         
diff --git a/tests/feature_extrude.earth b/tests/feature_extrude.earth
index 871b9a8..c7b72f9 100644
--- a/tests/feature_extrude.earth
+++ b/tests/feature_extrude.earth
@@ -6,6 +6,7 @@ Footprint Extrusion using the "feature_geom" driver.
 
 <map name="Extrusion Demo" type="geocentric" version="2">
       
+	
     <!-- Our features layer. The "feature_geom" driver will analyze the
          style sheet and determine how to render the feature data. -->
          
@@ -36,26 +37,29 @@ Footprint Extrusion using the "feature_geom" driver.
 
         <lighting>true</lighting>
     </model>
+	
+    <image name="mapquest_osm" driver="xyz" max_level="14">
+        <url>http://otile[1234].mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.jpg</url>
+        <profile>global-mercator</profile>
+    </image>
     
-    
-    <options>
-        <lighting>false</lighting>
-        <terrain>
-            <sample_ratio>0.2</sample_ratio>
-        </terrain>
-    </options> 
-    
-    <image name="ReadyMap.org - Imagery" driver="tms">
+    <image name="ReadyMap.org - Imagery" driver="tms" enabled="false">
         <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
     </image>
     
     <elevation name="ReadyMap.org - Elevation" driver="tms">
         <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
     </elevation>
+	
+	<options>
+		<terrain normalize_edges="true"/>
+	</options>
     
     <external>
-        <viewpoint name="Zoom to Buildings" heading="12.4" lat="38.8982" long="-77.0365" height="20.67" pitch="-51.4" range="4500" />
-        <sky hours="21.0"/>
+		<viewpoints>
+			<viewpoint name="Zoom to Buildings" heading="12.4" lat="38.8982" long="-77.0365" height="20.67" pitch="-51.4" range="4500" />
+		</viewpoints>
+        <sky driver="gl" hours="21.0"/>
     </external>
   
 </map>
diff --git a/tests/feature_geom.earth b/tests/feature_geom.earth
index 538fafe..0c799f9 100644
--- a/tests/feature_geom.earth
+++ b/tests/feature_geom.earth
@@ -1,15 +1,12 @@
 <!--
 osgEarth Sample
 
-This one demonstrates how to read feature data from a shapefile and build
+Basic example of how to read feature data from a shapefile and build
 OSG geometry out of it.
 -->
 
 <map name="Feature Geometry Demo" type="geocentric" version="2">
-    
-	<options lighting="false">
-	</options>
-	
+    	
     <image name="world" driver="gdal">
         <url>../data/world.tif</url>
     </image>
@@ -20,9 +17,6 @@ OSG geometry out of it.
         <features name="world" driver="ogr">
             <url>../data/usa.shp</url>
         </features>
-		
-        <!-- Ensure a vertex at least every 5 degrees -->
-        <max_granularity>5.0</max_granularity>
         
         <!-- Appearance details -->
         <styles>
diff --git a/tests/feature_labels.earth b/tests/feature_labels.earth
index 3bf09d9..fea8d67 100644
--- a/tests/feature_labels.earth
+++ b/tests/feature_labels.earth
@@ -12,10 +12,13 @@ This shows how to label point features with an attribute.
     <model name="cities" driver="feature_geom">
 
         <features name="cities" driver="ogr">
-            <url>../data/world.shp</url>
+            <url>../data/ne_cities.shp</url>
         </features>
 
         <styles>
+			<selector class="cities">
+				<query><expr><![CDATA[rank_max > 9]]></expr></query>
+			</selector>
             <style type="text/css">              
                 cities {
 					icon: "../data/placemark32.png";
@@ -24,7 +27,8 @@ This shows how to label point features with an attribute.
 					icon-occlusion-cull: false;
 					icon-occlusion-cull-altitude: 8000;
 					icon-declutter: true;
-					text-content: [cntry_name];
+					text-content: [name];
+					text-priority: [rank_max];
 					altitude-offset: 100;
 					altitude-clamping: terrain;
 					altitude-technique: scene;
diff --git a/tests/feature_labels.earth b/tests/feature_labels_script.earth
similarity index 79%
copy from tests/feature_labels.earth
copy to tests/feature_labels_script.earth
index 3bf09d9..6a403e5 100644
--- a/tests/feature_labels.earth
+++ b/tests/feature_labels_script.earth
@@ -1,6 +1,6 @@
 <!--
 osgEarth Sample
-This shows how to label point features with an attribute.
+This shows how to label point features with an attribute (with some extra zing).
 -->
 
 <map name="Feature Geometry Demo" type="geocentric" version="2">
@@ -16,6 +16,13 @@ This shows how to label point features with an attribute.
         </features>
 
         <styles>
+		    <script>
+			    function addSomeExcitement() {
+				    feature.properties.name = '***' + feature.properties.cntry_name + '!!!';
+					feature.save();
+				}
+			</script>
+			
             <style type="text/css">              
                 cities {
 					icon: "../data/placemark32.png";
@@ -24,7 +31,8 @@ This shows how to label point features with an attribute.
 					icon-occlusion-cull: false;
 					icon-occlusion-cull-altitude: 8000;
 					icon-declutter: true;
-					text-content: [cntry_name];
+					text-script: addSomeExcitement();
+					text-content: [name];
 					altitude-offset: 100;
 					altitude-clamping: terrain;
 					altitude-technique: scene;
diff --git a/tests/feature_levels_and_selectors.earth b/tests/feature_levels_and_selectors.earth
new file mode 100644
index 0000000..dc6719f
--- /dev/null
+++ b/tests/feature_levels_and_selectors.earth
@@ -0,0 +1,94 @@
+<!--
+osgEarth Sample
+Shows how to use Levels and Selectors together when rendering feature data.
+-->
+
+<map name="Feature Geometry Demo" type="geocentric" version="2">
+        
+    <image name="world" driver="gdal">
+        <url>../data/world.tif</url>
+    </image>
+    
+    <image name="boundaries" driver="agglite" max_data_level="7">
+        <features name="world" driver="ogr">
+            <url>../data/world.shp</url>
+        </features>
+        <styles>
+            <style type="text/css">
+                world {
+                   stroke:       #9f9f7f;
+                   stroke-width: 2px;
+                }            
+            </style>
+        </styles>        
+    </image>
+    
+    
+    <model name="cities" driver="feature_geom">
+
+        <features name="cities" driver="ogr">
+            <url>../data/ne_cities.shp</url>
+            <build_spatial_index>true</build_spatial_index>
+        </features>
+        
+        <layout>
+            <level name="far"    style="large"  max_range="1e10"/>
+            <level name="medium" style="medium" max_range="3000000"/>
+            <level name="close"  style="small"  max_range="200000"/>
+        </layout>
+
+        <styles>
+            <selector name="large" class="label-large">
+                <query>
+                    <expr> <![CDATA[ rank_max >= 13 ]]> </expr>
+                </query>
+            </selector>
+            
+            <selector name="medium" class="label-medium">
+                <query>
+                    <expr> <![CDATA[ rank_max < 13 AND rank_max >= 11 ]]> </expr>
+                </query>
+            </selector>
+            
+            <selector name="small" class="label-small">
+                <query>
+                    <expr> <![CDATA[ rank_max < 11 ]]> </expr>
+                </query>
+            </selector>
+            
+            <style type="text/css">              
+                label-large {
+                    text-declutter: true;
+                    text-content:   [name];
+                    text-size:      26.0;
+                    text-align:     center_center;
+                    text-priority:  [rank_max];
+                }           
+                label-medium {
+                    text-declutter: true;
+                    text-content:   [name];
+                    text-size:      20.0;
+                    text-align:     center_center;
+                    text-priority:  [rank_max];
+                }           
+                label-small {
+                    text-declutter: true;
+                    text-content:   [name];
+                    text-size:      14.0;
+                    text-align:     center_center;
+                    text-priority:  [rank_max];
+                }     
+            </style>
+        </styles>
+        
+    </model>
+    
+    <options lighting="false"/>
+    
+    <external>
+        <decluttering>
+            <sort_by_priority>true</sort_by_priority>
+        </decluttering>
+    </external>
+  
+</map>
diff --git a/tests/feature_model_scatter.earth b/tests/feature_model_scatter.earth
index 1114deb..7fdc17a 100644
--- a/tests/feature_model_scatter.earth
+++ b/tests/feature_model_scatter.earth
@@ -23,8 +23,7 @@ is randomized, but it is randomized exactly the same way each time.
             <build_spatial_index>true</build_spatial_index>
         </features>
         
-        <!-- Instancing enables GL's "DrawInstanced" support. -->
-             
+        <!-- Instancing enables GL's "DrawInstanced" support. -->             
         <instancing>true</instancing>
         
         <!-- Disables feature indexing, since the trees are not mapped 1-to-1 to
@@ -78,7 +77,7 @@ is randomized, but it is randomized exactly the same way each time.
     </model>
     
     
-    <model name="parks" driver="feature_geom" enabled="false">
+    <model name="parks" driver="feature_geom" enabled="true">
         <features name="parks" driver="ogr">
             <url>../data/parks.shp</url>
             <build_spatial_index>false</build_spatial_index>
@@ -87,17 +86,13 @@ is randomized, but it is randomized exactly the same way each time.
             <style type="text/css">      
                 default {
                    fill:                #00ff007f;
-                   altitide-clamping:   terrain-drape;
+                   altitude-clamping:   terrain-drape;
                 }                
             </style>
         </styles>
     </model>
     
     
-    <options>
-        <lighting>false</lighting>
-    </options> 
-    
     <image name="mapquest_open_aerial" driver="xyz">
         <url>http://oatile[1234].mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg</url>
         <profile>spherical-mercator</profile>
diff --git a/tests/feature_models.earth b/tests/feature_models.earth
index af0c7b9..0269704 100644
--- a/tests/feature_models.earth
+++ b/tests/feature_models.earth
@@ -1,11 +1,5 @@
-<map name="Model Auto-Scale Demo" type="geocentric" version="2">
-
-    <options lighting="false">
-    </options>
-      
-    <!-- Our features layer. The "feature_geom" driver will analyze the
-         style sheet and determine how to render the feature data. -->
-         
+<map name="Model Demo" type="geocentric" version="2">
+               
     <model name="points" driver="feature_geom">
                       
         <features name="points" driver="ogr">
@@ -30,7 +24,9 @@
     </image>
 	
     <external>	  
-        <viewpoint name="Models" lat="25.311" long="-80.807" pitch="-21" range="177351"/>        
+		<viewpoints>
+			<viewpoint name="Models" lat="25.311" long="-80.807" pitch="-21" range="177351"/>
+		</viewpoints>
     </external>
   
 </map>
\ No newline at end of file
diff --git a/tests/feature_multilod.earth b/tests/feature_multilod.earth
deleted file mode 100644
index 16f9fb3..0000000
--- a/tests/feature_multilod.earth
+++ /dev/null
@@ -1,62 +0,0 @@
-<!--
-osgEarth Sample
-Shows how to create a multi-LOD setup for GPU-clamped geometry.
--->
-
-<map name="Geometry Rasterizer Demo" type="round" version="2">
-  
-    <image name="readymap_imagery" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
-        <color_filters>
-            <gamma rgb="1.3"/>
-        </color_filters>
-    </image>
-        
-    <elevation name="readymap_elevation" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
-    </elevation>
-    
-    <options lighting="false">
-      <terrain normalize_edges="true"/>
-    </options>
-    
-    
-    <model name="roads" driver="feature_geom" lighting="false">
-
-        <features name="roads" driver="ogr">
-            <url>../data/istates_dissolve.shp</url>
-            <ogr_driver>ESRI Shapefile</ogr_driver>
-            <build_spatial_index>true</build_spatial_index>
-        </features>
-
-        <layout crop_features="true">
-            <level style="lo"  min_range="250000"/>
-            <level style="mid" max_range="350000" min_range="25000"/>
-            <level style="hi"  max_range="75000"/>
-        </layout>
-        
-        <styles>
-            <style type="text/css">
-                lo {
-                    stroke:              #ffff009f;
-                    stroke-width:        2;
-                    altitude-clamping:   terrain-gpu;
-                }           
-                mid {
-                    stroke:              #ffff009f;
-                    stroke-width:        3;
-                    altitude-clamping:   terrain-gpu;
-                }                
-                hi {
-                    stroke:              #ffff009f;
-                    stroke-width:        125m;
-                    stroke-tessellation: 5;
-                    altitude-clamping:   terrain-gpu;
-                    render-depth-offset-min-bias: 20;
-                }        
-            </style>
-        </styles>
-        
-    </model>
-  
-</map>
diff --git a/tests/feature_population_cylinders.earth b/tests/feature_population_cylinders.earth
new file mode 100644
index 0000000..5527b79
--- /dev/null
+++ b/tests/feature_population_cylinders.earth
@@ -0,0 +1,91 @@
+<!--
+osgEarth Sample
+Shows how to use JavaScript to create geometry parametrically.
+-->
+
+<map name="City Populations" type="geocentric" version="2">
+        
+    <image name="world" driver="gdal">
+        <url>../data/world.tif</url>
+    </image>
+    
+    <model name="cities" driver="feature_geom">
+
+        <features name="cities" driver="ogr" build_spatial_index="true">
+            <url>../data/ne_cities.shp</url>
+        </features>
+
+        <styles>
+            <script>
+              <![CDATA[
+			    var min_rank = 12;
+                function makePopCircles() {
+					if (feature.properties.rank_max >= min_rank) {
+						var radius = (feature.properties.rank_max-min_rank+1) * 75000;
+						feature.geometry = feature.geometry.buffer(radius);
+						feature.properties.height = radius*1.5;
+					}
+					else {
+						feature.geometry = null;
+					}
+					feature.save();
+                }
+              ]]>
+            </script>
+            <style type="text/css">              
+                default {
+                    fill:              #ff80007f;
+                    stroke:            #ffcf7f;
+					stroke-width:      1px;
+                    altitude-script:   makePopCircles();
+					extrusion-height:  feature.properties.height;
+                }     
+            </style>
+        </styles>
+        
+    </model>
+    
+	
+    <model name="cities" driver="feature_geom">
+
+        <features name="cities" driver="ogr">
+            <url>../data/ne_cities.shp</url>
+            <build_spatial_index>true</build_spatial_index>
+        </features>
+		
+		<layout>
+			<level name="far" style="large"  max_range="1e10"/>
+		</layout>
+
+        <styles>
+			<selector name="large" class="label-large">
+				<query>
+					<expr> <![CDATA[ rank_max >= 12 ]]> </expr>
+				</query>
+			</selector>
+			
+            <style type="text/css">              
+                label-large {
+					text-declutter: true;
+					text-content:   [name];
+					text-size:      16.0;
+					text-align:     center_center;
+					text-halo:      #1f1f1f;
+					text-priority:  [rank_max];
+				}
+            </style>
+        </styles>
+        
+    </model>
+    
+    <external>
+        <decluttering>
+            <out_animation_time>  0.0  </out_animation_time>
+            <in_animation_time>   0.25 </in_animation_time>
+            <min_animation_scale> 0.45 </min_animation_scale>
+            <min_animation_alpha> 0.35 </min_animation_alpha>
+            <sort_by_priority>    true </sort_by_priority>
+        </decluttering>
+    </external>
+  
+</map>
diff --git a/tests/feature_scripted_styling_2.earth b/tests/feature_scripted_styling_2.earth
index 48e3fc7..f3a1ccc 100644
--- a/tests/feature_scripted_styling_2.earth
+++ b/tests/feature_scripted_styling_2.earth
@@ -32,17 +32,17 @@ Demonstrates how to select a style name using javascript.
             </style>
             
             <script language="javascript">
-            <![CDATA[
+              <![CDATA[
                 function getStyleClass()
                 {
-                    var pop = parseInt(feature.attributes['pop_cntry']);
+                    var pop = parseInt(feature.properties.pop_cntry);
                     if      ( pop <= 14045470 )  return "p1";
                     else if ( pop <= 43410900 )  return "p2";
                     else if ( pop <= 97228750 )  return "p3";
                     else if ( pop <= 258833000 ) return "p4";
                     else                         return "p5";
                 }
-            ]]>
+              ]]>
             </script>
             
             <selector class_expr="getStyleClass()"/>
diff --git a/tests/feature_scripting.earth b/tests/feature_scripting.earth
deleted file mode 100644
index c278666..0000000
--- a/tests/feature_scripting.earth
+++ /dev/null
@@ -1,80 +0,0 @@
-<!--
-osgEarth Sample - Feature Scripting
-
-This example demonstrates the use of scripting to style features.
--->
-
-<map name="Feature Geometry Demo" type="geocentric" version="2">
-    
-    <options lighting="false"/>
-    
-    <image name="world" driver="gdal">
-        <url>../data/world.tif</url>
-    </image>
-    
-    <model name="cities" driver="feature_geom">
-
-        <features name="cities" driver="ogr">
-            <url>../data/world.shp</url>
-        </features>
-
-        <styles>
-            <script language="javascript">
-
-                  /* A global variable within this script block */
-                  var prefix = "";
-
-                  function initialize()
-                  {
-                    prefix = "This is ";
-                  }
-
-                  function getName()
-                  {
-                    /* Two objects, feature and context, are globally available in a script.
-                     * feature is the current osgEarth::Features::Feature being styled and
-                     * context is the osgEarth::Features::FilterContext.
-                     *
-                     * See the osgEarth wiki for full documentation.
-                     */
-
-                    if (feature.attributes["code"] == "US")
-                    {
-                      var extent = new GeoExtent(context.profile.srs, feature.geometry.bounds);
-                      if (extent != undefined)
-                      {
-                        /* Check if the extent contains the city of Hilo */
-                        if (extent.contains(-155.07, 19.72))
-                          return prefix + "Hawaii";
-
-                        /* Two extents representing Alaska's bounds to deal with hemisphere crossover */
-                        var alaskaExtent1 = new GeoExtent(context.profile.srs, -180.0, 49.7, -128.3, 72.5);
-                        var alaskaExtent2 = new GeoExtent(context.profile.srs, 171.0, 49.7, 180.0, 72.5);
-                        if (extent.intersects(alaskaExtent1) || extent.intersects(alaskaExtent2))
-                          return prefix + "Alaska";
-                      }
-                    }
-
-                    return prefix + feature.attributes["cntry_name"];
-                  }
-
-                  /* This call to initialize will execute when the script is initially processed */
-                  initialize();
-
-            </script>
-
-            <style type="text/css">              
-                cities {
-                   text-content:  getName() + " (" + [code] + ")";
-                   text-priority: [pop_cntry];
-                   text-halo:     #3f3f7f;
-                   text-font:     arial.ttf;
-                   text-size:     16;
-                   text-remove-duplicate-labels: true;
-                }     
-            </style>
-        </styles>
-        
-    </model>
-  
-</map>
diff --git a/tests/feature_stencil_line_draping.earth b/tests/feature_stencil_line_draping.earth
deleted file mode 100644
index 47883d0..0000000
--- a/tests/feature_stencil_line_draping.earth
+++ /dev/null
@@ -1,46 +0,0 @@
-<!--
-osgEarth Sample
-
-This one demonstrates how to "drape" line data on the map using a
-stenciling technique.
-
-NOTE: Draping lines with the feature_stencil driver requires that you
-build osgEarth with GEOS support.
--->
-
-<map name="Feature Stencil Demo" type="round" version="2">
-    
-    <options lighting="false"/>
-
-    <image name="world" driver="gdal">
-        <url>../data/world.tif</url>
-    </image>
-    
-    <model name="states" driver="feature_stencil">
-
-        <!-- Configure the OGR feature driver to read the shapefile.
-             Convert the goemetry to lines, and apply a small buffer
-             to give the lines thickness. -->
-             
-        <features name="world" driver="ogr">
-            <url>../data/world.shp</url>
-            <convert type="line"/>
-        </features>
-                
-        <!-- This driver uses the "stroke-width" property below to define
-             the width (in data source coordinated) of the draped lines.
-             In this case the units are degrees. -->
-             
-        <styles>
-            <style type="text/css">
-                world {
-                   stroke: #ffff00;
-                   stroke-opacity: 0.5;
-                   stroke-width: 0.1;
-                }            
-            </style>
-        </styles>
-        
-    </model>
-  
-</map>
diff --git a/tests/feature_stencil_polygon_draping.earth b/tests/feature_stencil_polygon_draping.earth
deleted file mode 100644
index a6122b8..0000000
--- a/tests/feature_stencil_polygon_draping.earth
+++ /dev/null
@@ -1,103 +0,0 @@
-<!--
-osgEarth Sample
-
-This one demonstrates how to read feature data from a shapefile and "drape" it
-on the map using a stenciling technique. This technique is more compute-intensive
-than the overlay technique, but it is much more flexible and it works in a 
-whole-earth environment.
-
-This demo loads a shapefile of the world, and colors each country based
-on its population count using feature style classes.
-
-Note the use of the XML CDATA tag in the expressions. This is only required if
-you use XML special characters in the expression (in this case, the less-than
-and greater-than symbols). CDATA "escapes out" of XML parsing within the CDATA
-block.
--->
-
-<map name="Feature Stencil Demo" type="geocentric" version="2">
-    
-    <options lighting="false"/>
-
-    <image name="world" driver="gdal">
-        <url>../data/world.tif</url>
-    </image>
-    
-    <model name="countries" driver="feature_stencil">
-
-        <!-- Configure the OGR feature driver to read the shapefile.
-             Applying a slight negative buffer will "erode" the
-             shapes, highlighting the borders between countries. -->
-                          
-        <features name="states" driver="ogr">
-            <url>../data/world.shp</url>
-            <buffer distance="-0.05"/>
-        </features>
-        
-        <!-- Since some countries span large areas on the globe, we need to
-             use a larger-than-normal extrusion distance on the stencil
-             volumes. (300000 is the default for a geocentric map.) -->
-             
-        <extrusion_distance>400000</extrusion_distance>
-        
-        <!-- Define a feature style class for each category: -->
-        
-        <styles>
-        
-            <style type="text/css">
-                p1 {
-                   fill: #ffff80;
-                   fill-opacity: 0.4;
-                }       
-                p2 {
-                   fill: #fad155;
-                   fill-opacity: 0.4;
-                }   
-                p3 {
-                   fill: #f2a82f;
-                   fill-opacity: 0.4;
-                }       
-                p4 {
-                   fill: #b3520d;
-                   fill-opacity: 0.4;
-                }     
-                p5 {
-                   fill: #6a0000;
-                   fill-opacity: 0.4;
-                }                                      
-            </style>
-        
-            <selector class="p1">
-                <query>
-                    <expr><![CDATA[ POP_CNTRY <= 14045470 ]]></expr>
-                </query>
-            </selector>
-        
-            <selector class="p2">
-                <query>
-                    <expr><![CDATA[ POP_CNTRY > 14045470 and POP_CNTRY <= 43410900 ]]></expr>
-                </query>
-            </selector>
-            
-            <selector class="p3">
-                <query>
-                    <expr><![CDATA[ POP_CNTRY > 43410900 and POP_CNTRY <= 97228750 ]]></expr>
-                </query>
-            </selector>
-            
-            <selector class="p4">
-                <query>
-                    <expr><![CDATA[ POP_CNTRY > 97228750 and POP_CNTRY <= 258833000 ]]></expr>
-                </query>
-            </selector>
-            
-            <selector class="p5">
-                <query>
-                    <expr><![CDATA[ POP_CNTRY > 258833000 ]]></expr>
-                </query>
-            </selector>
-            
-        </styles>
-        
-    </model>
-</map>
diff --git a/tests/feature_tfs.earth b/tests/feature_tfs.earth
index a55d04e..a0939e9 100644
--- a/tests/feature_tfs.earth
+++ b/tests/feature_tfs.earth
@@ -19,11 +19,11 @@ This example shows how to use the TFS driver.
         <styles>                
             <style type="text/css">
                 buildings {
-                    extrusion-height:  15;
-                    extrusion-flatten: true;
-                    fill:              #ff7f2f;
-                    altitude-clamping: terrain;
-					render-lighting:   true;
+                    extrusion-height:        Math.random()*30.0 + 5.0;
+                    extrusion-flatten:       true;
+                    fill:                    #ff7f2f;
+					stroke:                  #6f2f0f;
+                    altitude-clamping:       terrain;
                 }            
             </style>
         </styles>
diff --git a/tests/feature_tfs_scripting.earth b/tests/feature_tfs_scripting.earth
index 0f10998..f36d38f 100644
--- a/tests/feature_tfs_scripting.earth
+++ b/tests/feature_tfs_scripting.earth
@@ -7,8 +7,8 @@ This example shows how to use the TFS driver.
 
     <model name="buildings" driver="feature_geom">
     
-        <features name="buildings" driver="tfs">		                
-			<url>http://readymap.org/readymap/features/tfs/4/</url>
+        <features name="buildings" driver="tfs">                        
+            <url>http://readymap.org/readymap/features/tfs/4/</url>
             <format>json</format>            
         </features>
         
@@ -44,7 +44,7 @@ This example shows how to use the TFS driver.
             <selector name="default" style_expr="selectStyle()"/>
             
             <script language="javascript">
-            <![CDATA[
+              <![CDATA[
                 rotator = 0;
                 function selectStyle() {
                     rotator = (rotator+1)%3;
@@ -52,7 +52,7 @@ This example shows how to use the TFS driver.
                     else if (rotator==1) return "b2";
                     else                 return "b3";
                 }
-            ]]>
+              ]]>
             </script>
         </styles>  
         
diff --git a/tests/fractal_detail.earth b/tests/fractal_detail.earth
index f9baa89..b0e5d6f 100644
--- a/tests/fractal_detail.earth
+++ b/tests/fractal_detail.earth
@@ -31,16 +31,16 @@ have very high resolution data.
 	run once as normal, then again to further refine the results of the first
 	output, and so on.
 	
-	With each succesive octave, the output is scaled like this:
-	freq *= lacunarity, and scale *= persistence. This happens successivly for
+	With each successive octave, the output is scaled like this:
+	freq *= lacunarity, and scale *= persistence. This happens successively for
 	each octave.
 	-->
     <elevation driver="noise" name="noise" enabled="true" offset="true">
 		<tile_size>31</tile_size>
-		<frequency>0.004</frequency>	   <!-- one cycle every 250m -->
+		<frequency>0.004</frequency>      <!-- one cycle every 250m -->
 		<lacunarity>2.0</lacunarity>      <!-- freq *= lac for each octave (default = 2) -->
 		<persistence>0.5</persistence>    <!-- amp *= pers for each octave (default = 0.5) -->
-		<octaves>4</octaves>
+		<octaves>24</octaves>
 		<scale>20</scale>
 		<bias>0</bias>
 	</elevation>
diff --git a/tests/gdal_tiff.earth b/tests/gdal_tiff.earth
index 82534a5..6649d29 100644
--- a/tests/gdal_tiff.earth
+++ b/tests/gdal_tiff.earth
@@ -8,4 +8,4 @@ Demonstrates the simplest possible use of the GDAL driver to load a GeoTIFF imag
         <url>../data/world.tif</url>
         <caching_policy usage="no_cache"/>
     </image>
-</map>
+</map>
\ No newline at end of file
diff --git a/tests/glsl_filter.earth b/tests/glsl_filter.earth
index 3ac7377..ca7591c 100644
--- a/tests/glsl_filter.earth
+++ b/tests/glsl_filter.earth
@@ -1,15 +1,6 @@
 <!--
-osgEarth Sample - ReadyMap.ORG Server - http://readymap.org
-
-ReadyMap.ORG provides free global base map data for osgEarth developers!
-This tiled, worldwide dataset of imagery, elevation, and street map data
-is a great base map that provides global context for your own local datasets.
-It works "out of the box" with osgEarth applications.
-
-**** NOTICE ****
-YOU ARE RESPONSIBLE for abiding by the TERMS AND CONDITIONS outlined at:
-http://readymap.org
-
+osgEarth Sample
+Use a simple GLSL code snippet to adjust the color of a layer.
 -->
 <map name="readymap.org" type="geocentric" version="2">
 
@@ -21,15 +12,5 @@ http://readymap.org
 			</glsl>
         </color_filters>
     </image>
-        
-    <elevation name="readymap_elevation" driver="tms">
-        <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
-    </elevation>
-    
-    <options>
-		<lighting>false</lighting>
-		<elevation_tile_size>7</elevation_tile_size>
-        <terrain first_lod="1"/>
-    </options>
     
 </map>
diff --git a/tests/layer_opacity.earth b/tests/layer_opacity.earth
index 55f4aec..17db584 100644
--- a/tests/layer_opacity.earth
+++ b/tests/layer_opacity.earth
@@ -1,21 +1,14 @@
 <!--
 osgEarth Sample : Layer Opacity
 
-This example tests setting a layers initial opacity in the .earth file.
-
-The opacity will only take affect when using a technique that can blend layers together such as the multipass layering technique or the osgEarthUtil::FadeLayerNode
-
-Look for hi-res insets over the cities of Boston and New York.
+This example tests setting a layers initial opacity in the .earth file
 -->
 
 <map name="hi-res inset" type="geocentric" version="2">
 
-    <options>
-        <lighting>false</lighting>
-    </options>
-     
-    <image name="pelican nasa blue marble" driver="tms">
-        <url>http://demo.pelicanmapping.com/rmweb/data/bluemarble-tms/tms.xml</url>
+    <image driver="gdal" name="world-tiff" cache_enabled="false">
+        <url>../data/world.tif</url>
+        <caching_policy usage="no_cache"/>
     </image>
     
     <image name="boston_inset" driver="gdal" opacity="0.3">
diff --git a/tests/lod_blending.earth b/tests/lod_blending.earth
index 51da94b..b39cf8b 100644
--- a/tests/lod_blending.earth
+++ b/tests/lod_blending.earth
@@ -1,21 +1,12 @@
 <!--
-osgEarth Sample - ReadyMap.ORG Server - http://readymap.org
-
-ReadyMap.ORG provides free global base map data for osgEarth developers!
-This tiled, worldwide dataset of imagery, elevation, and street map data
-is a great base map that provides global context for your own local datasets.
-It works "out of the box" with osgEarth applications.
-
-**** NOTICE ****
-YOU ARE RESPONSIBLE for abiding by the TERMS AND CONDITIONS outlined at:
-http://readymap.org
-
+osgEarth Sample
+LOD Blending creates smoother transitions between levels of detail.
 -->
 <map name="readymap.org" type="geocentric" version="2">
     
     <options>
         <!-- elevation_tile_size must be an odd number for morphing. -->
-        <elevation_tile_size>15</elevation_tile_size>
+		<elevation_interpolation>triangulate</elevation_interpolation>
         
         <!-- extend the min_lod so we can see MORE morphing. -->
         <terrain first_lod="1" min_lod="19"/>
@@ -32,6 +23,8 @@ http://readymap.org
     <external>
         <lod_blending>
             <duration>1.0</duration>
+			<blend_imagery>true</blend_imagery>
+			<blend_elevation>true</blend_elevation>
         </lod_blending>
     </external>
     
diff --git a/tests/min_max_level.earth b/tests/min_max_level.earth
index 2372b77..aac5649 100644
--- a/tests/min_max_level.earth
+++ b/tests/min_max_level.earth
@@ -7,19 +7,16 @@ Please note that usage of Yahoo! map data is subject to Yahoo!'s terms of servic
 
 <map name="Yahoo Levels" type="geocentric" version="2">
 
-    <image name="yahoo" driver="composite">
-    
-        <!-- this level will be visible at lower resolutions -->
-        <image name="yahoo_sat" driver="yahoo">
-            <dataset>satellite</dataset>
-            <max_level>5</max_level>
-        </image> 
-
-        <!-- this level will be visible at higher resolutions -->
-        <image name="yahoo_maps" driver="yahoo">
-            <min_level>6</min_level>
-        </image> 
-        
+    <image name="mapquest_open_aerial" driver="xyz" max_level="5">
+        <url>http://oatile[1234].mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg</url>
+        <profile>spherical-mercator</profile>
+        <cache_policy usage="no_cache"/>
+        <nodata_image>http://oatile3.mqcdn.com/tiles/1.0.0/sat/13/636/6210.jpg</nodata_image>
+    </image>
+	
+    <image name="mapquest_osm" driver="xyz" min_level="6">
+        <url>http://otile[1234].mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.jpg</url>
+        <profile>global-mercator</profile>
     </image>
    
 </map>
diff --git a/tests/noise.earth b/tests/noise.earth
index 8ae330c..9b4ad47 100644
--- a/tests/noise.earth
+++ b/tests/noise.earth
@@ -10,15 +10,16 @@ We use a contour map to better visualize the terrain.
 <map version="2">
 
     <elevation driver="noise" name="noisy_terrain"
-               resolution ="3185500"
-               octaves    ="12"
-               persistence="0.49"
-               lacunarity ="3.0"
-               scale      ="5000" />
+               frequency  ="1" 
+               octaves    ="20"
+               persistence="0.53"
+			   lacunarity ="2.1"
+               scale      ="6000" />
                
 
     <options
-        lighting="true"/>
+        lighting="true"
+		elevation_interpolation="bilinear"/>
 
     <external>
         <contour_map/>
diff --git a/tests/normalmap.earth b/tests/normalmap.earth
index ad561c6..a2d82df 100644
--- a/tests/normalmap.earth
+++ b/tests/normalmap.earth
@@ -5,7 +5,7 @@ Demonstrates the application of a normal map to add terrain detail.
 
 <map>
     <options elevation_tile_size="15">
-        <terrain min_level="20"/>
+        <terrain min_level="20" normalize_edges="true"/>
     </options>
 
     <image name="readymap_imagery" driver="tms" visible="true">
diff --git a/tests/readymap.earth b/tests/readymap.earth
index 6e9c4ee..fd376cd 100644
--- a/tests/readymap.earth
+++ b/tests/readymap.earth
@@ -11,19 +11,17 @@ YOU ARE RESPONSIBLE for abiding by the TERMS AND CONDITIONS outlined at:
 http://readymap.org
 
 -->
-<map name="readymap.org" type="geocentric" version="2">
+<map name="readymap.org" type="geocentric">
     
     <options>
-        <elevation_tile_size>15</elevation_tile_size>
         <terrain first_lod="1"/>
     </options>
     
-    <image name="readymap_imagery" driver="tms" visible="true">
+    <image name="readymap_imagery" driver="tms">
         <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
     </image>
         
     <elevation name="readymap_elevation" driver="tms">
         <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
     </elevation>
-    
 </map>
diff --git a/tests/silverlining.earth b/tests/silverlining.earth
new file mode 100644
index 0000000..5141e60
--- /dev/null
+++ b/tests/silverlining.earth
@@ -0,0 +1,28 @@
+<!--
+Tests the SilverLining environment plugin.
+http://sundog-soft.com/sds/features/real-time-3d-clouds/
+-->
+<map name="readymap.org" type="geocentric" version="2">
+    
+    <options>
+        <elevation_tile_size>7</elevation_tile_size>
+        <terrain first_lod="1"/>
+    </options>
+    
+    <image name="readymap_imagery" driver="tms" visible="true">
+        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
+    </image>
+        
+    <elevation name="readymap_elevation" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
+    </elevation>
+
+    <external>
+        <sky driver="silverlining">
+			<user></user>
+			<license_code></license_code>
+			<clouds>true</clouds>
+			<clouds_max_altitude>100000</clouds_max_altitude>
+		</sky>
+    </external>
+</map>
diff --git a/tests/splat.earth b/tests/splat.earth
new file mode 100644
index 0000000..cb6a774
--- /dev/null
+++ b/tests/splat.earth
@@ -0,0 +1,118 @@
+<!-- 
+osgEarth Sample - Detail Texturing
+-->
+
+<map>
+
+    <options elevation_interpolation="bilinear">
+        <terrain min_lod="19" max_lod="19" min_tile_range_factor="7"/>
+    </options>
+    
+    <image name="readymap_imagery" driver="tms" visible="true" opacity="1">
+        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
+    </image>
+        
+    <elevation name="readymap_elevation" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
+    </elevation>
+    
+    <image driver="splat_mask" name="splat" shared="true" visible="false"
+           min_level     ="10" 
+           tile_size     ="5"
+           contrast      ="4.0">
+		<classification_path>
+			H:/data/esa/GLOBCOVER_L4_200901_200912_V2.3_tiled.tif
+		</classification_path>
+    </image>
+               
+    <image name="normalmap" driver="noise" shared="true" visible="false"
+           start_lod  ="10"
+           tile_size  ="64"
+           normal_map ="true"
+           frequency  =".001"
+           persistence="0.45"
+           lacunarity ="3.0"
+           octaves    ="6"
+           scale      ="15" >
+    </image> 
+
+    <external>
+        <detail_texture mask_layer="splat"
+                        start_lod ="10"
+                        scale     ="4"
+                        intensity ="0.9"
+                        octaves   ="3"
+                        attenuation_distance="20000">
+            <textures>
+                <texture name="rock"  url="../data/textures/seamless/rock.jpg"/>
+                <texture name="dirt"  url="../data/textures/seamless/dirt5.jpg"/>
+                <texture name="grass" url="../data/textures/seamless/grass.jpg"/>
+                <texture name="water" url="../data/textures/seamless/water.jpg"/>
+            </textures>
+        </detail_texture>
+        
+		<!--
+        <normal_map layer="normalmap"/>
+		-->
+		        
+        <viewpoints>
+            <viewpoint name="one" heading="15" height="4093" lat="46.8545" long="-121.7652" pitch="-29" range="41747"/>
+            <viewpoint name="two" heading="-145.5717976089649" height="1877.697350924835" lat="46.8725220319367" long="-121.6904520329511" pitch="-6.060576722588542" range="4608.703411154525"/>
+            <viewpoint name="three" heading="49.16286972143863" height="1596.743544967845" lat="46.79259290581516" long="-121.7574053228272" pitch="-5.905240611358435" range="2449.290556889881"/>
+        </viewpoints>
+    </external>
+    
+    
+    <model name="trees" driver="feature_geom" enabled="true">
+        <features name="trees" driver="ogr">
+			<geometry>
+				POLYGON((-121.82 46.833, -121.82 46.754, -121.63 46.754, -121.63 46.836))
+			</geometry>
+        </features>
+        
+        <instancing>true</instancing>
+        
+        <fading 
+            duration="1.0"
+            max_range="12000"
+            attenuation_distance="11000"/>
+        
+        <layout>
+            <tile_size_factor>3.5</tile_size_factor>
+            <crop_features>true</crop_features>
+            <level max_range="12000" style="parks-0"/>
+            <level max_range="3000" style="parks-2"/>
+            <level max_range="1000" style="parks-1"/>
+        </layout>
+             
+        <styles>
+            <style type="text/css">
+                parks-0 {
+                   model:               "../data/pinetree.ive";
+                   model-placement:     random;
+                   model-density:       50;
+                   model-scale:         24.0;
+                   altitude-clamping:   terrain;
+                   altitude-resolution: 0.001;
+                }            
+                parks-1 {
+                   model:               "../data/tree.ive";
+                   model-placement:     random;
+                   model-density:       650;
+                   model-scale:         3.0;
+                   altitude-clamping:   terrain;
+                   altitude-resolution: 0.001;
+                }            
+                parks-2 {
+                   model:               "../data/pinetree.ive";
+                   model-placement:     random;
+                   model-density:       350;
+                   model-scale:         20.5;
+                   model-random-seed:   1;
+                   altitude-clamping:   terrain;
+                   altitude-resolution: 0.001;
+                }              
+            </style>
+        </styles>        
+    </model>
+</map>
diff --git a/tests/srtm.earth b/tests/srtm.earth
deleted file mode 100644
index 5d50825..0000000
--- a/tests/srtm.earth
+++ /dev/null
@@ -1,18 +0,0 @@
-<!--
-osgEarth Sample - SRTM global elevation
-
-This sample demonstrates the use of a global SRTM TMS data source for world-wide elevation.
-The heightfield is draped with the NASA Blue Marble imagery.
--->
-
-<map name="srtm sample" type="globe" version="2">
-
-  <image name="pelican nasa blue marble" driver="tms">
-    <url>http://demo.pelicanmapping.com/rmweb/data/bluemarble-tms/tms.xml</url>
-  </image>
-  
-  <heightfield name="pelican srtm" driver="tms">
-    <url>http://demo.pelicanmapping.com/rmweb/data/srtm30_plus_tms/tms.xml</url>
-  </heightfield>
-
-</map>
diff --git a/tests/stamen_toner.earth b/tests/stamen_toner.earth
new file mode 100644
index 0000000..8b91b05
--- /dev/null
+++ b/tests/stamen_toner.earth
@@ -0,0 +1,15 @@
+<map name="Stamen Toner" type="geocentric" version="2">
+
+    <image name="stamen_toner" driver="xyz">
+        <url>http://tile.stamen.com/toner/{z}/{x}/{y}.png</url>
+        <profile>spherical-mercator</profile>
+    </image>
+
+    <elevation name="readymap_elevation" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
+    </elevation>
+
+    <options>
+        <lighting>false</lighting>
+    </options>
+</map>
\ No newline at end of file
diff --git a/tests/stamen_watercolor.earth b/tests/stamen_watercolor.earth
new file mode 100644
index 0000000..525d489
--- /dev/null
+++ b/tests/stamen_watercolor.earth
@@ -0,0 +1,15 @@
+<map name="Stamen Watercolor" type="geocentric" version="2">
+
+    <image name="stamen_watercolor" driver="xyz">
+        <url>http://tile.stamen.com/watercolor/{z}/{x}/{y}.jpg</url>
+        <profile>spherical-mercator</profile>
+    </image>
+
+    <elevation name="readymap_elevation" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
+    </elevation>
+
+    <options>
+        <lighting>false</lighting>
+    </options>
+</map>
\ No newline at end of file
diff --git a/tests/triton.earth b/tests/triton.earth
new file mode 100644
index 0000000..4de7895
--- /dev/null
+++ b/tests/triton.earth
@@ -0,0 +1,36 @@
+<!--
+Tests the Triton ocean plugin.
+http://sundog-soft.com/sds/features/real-time-3d-clouds/
+-->
+<map name="readymap.org" type="geocentric" version="2">
+    
+    <options>
+        <elevation_tile_size>7</elevation_tile_size>
+        <terrain first_lod="1"/>
+    </options>
+    
+    <image name="readymap_imagery" driver="tms" visible="true">
+        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
+    </image>
+        
+    <elevation name="readymap_elevation" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
+    </elevation>
+
+    <external>
+        <ocean driver="triton"
+               user="my_user_name"
+               license_code="my_license_code"
+			   max_altitude="30000"/>
+        <sky hours="18"/>
+        <viewpoints>
+            <viewpoint name="Click to see the water by Maui" heading="-29" height="-204" lat="20.6714" long="-156.5384" pitch="-4" range="3270"/>
+        </viewpoints>
+		<annotations>
+			<model name="Object in the water">
+				<url>../data/red_flag.osg.40.scale</url>
+				<position lat="20.6714" long="-156.5384" alt="0"/>
+			</model>
+		</annotations>
+    </external>
+</map>
diff --git a/tests/vpb_with_inset.earth b/tests/vpb_with_inset.earth
deleted file mode 100644
index 5d44c95..0000000
--- a/tests/vpb_with_inset.earth
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-osgEarth Sample
-
-Overlay a local imagery file on top of an existing VirtualPlanBuilder database.
-Zoom in to the city of Boston (east coast of the US) to see the inset.
--->
-
-<map name="Virtual Planet Builder inset" type="geocentric" version="2">
-
-    <image name="imagery layer 0" driver="vpb">
-        <url>http://www.openscenegraph.org/data/earth_bayarea/earth.ive</url>
-        <primary_split_level>5</primary_split_level>
-        <secondary_split_level>11</secondary_split_level>
-    </image>
-	
-    <image name="vpb hires inset" driver="gdal">
-	    <url>../data/boston-inset-wgs84.tif</url>
-    </image>
-	
-    <options>
-        <lighting>false</lighting>
-		<cache_policy usage="no_cache"/>
-    </options>
-    
-</map>
diff --git a/tests/wms-t_nexrad_animated.earth b/tests/wms-t_nexrad_animated.earth
index 943f900..d46eda2 100644
--- a/tests/wms-t_nexrad_animated.earth
+++ b/tests/wms-t_nexrad_animated.earth
@@ -35,11 +35,4 @@ Try running this with:
         <seconds_per_frame>0.25</seconds_per_frame>
         <cache_policy usage="no_cache"/>
     </image>
-    
-    <options>
-        <lighting>false</lighting>
-		
-		<!-- default engine doesn't support sequencing yet -->
-		<terrain driver="quadtree"/>
-    </options>
 </map>
diff --git a/tests/yahoo_aerial.earth b/tests/yahoo_aerial.earth
deleted file mode 100644
index fdd3a7a..0000000
--- a/tests/yahoo_aerial.earth
+++ /dev/null
@@ -1,20 +0,0 @@
-<!--
-osgEarth Sample
-
-This example pulls satellite/aerial map tiles from the Yahoo! Maps service.
-
-This is for educational purposes only - it is your responsibility to abide by
-the provider's terms of service when using their data.
--->
-
-<map name="Yahoo! Maps" type="geocentric" version="2">
-    
-    <image name="yahoo aerial" driver="yahoo">
-        <dataset>satellite</dataset>
-    </image>
-    
-    <options>
-        <lighting>false</lighting>
-    </options>
-   
-</map>
diff --git a/tests/yahoo_maps.earth b/tests/yahoo_maps.earth
deleted file mode 100644
index 3791a6f..0000000
--- a/tests/yahoo_maps.earth
+++ /dev/null
@@ -1,20 +0,0 @@
-<!--
-osgEarth Sample
-
-This example pulls road map tiles from the Yahoo! Maps service.
-
-This is for educational purposes only - it is your responsibility to abide by
-the provider's terms of service when using their data.
--->
-
-<map name="Yahoo maps" type="geocentric" version="2">
-    
-    <image name="roads map" driver="yahoo">
-        <dataset>roads</dataset>
-    </image>
-    
-    <options>
-        <lighting>false</lighting>
-    </options>
-   
-</map>

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/osgearth.git



More information about the Pkg-grass-devel mailing list